From 41f50a4212fd50d2c0acbac9a354a015ac61d355 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 28 Feb 2024 15:38:35 -0500 Subject: [PATCH 1/2] [recorder] Support Vitest/Mocha both --- common/config/rush/pnpm-lock.yaml | 28 ++-- sdk/test-utils/recorder/.gitignore | 2 + sdk/test-utils/recorder/.tshy/browser.json | 12 ++ sdk/test-utils/recorder/.tshy/build.json | 9 ++ sdk/test-utils/recorder/.tshy/commonjs.json | 16 +++ sdk/test-utils/recorder/.tshy/esm.json | 15 +++ .../recorder/.tshy/react-native.json | 15 +++ sdk/test-utils/recorder/karma.conf.js | 124 ------------------ sdk/test-utils/recorder/package.json | 104 ++++++++------- .../{test/utils/server.ts => server.mjs} | 2 +- sdk/test-utils/recorder/src/index.ts | 12 +- sdk/test-utils/recorder/src/matcher.ts | 6 +- sdk/test-utils/recorder/src/options.ts | 7 +- sdk/test-utils/recorder/src/recorder.ts | 86 ++++++++---- sdk/test-utils/recorder/src/sanitizer.ts | 13 +- sdk/test-utils/recorder/src/testInfo.ts | 91 +++++++++++++ sdk/test-utils/recorder/src/transform.ts | 6 +- sdk/test-utils/recorder/src/utils/delay.ts | 2 +- .../recorder/src/utils/encoding-browser.mts | 5 + .../recorder/src/utils/encoding.browser.ts | 5 - sdk/test-utils/recorder/src/utils/encoding.ts | 4 +- .../utils/{env.browser.ts => env-browser.ts} | 0 .../recorder/src/utils/envSetupForPlayback.ts | 8 +- ....ts => relativePathCalculator-browser.mts} | 4 +- .../src/utils/relativePathCalculator.ts | 6 +- .../recorder/src/utils/sessionFilePath.ts | 24 ++-- sdk/test-utils/recorder/src/utils/utils.ts | 2 +- sdk/test-utils/recorder/test/errors.spec.ts | 24 ++-- sdk/test-utils/recorder/test/recorder.spec.ts | 61 +++++++++ .../recorder/test/sanitizers.spec.ts | 13 +- .../recorder/test/testProxyClient.spec.ts | 65 ++++----- .../recorder/test/testProxyTests.spec.ts | 19 ++- .../recorder/test/utils/server.browser.ts | 4 - sdk/test-utils/recorder/test/utils/utils.ts | 10 +- .../recorder/tsconfig.browser.config.json | 10 ++ sdk/test-utils/recorder/tsconfig.json | 17 ++- .../recorder/vitest.browser.config.ts | 39 ++++++ sdk/test-utils/recorder/vitest.config.ts | 31 +++++ 38 files changed, 579 insertions(+), 322 deletions(-) create mode 100644 sdk/test-utils/recorder/.tshy/browser.json create mode 100644 sdk/test-utils/recorder/.tshy/build.json create mode 100644 sdk/test-utils/recorder/.tshy/commonjs.json create mode 100644 sdk/test-utils/recorder/.tshy/esm.json create mode 100644 sdk/test-utils/recorder/.tshy/react-native.json delete mode 100644 sdk/test-utils/recorder/karma.conf.js rename sdk/test-utils/recorder/{test/utils/server.ts => server.mjs} (97%) create mode 100644 sdk/test-utils/recorder/src/testInfo.ts create mode 100644 sdk/test-utils/recorder/src/utils/encoding-browser.mts delete mode 100644 sdk/test-utils/recorder/src/utils/encoding.browser.ts rename sdk/test-utils/recorder/src/utils/{env.browser.ts => env-browser.ts} (100%) rename sdk/test-utils/recorder/src/utils/{relativePathCalculator.browser.ts => relativePathCalculator-browser.mts} (84%) create mode 100644 sdk/test-utils/recorder/test/recorder.spec.ts delete mode 100644 sdk/test-utils/recorder/test/utils/server.browser.ts create mode 100644 sdk/test-utils/recorder/tsconfig.browser.config.json create mode 100644 sdk/test-utils/recorder/vitest.browser.config.ts create mode 100644 sdk/test-utils/recorder/vitest.config.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 6deb4f09fb02..a414c4eb0979 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -19287,7 +19287,7 @@ packages: dev: false file:projects/identity-cache-persistence.tgz: - resolution: {integrity: sha512-RFdIlHIdZI9Zh26wyT5eDW9DpjGuVaLV1soVgKF7Vy2hqtoJr302KYkhWs9NXcdkPOPiSIGOBsWni8XmhOAUTA==, tarball: file:projects/identity-cache-persistence.tgz} + resolution: {integrity: sha512-3yTUwwHHm7cLqyVBM7+0ib4KVQ3shK7ztMPmQXlPaYFy8iES/flshTkponZ1+NewX5vJOtqkpobW+XpKPn9xBA==, tarball: file:projects/identity-cache-persistence.tgz} name: '@rush-temp/identity-cache-persistence' version: 0.0.0 dependencies: @@ -22070,7 +22070,7 @@ packages: dev: false file:projects/test-recorder.tgz: - resolution: {integrity: sha512-59wCpvAIFd+hVOdlADUEn0fb/0yNC20+SdKIC54kkGQPlpPxkpnC7N2EAFtg+z6FI5UOSvjTsDeqTr5u11RBWA==, tarball: file:projects/test-recorder.tgz} + resolution: {integrity: sha512-PkcZ9A/Gj7uV/6GUvpWfLDJPe07Wa8epPQphKKmz6nQ5WFhJOdmF+X2YiWRJnIGZ7Js5gj3oWwRUIWyVw21dCg==, tarball: file:projects/test-recorder.tgz} name: '@rush-temp/test-recorder' version: 0.0.0 dependencies: @@ -22079,6 +22079,8 @@ packages: '@types/fs-extra': 8.1.5 '@types/mocha': 10.0.6 '@types/node': 18.19.18 + '@vitest/browser': 1.3.1(playwright@1.41.2)(vitest@1.3.1) + '@vitest/coverage-istanbul': 1.3.1(vitest@1.3.1) c8: 8.0.1 chai: 4.3.10 concurrently: 8.2.2 @@ -22096,17 +22098,27 @@ packages: karma-mocha-reporter: 2.2.5(karma@6.4.3) karma-sourcemap-loader: 0.3.8 mocha: 10.3.0 - rimraf: 3.0.2 + rimraf: 5.0.5 ts-node: 10.9.2(@types/node@18.19.18)(typescript@5.3.3) + tshy: 1.11.1 tslib: 2.6.2 typescript: 5.3.3 + vitest: 1.3.1(@types/node@18.19.18)(@vitest/browser@1.3.1) transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - bufferutil - - debug + - '@edge-runtime/vm' + - '@vitest/ui' + - happy-dom + - jsdom + - less + - lightningcss + - playwright + - safaridriver + - sass + - stylus + - sugarss - supports-color - - utf-8-validate + - terser + - webdriverio dev: false file:projects/test-utils-perf.tgz: diff --git a/sdk/test-utils/recorder/.gitignore b/sdk/test-utils/recorder/.gitignore index 966433321863..21c7e70181bf 100644 --- a/sdk/test-utils/recorder/.gitignore +++ b/sdk/test-utils/recorder/.gitignore @@ -1,2 +1,4 @@ # Ignore the recordings directory in this project since they get regenerated every test run recordings/ + +log.txt diff --git a/sdk/test-utils/recorder/.tshy/browser.json b/sdk/test-utils/recorder/.tshy/browser.json new file mode 100644 index 000000000000..32e74e04ec62 --- /dev/null +++ b/sdk/test-utils/recorder/.tshy/browser.json @@ -0,0 +1,12 @@ +{ + "extends": "./build.json", + "include": [ + "../src/**/*.ts", + "../src/**/*.mts", + "../src/**/*.tsx" + ], + "exclude": [], + "compilerOptions": { + "outDir": "../.tshy-build/browser" + } +} diff --git a/sdk/test-utils/recorder/.tshy/build.json b/sdk/test-utils/recorder/.tshy/build.json new file mode 100644 index 000000000000..c51b7f53052c --- /dev/null +++ b/sdk/test-utils/recorder/.tshy/build.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "../src", + "target": "es2022", + "module": "nodenext", + "moduleResolution": "nodenext" + } +} diff --git a/sdk/test-utils/recorder/.tshy/commonjs.json b/sdk/test-utils/recorder/.tshy/commonjs.json new file mode 100644 index 000000000000..b9db1350ddcc --- /dev/null +++ b/sdk/test-utils/recorder/.tshy/commonjs.json @@ -0,0 +1,16 @@ +{ + "extends": "./build.json", + "include": [ + "../src/**/*.ts", + "../src/**/*.cts", + "../src/**/*.tsx" + ], + "exclude": [ + "../src/**/*.mts", + "../src/utils/encoding-browser.mts", + "../src/utils/relativePathCalculator-browser.mts" + ], + "compilerOptions": { + "outDir": "../.tshy-build/commonjs" + } +} diff --git a/sdk/test-utils/recorder/.tshy/esm.json b/sdk/test-utils/recorder/.tshy/esm.json new file mode 100644 index 000000000000..69aabf988587 --- /dev/null +++ b/sdk/test-utils/recorder/.tshy/esm.json @@ -0,0 +1,15 @@ +{ + "extends": "./build.json", + "include": [ + "../src/**/*.ts", + "../src/**/*.mts", + "../src/**/*.tsx" + ], + "exclude": [ + ".././src/utils/encoding-browser.mts", + ".././src/utils/relativePathCalculator-browser.mts" + ], + "compilerOptions": { + "outDir": "../.tshy-build/esm" + } +} diff --git a/sdk/test-utils/recorder/.tshy/react-native.json b/sdk/test-utils/recorder/.tshy/react-native.json new file mode 100644 index 000000000000..93ce67a66563 --- /dev/null +++ b/sdk/test-utils/recorder/.tshy/react-native.json @@ -0,0 +1,15 @@ +{ + "extends": "./build.json", + "include": [ + "../src/**/*.ts", + "../src/**/*.mts", + "../src/**/*.tsx" + ], + "exclude": [ + ".././src/utils/encoding-browser.mts", + ".././src/utils/relativePathCalculator-browser.mts" + ], + "compilerOptions": { + "outDir": "../.tshy-build/react-native" + } +} diff --git a/sdk/test-utils/recorder/karma.conf.js b/sdk/test-utils/recorder/karma.conf.js deleted file mode 100644 index 42f86072b3f1..000000000000 --- a/sdk/test-utils/recorder/karma.conf.js +++ /dev/null @@ -1,124 +0,0 @@ -// https://github.com/karma-runner/karma-chrome-launcher -const { relativeRecordingsPath, relativeAssetsPath } = require("./dist/index.js"); -process.env.CHROME_BIN = require("puppeteer").executablePath(); -require("dotenv").config({ path: "../.env" }); - -process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath(); - -module.exports = function (config) { - config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: "./", - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ["mocha"], - - plugins: [ - "karma-mocha", - "karma-mocha-reporter", - "karma-chrome-launcher", - "karma-firefox-launcher", - "karma-env-preprocessor", - "karma-coverage", - "karma-sourcemap-loader", - "karma-junit-reporter", - "karma-json-preprocessor", - ], - - // list of files / patterns to load in the browser - files: [ - "dist-test/index.browser.js", - { pattern: "dist-test/index.browser.js.map", type: "html", included: false, served: true }, - ], - - // list of files / patterns to exclude - exclude: [], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - "**/*.js": ["sourcemap", "env"], - // IMPORTANT: COMMENT following line if you want to debug in your browsers!! - // Preprocess source file to calculate code coverage, however this will make source file unreadable - // "dist-test/index.browser.js": ["coverage"] - }, - - // inject following environment values into browser testing with window.__env__ - // environment values MUST be exported or set with same console running "karma start" - // https://www.npmjs.com/package/karma-env-preprocessor - envPreprocessor: ["RECORDINGS_RELATIVE_PATH"], - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["mocha", "coverage", "junit"], - - coverageReporter: { - // specify a common output directory - dir: "coverage-browser/", - reporters: [ - { type: "json", subdir: ".", file: "coverage.json" }, - { type: "lcovonly", subdir: ".", file: "lcov.info" }, - { type: "html", subdir: "html" }, - { type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" }, - ], - }, - - junitReporter: { - outputDir: "", // results will be saved as $outputDir/$browserName.xml - outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile - suite: "", // suite will become the package name attribute in xml testsuite element - useBrowserName: false, // add browser name to report and classes names - nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element - classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element - properties: {}, // key value pair of properties to add to the section of the report - }, - - // web server port - port: 9328, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - // 'ChromeHeadless', 'Chrome', 'Firefox', 'Edge', 'IE' - // --no-sandbox allows our tests to run in Linux without having to change the system. - // --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex. - browsers: ["ChromeHeadlessNoSandbox"], - customLaunchers: { - ChromeHeadlessNoSandbox: { - base: "ChromeHeadless", - flags: ["--no-sandbox", "--disable-web-security"], - }, - }, - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: 1, - - browserNoActivityTimeout: 600000, - browserDisconnectTimeout: 10000, - browserDisconnectTolerance: 3, - - client: { - mocha: { - // change Karma's debug.html to the mocha web reporter - reporter: "html", - timeout: "600000", - }, - }, - }); -}; diff --git a/sdk/test-utils/recorder/package.json b/sdk/test-utils/recorder/package.json index 0b573097b7e7..78319e83a399 100644 --- a/sdk/test-utils/recorder/package.json +++ b/sdk/test-utils/recorder/package.json @@ -3,32 +3,24 @@ "version": "3.1.0", "sdk-type": "utility", "description": "This library provides interfaces and helper methods to provide recording and playback capabilities for the tests in Azure JS/TS SDKs", - "main": "dist/index.js", - "module": "dist-esm/src/index.js", - "types": "./types/src/index.d.ts", - "browser": { - "./dist-esm/src/utils/relativePathCalculator.js": "./dist-esm/src/utils/relativePathCalculator.browser.js", - "./dist-esm/src/utils/env.js": "./dist-esm/src/utils/env.browser.js", - "./dist-esm/src/utils/encoding.js": "./dist-esm/src/utils/encoding.browser.js", - "./dist-esm/test/utils/server.js": "./dist-esm/test/utils/server.browser.js" - }, + "type": "module", "scripts": { "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit", "build:browser": "echo skipped", - "build:node": "tsc -p . && dev-tool run bundle", + "build:node": "tshy && dev-tool run build-test", "build:samples": "echo Skipped.", - "build:test": "tsc -p . && dev-tool run bundle", - "build": "npm run clean && tsc -p . && dev-tool run bundle", + "build:test": "tshy && dev-tool run build-test", + "build": "npm run clean && tshy && dev-tool run build-test", "check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "clean": "rimraf dist dist-esm test-dist typings *.tgz *.log", + "clean": "rimraf --glob dist dist-test *.tgz *.log", "extract-api": "echo skipped", "format": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", "integration-test:browser": "concurrently \"npm run tests:server\" \"npm run test:browser-with-proxy\" --kill-others --success first", "integration-test:node": "concurrently \"npm run tests:server\" \"npm run test:node-with-proxy\" --kill-others --success first", - "test:node-with-proxy": "dev-tool run test:node-ts-input -- --timeout 1200000 \"test/*.spec.ts\"", - "test:browser-with-proxy": "dev-tool run test:browser", + "test:node-with-proxy": "dev-tool run test:vitest", + "test:browser-with-proxy": "dev-tool run test:vitest --browser", "integration-test": "npm run integration-test:node && npm run integration-test:browser", - "tests:server": "cross-env TS_NODE_COMPILER_OPTIONS=\"{\\\"module\\\": \\\"commonjs\\\"}\" ts-node test/utils/server.ts", + "tests:server": "node server.mjs", "lint:fix": "eslint --no-eslintrc -c ../../.eslintrc.internal.json package.json src test --ext .ts --fix --fix-type [problem,suggestion]", "lint": "eslint --no-eslintrc -c ../../.eslintrc.internal.json package.json src test --ext .ts", "pack": "npm pack 2>&1", @@ -42,8 +34,6 @@ }, "files": [ "dist/", - "dist-esm/src/", - "types/src", "README.md", "LICENSE" ], @@ -68,37 +58,63 @@ "@azure/core-rest-pipeline": "^1.13.0", "@azure/core-auth": "^1.3.2", "@azure/core-util": "^1.6.1", - "@azure/logger": "^1.0.0" + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" }, "devDependencies": { "@azure/core-client": "^1.0.0", "@azure/dev-tool": "^1.0.0", "@azure/eslint-plugin-azure-sdk": "^3.0.0", - "@types/express": "^4.16.0", - "@types/fs-extra": "^8.0.0", - "@types/chai": "^4.1.6", - "@types/mocha": "^10.0.0", + "@types/express": "^4.17.21", "@types/node": "^18.0.0", - "c8": "^8.0.0", - "chai": "^4.2.0", - "concurrently": "^8.2.0", + "@vitest/browser": "^1.3.1", + "@vitest/coverage-istanbul": "^1.3.1", + "concurrently": "^8.2.2", "cross-env": "7.0.3", - "dotenv": "^16.0.0", - "eslint": "^8.0.0", - "express": "^4.16.3", - "karma": "^6.2.0", - "karma-chrome-launcher": "^3.0.0", - "karma-coverage": "^2.0.0", - "karma-env-preprocessor": "^0.1.1", - "karma-firefox-launcher": "^1.1.0", - "karma-junit-reporter": "^2.0.1", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-sourcemap-loader": "^0.3.8", - "mocha": "^10.0.0", - "rimraf": "^3.0.0", - "tslib": "^2.2.0", - "ts-node": "^10.0.0", - "typescript": "~5.3.3" - } + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "express": "^4.18.2", + "rimraf": "^5.0.5", + "tshy": "^1.11.1", + "typescript": "~5.3.3", + "vitest": "^1.3.1" + }, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts" + }, + "dialects": [ + "esm", + "commonjs" + ], + "esmDialects": [ + "browser", + "react-native" + ], + "selfLink": false + }, + "exports": { + "./package.json": "./package.json", + ".": { + "browser": { + "types": "./dist/browser/index.d.ts", + "default": "./dist/browser/index.js" + }, + "react-native": { + "types": "./dist/react-native/index.d.ts", + "default": "./dist/react-native/index.js" + }, + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "main": "./dist/commonjs/index.js", + "types": "./dist/commonjs/index.d.ts" } diff --git a/sdk/test-utils/recorder/test/utils/server.ts b/sdk/test-utils/recorder/server.mjs similarity index 97% rename from sdk/test-utils/recorder/test/utils/server.ts rename to sdk/test-utils/recorder/server.mjs index 50e877413ef6..f854f043c04d 100644 --- a/sdk/test-utils/recorder/test/utils/server.ts +++ b/sdk/test-utils/recorder/server.mjs @@ -2,7 +2,7 @@ // Licensed under the MIT license. import express from "express"; -import { randomUUID } from "@azure/core-util"; +import { randomUUID } from "crypto"; const app = express(); const port = 8080; diff --git a/sdk/test-utils/recorder/src/index.ts b/sdk/test-utils/recorder/src/index.ts index 74118cabef02..f927f667aa89 100644 --- a/sdk/test-utils/recorder/src/index.ts +++ b/sdk/test-utils/recorder/src/index.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export { Recorder } from "./recorder"; -export { relativeRecordingsPath } from "./utils/relativePathCalculator"; +export { Recorder } from "./recorder.js"; +export { relativeRecordingsPath } from "./utils/relativePathCalculator.js"; export { SanitizerOptions, RecorderStartOptions, @@ -12,7 +12,7 @@ export { assertEnvironmentVariable, setEnvironmentVariables, testPollingOptions, -} from "./utils/utils"; -export { env } from "./utils/env"; -export { delay } from "./utils/delay"; -export { CustomMatcherOptions } from "./matcher"; +} from "./utils/utils.js"; +export { env } from "./utils/env.js"; +export { delay } from "./utils/delay.js"; +export { CustomMatcherOptions } from "./matcher.js"; diff --git a/sdk/test-utils/recorder/src/matcher.ts b/sdk/test-utils/recorder/src/matcher.ts index 1403e85c0d2e..2d857a9e5635 100644 --- a/sdk/test-utils/recorder/src/matcher.ts +++ b/sdk/test-utils/recorder/src/matcher.ts @@ -2,9 +2,9 @@ // Licensed under the MIT license. import { createPipelineRequest, HttpClient } from "@azure/core-rest-pipeline"; -import { logger } from "./log"; -import { paths } from "./utils/paths"; -import { RecorderError } from "./utils/utils"; +import { logger } from "./log.js"; +import { paths } from "./utils/paths.js"; +import { RecorderError } from "./utils/utils.js"; export type Matcher = "HeaderlessMatcher" | "BodilessMatcher" | "CustomDefaultMatcher"; diff --git a/sdk/test-utils/recorder/src/options.ts b/sdk/test-utils/recorder/src/options.ts index 52f1496c9bee..091876dace59 100644 --- a/sdk/test-utils/recorder/src/options.ts +++ b/sdk/test-utils/recorder/src/options.ts @@ -1,6 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { createHttpHeaders, createPipelineRequest, HttpClient } from "@azure/core-rest-pipeline"; -import { paths } from "./utils/paths"; -import { RecorderError } from "./utils/utils"; +import { paths } from "./utils/paths.js"; +import { RecorderError } from "./utils/utils.js"; export type RecordingOptions = { handleRedirects?: boolean; diff --git a/sdk/test-utils/recorder/src/recorder.ts b/sdk/test-utils/recorder/src/recorder.ts index f94374f7278a..18461d560388 100644 --- a/sdk/test-utils/recorder/src/recorder.ts +++ b/sdk/test-utils/recorder/src/recorder.ts @@ -19,22 +19,60 @@ import { RecorderError, RecorderStartOptions, RecordingStateManager, -} from "./utils/utils"; -import { Test } from "mocha"; -import { assetsJsonPath, sessionFilePath } from "./utils/sessionFilePath"; -import { SanitizerOptions } from "./utils/utils"; -import { paths } from "./utils/paths"; -import { addSanitizers, transformsInfo } from "./sanitizer"; -import { handleEnvSetup } from "./utils/envSetupForPlayback"; -import { CustomMatcherOptions, Matcher, setMatcher } from "./matcher"; -import { addTransform, Transform } from "./transform"; -import { createRecordingRequest } from "./utils/createRecordingRequest"; -import { logger } from "./log"; -import { setRecordingOptions } from "./options"; +} from "./utils/utils.js"; +import { assetsJsonPath, sessionFilePath, TestContext } from "./utils/sessionFilePath.js"; +import { SanitizerOptions } from "./utils/utils.js"; +import { paths } from "./utils/paths.js"; +import { addSanitizers, transformsInfo } from "./sanitizer.js"; +import { handleEnvSetup } from "./utils/envSetupForPlayback.js"; +import { CustomMatcherOptions, Matcher, setMatcher } from "./matcher.js"; +import { addTransform, Transform } from "./transform.js"; +import { createRecordingRequest } from "./utils/createRecordingRequest.js"; +import { logger } from "./log.js"; +import { setRecordingOptions } from "./options.js"; import { isBrowser, isNode } from "@azure/core-util"; -import { env } from "./utils/env"; -import { decodeBase64 } from "./utils/encoding"; +import { env } from "./utils/env.js"; +import { decodeBase64 } from "./utils/encoding.js"; import { AdditionalPolicyConfig } from "@azure/core-client"; +import { isMochaTest, isVitestTestContext, TestInfo, VitestSuite } from "./testInfo.js"; + +/** + * Caculates session file path and JSON assets path from test context + * + * @internal + */ +export function calculatePaths(testContext: TestInfo): TestContext { + if (isMochaTest(testContext)) { + if (!testContext.parent) { + throw new RecorderError( + `The parent of test '${testContext.title}' is undefined, so a file path for its recording could not be generated. Please place the test inside a describe block.`, + ); + } + return { + suiteTitle: testContext.parent.fullTitle(), + testTitle: testContext.title, + }; + } else if (isVitestTestContext(testContext)) { + if (!testContext.task.name || !testContext.task.suite.name) { + throw new RecorderError( + `Unable to determine the recording file path. Unexpected empty Vitest context`, + ); + } + const suites: string[] = []; + let p: VitestSuite | undefined = testContext.task.suite; + while (p?.name) { + suites.push(p.name); + p = p.suite; + } + + return { + suiteTitle: suites.reverse().join("_"), + testTitle: testContext.task.name, + }; + } else { + throw new RecorderError(`Unrecognized test info: ${testContext}`); + } +} /** * This client manages the recorder life cycle and interacts with the proxy-tool to do the recording, @@ -54,19 +92,23 @@ export class Recorder { private variables: Record; private matcherSet = false; - constructor(private testContext?: Test | undefined) { + constructor(private testContext?: TestInfo) { + if (!this.testContext) { + throw new Error( + "Unable to determine the recording file path, testContext provided is not defined.", + ); + } + logger.info(`[Recorder#constructor] Creating a recorder instance in ${getTestMode()} mode`); if (isRecordMode() || isPlaybackMode()) { - if (this.testContext) { - this.sessionFile = sessionFilePath(this.testContext); - this.assetsJson = assetsJsonPath(); + const context = calculatePaths(this.testContext); + + this.sessionFile = sessionFilePath(context); + this.assetsJson = assetsJsonPath(); + if (this.testContext) { logger.info(`[Recorder#constructor] Using a session file located at ${this.sessionFile}`); this.httpClient = createDefaultHttpClient(); - } else { - throw new Error( - "Unable to determine the recording file path, testContext provided is not defined.", - ); } } this.variables = {}; diff --git a/sdk/test-utils/recorder/src/sanitizer.ts b/sdk/test-utils/recorder/src/sanitizer.ts index 35f5eea6fe9e..edb46b283f25 100644 --- a/sdk/test-utils/recorder/src/sanitizer.ts +++ b/sdk/test-utils/recorder/src/sanitizer.ts @@ -1,8 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { HttpClient } from "@azure/core-rest-pipeline"; -import { logger } from "./log"; -import { getRealAndFakePairs } from "./utils/connectionStringHelpers"; -import { createRecordingRequest } from "./utils/createRecordingRequest"; -import { paths } from "./utils/paths"; +import { logger } from "./log.js"; +import { getRealAndFakePairs } from "./utils/connectionStringHelpers.js"; +import { createRecordingRequest } from "./utils/createRecordingRequest.js"; +import { paths } from "./utils/paths.js"; import { ConnectionStringSanitizer, ContinuationSanitizer, @@ -15,7 +18,7 @@ import { RecorderError, RemoveHeaderSanitizer, SanitizerOptions, -} from "./utils/utils"; +} from "./utils/utils.js"; /** * Signature of a function that adds a sanitizer of type T. diff --git a/sdk/test-utils/recorder/src/testInfo.ts b/sdk/test-utils/recorder/src/testInfo.ts new file mode 100644 index 000000000000..876013721343 --- /dev/null +++ b/sdk/test-utils/recorder/src/testInfo.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * Represents a Test. + */ +export type TestInfo = MochaTest | VitestTestContext; + +/** + * Represents a Mocha Test. + */ +export interface MochaTest { + /** + * The title of the test. + */ + title: string; + /** + * The parent of the Mocha Test Suite. + */ + parent?: MochaTestSuite; +} + +/** + * Represents a Mocha Test Suite. + */ +export interface MochaTestSuite { + fullTitle(): string; +} + +/** + * Represents a Vitest Test Context + */ +export interface VitestTestContext { + /** + * The Vitest Context Task. + */ + task: VitestTask; +} + +export interface VitestTaskBase { + name: string; + suite?: VitestSuite; +} + +/** + * Represents a Vitest Test Context Task + */ +export interface VitestTask extends VitestTaskBase { + /** + * The Vitest Context Task Name. + */ + name: string; + /** + * The Vitest Context Task Suite. + */ + suite: VitestSuite; +} + +/** + * Represents a Vitest Test Suite. + */ +export interface VitestSuite extends VitestTaskBase { + /** + * The Vitest Context Task Suite Name. + */ + name: string; +} + +/** + * Determines whether the given test is a Mocha Test. + * @param test - The test to check. + * @returns true if the given test is a Mocha Test. + */ +export function isMochaTest(test: unknown): test is MochaTest { + return typeof test === "object" && test != null && "title" in test; +} + +/** + * Determines whether the given test is a Vitest Test. + * @param test - The test to check. + * @returns true if the given test is a Vitest Test. + */ +export function isVitestTestContext(test: unknown): test is VitestTestContext { + return ( + typeof test == "function" && + "task" in test && + typeof test.task === "object" && + test.task != null && + "name" in test.task + ); +} diff --git a/sdk/test-utils/recorder/src/transform.ts b/sdk/test-utils/recorder/src/transform.ts index 13ab3436bbae..701805ff45a5 100644 --- a/sdk/test-utils/recorder/src/transform.ts +++ b/sdk/test-utils/recorder/src/transform.ts @@ -2,9 +2,9 @@ // Licensed under the MIT license. import { createPipelineRequest, HttpClient } from "@azure/core-rest-pipeline"; -import { paths } from "./utils/paths"; -import { RecorderError } from "./utils/utils"; -import { logger } from "./log"; +import { paths } from "./utils/paths.js"; +import { RecorderError } from "./utils/utils.js"; +import { logger } from "./log.js"; interface ApplyCondition { uriRegex: string; diff --git a/sdk/test-utils/recorder/src/utils/delay.ts b/sdk/test-utils/recorder/src/utils/delay.ts index 306ca65443bf..76400a7d7082 100644 --- a/sdk/test-utils/recorder/src/utils/delay.ts +++ b/sdk/test-utils/recorder/src/utils/delay.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { isPlaybackMode } from "./utils"; +import { isPlaybackMode } from "./utils.js"; /** * Usage - `await delay()` diff --git a/sdk/test-utils/recorder/src/utils/encoding-browser.mts b/sdk/test-utils/recorder/src/utils/encoding-browser.mts new file mode 100644 index 000000000000..7d59198b1207 --- /dev/null +++ b/sdk/test-utils/recorder/src/utils/encoding-browser.mts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export const encodeBase64 = (data: string): string => btoa(data); +export const decodeBase64 = (data: string): string => atob(data); diff --git a/sdk/test-utils/recorder/src/utils/encoding.browser.ts b/sdk/test-utils/recorder/src/utils/encoding.browser.ts deleted file mode 100644 index 3d16fc49c958..000000000000 --- a/sdk/test-utils/recorder/src/utils/encoding.browser.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export const encodeBase64 = (data: string) => btoa(data); -export const decodeBase64 = (data: string) => atob(data); diff --git a/sdk/test-utils/recorder/src/utils/encoding.ts b/sdk/test-utils/recorder/src/utils/encoding.ts index 4699350e1107..368f70d87a96 100644 --- a/sdk/test-utils/recorder/src/utils/encoding.ts +++ b/sdk/test-utils/recorder/src/utils/encoding.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export const encodeBase64 = (data: string) => Buffer.from(data).toString("base64"); -export const decodeBase64 = (data: string) => Buffer.from(data, "base64").toString("ascii"); +export const encodeBase64 = (data: string): string => Buffer.from(data).toString("base64"); +export const decodeBase64 = (data: string): string => Buffer.from(data, "base64").toString("ascii"); diff --git a/sdk/test-utils/recorder/src/utils/env.browser.ts b/sdk/test-utils/recorder/src/utils/env-browser.ts similarity index 100% rename from sdk/test-utils/recorder/src/utils/env.browser.ts rename to sdk/test-utils/recorder/src/utils/env-browser.ts diff --git a/sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts b/sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts index 625dab92c1f6..119920eb50c3 100644 --- a/sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts +++ b/sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts @@ -2,15 +2,15 @@ // Licensed under the MIT license. import { HttpClient } from "@azure/core-rest-pipeline"; -import { logger } from "../log"; -import { addSanitizers } from "../sanitizer"; -import { env } from "./env"; +import { logger } from "../log.js"; +import { addSanitizers } from "../sanitizer.js"; +import { env } from "./env.js"; import { isPlaybackMode, isRecordMode, setEnvironmentVariables, FindReplaceSanitizer, -} from "./utils"; +} from "./utils.js"; /** * Supposed to be used in record and playback modes. diff --git a/sdk/test-utils/recorder/src/utils/relativePathCalculator.browser.ts b/sdk/test-utils/recorder/src/utils/relativePathCalculator-browser.mts similarity index 84% rename from sdk/test-utils/recorder/src/utils/relativePathCalculator.browser.ts rename to sdk/test-utils/recorder/src/utils/relativePathCalculator-browser.mts index c8d2bb051748..c88ddfeabfef 100644 --- a/sdk/test-utils/recorder/src/utils/relativePathCalculator.browser.ts +++ b/sdk/test-utils/recorder/src/utils/relativePathCalculator-browser.mts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { env } from "./env"; -import { RecorderError } from "./utils"; +import { env } from "./env.js"; +import { RecorderError } from "./utils.js"; export function relativeRecordingsPath(): string { if (env.RECORDINGS_RELATIVE_PATH) { diff --git a/sdk/test-utils/recorder/src/utils/relativePathCalculator.ts b/sdk/test-utils/recorder/src/utils/relativePathCalculator.ts index bcf0ea96986e..383e03cb5044 100644 --- a/sdk/test-utils/recorder/src/utils/relativePathCalculator.ts +++ b/sdk/test-utils/recorder/src/utils/relativePathCalculator.ts @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import path from "path"; -import fs from "fs"; -import { RecorderError } from "./utils"; +import path from "node:path"; +import fs from "node:fs"; +import { RecorderError } from "./utils.js"; /** * Replace backslashes in a path with forward slashes so they are not treated as escape characters diff --git a/sdk/test-utils/recorder/src/utils/sessionFilePath.ts b/sdk/test-utils/recorder/src/utils/sessionFilePath.ts index f494814540cb..5918d4d62675 100644 --- a/sdk/test-utils/recorder/src/utils/sessionFilePath.ts +++ b/sdk/test-utils/recorder/src/utils/sessionFilePath.ts @@ -2,11 +2,15 @@ // Licensed under the MIT license. import { isNode } from "@azure/core-util"; -import { generateTestRecordingFilePath } from "./filePathGenerator"; -import { relativeRecordingsPath } from "./relativePathCalculator"; -import { RecorderError } from "./utils"; +import { generateTestRecordingFilePath } from "./filePathGenerator.js"; +import { relativeRecordingsPath } from "./relativePathCalculator.js"; -export function sessionFilePath(testContext: Mocha.Test): string { +export interface TestContext { + suiteTitle: string; // describe(suiteTitle, () => {}) + testTitle: string; // it(testTitle, () => {}) +} + +export function sessionFilePath(testContext: TestContext): string { // sdk/service/project/recordings/{node|browsers}//recording_.json return `${relativeRecordingsPath()}/${recordingFilePath(testContext)}`; } @@ -16,17 +20,11 @@ export function sessionFilePath(testContext: Mocha.Test): string { * * `{node|browsers}//recording_.json` */ -export function recordingFilePath(testContext: Mocha.Test): string { - if (!testContext.parent) { - throw new RecorderError( - `Test ${testContext.title} is not inside a describe block, so a file path for its recording could not be generated. Please place the test inside a describe block.`, - ); - } - +export function recordingFilePath(testContext: TestContext): string { return generateTestRecordingFilePath( isNode ? "node" : "browsers", - testContext.parent.fullTitle(), - testContext.title, + testContext.suiteTitle, + testContext.testTitle, ); } diff --git a/sdk/test-utils/recorder/src/utils/utils.ts b/sdk/test-utils/recorder/src/utils/utils.ts index 19cbe16eb927..aba5626faaec 100644 --- a/sdk/test-utils/recorder/src/utils/utils.ts +++ b/sdk/test-utils/recorder/src/utils/utils.ts @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { env } from "./env"; +import { env } from "./env.js"; /** * A custom error type for failed pipeline requests. */ diff --git a/sdk/test-utils/recorder/test/errors.spec.ts b/sdk/test-utils/recorder/test/errors.spec.ts index 42211e6ecc2b..d0afafcd1cb9 100644 --- a/sdk/test-utils/recorder/test/errors.spec.ts +++ b/sdk/test-utils/recorder/test/errors.spec.ts @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { RecorderError, RecordingStateManager } from "../src/utils/utils"; -import { expect } from "chai"; -import { Recorder } from "../src/recorder"; +import { describe, it, assert, beforeEach } from "vitest"; +import { RecorderError, RecordingStateManager } from "../src/utils/utils.js"; +import { Recorder } from "../src/recorder.js"; import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline"; -import { encodeBase64 } from "../src/utils/encoding"; +import { encodeBase64 } from "../src/utils/encoding.js"; describe("State Manager", function () { it("throws error if started twice", function () { @@ -15,8 +15,8 @@ describe("State Manager", function () { manager.state = "started"; throw new Error("should not have reached here, previous assignment should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal( + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "Already started, should not have called start again.", ); } @@ -28,8 +28,8 @@ describe("State Manager", function () { manager.state = "stopped"; throw new Error("should not have reached here, previous assignment should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal( + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "Already stopped, should not have called stop again.", ); } @@ -38,8 +38,8 @@ describe("State Manager", function () { describe("handleTestProxyErrors", function () { let recorder: Recorder; - beforeEach(function () { - recorder = new Recorder(this.currentTest); + beforeEach(function (ctx) { + recorder = new Recorder(ctx); }); it("x-request-mismatch header", function () { @@ -53,8 +53,8 @@ describe("handleTestProxyErrors", function () { headers, }); } catch (error) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal("this is a mismatch error"); + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "this is a mismatch error"); } }); }); diff --git a/sdk/test-utils/recorder/test/recorder.spec.ts b/sdk/test-utils/recorder/test/recorder.spec.ts new file mode 100644 index 000000000000..a64a4a4f0848 --- /dev/null +++ b/sdk/test-utils/recorder/test/recorder.spec.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { describe, it, assert } from "vitest"; +import { calculatePaths } from "../src/recorder.js"; + +describe("Recorder file paths", () => { + it("calculates paths for a Mocha test", () => { + const mochaTest = { + title: "mocha test title", + parent: { + fullTitle: () => "mocha suite title", + }, + }; + const context = calculatePaths(mochaTest); + + assert.equal(context, { + suiteTitle: "mocha suite title", + testTitle: "mocha test title", + }); + }); + + it("calculates paths for a vitest test", () => { + const vitestTest = () => { + /* no-op */ + }; + (vitestTest as any).task = { + name: "vitest test title", + suite: { + name: "vitest suite title", + }, + }; + + const context = calculatePaths(vitestTest as any); + assert.equal(context, { + suiteTitle: "vitest suite title", + testTitle: "vitest test title", + }); + }); + + it("calculates paths for a vitest test with nested suites", () => { + const vitestTest = () => { + /* no-op */ + }; + (vitestTest as any).task = { + name: "vitest test title", + suite: { + name: "vitest suite title", + suite: { + name: "toplevel suite", + }, + }, + }; + + const context = calculatePaths(vitestTest as any); + assert.equal(context, { + suiteTitle: "toplevel suite_vitest suite title", + testTitle: "vitest test title", + }); + }); +}); diff --git a/sdk/test-utils/recorder/test/sanitizers.spec.ts b/sdk/test-utils/recorder/test/sanitizers.spec.ts index dda06da3634a..c5ac14e9dd19 100644 --- a/sdk/test-utils/recorder/test/sanitizers.spec.ts +++ b/sdk/test-utils/recorder/test/sanitizers.spec.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { describe, it, beforeAll, beforeEach, afterEach } from "vitest"; import { ServiceClient } from "@azure/core-client"; -import { env, isPlaybackMode, Recorder } from "../src"; -import { TestMode } from "../src/utils/utils"; -import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./utils/utils"; +import { env, isPlaybackMode, Recorder } from "../src/index.js"; +import { TestMode } from "../src/utils/utils.js"; +import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./utils/utils.js"; import { randomUUID } from "@azure/core-util"; // These tests require the following to be running in parallel @@ -18,14 +19,14 @@ import { randomUUID } from "@azure/core-util"; const secretValue = "abcdef"; let currentValue: string; - before(() => { + beforeAll(() => { setTestMode(mode); }); - beforeEach(async function () { + beforeEach(async function (ctx) { env.TEST_VARIABLE_1 = "the answer!"; env.TEST_VARIABLE_2 = "answer!"; - recorder = new Recorder(this.currentTest); + recorder = new Recorder(ctx); client = new ServiceClient(recorder.configureClientOptions({ baseUri: TEST_SERVER_URL })); currentValue = isPlaybackMode() ? fakeSecretValue : secretValue; }); diff --git a/sdk/test-utils/recorder/test/testProxyClient.spec.ts b/sdk/test-utils/recorder/test/testProxyClient.spec.ts index 963c82bb282b..9cc32b8b6ae4 100644 --- a/sdk/test-utils/recorder/test/testProxyClient.spec.ts +++ b/sdk/test-utils/recorder/test/testProxyClient.spec.ts @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { describe, it, assert, beforeEach, afterEach } from "vitest"; import { createHttpHeaders, HttpClient, PipelineRequest, PipelineResponse, } from "@azure/core-rest-pipeline"; -import { expect } from "chai"; -import { env, Recorder } from "../src"; -import { createRecordingRequest } from "../src/utils/createRecordingRequest"; -import { paths } from "../src/utils/paths"; -import { getTestMode, isLiveMode, isRecordMode, RecorderError } from "../src/utils/utils"; +import { env, Recorder } from "../src/index.js"; +import { createRecordingRequest } from "../src/utils/createRecordingRequest.js"; +import { paths } from "../src/utils/paths.js"; +import { getTestMode, isLiveMode, isRecordMode, RecorderError } from "../src/utils/utils.js"; +import { TestInfo } from "../src/testInfo.js"; const testRedirectedRequest = ( client: Recorder, @@ -20,17 +21,17 @@ const testRedirectedRequest = ( ) => { const redirectedRequest = makeRequest(); client["redirectRequest"](redirectedRequest); - expect(redirectedRequest).to.deep.equal(expectedModification(makeRequest())); + assert.deepEqual(redirectedRequest, expectedModification(makeRequest())); }; describe("TestProxyClient functions", () => { let client: Recorder; let clientHttpClient: HttpClient; - let testContext: Mocha.Test | undefined; - beforeEach(function () { - client = new Recorder(this.currentTest); + let testContext: TestInfo | undefined; + beforeEach(function (ctx) { + client = new Recorder(ctx); clientHttpClient = client["httpClient"] as HttpClient; - testContext = this.currentTest; + testContext = ctx; }); afterEach(() => { @@ -125,7 +126,7 @@ describe("TestProxyClient functions", () => { }); }; await client.start({ envSetupForPlayback: {} }); - expect(client.recordingId).to.eql(recordingId); + assert.equal(client.recordingId, recordingId); }, ); @@ -151,8 +152,8 @@ describe("TestProxyClient functions", () => { await client.start({ envSetupForPlayback: {} }); throw new Error("should not have reached here, start() call should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal("Start request failed."); + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "Start request failed."); } }); @@ -169,8 +170,8 @@ describe("TestProxyClient functions", () => { await client.start({ envSetupForPlayback: {} }); throw new Error("should not have reached here, start() call should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal( + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "No recording ID returned for a successful start request.", ); } @@ -204,8 +205,8 @@ describe("TestProxyClient functions", () => { await client.stop(); throw new Error("should not have reached here, stop() call should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal( + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "Bad state, recordingId is not defined when called stop.", ); } @@ -227,8 +228,8 @@ describe("TestProxyClient functions", () => { await client.stop(); throw new Error("should not have reached here, stop() call should have failed"); } catch (error: any) { - expect((error as RecorderError).name).to.equal("RecorderError"); - expect((error as RecorderError).message).to.equal("Stop request failed."); + assert.equal((error as RecorderError).name, "RecorderError"); + assert.equal((error as RecorderError).message, "Stop request failed."); } }); @@ -251,7 +252,7 @@ describe("TestProxyClient functions", () => { describe("variable method", () => { it("throws an error in record mode if a variable is accessed without giving it a value", () => { env.TEST_MODE = "record"; - expect(() => client.variable("nonExistentVariable")).to.throw( + assert.throws(() => client.variable("nonExistentVariable"), "Tried to access uninitialized variable: nonExistentVariable. You must initialize it with a value before using it.", ); }); @@ -259,24 +260,24 @@ describe("TestProxyClient functions", () => { it("sets the variable correctly in record mode", () => { env.TEST_MODE = "record"; client.variable("var1", "value"); - expect(client["variables"]["var1"]).to.equal("value"); + assert.equal(client["variables"]["var1"], "value"); }); it("allows for the shorthand syntax to be used in record mode after a variable is initialized", () => { env.TEST_MODE = "record"; client.variable("var1", "value"); - expect(client.variable("var1")).to.equal("value"); + assert.equal(client.variable("var1"), "value"); }); it("recalls the variable in playback mode", () => { env.TEST_MODE = "playback"; client["variables"]["var1"] = "realValue"; - expect(client.variable("var1", "ignored")).to.equal("realValue"); + assert.equal(client.variable("var1", "ignored"), "realValue"); }); it("throws an error if a variable does not exist in playback mode", () => { env.TEST_MODE = "playback"; - expect(() => client.variable("var1", "ignored")).to.throw( + assert.throws(() => client.variable("var1", "ignored"), "Tried to access a variable in playback that was not set in recording: var1", ); }); @@ -289,11 +290,11 @@ describe("TestProxyClient functions", () => { client["sessionFile"], client.recordingId, ); - expect(returnedRequest.url).to.equal(initialRequest.url); - expect(returnedRequest.method).to.equal("POST"); - expect(returnedRequest.body).not.to.be.undefined; - expect(returnedRequest.headers.get("x-recording-id")).to.equal(client.recordingId); - expect(returnedRequest.url).to.equal(initialRequest.url); + assert.equal(returnedRequest.url, initialRequest.url); + assert.equal(returnedRequest.method, "POST"); + assert.isDefined(returnedRequest.body); + assert.equal(returnedRequest.headers.get("x-recording-id"), client.recordingId); + assert.equal(returnedRequest.url, initialRequest.url); }); }); @@ -311,9 +312,9 @@ describe("TestProxyClient functions", () => { "Live", ].forEach((testMode) => { env.TEST_MODE = testMode; - expect(getTestMode()).to.equal(testMode.toLowerCase()); - expect(isRecordMode()).to.equal(testMode.toLowerCase() === "record"); - expect(isLiveMode()).to.equal(testMode.toLowerCase() === "live"); + assert.equal(getTestMode(), testMode.toLowerCase()); + assert.equal(isRecordMode(), testMode.toLowerCase() === "record"); + assert.equal(isLiveMode(), testMode.toLowerCase() === "live"); }); }); }); diff --git a/sdk/test-utils/recorder/test/testProxyTests.spec.ts b/sdk/test-utils/recorder/test/testProxyTests.spec.ts index 6fc6b65c06dc..7e6431b32550 100644 --- a/sdk/test-utils/recorder/test/testProxyTests.spec.ts +++ b/sdk/test-utils/recorder/test/testProxyTests.spec.ts @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { describe, it, assert, expect, beforeAll, beforeEach, afterEach } from "vitest"; import { ServiceClient } from "@azure/core-client"; import { createPipelineRequest } from "@azure/core-rest-pipeline"; -import assert from "assert"; -import { expect } from "chai"; -import { CustomMatcherOptions, isPlaybackMode, Recorder } from "../src"; -import { isLiveMode, TestMode } from "../src/utils/utils"; -import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./utils/utils"; +import { CustomMatcherOptions, isPlaybackMode, Recorder } from "../src/index.js"; +import { isLiveMode, TestMode } from "../src/utils/utils.js"; +import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./utils/utils.js"; // These tests require the following to be running in parallel // - utils/server.ts (to serve requests to act as a service) @@ -17,12 +16,12 @@ import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./ut let recorder: Recorder; let client: ServiceClient; - before(() => { + beforeAll(() => { setTestMode(mode); }); - beforeEach(async function () { - recorder = new Recorder(this.currentTest); + beforeEach(async function (ctx) { + recorder = new Recorder(ctx); client = new ServiceClient(recorder.configureClientOptions({ baseUri: TEST_SERVER_URL })); }); @@ -39,7 +38,7 @@ import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./ut ); }); - it("redirect (redirect location has host)", async function (this: Mocha.Context) { + it("redirect (redirect location has host)", async function () { await recorder.start({ envSetupForPlayback: {} }); await makeRequestAndVerifyResponse( @@ -49,7 +48,7 @@ import { TEST_SERVER_URL, makeRequestAndVerifyResponse, setTestMode } from "./ut ); }); - it("redirect (redirect location is relative)", async function (this: Mocha.Context) { + it("redirect (redirect location is relative)", async function () { await recorder.start({ envSetupForPlayback: {} }); await makeRequestAndVerifyResponse( diff --git a/sdk/test-utils/recorder/test/utils/server.browser.ts b/sdk/test-utils/recorder/test/utils/server.browser.ts deleted file mode 100644 index 953bf9b0b813..000000000000 --- a/sdk/test-utils/recorder/test/utils/server.browser.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -// Empty file for mapping diff --git a/sdk/test-utils/recorder/test/utils/utils.ts b/sdk/test-utils/recorder/test/utils/utils.ts index aca290bf8b57..c5166117202c 100644 --- a/sdk/test-utils/recorder/test/utils/utils.ts +++ b/sdk/test-utils/recorder/test/utils/utils.ts @@ -2,9 +2,9 @@ // Licensed under the MIT license. import { createPipelineRequest, HttpMethods, PipelineResponse } from "@azure/core-rest-pipeline"; -import { expect } from "chai"; -import { env } from "../../src"; -import { isLiveMode, TestMode } from "../../src/utils/utils"; +import { assert } from "vitest"; +import { env } from "../../src/index.js"; +import { isLiveMode, TestMode } from "../../src/utils/utils.js"; import { ServiceClient } from "@azure/core-client"; export const setTestMode = (mode: TestMode): TestMode => { @@ -46,12 +46,12 @@ export async function makeRequestAndVerifyResponse( throw new Error("Expected response.bodyAsText to be defined"); } - expect(JSON.parse(response.bodyAsText)).to.deep.equal(expectedResponse); + assert.deepEqual(JSON.parse(response.bodyAsText), expectedResponse); } if (expectedHeaders) { for (const [headerName, headerValue] of Object.entries(expectedHeaders)) { - expect(response.headers.get(headerName)).to.equal(headerValue); + assert.equal(response.headers.get(headerName), headerValue); } } diff --git a/sdk/test-utils/recorder/tsconfig.browser.config.json b/sdk/test-utils/recorder/tsconfig.browser.config.json new file mode 100644 index 000000000000..1b37aebc5457 --- /dev/null +++ b/sdk/test-utils/recorder/tsconfig.browser.config.json @@ -0,0 +1,10 @@ +{ + "extends": "./.tshy/build.json", + "include": ["./src/**/*.ts", "./src/**/*.mts", "./test/**/*.spec.ts"], + "exclude": ["./test/**/node/**/*.ts"], + "compilerOptions": { + "outDir": "./dist-test/browser", + "rootDir": ".", + "skipLibCheck": true + } +} diff --git a/sdk/test-utils/recorder/tsconfig.json b/sdk/test-utils/recorder/tsconfig.json index cc27adb959de..0a5c58dd458d 100644 --- a/sdk/test-utils/recorder/tsconfig.json +++ b/sdk/test-utils/recorder/tsconfig.json @@ -1,9 +1,18 @@ { "extends": "../../../tsconfig.package", "compilerOptions": { - "declarationDir": "./types", - "outDir": "./dist-esm", - "lib": ["dom"] + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": [ + "dom" + ] }, - "include": ["./src/**/*.ts", "./test/**/*.ts"] + "include": [ + "./src/**/*.ts", + "./src/**/*.mts", + "./src/**/*.cts", + "./test/**/*.ts", + "./test/**/*.mts", + "./test/**/*.cts" + ] } diff --git a/sdk/test-utils/recorder/vitest.browser.config.ts b/sdk/test-utils/recorder/vitest.browser.config.ts new file mode 100644 index 000000000000..0b05437f23ac --- /dev/null +++ b/sdk/test-utils/recorder/vitest.browser.config.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { defineConfig } from "vitest/config"; +import browserMap from "@azure-tools/vite-plugin-browser-test-map"; + +export default defineConfig({ + define: { + "process.env": process.env, + }, + plugins: [browserMap()], + test: { + reporters: ["basic", "junit"], + outputFile: { + junit: "test-results.browser.xml", + }, + browser: { + enabled: true, + headless: true, + name: "chromium", + provider: "playwright", + }, + fakeTimers: { + toFake: ["setTimeout", "Date"], + }, + watch: false, + include: ["dist-test/browser/**/*.spec.js"], + coverage: { + include: ["dist-test/browser/**/*.js"], + exclude: [ + "dist-test/browser/**/*./*-browser.mjs", + "dist-test/browser/**/*./*-react-native.mjs", + ], + provider: "istanbul", + reporter: ["text", "json", "html"], + reportsDirectory: "coverage-browser", + }, + }, +}); diff --git a/sdk/test-utils/recorder/vitest.config.ts b/sdk/test-utils/recorder/vitest.config.ts new file mode 100644 index 000000000000..e94c7dd91c76 --- /dev/null +++ b/sdk/test-utils/recorder/vitest.config.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + reporters: ["basic", "junit"], + outputFile: { + junit: "test-results.xml", + }, + fakeTimers: { + toFake: ["setTimeout", "Date"], + }, + watch: false, + include: ["test/**/*.spec.ts"], + exclude: ["test/**/browser/*.spec.ts"], + coverage: { + include: ["src/**/*.ts"], + exclude: [ + "src/**/*-browser.mts", + "src/**/*-react-native.mts", + "vitest*.config.ts", + "samples-dev/**/*.ts", + ], + provider: "istanbul", + reporter: ["text", "json", "html"], + reportsDirectory: "coverage", + }, + }, +}); From 073a00110293308de2b01f25a796d5a02297028a Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 28 Feb 2024 17:10:27 -0500 Subject: [PATCH 2/2] [recorder] update formatting --- sdk/test-utils/recorder/test/errors.spec.ts | 6 ++++-- sdk/test-utils/recorder/test/testProxyClient.spec.ts | 12 ++++++++---- sdk/test-utils/recorder/tsconfig.json | 4 +--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/sdk/test-utils/recorder/test/errors.spec.ts b/sdk/test-utils/recorder/test/errors.spec.ts index d0afafcd1cb9..a494cf87b950 100644 --- a/sdk/test-utils/recorder/test/errors.spec.ts +++ b/sdk/test-utils/recorder/test/errors.spec.ts @@ -16,7 +16,8 @@ describe("State Manager", function () { throw new Error("should not have reached here, previous assignment should have failed"); } catch (error: any) { assert.equal((error as RecorderError).name, "RecorderError"); - assert.equal((error as RecorderError).message, + assert.equal( + (error as RecorderError).message, "Already started, should not have called start again.", ); } @@ -29,7 +30,8 @@ describe("State Manager", function () { throw new Error("should not have reached here, previous assignment should have failed"); } catch (error: any) { assert.equal((error as RecorderError).name, "RecorderError"); - assert.equal((error as RecorderError).message, + assert.equal( + (error as RecorderError).message, "Already stopped, should not have called stop again.", ); } diff --git a/sdk/test-utils/recorder/test/testProxyClient.spec.ts b/sdk/test-utils/recorder/test/testProxyClient.spec.ts index 9cc32b8b6ae4..8ec82511d90c 100644 --- a/sdk/test-utils/recorder/test/testProxyClient.spec.ts +++ b/sdk/test-utils/recorder/test/testProxyClient.spec.ts @@ -171,7 +171,8 @@ describe("TestProxyClient functions", () => { throw new Error("should not have reached here, start() call should have failed"); } catch (error: any) { assert.equal((error as RecorderError).name, "RecorderError"); - assert.equal((error as RecorderError).message, + assert.equal( + (error as RecorderError).message, "No recording ID returned for a successful start request.", ); } @@ -206,7 +207,8 @@ describe("TestProxyClient functions", () => { throw new Error("should not have reached here, stop() call should have failed"); } catch (error: any) { assert.equal((error as RecorderError).name, "RecorderError"); - assert.equal((error as RecorderError).message, + assert.equal( + (error as RecorderError).message, "Bad state, recordingId is not defined when called stop.", ); } @@ -252,7 +254,8 @@ describe("TestProxyClient functions", () => { describe("variable method", () => { it("throws an error in record mode if a variable is accessed without giving it a value", () => { env.TEST_MODE = "record"; - assert.throws(() => client.variable("nonExistentVariable"), + assert.throws( + () => client.variable("nonExistentVariable"), "Tried to access uninitialized variable: nonExistentVariable. You must initialize it with a value before using it.", ); }); @@ -277,7 +280,8 @@ describe("TestProxyClient functions", () => { it("throws an error if a variable does not exist in playback mode", () => { env.TEST_MODE = "playback"; - assert.throws(() => client.variable("var1", "ignored"), + assert.throws( + () => client.variable("var1", "ignored"), "Tried to access a variable in playback that was not set in recording: var1", ); }); diff --git a/sdk/test-utils/recorder/tsconfig.json b/sdk/test-utils/recorder/tsconfig.json index 0a5c58dd458d..86f68719567f 100644 --- a/sdk/test-utils/recorder/tsconfig.json +++ b/sdk/test-utils/recorder/tsconfig.json @@ -3,9 +3,7 @@ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", - "lib": [ - "dom" - ] + "lib": ["dom"] }, "include": [ "./src/**/*.ts",