diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 2013c5387d84..d12fd11a3c7c 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -192,7 +192,6 @@ specifiers: '@rush-temp/template': file:./projects/template.tgz '@rush-temp/test-credential': file:./projects/test-credential.tgz '@rush-temp/test-recorder': file:./projects/test-recorder.tgz - '@rush-temp/test-recorder-new': file:./projects/test-recorder-new.tgz '@rush-temp/test-utils': file:./projects/test-utils.tgz '@rush-temp/test-utils-perf': file:./projects/test-utils-perf.tgz '@rush-temp/testing-recorder-new': file:./projects/testing-recorder-new.tgz @@ -392,7 +391,6 @@ dependencies: '@rush-temp/template': file:projects/template.tgz '@rush-temp/test-credential': file:projects/test-credential.tgz '@rush-temp/test-recorder': file:projects/test-recorder.tgz - '@rush-temp/test-recorder-new': file:projects/test-recorder-new.tgz '@rush-temp/test-utils': file:projects/test-utils.tgz '@rush-temp/test-utils-perf': file:projects/test-utils-perf.tgz '@rush-temp/testing-recorder-new': file:projects/testing-recorder-new.tgz @@ -449,6 +447,21 @@ packages: - supports-color dev: false + /@azure-tools/test-recorder/1.0.2: + resolution: {integrity: sha512-s29YTbvD6Pr2sTgRLHXXs8zJZmFnGO0n0F1UICxYMmf8hItgXvSa9DwTx/qg7j3v65hrFn+gx9IedI7YkLE5KA==} + engines: {node: '>=12.0.0'} + dependencies: + '@azure/core-http': 2.2.2 + '@azure/core-tracing': 1.0.0-preview.13 + fs-extra: 8.1.0 + md5: 2.3.0 + nise: 4.1.0 + nock: 12.0.3 + tslib: 2.3.1 + transitivePeerDependencies: + - supports-color + dev: false + /@azure/abort-controller/1.0.4: resolution: {integrity: sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==} engines: {node: '>=8.0.0'} @@ -1489,6 +1502,7 @@ packages: /@opentelemetry/node/0.22.0_@opentelemetry+api@1.0.3: resolution: {integrity: sha512-+HhGbDruQ7cwejVOIYyxRa28uosnG8W95NiQZ6qE8PXXPsDSyGeftAPbtYpGit0H2f5hrVcMlwmWHeAo9xkSLA==} engines: {node: '>=8.0.0'} + deprecated: Package renamed to @opentelemetry/sdk-trace-node peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: @@ -8136,12 +8150,13 @@ packages: dev: false file:projects/agrifood-farming.tgz: - resolution: {integrity: sha512-6HLJpG4IU5XCZfGM8/Nd5l+W4XzXV0a0UVKBLnTCw8VJEbDUCL9/8VAqd52uJiFzmOjVfR2ZmohSCysETn9UXA==, tarball: file:projects/agrifood-farming.tgz} + resolution: {integrity: sha512-tIbV71Sekmb01b/mAU9iH2ZNEe2nbzNTiGPFmlNRnGLOAEXkxRYdLDbSR26JEGicN3V76kUlOxbLl3wkCLHY+Q==, tarball: file:projects/agrifood-farming.tgz} name: '@rush-temp/agrifood-farming' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 '@azure-rest/core-client-paging': 1.0.0-beta.1 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -8182,10 +8197,11 @@ packages: dev: false file:projects/ai-anomaly-detector.tgz: - resolution: {integrity: sha512-vyIx2mIY55b8rYGptKRmKMLOCIrjXaHu62WE5UTJULDp9hjlEYx9+Ms1Cp2E+b/1GI/pzL3W8q/y7l6qoOi5mA==, tarball: file:projects/ai-anomaly-detector.tgz} + resolution: {integrity: sha512-+pPYOEXb6X1e6RQ+0N62qByd/srbq8SVmfnFzKt9EpjZ7tGRoZbWHKW7aE1EM9cdXqmllviVRsrluAIp1T9//A==, tarball: file:projects/ai-anomaly-detector.tgz} name: '@rush-temp/ai-anomaly-detector' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -8225,11 +8241,12 @@ packages: dev: false file:projects/ai-document-translator.tgz: - resolution: {integrity: sha512-pzYni3C2PkECKsWO3pWvfsL4xrxPSja6+Es3Red7nnwVk+DVVh06G2Bi+Bxyl7EBQ8W2OzIA66XmaF1S23f/7Q==, tarball: file:projects/ai-document-translator.tgz} + resolution: {integrity: sha512-WWzWMe/egbs1Hokt1C5BsRcBZPbR3EQpwuDHGtTM+cMu8QcQq1Vk0O28eDFxC6XhUapCKqjxDOhxVKc4CTDlFQ==, tarball: file:projects/ai-document-translator.tgz} name: '@rush-temp/ai-document-translator' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -8269,10 +8286,11 @@ packages: dev: false file:projects/ai-form-recognizer.tgz: - resolution: {integrity: sha512-DjMBkNtgOru8QhzYFXwGpURCa6pwmwnzy9YFQwGch4HOSzkN9Svw8shUPDeAXyv9H/ubT6y9vlkFZpK0V4owIw==, tarball: file:projects/ai-form-recognizer.tgz} + resolution: {integrity: sha512-IOblFmJdcB59H5CmcWnGRruFwYTBvLPesi9fah8L9yZO/1YdEgcFQjFqcdp5exeJ0C9LzCPgnXw7+QBiUaMtkg==, tarball: file:projects/ai-form-recognizer.tgz} name: '@rush-temp/ai-form-recognizer' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -8315,10 +8333,11 @@ packages: dev: false file:projects/ai-metrics-advisor.tgz: - resolution: {integrity: sha512-U0OBdhA2UsNoYOBkp/0tnP6b3pblzKhmggJFKaC2pUrpKftVfWGTCh3c1B4pzpfA7YG4tjrp2B3A7EbuWpU83A==, tarball: file:projects/ai-metrics-advisor.tgz} + resolution: {integrity: sha512-sfUrkxj9hjIvrFbNxRwyh07B3efxRaeFXw3os+08OEsaX2mGFyPXJAV1Gcz0S5noxfOFklCcztw0Y8iXqkz5hQ==, tarball: file:projects/ai-metrics-advisor.tgz} name: '@rush-temp/ai-metrics-advisor' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -8364,10 +8383,11 @@ packages: dev: false file:projects/ai-text-analytics.tgz: - resolution: {integrity: sha512-CgeLVCZwfspweQBHcNd4/2uI/rEvbU60GAjFDSUwGnXFVywaMqd6i1jPcZlkCGNitvctyEsQDnJc0PiIN4pRXw==, tarball: file:projects/ai-text-analytics.tgz} + resolution: {integrity: sha512-cJw2hAn1kN+Nb7pTljSWInzerPT/IKMB+IURRhR+NHuBnoJiBLsa8iaglVjm5MEdATX7jNvkzqlUVGb/MsGBtQ==, tarball: file:projects/ai-text-analytics.tgz} name: '@rush-temp/ai-text-analytics' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -8415,10 +8435,11 @@ packages: dev: false file:projects/app-configuration.tgz: - resolution: {integrity: sha512-D+bZ6xyRBL7CxUdEQMjrRgvDMN00fAG2rF73qU2xAlkEiMmO7hCw+jPZK7Z7olRVT+onoRiDjJkIAHF+vW10tA==, tarball: file:projects/app-configuration.tgz} + resolution: {integrity: sha512-HxGlxno8MeqM707s7GwEVtPeoQM5sDkKFrspB9Bxumf1+EHJaeT32A7wP/eQtz8zo97tQUAk5NbBkwodkWj6Dg==, tarball: file:projects/app-configuration.tgz} name: '@rush-temp/app-configuration' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/keyvault-secrets': 4.3.0 '@microsoft/api-extractor': 7.19.2 @@ -8473,10 +8494,11 @@ packages: dev: false file:projects/arm-advisor.tgz: - resolution: {integrity: sha512-sXLZvgEK3M/0EB9904jwVVIMnEZmc/NGhojlcJ0SprVlvz4syC8LQywjnworwQH90arrpiG+1hcsVKiPG+lGqg==, tarball: file:projects/arm-advisor.tgz} + resolution: {integrity: sha512-zlVdgoQEdav4ZAAnW4VIm7jY5JILZYfpxUsOT+HKwbt7OQS+Rh8xl58vKGOzRKagM1iN+pPJ1giqTj1C+p4jmQ==, tarball: file:projects/arm-advisor.tgz} name: '@rush-temp/arm-advisor' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8491,13 +8513,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-analysisservices.tgz: - resolution: {integrity: sha512-yadXxpeaoCaSs2kXugUgnMRlelZKJFJiXqJaqkgvnUuSGtknGjx7V+gFpHCKo9GtvVzeATtEnmQlYnYoZoAHkg==, tarball: file:projects/arm-analysisservices.tgz} + resolution: {integrity: sha512-hP7EfeF7VXwrcN5cVtFSvN0Mdfs78+qQg+C4jnhW7OJ9p5npL0oT41xjijeX2FaFcrGjPXXkCyuSZHz/qu5RaA==, tarball: file:projects/arm-analysisservices.tgz} name: '@rush-temp/arm-analysisservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8512,13 +8537,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-apimanagement.tgz: - resolution: {integrity: sha512-Ek2wAr+FBkISa7fXj4rW+E9UiplbpS7iXxXyrsUikpgJgHWb5YSnG+5RAFPKCKaeoDUyxXtr1qD1rG7GZsZauA==, tarball: file:projects/arm-apimanagement.tgz} + resolution: {integrity: sha512-wyoo3HYVA9rNAw29fWl4F/b97BrsD0D6tLSzH9fwwIjdttvNR+oFd5tcY/lW14BMZLJgLnTWxDb5XDX7WS2QRg==, tarball: file:projects/arm-apimanagement.tgz} name: '@rush-temp/arm-apimanagement' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8533,13 +8561,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-appconfiguration.tgz: - resolution: {integrity: sha512-uPEV3pZMXMZQyQ7wCpm16uQfLLjAW/JOVzRX/HOU4VfHw3YPpj0U09oo9jt0ihSuYD28/Jb8hyphiAoQyc4jvw==, tarball: file:projects/arm-appconfiguration.tgz} + resolution: {integrity: sha512-OK1/po2lJUawyQisdSMN/eVmWFM01FM/Kel71NUdBm0QWsCUt6TYmlarSnrSIW7v21JiS1TmbOOg6UV4MEbd7g==, tarball: file:projects/arm-appconfiguration.tgz} name: '@rush-temp/arm-appconfiguration' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8554,13 +8585,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-appplatform.tgz: - resolution: {integrity: sha512-Pel4tu/SIfuYKVrehY87LfAoYBiwyuraQp2/PdUzhmuX6p0wXK6BUGEjZj7vC2IAAaI4k/Cnh4QLdMLvRdZUuw==, tarball: file:projects/arm-appplatform.tgz} + resolution: {integrity: sha512-4ViCtaJaq0hg3Ed7lIB7V1EWyRJiGk1Xifc8SHCQTUzizQm2Ke36e635M0aEuH4oUBo+XhirXJKMuNOfiaa0RQ==, tarball: file:projects/arm-appplatform.tgz} name: '@rush-temp/arm-appplatform' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8575,13 +8609,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-appservice.tgz: - resolution: {integrity: sha512-XIoOFmPuJZ6mZqxVNMcVXvYwTzAzdzCMuF1jGGex+3+kC2m1R0Td+YwQw12Xd1qfTrSCRIQXWdqonBi/z/mJow==, tarball: file:projects/arm-appservice.tgz} + resolution: {integrity: sha512-i04Lrj7Q83ukyppDjUKi9FHWamkC6My+2b1aVzyiH6n29PwAFTvvbha4aeg1wpkFVUJKH16SslgVXtLI4DkAVg==, tarball: file:projects/arm-appservice.tgz} name: '@rush-temp/arm-appservice' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8596,13 +8633,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-attestation.tgz: - resolution: {integrity: sha512-jIUHVpNd+Vexi4RG/knyP+kjgAzI4HA9bu3J600ZXZ/avKoy/Hut+Pd4LWKd1pR3JETpM1t4kMyw/vIGBlSItg==, tarball: file:projects/arm-attestation.tgz} + resolution: {integrity: sha512-OCRQAHTazknsu1kMCBBDvWE6ibxAus4XWL0XaaCaOr5brjdvje9DX8AXHRxe77ToQRM9TWmGvg8H3fh2YGJNtA==, tarball: file:projects/arm-attestation.tgz} name: '@rush-temp/arm-attestation' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8617,6 +8657,8 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-authorization.tgz: @@ -8638,10 +8680,11 @@ packages: dev: false file:projects/arm-avs.tgz: - resolution: {integrity: sha512-ZVT73iZjn66o9nf93/mdkPypd8ugF5FG6pSAiI2DTRZjavBhEPi+BkQ4mbz1yQ8gxgGV5divZ/dBGYYefDranw==, tarball: file:projects/arm-avs.tgz} + resolution: {integrity: sha512-RdwLZ9hAJV8c9izgG2zvh+nNWtRflty3+fD2kaPJGMKUDxkjdJWUhjXlMRZJ8hXml0lYugXKdfBU39wvfcN1ow==, tarball: file:projects/arm-avs.tgz} name: '@rush-temp/arm-avs' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8656,13 +8699,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-azurestack.tgz: - resolution: {integrity: sha512-nx1zmEvcREyoaIZ/93bbMgWvGE7yOL/imhdnkRkyHyHm3ek5MjE8jW4PeY3WwhVx9ZvHqYmnQqk53QgYbhlFjw==, tarball: file:projects/arm-azurestack.tgz} + resolution: {integrity: sha512-M7LKdk9aJME+IMCHXFnZ2mnn2Wgv0JJBm5qFuKGpAlhVXe735LPLSbaC/tjPBY3UFEzkrOtbZ3NRsdE5yXoZzw==, tarball: file:projects/arm-azurestack.tgz} name: '@rush-temp/arm-azurestack' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8677,13 +8723,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-azurestackhci.tgz: - resolution: {integrity: sha512-fS7YNoGtj+meNXuUKFvQds/Ug+ZLwwjH742j9eUfULcEgcTzKZv747pSGTQzPT+V9xT+psO6E4d/zHdLfdsQEw==, tarball: file:projects/arm-azurestackhci.tgz} + resolution: {integrity: sha512-n1yrV6AKJAvplsPmDg2PO0M5QOhsX2v41Y3xKcgmTB6Z/OLzncdJlZyE9GlqjRsmPV4NoLlUFcF0ABq37rTEoA==, tarball: file:projects/arm-azurestackhci.tgz} name: '@rush-temp/arm-azurestackhci' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8698,13 +8747,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-batch.tgz: - resolution: {integrity: sha512-3Icocw0QnB8/JYmiR/jIr/2A2XhYvFbte2ah1d8s7Mx4Bb23RCnZjTREPQinlGgdpuNSUL0va2ixFwyXTRCiBA==, tarball: file:projects/arm-batch.tgz} + resolution: {integrity: sha512-pY6AfIYpdFqP+h5xLI9a/M8G7jyv3oJJUdZybMSzXofqoaF6Kitasm26yxA39Ze/fLyD8xtqKEfSykopmlSthg==, tarball: file:projects/arm-batch.tgz} name: '@rush-temp/arm-batch' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8719,13 +8771,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-billing.tgz: - resolution: {integrity: sha512-nv3g+ZrM99I+3cipjvr8ZuUQqCNx9LBNGJkPGfDVpPR888661Qsi722yGl2xZEtRt+dvBvFleqg2CsYAM9UZWw==, tarball: file:projects/arm-billing.tgz} + resolution: {integrity: sha512-8GqXmaxU/AaNYnkLfKC0GfsaaBaPw7Bo6+EQYxO5rW8VntWnv5D1RMSptiR9lEev0nSfj6kG/bGG8uLAxrNBQg==, tarball: file:projects/arm-billing.tgz} name: '@rush-temp/arm-billing' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8740,13 +8795,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-botservice.tgz: - resolution: {integrity: sha512-l4zOv13nmgSu6x2rtntla0UpOvUbTtPw7DomEYu8iWTA6m9KwpI2K+AFMtMibnOcDnLxPp8qPj7X24nh1L1+Ug==, tarball: file:projects/arm-botservice.tgz} + resolution: {integrity: sha512-4SWzvJHQgV0XLoBAqApUZf7gfuMrC2ayHu0gaDmBRdHizZslTniUJ+2i5KSq+ySXEwbK1ft0KNI3xhzRWu2gMg==, tarball: file:projects/arm-botservice.tgz} name: '@rush-temp/arm-botservice' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8761,13 +8819,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-cdn.tgz: - resolution: {integrity: sha512-QCvULGUI7auPq6N0y49Ye1S3MoCEEytee1/fE9MrZgfiq99sQwZTM847vICIxOajGHBPWbr+OhHWO/ysLBQGOg==, tarball: file:projects/arm-cdn.tgz} + resolution: {integrity: sha512-IUSZm18xXoDymVuC+jaCwR4csrClYogUR2aKcHZKMLYIp+M12ryhVkKB5EV1hfIl3MPiUQHoLulaIaRh21QtBw==, tarball: file:projects/arm-cdn.tgz} name: '@rush-temp/arm-cdn' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8782,13 +8843,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-changeanalysis.tgz: - resolution: {integrity: sha512-3twC5GwvqsATQAaGPfVWKgRKHYil852nrNmykif6kpTfjP8tclxzmQpUlzEajvVionV03sce092R+b2tyBaZPQ==, tarball: file:projects/arm-changeanalysis.tgz} + resolution: {integrity: sha512-4ylt0JNQvSXlXlly2+GtftNbVhRigjJzI+3SB0w4gt0TcHScOgbs6qDn+1FoTbl31c5jXHtKNJmhVFByiS+Ycg==, tarball: file:projects/arm-changeanalysis.tgz} name: '@rush-temp/arm-changeanalysis' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8803,13 +8867,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-cognitiveservices.tgz: - resolution: {integrity: sha512-nhXO2X6wsidbD/bRFm7geAnKOO7NFBrM7vNIOi5uSAzpGPhfWpqBwQBp4QvLeAtBXEnfjWJuRSvQwRa/9bzXWg==, tarball: file:projects/arm-cognitiveservices.tgz} + resolution: {integrity: sha512-O776lng8vaJyBcsDisT6PeW2hy59v/EcOgMx4R2KRVHojnyZpNs9Wjjf85JuReyYHmTX2SvfVo4pkgVp7w7CMQ==, tarball: file:projects/arm-cognitiveservices.tgz} name: '@rush-temp/arm-cognitiveservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8824,13 +8891,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-communication.tgz: - resolution: {integrity: sha512-ey0KF76WOY3EWT1lfVsD2F2UjW973Du8mxTdDFAyjmX3ifgXfHdNljvJjQNS9aDJx0aWIv4QanT8i+45OAtMhQ==, tarball: file:projects/arm-communication.tgz} + resolution: {integrity: sha512-e53reTD+lNsFKL9zM0QfPMad4xvgBQbWNOhhSAeeABFngjM4wfVG5M4GJHfNsTTpQXe13LXYpCXNZjUyE4rJ0w==, tarball: file:projects/arm-communication.tgz} name: '@rush-temp/arm-communication' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8845,13 +8915,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-compute.tgz: - resolution: {integrity: sha512-zk0xILY5nmiZpZZiPD05eJEXDKOzYLSQO7j5lE9SFmtEQg7Toc5DfoXXEvpCCOx0AqmCDj9G0V9g3eXUn+d8pg==, tarball: file:projects/arm-compute.tgz} + resolution: {integrity: sha512-gN6VdLPNFLxYG9Q/S//Ru95Im4dd2dhhBU9HrtSa3nLDVk0UPJKzE81RmUaTXyj2O5oZh524mAlC+lJWR2boTg==, tarball: file:projects/arm-compute.tgz} name: '@rush-temp/arm-compute' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8866,13 +8939,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-confluent.tgz: - resolution: {integrity: sha512-jXenjQsBBnIV0WeP9lGHDgrWpTQBr0nOQF4RCcTKTA9tD8f6B/DhYy6m+fgc1ejTXlZqIfp20kTKc3vOI0FIBQ==, tarball: file:projects/arm-confluent.tgz} + resolution: {integrity: sha512-1CWaU8CRg3YDauVUqpkVfcdow4GYqBY7wmhA730YnV6+m8RDXWMBUlkOXFXJ8eS7cegurvWM70EGNednxbtQFQ==, tarball: file:projects/arm-confluent.tgz} name: '@rush-temp/arm-confluent' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8887,13 +8963,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-consumption.tgz: - resolution: {integrity: sha512-m+tcI8203C8X1l/+HsD9meTIpK7WFovm3PIq8aPtplYANgJya1BCr/a8aFHJGlLx5SRquHceWeAZeP0Iqn6jTQ==, tarball: file:projects/arm-consumption.tgz} + resolution: {integrity: sha512-LOZyDLbHTPNE0mJfWx7E7zl7wux5zK/fTA0tZCUKs9l2lf8mXW5bFI4wsI+sfiiX/6Y2eIz9L5C0WPQt8/nHCw==, tarball: file:projects/arm-consumption.tgz} name: '@rush-temp/arm-consumption' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8908,13 +8987,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-containerinstance.tgz: - resolution: {integrity: sha512-eyVMcOiikD+ty91N8PTE0CnKCQUfBHsYCFc/UB1W4iwElyPwOY7qALFKWDTnYG1ze2joiswwnOCelIEQTTEhkA==, tarball: file:projects/arm-containerinstance.tgz} + resolution: {integrity: sha512-wbLlzTlTvrxCd2KNeE+fWCHgdLaJWAtn2rIcmN5sEFRj1TjW3XPT096F6rMhJK1efkV7V8+kbxJnsh1WdBSfIg==, tarball: file:projects/arm-containerinstance.tgz} name: '@rush-temp/arm-containerinstance' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8929,13 +9011,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-containerregistry.tgz: - resolution: {integrity: sha512-si1/HJZ4OLzHAuxrVsQZNQv3SssXpBSFcpQGVOWTC37j48kEg0JxQREIfu0vhIteBBSLDR0IOCDA22z1QUKxyA==, tarball: file:projects/arm-containerregistry.tgz} + resolution: {integrity: sha512-XU2Z2hlvg/oEB7a7stq/cM5K+2blJ5Xeq/ZLhDBP/I14lfi9ktO+DjvCvzANITsZLv0gf/8K28A4ASzsxo9vsw==, tarball: file:projects/arm-containerregistry.tgz} name: '@rush-temp/arm-containerregistry' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8950,13 +9035,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-containerservice.tgz: - resolution: {integrity: sha512-G1fpEz1Ehxk5VF9ab3JJ20HwP7a0N2wmNuEOAZsEyNzGd0rz6J0E8OpYaELJWVI+gy8JRb2dCVIYtldL6bORPg==, tarball: file:projects/arm-containerservice.tgz} + resolution: {integrity: sha512-khn+KpzbhuTfp6rFjPUv4KfXVW9UkS5WGIxM9E+bNIpMRrX7OQk/N1yrlVoJrJfRscE5VK3VlFXxHjkmULPBdA==, tarball: file:projects/arm-containerservice.tgz} name: '@rush-temp/arm-containerservice' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8971,13 +9059,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-cosmosdb.tgz: - resolution: {integrity: sha512-/WiC1LhbvLepYwJyUjkOh4/+dpPcXSgkElxQzud5RJRW/sj3//z7/K9jDpi01cn4/wcAxjXss3nxSIs99wURbQ==, tarball: file:projects/arm-cosmosdb.tgz} + resolution: {integrity: sha512-IEwcvn1XDM86WgzHS3cJmRP+cdzmGyH3KYxJlh4VqcTzGwW1vK+8n+/uCajQ2hKS2CGQHqBD/A8YmhMp9IYgBw==, tarball: file:projects/arm-cosmosdb.tgz} name: '@rush-temp/arm-cosmosdb' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -8992,13 +9083,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-customerinsights.tgz: - resolution: {integrity: sha512-p8OzTTR9ytYAZ14JFlPSrtMeKAi7VwkcKnpSRrDtU5Aw3FVtjJWx9fYVye/jCgAdSWWc+T/Z2KUZhI0bSSzvfQ==, tarball: file:projects/arm-customerinsights.tgz} + resolution: {integrity: sha512-vDGfHEfF0R3Zu0/Wf6x8Wt4anyb1VGQbOuC3LjGCULQbKzI5N0kuqMyWqXgOVdqNmXEIaZgP4iFVXb5I6amBCg==, tarball: file:projects/arm-customerinsights.tgz} name: '@rush-temp/arm-customerinsights' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9013,13 +9107,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-databox.tgz: - resolution: {integrity: sha512-47bAdlP3IA+sZDyX6lHa/mJPm33/LT8PLcByf5TzJa2XS5zq7+vK+rAcVarOiy1h20saKmAK0/2uDFPxwRemHw==, tarball: file:projects/arm-databox.tgz} + resolution: {integrity: sha512-cmMTMEKWaaWwnOVZXW0N2+/8hBdEXC1DN2AUJGWiUJxyXPFdLGdC7EuQMmNM/P3EjU7FjcSwE9gI0JqgPoy/hw==, tarball: file:projects/arm-databox.tgz} name: '@rush-temp/arm-databox' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9034,13 +9131,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-databoxedge.tgz: - resolution: {integrity: sha512-ooDmyCApk6IYIB+ItMNMhYu8CSeapd8eri9GflNjgL/mXSrwTUCDlBXTDt5Hiz2zP/+8bqCjgN426EcLZ08cIQ==, tarball: file:projects/arm-databoxedge.tgz} + resolution: {integrity: sha512-wKeWhvRYoBOG95syMuukjt4gtkRe7AaQq8YFZ1zAXpU+GsOpsfHXL9SmCNGde5RpFS8WHqkHoL8vJRK3rZtz5w==, tarball: file:projects/arm-databoxedge.tgz} name: '@rush-temp/arm-databoxedge' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9055,13 +9155,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-databricks.tgz: - resolution: {integrity: sha512-YF8do+oy/XQztCzwKGbJJBIhGm+qDA1Cgk7NPYGNqMPGx5KagAFO85KNfRLAzUm4AMAAy7nZ7guBbr52xWRFrw==, tarball: file:projects/arm-databricks.tgz} + resolution: {integrity: sha512-snjLp9Ddb90jQc5iuZaXMveg83D4YZfSmyBFR++XcPN6iRkmWy9BxrRqK0lNBBxOVsQbbzJrqhsZKQau31oQVA==, tarball: file:projects/arm-databricks.tgz} name: '@rush-temp/arm-databricks' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9076,13 +9179,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-datadog.tgz: - resolution: {integrity: sha512-ZCNlU3J5D1cwtcM8skzZi9ZMmRSN+wK2s48soGZ4Gu9+BT2HhqREfc7wkN6UM6hTmZVmxenolB7FWHBUo5phdg==, tarball: file:projects/arm-datadog.tgz} + resolution: {integrity: sha512-ieKcaxZAw8yaT2wFKzLROyOruNTfQeN26BBkMHkz6JmZDGMYUdzI6dvTak/swksTZAr/prEb7pCaMNKUJ8ZcUA==, tarball: file:projects/arm-datadog.tgz} name: '@rush-temp/arm-datadog' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9097,13 +9203,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-datafactory.tgz: - resolution: {integrity: sha512-wm2n/Ns8vlhtEN0s+6Dp62FqXTpObLqUM/gtWBopBYVv45/1htEURor+8vevFUbYVOMwLXrqqtk11/5qnu34RQ==, tarball: file:projects/arm-datafactory.tgz} + resolution: {integrity: sha512-UH32IrZ05BYFMqfakt70EmTQsW+QVGgMw9gtJnETJPpfoFnPYlW51xBFXxTRjHU22gqHAFjxyVHhqfF3NucErQ==, tarball: file:projects/arm-datafactory.tgz} name: '@rush-temp/arm-datafactory' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9118,13 +9227,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-datalake-analytics.tgz: - resolution: {integrity: sha512-cO1sYGfaeaLrSrVvasTylGzuQT87Qsn6qe32w9vjYHIZzmTz4Rtgpwc6qjBO+fcytNUp/nDco8/wBWUPRR7b2Q==, tarball: file:projects/arm-datalake-analytics.tgz} + resolution: {integrity: sha512-OwqLiwcjD71laV+t8zpNFAuf9ysHxxEKhmHuSQtr0Urb+jkhzP5PIfw1mS/o1hAeIPyrbyA7H0N6/96QV0WUmA==, tarball: file:projects/arm-datalake-analytics.tgz} name: '@rush-temp/arm-datalake-analytics' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9139,13 +9251,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-desktopvirtualization.tgz: - resolution: {integrity: sha512-3U19vdKa3z0O/9CGkQMZc3+Q1ZmAx19snUOq6QF/djKcQEfdiP40NGU+EsN+Suc2sgUZZQUgw2mU7XMBvjd8Ag==, tarball: file:projects/arm-desktopvirtualization.tgz} + resolution: {integrity: sha512-Z2PAETG9cB4D5QoMn06A6fQnvMr7WVfPTLZSgSpQyJlGjYUI+sfLGPdScWpCCUYMHRrwCrRSGFj1cvO/Uzd3ww==, tarball: file:projects/arm-desktopvirtualization.tgz} name: '@rush-temp/arm-desktopvirtualization' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9160,13 +9275,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-deviceprovisioningservices.tgz: - resolution: {integrity: sha512-i3LMOSFdCzqCSjBWqkp3S2Se7ecPHDRno4DQWL9n2xphAJ6BoAVkhW4l5oX01BC1NBUf11J0NXcmBEeAqIJRMA==, tarball: file:projects/arm-deviceprovisioningservices.tgz} + resolution: {integrity: sha512-+wQoCPyQYPHr1E4+Z8wa4FeVfKfzHfeloGUgEOA8IuMt7rp1mpuJGjANjBO0Z4wCj/cAE3aFqF7Xjla6Qyk11Q==, tarball: file:projects/arm-deviceprovisioningservices.tgz} name: '@rush-temp/arm-deviceprovisioningservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9181,13 +9299,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-devspaces.tgz: - resolution: {integrity: sha512-Eh3IEnQUI62Nm7NiX1P93xdOkB7vAQc6HPSIIo8UvJFYFhtkXMUoBZNqoGu5qkaVLFcD+rsCY2oGIsatuAgTyg==, tarball: file:projects/arm-devspaces.tgz} + resolution: {integrity: sha512-GtCH6Bb15WWmRXSikxmPsofE7ENC8RkOOrf9Pu9c1XCZaA7k947rm9DNS1YtoOAtJArHnqIBKXl5itWjU8o4pw==, tarball: file:projects/arm-devspaces.tgz} name: '@rush-temp/arm-devspaces' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9202,13 +9323,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-devtestlabs.tgz: - resolution: {integrity: sha512-Da7OLti0yE0tUlnxlxMEF2r/WEXJbp1hpjEWjPZuyxtAo1ueSJrJgyDJo82zdyzdrc6C0NbuU/xA0vliNtAp0g==, tarball: file:projects/arm-devtestlabs.tgz} + resolution: {integrity: sha512-zBD3l8QW1DATs5C3pTZ6LS4GTRhxyXWe3w+WWj+hyTKPmwvEl1N9eYyuHNBXKITphpDtGlboeMhqqhtefwQJ4g==, tarball: file:projects/arm-devtestlabs.tgz} name: '@rush-temp/arm-devtestlabs' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9223,13 +9347,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-digitaltwins.tgz: - resolution: {integrity: sha512-fJw3Lem9lNeRFFW9akRsVx6jUxVLqilkTGeTZK1OLVCERqNsl0DIgW49BgMUgob/DITGPGAj1/kgmCmD5sJ1jA==, tarball: file:projects/arm-digitaltwins.tgz} + resolution: {integrity: sha512-IHsV4MSiLCv6vtX9ctvcD++6W94Tux0LMCV2IR8vbeAYJqcSX60gsFbwWgjmNvfY98CMBdy15am2TGXKwQe7YQ==, tarball: file:projects/arm-digitaltwins.tgz} name: '@rush-temp/arm-digitaltwins' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9244,13 +9371,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-dns.tgz: - resolution: {integrity: sha512-iTkokjSSWrYz6+DTYquJPKfNupoCM6dz6LNvozORK9hyp68ZcQLPlF6c0+ug8zf5QU4X2SNYJSu6I+3RkRTyuQ==, tarball: file:projects/arm-dns.tgz} + resolution: {integrity: sha512-QaiOCA+H8lihU2t1iOctNFHnflpR68xMyjg4qNq1RnSurk7LM+KYAbPNvBbBbm28XoDe2Mpy+/PGFh1fEl4Nqg==, tarball: file:projects/arm-dns.tgz} name: '@rush-temp/arm-dns' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9265,13 +9395,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-domainservices.tgz: - resolution: {integrity: sha512-bOBRwhgeKkMAJ2uokx31agEtZTBcL3L9c//ZFpsirUk0UkoY97OXW3g/d0I/rsRj29SVkwiBRjwJQBQQImW3hg==, tarball: file:projects/arm-domainservices.tgz} + resolution: {integrity: sha512-SH9X/gD7Qn7KKokcEOBqEjoJrfDOTZESeFmTm7FBM2Dbzfx08xXk0eB0+MWK0aPGWuVWcTrSGffe78WEPTuVew==, tarball: file:projects/arm-domainservices.tgz} name: '@rush-temp/arm-domainservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9286,13 +9419,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-eventgrid.tgz: - resolution: {integrity: sha512-26uyTQ6UX9N1i+BWqT3Ga154/45qazjGOcYOuleDzv7+gERljkMNe6dHkAbeLpwr8k/Tt9mdHY/1dLHrcRIa+w==, tarball: file:projects/arm-eventgrid.tgz} + resolution: {integrity: sha512-IkuzvU/A0D0goVCKekkJpgWmJPf+lDybecwkFb9tKADAfp0XniVPn6yO0b4qu1b1vciL0VtPYeevl+ePYG3kIw==, tarball: file:projects/arm-eventgrid.tgz} name: '@rush-temp/arm-eventgrid' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9307,13 +9443,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-eventhub.tgz: - resolution: {integrity: sha512-cWryEMqcziFmqSkDI9ZMhEhjBmX3UEdauByCW1fFHAJ9XGHISFxr8jpJ1hKOj+c6mbp5JBD0UIU2MyceWk/OMg==, tarball: file:projects/arm-eventhub.tgz} + resolution: {integrity: sha512-5ClisVtvID+83iw1KvnH0GlurB224ykbSdBhF+IexseCdDFEhkyBeE064Hh9lpoJKzEDEwqe/Mqjq/w69b6rcQ==, tarball: file:projects/arm-eventhub.tgz} name: '@rush-temp/arm-eventhub' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9328,13 +9467,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-extendedlocation.tgz: - resolution: {integrity: sha512-MfgUQPGg4gQIi4DU9vT6+h3JxtSfeXf5s4+QTNWpfT2UU7zlB5SMUruBh4JiPKOMsUaFcERCw3mJesdDvlMKaQ==, tarball: file:projects/arm-extendedlocation.tgz} + resolution: {integrity: sha512-TT6AMiggnN2B3oM5UYeDNCPSRyGfVp4R7bDOokgzyfgFQo//yzW2i3gEJjMIreM4G3Uy7q8/r/X7w+a83BBw8g==, tarball: file:projects/arm-extendedlocation.tgz} name: '@rush-temp/arm-extendedlocation' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9348,13 +9490,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-features.tgz: - resolution: {integrity: sha512-AwBLfC1qhx4IDWJ2abjAoE8klLea2wxsOl1ZwyW4BTMjEV7iHOzZJvxoSwQowhzuRGRgNwKZ+c9Y/uk6wvHjqA==, tarball: file:projects/arm-features.tgz} + resolution: {integrity: sha512-xw9HgItxghDJbwiaXXuSxFsEe5t+3Sm9vIuyKVmkkYMHk4zGlaVuDfAN+xUeOTlnjhQ7TBa0uyckQvnaRX5FAg==, tarball: file:projects/arm-features.tgz} name: '@rush-temp/arm-features' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9369,13 +9514,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-frontdoor.tgz: - resolution: {integrity: sha512-hNt0BJ4jsmT/jxeLaPZMOHZbguaxFSM7F+/NpmmV+dTHaK9A8/Usg+FJBAsuqV3NiRKhaM2SVBxqqdcCtBZN4w==, tarball: file:projects/arm-frontdoor.tgz} + resolution: {integrity: sha512-rgsJhoEqhrXnjegfIw/0VLqzWn2SPqE6vazXykEu3zAIU3z2w30D4/M3W5/V5/8/C7O8HWBhuYPSXjiVPchTug==, tarball: file:projects/arm-frontdoor.tgz} name: '@rush-temp/arm-frontdoor' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9390,13 +9538,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-hdinsight.tgz: - resolution: {integrity: sha512-MxGX73BGBcYHfMjJFjOMYtx6Z/hU8PsFZyudN1LPg+kjlghRCdfOpsSLkSl9YmekZEVGxkR9/DyeFQBfg5/+IA==, tarball: file:projects/arm-hdinsight.tgz} + resolution: {integrity: sha512-J2Mt65iyBDMU6iMXq0nc4EXzX3OTHtEokWVJJ/tWfEVVlcE7EuR1NCOlFNBxIWiF/ItnTZNILGK2R7lLAmvIWw==, tarball: file:projects/arm-hdinsight.tgz} name: '@rush-temp/arm-hdinsight' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9411,13 +9562,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-healthbot.tgz: - resolution: {integrity: sha512-13CL73gb1gRzed9C1GqCtScDQm2BpwAj1ValTXbFYGxxOtbmJUtjJmw9g0xnwsAxuHr1qIMrMs48IPMoXdoilA==, tarball: file:projects/arm-healthbot.tgz} + resolution: {integrity: sha512-0qVfK/H+gSWbbK5S4KBq9tqsL1fmacuZ+DfD3aVAzUpmyzBrNmmjc9EvZpr0fBwrdF6VvEyLmiCmaybdVbK0iw==, tarball: file:projects/arm-healthbot.tgz} name: '@rush-temp/arm-healthbot' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9432,13 +9586,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-imagebuilder.tgz: - resolution: {integrity: sha512-B799VwkrNiyXYwO50wx8MUAixrwU14vIOD/zW01n/i2Dxa8nYggsoGMFbqk8itwj35VpaoZ3u542uzhkqzubbw==, tarball: file:projects/arm-imagebuilder.tgz} + resolution: {integrity: sha512-/aJpNGWOV825XGXVuaOPrTSIOCo5u/7rL9DHpikVBKl1ELqMbqOmdTbF7hAwVc2IIIwv1/XUHYkXsohPu9I/dQ==, tarball: file:projects/arm-imagebuilder.tgz} name: '@rush-temp/arm-imagebuilder' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9454,13 +9611,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-iothub.tgz: - resolution: {integrity: sha512-PzFOxAYNZCA5fFinZSNlhYM6Z5GYX/yJkJAKycsLISLkEDrj70v3ihSJqSoyI3o9y0oxM1GqeijIgfPgvj4zWg==, tarball: file:projects/arm-iothub.tgz} + resolution: {integrity: sha512-FNymR7VOSktBIh+IkM4gluBtugclvlGiK+jLBbHLyU24SjOXf/wy6dTgKPavFB8r/rLJ6FSdiwqx0Dzdy8g2NQ==, tarball: file:projects/arm-iothub.tgz} name: '@rush-temp/arm-iothub' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9475,6 +9635,8 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-keyvault.tgz: @@ -9497,10 +9659,11 @@ packages: dev: false file:projects/arm-links.tgz: - resolution: {integrity: sha512-t3TXBd/Fxm80E+cWuS3tQiUdSUeaEtul7+exkY4rNj9ecjK93FsfYTQ4a5FpR2WcEecOKQYXLYBfz4JgODQlbw==, tarball: file:projects/arm-links.tgz} + resolution: {integrity: sha512-FxAd5v0wUR9NU1MKlW1sDzDX2mliIrqCRdlmSHSf0u/NYd0PJrmkEXJcZ8u4pfLmyragf8OhKeH2junip+DK+g==, tarball: file:projects/arm-links.tgz} name: '@rush-temp/arm-links' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9515,13 +9678,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-loadtestservice.tgz: - resolution: {integrity: sha512-3l/W5FfyJNffvaFfoxv6k4msHAjJl/50pBxia8D+H4qHrdvV5GnujnpvE/tDv7s+eIfWqgymB1AHpXM79z6z5Q==, tarball: file:projects/arm-loadtestservice.tgz} + resolution: {integrity: sha512-LKFhNJF/aNKPbAq2lEX4BCRx0lZdzp9Ie7UR8VlM2pbOr9zEeXmOKg45Aaq+/hLz6K2pkNAaxJjHv6FE1qVsJQ==, tarball: file:projects/arm-loadtestservice.tgz} name: '@rush-temp/arm-loadtestservice' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9536,13 +9702,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-locks.tgz: - resolution: {integrity: sha512-0Qahks7SI2QPbAb0Sz5jVOFBHCj7pVW09BWU6pEbAoco3AVRDysIT3W6GzwfijdoGQsFC9tvDKRx1Od+Qxzobg==, tarball: file:projects/arm-locks.tgz} + resolution: {integrity: sha512-N4orsMJmesGip5EVaSD5phi6nhx1jdQhhUF7N9uBnosLk32xJqhinZIGkFL1D1LNM72YLk+oQ3jR0DJOJwSLbA==, tarball: file:projects/arm-locks.tgz} name: '@rush-temp/arm-locks' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9557,13 +9726,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-logic.tgz: - resolution: {integrity: sha512-3bo1wJB2/V9hWFMSjerYrfmosq0ChE0OI0Qwf8GO4oEpST2vW6y+AOJbOY7+QPvy7DR3eFLtrh4IWwsc1Fj/+g==, tarball: file:projects/arm-logic.tgz} + resolution: {integrity: sha512-DJ/Dk2yD4GDkHKbprdNE/d2V8132hmWq0RBIc74T4hPvuQsB4IJVtpVReZpVaKwwWuqBj2qRfuDqAdPoVrkoYg==, tarball: file:projects/arm-logic.tgz} name: '@rush-temp/arm-logic' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9578,13 +9750,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-managedapplications.tgz: - resolution: {integrity: sha512-5kLfrfEy5OIjFS2BfTSnbWRIh5NDMaXjYdA9yWjpD+9Dk2rsI5/ifntBT7zm0klJ9YmrszjI6IjXzZw37FnLzg==, tarball: file:projects/arm-managedapplications.tgz} + resolution: {integrity: sha512-oX2g+2gmn9C7tEb4bEGrjA6yXkP2RR7PG1NlAHDksrDPwmbsRR03139eUDIcF0Hb+quKuRseakzCDYMRcNEP0A==, tarball: file:projects/arm-managedapplications.tgz} name: '@rush-temp/arm-managedapplications' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9599,13 +9774,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-managementgroups.tgz: - resolution: {integrity: sha512-DgVVFiqdpYTz4RtGWBKFtrIwwn3aUcH4FfAILWaU6mT4CUq46F56HDn7cHqyMvTgg/IuAcw00drFBHoOMjZq8g==, tarball: file:projects/arm-managementgroups.tgz} + resolution: {integrity: sha512-W1CXloYU9yB3ffozven4CugCgSSEziq8lNSrZWdIJBTxtifKLG+aV/n0z1UQFuaZbd80e68TaZ4ADrDBbiUKwQ==, tarball: file:projects/arm-managementgroups.tgz} name: '@rush-temp/arm-managementgroups' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9620,13 +9798,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-mediaservices.tgz: - resolution: {integrity: sha512-wt5O7fsBlkomvh57wJRSiBwgIAAmjjLwaX3HhUj2WNHhIjY/M3PQ8n/172v99nzZ2hTxBQPmokO4eyz9sKSqDA==, tarball: file:projects/arm-mediaservices.tgz} + resolution: {integrity: sha512-F8O2XQ9PlyZo61/j0AJnv79/UtvW4vie3jIzD5jINbSVB61hLvFNsfkLQIekN8V0mQzmaEHOth+U+WyNvnHrVg==, tarball: file:projects/arm-mediaservices.tgz} name: '@rush-temp/arm-mediaservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9641,13 +9822,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-monitor.tgz: - resolution: {integrity: sha512-JKEFYPVFKOBzJ9+vOePl5GrSj73wyUUuLfuMuA1k1WrPXcLMZiTd+4RincSVkkj9niOt5z2w0QJsMFytpEyoKA==, tarball: file:projects/arm-monitor.tgz} + resolution: {integrity: sha512-fGEPUwHrAFIOY7+1ZoH/b4M+AgudqMrq42bQJTcvBtZXpPz5CaTn3DQmJpPQEUultLOwocA/k9NOsXEk+BxrHQ==, tarball: file:projects/arm-monitor.tgz} name: '@rush-temp/arm-monitor' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9662,13 +9846,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-msi.tgz: - resolution: {integrity: sha512-2mvUkEaqwzlZokmCeXIbM8nI3j3HEs25hchg2mQOO5tHa7AFYV5mQPQMsi2GgY52D1k8FSEB+cH0yUbxk02rjg==, tarball: file:projects/arm-msi.tgz} + resolution: {integrity: sha512-Nq6V7eAcJF2vOR+CqJwnLQk7eUilhOVrxwJ71Q3rwDkXMG5kdB5GElWKTROEEn1uVOdh5ViamvgYw7rSqDhnyg==, tarball: file:projects/arm-msi.tgz} name: '@rush-temp/arm-msi' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9683,13 +9870,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-mysql.tgz: - resolution: {integrity: sha512-FZ9HCp8D6jDLVs6MYV7Y38NjDfjH7egMr+ubdGHz8cY1LDR+8hy99lEVAdAV7JP5dTP6lV5U4LqLzw7LBlpkMw==, tarball: file:projects/arm-mysql.tgz} + resolution: {integrity: sha512-DtoJCFKvCMrFAZH14Zwq83plT5nYv1cfVbAwPa6nDXZUkYORnbTekg75ih3/Q9aSbfYI3PomJMl7NsglOSnH6g==, tarball: file:projects/arm-mysql.tgz} name: '@rush-temp/arm-mysql' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9704,13 +9894,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-network.tgz: - resolution: {integrity: sha512-J8QVJGGP8gib3NmM31KhY3xrAZVOikN3WQ0ORE5D6pol6+kD9/13usF/G0OEW6xaHZMmCTLTID2q8iBKkffOIw==, tarball: file:projects/arm-network.tgz} + resolution: {integrity: sha512-F0jII+BPxVfU0wq/hm2i2XXptnRmSyly/ESV6X+UtvAD5xWcbLUb1KEh1EzIsKUkPPbxUOhSiAiACHVDPDbjSg==, tarball: file:projects/arm-network.tgz} name: '@rush-temp/arm-network' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9725,13 +9918,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-notificationhubs.tgz: - resolution: {integrity: sha512-RhRLMGhot8TQiopddA9dI4GHyIbwzQ9GImrji0Pe+R9PJRLXtsZyhZ62m9ZxSEk4VR8G25QUxBNsHXV2d2H6EQ==, tarball: file:projects/arm-notificationhubs.tgz} + resolution: {integrity: sha512-Fb3UluOHKNygsOZ5zVRCuQgvJiMtbw+zp4e8Z8QD5oGzrB5uBWpo8dB+W2ijmwIsz3n73bQz12AtM7G/dw7bDw==, tarball: file:projects/arm-notificationhubs.tgz} name: '@rush-temp/arm-notificationhubs' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9746,13 +9942,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-operationalinsights.tgz: - resolution: {integrity: sha512-aCOEc9JiPgRSPW5sSz+jEIATAz64iWyKGgCEEIx7/MeTgkvw2BRcfA5Hod2fNS+nOP3KCIeH/Pqbqc+if65uQw==, tarball: file:projects/arm-operationalinsights.tgz} + resolution: {integrity: sha512-Z1LtfjTTB0gUbhF3V/3tMil95KRu75U+qvrFwfrORaV9OKP2IL64INaBLdZlfgP+BLhpuq94wSyM+euxavx/qA==, tarball: file:projects/arm-operationalinsights.tgz} name: '@rush-temp/arm-operationalinsights' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9767,13 +9966,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-operations.tgz: - resolution: {integrity: sha512-dozzs5LRkfXJG3auBWc6nrnIrM0aMuuI30vA9ezlURxPZwSx0aqgqCCch/JpcXde0fGoJW3gxpzaeqC7VWHgHg==, tarball: file:projects/arm-operations.tgz} + resolution: {integrity: sha512-nzZDyQ21diBJa8uHKnR6LvCbl7BI54ggPpPpBkArXeJHv//pwfU18/9HRl4svqB8jwlP7Ow3dv9gFHldakyn/w==, tarball: file:projects/arm-operations.tgz} name: '@rush-temp/arm-operations' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9788,13 +9990,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-orbital.tgz: - resolution: {integrity: sha512-a+8J0Bt6Uy4UhaHEMMyEhsUKR1JMRiNUDlNfMZtm8J3s8PMYtV/V0ELVmN/CnX1nuQ4/CrsKYiYyTvaUeZamuQ==, tarball: file:projects/arm-orbital.tgz} + resolution: {integrity: sha512-pn+iKIy5zwxzCEGeepT4TAnUrty27BDpuJoEePHXN56whuiFspU1FZKVx7HqCCNQ2/UlYhmNHviu8Wf7QJAzDQ==, tarball: file:projects/arm-orbital.tgz} name: '@rush-temp/arm-orbital' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9809,13 +10014,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-policy.tgz: - resolution: {integrity: sha512-YceZTHHZ9R04RdMSzFne+FJblJv2Mt7pMcQnvY9Z7GBCo7yLi/5jSmSZTYXB9u7Xx0P6nIG6wy+Ur9JXt3H4Kg==, tarball: file:projects/arm-policy.tgz} + resolution: {integrity: sha512-a/9y5aQmt81i9+TUD4TEXZvhih78ohaVG8NbUC2MrRjwR7d5C16dUQualEY/4ziZppqS21SOFV8ZcdG7vlPtkg==, tarball: file:projects/arm-policy.tgz} name: '@rush-temp/arm-policy' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9830,13 +10038,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-portal.tgz: - resolution: {integrity: sha512-uW56O3SchJbqVBOehKnE9Vg+WlJtDZI/0tLjS6J70AETVrtUoMRmZWdHufMrJffyTiMiJcJ7quvR5PMAXgb8RQ==, tarball: file:projects/arm-portal.tgz} + resolution: {integrity: sha512-yC14xaWUm+sXwEikuQupjWmfGWadYEzupwOVR6jo+ofVraXI1GSY/LUPGXnlzpSGpqrIbJ0HNrjrhWac4CpjmQ==, tarball: file:projects/arm-portal.tgz} name: '@rush-temp/arm-portal' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9851,13 +10062,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-postgresql.tgz: - resolution: {integrity: sha512-zSCnZiTUuJ3qyfFeLqzJl5p8diq7IGmXoxGemsFr3ApHDmnkHdxOkA7a0aRLnM/0fxJt9Nlvi0AA6GQgq2NB1w==, tarball: file:projects/arm-postgresql.tgz} + resolution: {integrity: sha512-Q24VoWR609VN33ydnXCzyVL1YUnmXe23Cw/eUe01xxBG8uaZ0mG7IVpFbCa0xCjCb3ap5kM4cP+S4eCT88TV8Q==, tarball: file:projects/arm-postgresql.tgz} name: '@rush-temp/arm-postgresql' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9872,13 +10086,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-purview.tgz: - resolution: {integrity: sha512-Vb376ewu/nl1fRue8La/7duUXrW8YYqLX4vjzc+vmlsQ7eBjCsJsY9J+P+hIPx0VVG2dC0fwUEzvybRf7n8+Sg==, tarball: file:projects/arm-purview.tgz} + resolution: {integrity: sha512-3MCuWOyQrNdtJRbhS9u+hCbY7yKOOEbqaC47SdG6iu4GqHEM21VoGF1yzk7zfinWZZo1rZGMgrn/33BwGlljxg==, tarball: file:projects/arm-purview.tgz} name: '@rush-temp/arm-purview' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9893,13 +10110,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-quota.tgz: - resolution: {integrity: sha512-TMLTjmhxzJGCxaKcAU/0C+jUAfbWIeW9ttHy+6QTqAckGdVS9VuKuFih7CH0CwWl/3+Z6wGGaHwsoKF77DqJ/Q==, tarball: file:projects/arm-quota.tgz} + resolution: {integrity: sha512-KfH2EzKEOUXdiJaAz1uuxyZwN8+CNS983ksD6YHlnjwmHwQsHVJqqeJQWgyXhDS3Bq7oxISsvG56s8mfZbJ/yw==, tarball: file:projects/arm-quota.tgz} name: '@rush-temp/arm-quota' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9914,13 +10134,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-recoveryservices.tgz: - resolution: {integrity: sha512-HGrDxGZ5sBGnQ1eYbkkEuuW8m9mUmNHXCeZOje+hDE2MZGfBdhHRCgYoNnadEiiQRjugxn5RMvbB5Mp8GZ/XXA==, tarball: file:projects/arm-recoveryservices.tgz} + resolution: {integrity: sha512-sayWHw8UlhCOoi85eGj/ezg4y7v7v/WZrbgg6TFaa7BBTpfvLRgF5ltp06oANYStYTAS3wDhnukvq2/MtB2MGw==, tarball: file:projects/arm-recoveryservices.tgz} name: '@rush-temp/arm-recoveryservices' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9935,13 +10158,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-rediscache.tgz: - resolution: {integrity: sha512-aOAyRJHWE7GUOtUWaHTiFCa3SurFmsvsG3u8DDDBAGlXVxSc0OCKPfyr0RkqL8k+N2dsS1s5qVzSWPfKuaP1mA==, tarball: file:projects/arm-rediscache.tgz} + resolution: {integrity: sha512-//ndWz9FR+egmG9cRIXdMc+vHP0f+SL+kIkI4c+ULDYRTTiWQofQvjIGqNEcqxQthjQdu93oIRv6aRjG2GM/jg==, tarball: file:projects/arm-rediscache.tgz} name: '@rush-temp/arm-rediscache' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9956,13 +10182,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-relay.tgz: - resolution: {integrity: sha512-GTqmfFZ2ToplfgtJe7mDyTK9SRPuo5niMG5PTxOuHnjdqY9KG8+6nKu5rVgFsjIinMD+A/V52b1zHMK5cIjYfQ==, tarball: file:projects/arm-relay.tgz} + resolution: {integrity: sha512-ALfj1XBS8WRgC5ncN0qI58fesq3xQyp0wh9ID9KyGr+b6hJPGloDgu9qRcCv0Py2TC8bcqI2KlZ08lVEctdKKg==, tarball: file:projects/arm-relay.tgz} name: '@rush-temp/arm-relay' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9977,13 +10206,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-resourcehealth.tgz: - resolution: {integrity: sha512-2aZ/KsYKwAjiAVYNgPcnIHIZ1zPnIhoqJzsV188fdQ+QJAf5uP3LuyYbqv128SbEqrHQK5XSbAgWNQiAk7DecQ==, tarball: file:projects/arm-resourcehealth.tgz} + resolution: {integrity: sha512-ssLCh8GgbsVtwLFyE5qlPtC6OjpWdCtYeIbKxvNKerfJOMH/Vb15Osy2TMpWEvUmLPSSeyiRXz5ZBMYg+AKK5g==, tarball: file:projects/arm-resourcehealth.tgz} name: '@rush-temp/arm-resourcehealth' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -9998,13 +10230,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-resources-subscriptions.tgz: - resolution: {integrity: sha512-ECd/DXrRi9Go8MpTBxKHfDrjXwaLr5KE9tG+pRnZLzqzl9E1VsCUXV85Z1UZrAaI+e8U5a1dVCctXpNYjquf5A==, tarball: file:projects/arm-resources-subscriptions.tgz} + resolution: {integrity: sha512-Y8XL95tGixEM0br4pDJgRVqqa4CZWJRbu1SGkBtHZNKbpJ1aU3OXeei1dk84bEtu+FvjDh3j2QOFKKhnMoSFgA==, tarball: file:projects/arm-resources-subscriptions.tgz} name: '@rush-temp/arm-resources-subscriptions' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10019,13 +10254,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-resources.tgz: - resolution: {integrity: sha512-OnGTFVLnbFLJfLHJNm2F4pGCDPdXAzGGfPqxxOHajg3qJUKAv8sKQDRXkHti5NRXqy1Aqb1vIcIG2Hwh8iVkQA==, tarball: file:projects/arm-resources.tgz} + resolution: {integrity: sha512-dZ/z3nCQ43lFVZpX8UP+Juqk9IkSNc7thAziWRyWzYXOB34A1o228B0+2J24lPnO07cU/tuyQb9jeBMzCy4ydQ==, tarball: file:projects/arm-resources.tgz} name: '@rush-temp/arm-resources' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10040,13 +10278,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-search.tgz: - resolution: {integrity: sha512-4+mXE+vttvJx6IJk7orgfc07g9HHSnEO3W1jjXslwshUQNBaUj9uvxYsMzD6ldSAXkv1hTEYWD7QLpAZA006GA==, tarball: file:projects/arm-search.tgz} + resolution: {integrity: sha512-0Ez9f6yhxcVK2u1GSOgvSlML74EPybmNkflA2i2D/5g1nNGv9OO0QOE0xUlkvdjvJS6HvYzHfxbCrIn3hxjv1w==, tarball: file:projects/arm-search.tgz} name: '@rush-temp/arm-search' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10061,13 +10302,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-security.tgz: - resolution: {integrity: sha512-F3OhbZcyJmfqpT5xlnBR1/4sgGLw9DlPrDMZ3PmKZOlqG1joUvrcQ57aE+YAmwMR+dr2dFB3Dvyb6pP1dwQp8Q==, tarball: file:projects/arm-security.tgz} + resolution: {integrity: sha512-X/nlLia9k75M0GhNEY+gfzOYiyQKI6xhf6iaTdMDKCYijqt8UTqiZfGvk3V1DkZzW3aJI6Rr0Uqp/nFs9pAggg==, tarball: file:projects/arm-security.tgz} name: '@rush-temp/arm-security' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10082,13 +10326,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-servicebus.tgz: - resolution: {integrity: sha512-wpZLIJN8lPkDfOvX+vdJNe1GmwrqRaNU5eHYWWdAuag36dZYmuRnnnBt7B31oJ8mOwAjIbykSDeOYX+t9ZgghA==, tarball: file:projects/arm-servicebus.tgz} + resolution: {integrity: sha512-xUyJSlaPZccUpQ99rOf2TIUziaa7K1Ch4vcz0Jp+Dj/eam95rdQFtidaAoubJTI+Wcom0OsKDHWVqLbUyS8GpA==, tarball: file:projects/arm-servicebus.tgz} name: '@rush-temp/arm-servicebus' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10103,13 +10350,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-servicefabric.tgz: - resolution: {integrity: sha512-9EyhaJlT/LbQneQqkv15+t+E+/QlQ/OfgHQrC+r2bG1hY3DrVkGw9F8DD6liJcURhUpFhzRtwbg//GSS1PV/xQ==, tarball: file:projects/arm-servicefabric.tgz} + resolution: {integrity: sha512-0tseYT+OiiJ2Q6IiCWBeS3k1wYOPznYIepph3EuH2y5svL5CVV8GlWvuWhk2kbT/8wmm69dQXBFjpx6nUXavSw==, tarball: file:projects/arm-servicefabric.tgz} name: '@rush-temp/arm-servicefabric' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10124,13 +10374,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-sql.tgz: - resolution: {integrity: sha512-Y9z4v0i1QfZQ5oO9jP3D0fTaQmDlc4a0Ak8h1sR+pBMwngAuIA95jb1LfiK94aZ3LblRjPdMHQFNVCm8eHOUyQ==, tarball: file:projects/arm-sql.tgz} + resolution: {integrity: sha512-CEtDIxHX9rKD5MgweS7t+aOwxGDS8ozHLc5Ur5POzatrFrpSFF37FeM7rT80sdAuG/YVfRVq0VCQZ5OXr18GLw==, tarball: file:projects/arm-sql.tgz} name: '@rush-temp/arm-sql' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10145,13 +10398,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-sqlvirtualmachine.tgz: - resolution: {integrity: sha512-rtXBOzpwBMONLXoZFAcEF6uZSLYhu20zmUHptexW4oyAoFl+v1747y25PqA+ASaMbfjxblGlpa+Zdnw8bGkRsA==, tarball: file:projects/arm-sqlvirtualmachine.tgz} + resolution: {integrity: sha512-ZwbDoLnI41nAC4XNdm/EkJrt0zaohPwlcYqDIOXTYFvbSDskYR6M5Pi15q+yecUcTAMbzUCnkNGicF69iy8Q8A==, tarball: file:projects/arm-sqlvirtualmachine.tgz} name: '@rush-temp/arm-sqlvirtualmachine' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10166,13 +10422,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-storage.tgz: - resolution: {integrity: sha512-gOlTporPWRuODrPIhc0BJpxLuJRYjPGdz7BRKtmJMs780nc2WoV7rETGIEGrRsd/ztxhbjovu8Ng0o2gF8hEIA==, tarball: file:projects/arm-storage.tgz} + resolution: {integrity: sha512-FMNMh6WilO4ePgzUX86Xk1Bqq4VHtRuqvFfXZkW9O9OEXWB1QtLhNN0KHNq6gpwMkC5Xam4vEzIRaWoN9bEaBg==, tarball: file:projects/arm-storage.tgz} name: '@rush-temp/arm-storage' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10189,13 +10448,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-streamanalytics.tgz: - resolution: {integrity: sha512-2fjxFfFfAfxE5v0wXvUNE5FpL7shgJgqNvzFVbs6GOZFgPIMlhX5rr71h9S38XEUehUVyYtTHKh+Mmxlvx6Uqw==, tarball: file:projects/arm-streamanalytics.tgz} + resolution: {integrity: sha512-anS7TXMqYdobBqt4VnMVXs6geNykd9Wod2fgz7EEfpPT4lMmA2I9MSeBA+zAZp5JvmuiWS+2SRFhjmfwWRFIzA==, tarball: file:projects/arm-streamanalytics.tgz} name: '@rush-temp/arm-streamanalytics' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10210,13 +10472,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-subscriptions.tgz: - resolution: {integrity: sha512-+WHfAMiKSndfe4bSl8jXJEa53S1Vv+5IDGDJBkmcaIJ36eADiMFaEsdd6GqJ9gPFeZLGv0v55CiTCUbszEoIqA==, tarball: file:projects/arm-subscriptions.tgz} + resolution: {integrity: sha512-nkgQ2lRqBqQy/cKqSxHaXlKdd/2pIHsVZnzha+tQwKGycwwCr8bT+weIcboHDeh12tjE2yG/a0beTa1oQzklow==, tarball: file:projects/arm-subscriptions.tgz} name: '@rush-temp/arm-subscriptions' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10231,13 +10496,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-synapse.tgz: - resolution: {integrity: sha512-k5LYmIg1eWN/BaoTjMTS9oNqNXJZ0jY5pFLxhD85g6HUkz81Ay1FOXp0RR6Om6Wflhxk/rHJoEaKUCR+KeTV0g==, tarball: file:projects/arm-synapse.tgz} + resolution: {integrity: sha512-pXWYvHxQV3L/tE5ZPje6k/4e3OB5Ubn1EoyRvRcPA45fKArYudHUfF6+3ifuDJg9pmuGSZBs98wNFOXtElP90A==, tarball: file:projects/arm-synapse.tgz} name: '@rush-temp/arm-synapse' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10252,13 +10520,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-templatespecs.tgz: - resolution: {integrity: sha512-PsUhG3/DdSL17PowykaRCmgY50iuzawFwrM+wyKMoxzhOY/W4saWMWRz+ggnc8OyaAX19fZuTq56vOZrQyb9Qw==, tarball: file:projects/arm-templatespecs.tgz} + resolution: {integrity: sha512-r1KzXFTfAwSRaLKrPywoEtLobNOhPsp+wwWta65dk3Yp27sLhJthf1JqNo33+ivhPluWGOOge6h5bI3JGGJBGg==, tarball: file:projects/arm-templatespecs.tgz} name: '@rush-temp/arm-templatespecs' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10273,13 +10544,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-videoanalyzer.tgz: - resolution: {integrity: sha512-2nnpgCmdUCStf3mGHFx1YnHW9idrPuR+sUfREpuMTMRoRNwkoRtzWi7lgmSG85qCJJZ8KO9RXHB/LJlO8n+A4g==, tarball: file:projects/arm-videoanalyzer.tgz} + resolution: {integrity: sha512-9cKpE8i/nJqJxDSzN5MbXRNNE80OH1zbrVCHfE215Wirf+1lY/ZAQUo4Vta/OabkJBwXRPoI7UngYMnh25g/Cw==, tarball: file:projects/arm-videoanalyzer.tgz} name: '@rush-temp/arm-videoanalyzer' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10294,13 +10568,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/arm-webpubsub.tgz: - resolution: {integrity: sha512-eLY8zVVnNv6rhrsCO3lDa/BWU7ep4cfwt3mOU0Dli0yd8n+OqxouRs6zPC0caWYCBxVjYT/Zl+zyTSR/hPPphQ==, tarball: file:projects/arm-webpubsub.tgz} + resolution: {integrity: sha512-HvoZFROZyHqpeIrdj2Yb6GXGeJIA8tyEM5cS+8h+AqimdVQagsotBV8hicGPga143QNp9LuHsUFpPjkJstz4Kw==, tarball: file:projects/arm-webpubsub.tgz} name: '@rush-temp/arm-webpubsub' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-json': 4.1.0_rollup@1.32.1 @@ -10315,13 +10592,16 @@ packages: tslib: 2.3.1 typescript: 4.2.4 uglify-js: 3.14.5 + transitivePeerDependencies: + - supports-color dev: false file:projects/attestation.tgz: - resolution: {integrity: sha512-u4BVojgzVwcsx8f+pGF/rQ0rWpVUhSlI3CrA0FDGjT5W83dyIeCIskIqWP18HyVd3ozZRDA3fCQfIT5PmG6zfw==, tarball: file:projects/attestation.tgz} + resolution: {integrity: sha512-ogU6QtiqEAL5lZOmOM8hyiv+3VMBNnLYi7q7oyzKXdSwQJZmgpAeAjmsulSRn0KMeRFVKP+Yi6VNJkCfnrl9Qg==, tarball: file:projects/attestation.tgz} name: '@rush-temp/attestation' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -10374,10 +10654,11 @@ packages: dev: false file:projects/communication-chat.tgz: - resolution: {integrity: sha512-fhcFGYpz6eNL0wS9zadrtmRl960ia2mqyYsyzxHUczrVwRdVd6jCk8xB1Y0sUdcXb+q9eNlOaglROdPstwaV/Q==, tarball: file:projects/communication-chat.tgz} + resolution: {integrity: sha512-1I3EDeAMcYhQPkasUgQrONpvh4qk4DziktDz7wBNpeoFZQFV0jTVqBlEfFQTTQ1ixyyidFfUhIpj1UoLtoSyfQ==, tarball: file:projects/communication-chat.tgz} name: '@rush-temp/communication-chat' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/communication-identity': 1.0.0 '@azure/communication-signaling': 1.0.0-beta.12 '@azure/core-tracing': 1.0.0-preview.13 @@ -10490,10 +10771,11 @@ packages: dev: false file:projects/communication-identity.tgz: - resolution: {integrity: sha512-FDXs0o+ewJsf9sVFMCNjZ7LyfvhGYff1X1E9Ehp+DgavGBCE8JQntX3+IDOS4xhNBO+A55qu5lNnRGr1dL7U2w==, tarball: file:projects/communication-identity.tgz} + resolution: {integrity: sha512-0dAPZ0fV/ns96wGfBwSE+8k4FFhPgFx1gS5Ew712eQuytL5IXlkuKtMZWI3MMwO4xgQxWq6eoiumv11HJyWgbw==, tarball: file:projects/communication-identity.tgz} name: '@rush-temp/communication-identity' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10545,10 +10827,11 @@ packages: dev: false file:projects/communication-network-traversal.tgz: - resolution: {integrity: sha512-El7dbzRK4iVIXh6YS0LLipwQqnvmV/cQrsibu9txTyotrP1E9TeLimSEX7G2gUyV4tHxwiyzVOgkIjuQz0n0hg==, tarball: file:projects/communication-network-traversal.tgz} + resolution: {integrity: sha512-9KLH+Jj9+67eig7H1DEUDjuprIBy+nGI6wKRzR3H/1A8D1i7IboEWN4zpw8GYYgaUArhfEMbi0b0q0sEkkL9Dg==, tarball: file:projects/communication-network-traversal.tgz} name: '@rush-temp/communication-network-traversal' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/communication-identity': 1.0.0 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 @@ -10601,10 +10884,11 @@ packages: dev: false file:projects/communication-phone-numbers.tgz: - resolution: {integrity: sha512-M8FHNvGy7u90ya/gC+6k3bP7Wkk68ZpO3uOMjxWcWoIjygt7XdkkpqS5/Lfj8i2GrQfowdVmZmoegDSEwbgrxA==, tarball: file:projects/communication-phone-numbers.tgz} + resolution: {integrity: sha512-KkwrRFQQsCIYjzpxQbLQZCSfhr1XjyCbB7qj6S+F2XMNUylGgEUgvNXJejlj9BokakugaYrXHDYJVYayjZcZHw==, tarball: file:projects/communication-phone-numbers.tgz} name: '@rush-temp/communication-phone-numbers' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10656,10 +10940,11 @@ packages: dev: false file:projects/communication-short-codes.tgz: - resolution: {integrity: sha512-9pFHlwq56rAjHlB2edrP1EF6fDNleBg5eyODiuWH+ImQbuTFDFtkOaMq3eIJoUoFgna//ylYbcocQBm9Wc9hNA==, tarball: file:projects/communication-short-codes.tgz} + resolution: {integrity: sha512-FdCiH1V0dEqtPAEb6Z2tDg30t1pso5JDAi1Ay/tKz88GtvAfywawlAcO1q8G0iNW13pKM09uLeUm4BBHaLN2NA==, tarball: file:projects/communication-short-codes.tgz} name: '@rush-temp/communication-short-codes' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10711,10 +10996,11 @@ packages: dev: false file:projects/communication-sms.tgz: - resolution: {integrity: sha512-DVCE2XYBDoK8q33yzwEeoadJYnADTspkB/1YdABVIp1qhbU4jDppQUjoqlDsrjcIMWHdP/6U5/m6yZssmP669w==, tarball: file:projects/communication-sms.tgz} + resolution: {integrity: sha512-wTAucARuZRcZHDmAEAWoRTndTqCp/7R+b4p2RJB13wzrW8+EbxkgIr+oqkCoOleBW4s3JjD30ihvNadh117qJw==, tarball: file:projects/communication-sms.tgz} name: '@rush-temp/communication-sms' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10765,11 +11051,12 @@ packages: dev: false file:projects/confidential-ledger.tgz: - resolution: {integrity: sha512-/lx2XG0M0Ueh1li+ZJdTqeGu2RSwfjbLyuM/Tq5W+OppFVaxzEjkWIwlbjMmuE70rU0vufg5YeeT2jWE622ilg==, tarball: file:projects/confidential-ledger.tgz} + resolution: {integrity: sha512-Ujm3OZs1W5eFcggMOXrVnZhAiD3JrZAR71vznUyW6sH0CvsAeiFQZGxV60t5OlEIQgRNlBrZS8vj56pgqEHpYg==, tarball: file:projects/confidential-ledger.tgz} name: '@rush-temp/confidential-ledger' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.8 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -10809,10 +11096,11 @@ packages: dev: false file:projects/container-registry.tgz: - resolution: {integrity: sha512-c8AfG7dxgv5jrx3HdhUbRw21oGFr70YrFX3F+14EL4gXTYQUEpPhQbsDkCIqrCn7gmd8YK+FvsZe3O7oVedpkA==, tarball: file:projects/container-registry.tgz} + resolution: {integrity: sha512-FuXY2wkddwW72qhncWfqmiOPIhu0MZ614NKPM397yf44ZxAkkcAErs/8twEeTpPVzb+wvURYMD1XHBv26T14aQ==, tarball: file:projects/container-registry.tgz} name: '@rush-temp/container-registry' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -11542,10 +11830,11 @@ packages: dev: false file:projects/data-tables.tgz: - resolution: {integrity: sha512-lnHUQ2BpUX+4KJ+w9S4hIxhUhBxVoAEcIov2CC1FuO1OQ2JQ5U8lYH/zEXxHfhDjrKEeKwHIGmNxSxvdabzvGw==, tarball: file:projects/data-tables.tgz} + resolution: {integrity: sha512-haBWmqLlQ046HfHSvznwsQekyIc+Pual6+4iiNUnwiDjyDObrdaKNbL6IHQHPLBWWZgtzmBAneHQdwrhZHntsw==, tarball: file:projects/data-tables.tgz} name: '@rush-temp/data-tables' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -11644,10 +11933,11 @@ packages: dev: false file:projects/digital-twins-core.tgz: - resolution: {integrity: sha512-+g1UwytCUmPweJ5AQbmVt9MgiJZ/LKESrTbTQnPspd235nWy3EcPruTNZe1aGsN2gkasNWHJu4peD5EQ+hfhTA==, tarball: file:projects/digital-twins-core.tgz} + resolution: {integrity: sha512-EBNNnwU7Od1QiHO8XPwOyjERFRBSgBN77NNgjgrgakH6MLzCe1aA0SQnJC5uEhdwhRbGtVGP1+CidmtUrAzCxQ==, tarball: file:projects/digital-twins-core.tgz} name: '@rush-temp/digital-twins-core' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -11804,10 +12094,11 @@ packages: dev: false file:projects/eventgrid.tgz: - resolution: {integrity: sha512-yEsNmtsrNOZZh3o8ggYy1epV5p8qjZEkYAoGzKBjf4yRrSi26VbwnS+wi1T0zjKqZTq4so6VaSzH0AmYNfLpWQ==, tarball: file:projects/eventgrid.tgz} + resolution: {integrity: sha512-OU9oTUOEpUx2Caq6WlXwP2F6ynWkU+3gBIc+cK4jprZoM3geEHC/e51zyP4goMlqSjAy6wR45zizbikWRo04bw==, tarball: file:projects/eventgrid.tgz} name: '@rush-temp/eventgrid' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/service-bus': 7.4.0 '@microsoft/api-extractor': 7.19.2 @@ -11973,10 +12264,11 @@ packages: dev: false file:projects/identity-cache-persistence.tgz: - resolution: {integrity: sha512-eWuDuu/VqbRkGOvNALANhFYOCo97TQURSEMXvaIQvDz0Wvsqolskgb5ZS/Hqx6bYHNZBjHYJmVAkBUA9BLLIvw==, tarball: file:projects/identity-cache-persistence.tgz} + resolution: {integrity: sha512-vhMGAjPw2fgVHO33Agzqb6uEIDT170iQd5+JLDeBrG2/m9DT69yRRJbh//4E6F4lt/M7hso6JPGb+H+CeGluTQ==, tarball: file:projects/identity-cache-persistence.tgz} name: '@rush-temp/identity-cache-persistence' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/msal-node': 1.4.0 '@azure/msal-node-extensions': 1.0.0-alpha.13 '@microsoft/api-extractor': 7.19.2 @@ -12008,10 +12300,11 @@ packages: dev: false file:projects/identity-vscode.tgz: - resolution: {integrity: sha512-sYyTuKwqVBiBWa6tC9drBQ17qHgH18UM+Qopub/wWSWR9U/+yugSN05PjD05JkbW4SRs1oFSHei1kx+fKaZl7A==, tarball: file:projects/identity-vscode.tgz} + resolution: {integrity: sha512-biczMclMIDbUfJP7PxqDEhUXYp2UI0IPuRtccUFGUBdF8tavuUU7j/Sr+GfhNHzjTGuirY1GrJ+ui/g9KAv9dg==, tarball: file:projects/identity-vscode.tgz} name: '@rush-temp/identity-vscode' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/jws': 3.2.4 '@types/mocha': 7.0.2 @@ -12041,10 +12334,11 @@ packages: dev: false file:projects/identity.tgz: - resolution: {integrity: sha512-SK/VeSO2B/mKnApYK1xH4jNXvjgi8T0tolsudmTngUVQvas7ynurfxO/7MZwzxiL3ZnztPZZg3RNrv8mPbmphg==, tarball: file:projects/identity.tgz} + resolution: {integrity: sha512-omJVgJ839AS0JT5TP4ZgwFNpaZ6qwwLX27HdI9KUXjTYDP1nqUZSdeZUGQHzoFN5mhKics82TZFc/bfbZppKVA==, tarball: file:projects/identity.tgz} name: '@rush-temp/identity' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/keyvault-keys': 4.2.0 '@azure/msal-browser': 2.20.0 @@ -12120,10 +12414,11 @@ packages: dev: false file:projects/iot-modelsrepository.tgz: - resolution: {integrity: sha512-FHhmlwhSRL5pN/Ad8/v2HvyQ2ZxCBy9l0A8k6+lnAueHzfem8D/UTvdjFomm0IBx6nXT8GpgSG5Uz4kajivB/A==, tarball: file:projects/iot-modelsrepository.tgz} + resolution: {integrity: sha512-wMOH+/0swnqt1aoiEbRc7rXEUv967MozN3/ByLxTazG5kFY0QVaI39FuthGz6VX4hoRpAuObO+UCgmZa+g+pbA==, tarball: file:projects/iot-modelsrepository.tgz} name: '@rush-temp/iot-modelsrepository' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -12176,10 +12471,11 @@ packages: dev: false file:projects/keyvault-admin.tgz: - resolution: {integrity: sha512-WishpCTglzE/R1KHqr1CsFyB4FFl/wuA5M6s8hpDxJszHz1qeUQacRQPEIDxBsjhexwUTL9bYA6nXVPCguGvaw==, tarball: file:projects/keyvault-admin.tgz} + resolution: {integrity: sha512-QlWm4/nunetF9va9edCbJZK52gJzAvE/qW5pwXS6u1pJKeUOTifuXkxBnASaI0MsexYO4qlyH9l+O3Zj/O4IgQ==, tarball: file:projects/keyvault-admin.tgz} name: '@rush-temp/keyvault-admin' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/keyvault-keys': 4.3.0 '@microsoft/api-extractor': 7.19.2 @@ -12211,10 +12507,11 @@ packages: dev: false file:projects/keyvault-certificates.tgz: - resolution: {integrity: sha512-aowXHcSOxD6UUWlOiIzOc+Y+qf8jOlVdnaIn8L/VGSleXtiFrQjPuWj6hU2ec8w8aTytReW3nphgigR5aXFynw==, tarball: file:projects/keyvault-certificates.tgz} + resolution: {integrity: sha512-wN9zw6B3W1knb0rB7xNd36Ie32fnO2pjG8RPW5TkZbcB6UuEFFp7pffcxfYnHdOvdtlSFTxdcZyLrMAzrTpiqg==, tarball: file:projects/keyvault-certificates.tgz} name: '@rush-temp/keyvault-certificates' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/keyvault-secrets': 4.3.0 '@microsoft/api-extractor': 7.19.2 @@ -12284,10 +12581,11 @@ packages: dev: false file:projects/keyvault-keys.tgz: - resolution: {integrity: sha512-DcMai4r3B5EuYLtn/gB1h6UtONfVOPzla31wkii1KDZYNlvFYqD18Jpttff+jWG1r10Zk+eJaCGvozTdOl+YJw==, tarball: file:projects/keyvault-keys.tgz} + resolution: {integrity: sha512-LIFd6OV6DQUq6WQYDE0VM7ECR6RQ8m4Uszn5P1y80Sf9IThLHKf/lMjSkjFLlMB7JvjT5kOb4eKGaUbI9wxImw==, tarball: file:projects/keyvault-keys.tgz} name: '@rush-temp/keyvault-keys' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -12336,10 +12634,11 @@ packages: dev: false file:projects/keyvault-secrets.tgz: - resolution: {integrity: sha512-jMOyRWijzmuL2hpRiyy4x0uT6LTUVjfnLhR70ai98MRM2jjUzwVaubd87d9LPlO337sZjgQ37UXkUEpVK/gNhg==, tarball: file:projects/keyvault-secrets.tgz} + resolution: {integrity: sha512-eejd4E2JX75T8Xxks8UjeO6qyHPF6Z8gkGER2vZx3j+lcvlMo+eq7lLKTtgic3PFmKovkHLa/436wZF1TVW4aQ==, tarball: file:projects/keyvault-secrets.tgz} name: '@rush-temp/keyvault-secrets' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -12439,10 +12738,11 @@ packages: dev: false file:projects/mixed-reality-authentication.tgz: - resolution: {integrity: sha512-ASSVnNvRS+4ZYOinqvDRFz8UD1zq92NrhWMFIiJUKqaceMHWKAH6EScmIGFmhyHSEE6MFPwOACDK6RPImtqOHA==, tarball: file:projects/mixed-reality-authentication.tgz} + resolution: {integrity: sha512-/957lA9+0kU7h+wqlphxrYOHnLSJBgbv3+Dn0HvxGS2mLlF2KPOG9ij4ryTjgmw4GuKgAt/J2QE9lGrw3nqsog==, tarball: file:projects/mixed-reality-authentication.tgz} name: '@rush-temp/mixed-reality-authentication' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -12485,10 +12785,11 @@ packages: dev: false file:projects/mixed-reality-remote-rendering.tgz: - resolution: {integrity: sha512-wDQhN9kT7dRXkO+aO2AVV7C94TBvPTPE9vIjHX/ncU0bkFgM//UwgSm9Lmr0wUL86eXyFCImdz6Kx5412V+thQ==, tarball: file:projects/mixed-reality-remote-rendering.tgz} + resolution: {integrity: sha512-AxE7atwTTZPNNrszrn4Mb8QXWTuDR6+RztTE5poid/Qzua6tbL6ypRJFWTxIJhIe9wxndMQohtDCq1muNNehTA==, tarball: file:projects/mixed-reality-remote-rendering.tgz} name: '@rush-temp/mixed-reality-remote-rendering' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -12585,10 +12886,11 @@ packages: dev: false file:projects/monitor-query.tgz: - resolution: {integrity: sha512-wa2TyQf7Py/7x6iBszhlSgUKRzxTrTkgnzqwSu9ljBySrumsaw7kWQmXU44csChTxhxpul2PFovJOGCWpl4LbA==, tarball: file:projects/monitor-query.tgz} + resolution: {integrity: sha512-IPdN5i0f2LEu5GnDidbvwwrnsDLs9oi/pmR+/XC4tpFX3CZpmlDARk0RL7cVzasJyEzBFOQO+X4TDLAqqGOUIg==, tarball: file:projects/monitor-query.tgz} name: '@rush-temp/monitor-query' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/monitor-opentelemetry-exporter': 1.0.0-beta.4 '@microsoft/api-extractor': 7.19.2 @@ -12637,10 +12939,11 @@ packages: dev: false file:projects/opentelemetry-instrumentation-azure-sdk.tgz: - resolution: {integrity: sha512-Dz8EkDVTh8SfKUFatcu/WZUX6YQjIQaE3A1qmM9RPSGXloRh/3TmyrrqqKvP6WRoyYIosCqRbwvzMUdyPljP/g==, tarball: file:projects/opentelemetry-instrumentation-azure-sdk.tgz} + resolution: {integrity: sha512-dS6Lqi/ySldCjaGQqPrcd4zl1sFaxqPf29+ZcZQKkL+wuZd8f4Dp7++aw++sv8swG0tvGLeCRwFu54cNC5006A==, tarball: file:projects/opentelemetry-instrumentation-azure-sdk.tgz} name: '@rush-temp/opentelemetry-instrumentation-azure-sdk' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@opentelemetry/api': 1.0.3 '@opentelemetry/core': 1.0.1_@opentelemetry+api@1.0.3 @@ -12807,7 +13110,7 @@ packages: dev: false file:projects/perf-data-tables.tgz: - resolution: {integrity: sha512-PgjuJKqYgdWR6o7kB/EjjXwR5RMIYgjZMHBwucULF8XfMytM6SBiWpqG72edRxEFo66OxmlLR3ByLfsjUNr+Xg==, tarball: file:projects/perf-data-tables.tgz} + resolution: {integrity: sha512-0HnabFIBko9eTEkMSRH34JvoRtKba6Av18Q/4bbi+k31s2M8mBVXSjIrjlwJYWHP7Y69kS6J3kbV94L7SYf6Kg==, tarball: file:projects/perf-data-tables.tgz} name: '@rush-temp/perf-data-tables' version: 0.0.0 dependencies: @@ -13102,12 +13405,13 @@ packages: dev: false file:projects/purview-account.tgz: - resolution: {integrity: sha512-2SgZF7TpHa9W54okUQr0+0TPdV6LnA9Op5yR0b5jrVC+XYhwO2bclHoU9dDESg2Ehr6ZkW+qfLUF6o7b+PmgAw==, tarball: file:projects/purview-account.tgz} + resolution: {integrity: sha512-leMQ9qnzNBLZmtEkf+cdL5rZ7p+wE9Oj4GBzWEhiCUSe7kxS7Q5qrFjLo/ajrHNz9PQsx/uelkvbDKmjb8Qbaw==, tarball: file:projects/purview-account.tgz} name: '@rush-temp/purview-account' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 '@azure-rest/core-client-paging': 1.0.0-beta.1 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -13148,11 +13452,12 @@ packages: dev: false file:projects/purview-administration.tgz: - resolution: {integrity: sha512-UlUeRaxkiHu2VdQj3jStF16Gp3H7GXx7yIvRxUKq5nbDL+krcnBAyXUzAU7ozWUmLEdUieYNSKpV7Dn6rbIvAA==, tarball: file:projects/purview-administration.tgz} + resolution: {integrity: sha512-+f2xAQp9pW5Iq912G3NdWDzEh23gD1nNxL4cv1JBEJbRcuELdwlkkgedV7ywjM6fkFz385l9GfTg2M8lJabKyQ==, tarball: file:projects/purview-administration.tgz} name: '@rush-temp/purview-administration' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -13193,11 +13498,12 @@ packages: dev: false file:projects/purview-catalog.tgz: - resolution: {integrity: sha512-kBvU8BT/PeSzUR/VIojuRzD1rOd9HfR4EJUf1seJy6FElzCpDlLDk3R61nAoHQn88tg7DB6akNCQp8QIOm+89g==, tarball: file:projects/purview-catalog.tgz} + resolution: {integrity: sha512-1MpYDoi0VDhiLGioio6bemTLBncssG2zouXBOQCjitG0FkEv4ORQRO+CNXa4j0Kw86iX4xcEFH/ZormVwIecyQ==, tarball: file:projects/purview-catalog.tgz} name: '@rush-temp/purview-catalog' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -13238,11 +13544,12 @@ packages: dev: false file:projects/purview-scanning.tgz: - resolution: {integrity: sha512-P9yipFZeWLQDurFXb2cGM4raLy6GNanN8c2qdEkf+IsMYcPyctwBzhL1Nd0DQbf2ZwFndYjhWvZSDXtaEanYfw==, tarball: file:projects/purview-scanning.tgz} + resolution: {integrity: sha512-xeP7c4Yy8xpapk5LfqZzi4zBqd/Zbnnmo9R02dAYTrAxsJuYaNJB9WWvfk8y8DeDarck/lfUgI2mSVf1yw4P7g==, tarball: file:projects/purview-scanning.tgz} name: '@rush-temp/purview-scanning' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/mocha': 7.0.2 @@ -13283,10 +13590,11 @@ packages: dev: false file:projects/quantum-jobs.tgz: - resolution: {integrity: sha512-qSzsQXvfjB9gOhpVkOEdqUYXRJjxFx5F1PVTU6Hdnm2rp1h+12RzDJ1Fk6HeScoUVFC/99sl6mWKJBc9NLZsng==, tarball: file:projects/quantum-jobs.tgz} + resolution: {integrity: sha512-I2xSt1eTaKYnLj8+OzqvYlV0Oz724H9Zw9nUfG6q67rQtKMdN4wUd56oz68MGZlVNX73rXZizkV5rdAwS0xn9g==, tarball: file:projects/quantum-jobs.tgz} name: '@rush-temp/quantum-jobs' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/storage-blob': 12.8.0 '@microsoft/api-extractor': 7.19.2 @@ -13331,10 +13639,11 @@ packages: dev: false file:projects/schema-registry-avro.tgz: - resolution: {integrity: sha512-X/hZLrVREZDlgnJQnf3F/s3J0Jo0enaPAP3Ydr6v2yVL6eqEXQx5ssoA6h7PJLq8rgYvn4Tt2NBeptvzswvOoQ==, tarball: file:projects/schema-registry-avro.tgz} + resolution: {integrity: sha512-nYoV4QCq4gGBjhk2hz9opui4CbCRNv3bcVGgDVCIqiso9PbCyWb6CqNqsSZyT7F2NIpVfGSNtElbrVDgx/2i/g==, tarball: file:projects/schema-registry-avro.tgz} name: '@rush-temp/schema-registry-avro' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-inject': 4.0.3_rollup@1.32.1 @@ -13382,10 +13691,11 @@ packages: dev: false file:projects/schema-registry.tgz: - resolution: {integrity: sha512-s7CW2urfzFLDuxStn5xO1RvtGWA15/HoLDWb+0fX0CYRkQGrdCvSXGDcTpPTncgjEkmvtGA2QL7ZcPMRgzccBA==, tarball: file:projects/schema-registry.tgz} + resolution: {integrity: sha512-fucVewqXE8TZV5enQSHpfLRsx8+I+1w1Cec81+x0xHbAcEs0ykoXa7NCNYQMAPi+V0Pu99w+Hqmjsrbn1SK6yg==, tarball: file:projects/schema-registry.tgz} name: '@rush-temp/schema-registry' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -13427,10 +13737,11 @@ packages: dev: false file:projects/search-documents.tgz: - resolution: {integrity: sha512-ER3tN8wTUDV1MyrXM0YHcbDxBYzFvtM4LSr0nLtx2yDRiaEthfkXy9lD0kQLVQDTIDaDhZa0xLNZa7DJORiwNg==, tarball: file:projects/search-documents.tgz} + resolution: {integrity: sha512-gXZcks+emsytlNuUR+n5kl0TPeGi9xc1zDS1lwTrB408hnCny7uFqP9DpFqBdJN2rq02Rxku+DEtFgxZOEOBdw==, tarball: file:projects/search-documents.tgz} name: '@rush-temp/search-documents' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -13545,10 +13856,11 @@ packages: dev: false file:projects/storage-blob-changefeed.tgz: - resolution: {integrity: sha512-c3o02EXdfoBIANBCD0d0gXXJRgB6ElStsNqQEBJOyvtdiwxagY0AvHi52dgKPt5pV2L4zm6HN+7HZsBIOYsfyg==, tarball: file:projects/storage-blob-changefeed.tgz} + resolution: {integrity: sha512-cVm9VsycWYrsolHlAF+VlbCZs/5hthXlfSZ6LWjkg0MPi2ZoVB9J9GDckHflbJwE99T09ete5RlzutM5lifkLw==, tarball: file:projects/storage-blob-changefeed.tgz} name: '@rush-temp/storage-blob-changefeed' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@azure/storage-blob': 12.8.0 '@microsoft/api-extractor': 7.19.2 @@ -13609,10 +13921,11 @@ packages: dev: false file:projects/storage-blob.tgz: - resolution: {integrity: sha512-WwMq0ZEEFMwrxFLKvCxrP0/E+GUMaui7h0bQ3PJ3IWjcJReMJ1qnbtFtNDZ9vUONcM5ffws24wTpoZU0XCMdPA==, tarball: file:projects/storage-blob.tgz} + resolution: {integrity: sha512-/jRPqY4sBNbjz87glN6HtF/610/p+YOlmDB+Vwn9+M2LgmT2lXWlVFImNK247MxrDMtbOuBTvyhiJtluQvCLmw==, tarball: file:projects/storage-blob.tgz} name: '@rush-temp/storage-blob' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -13672,10 +13985,11 @@ packages: dev: false file:projects/storage-file-datalake.tgz: - resolution: {integrity: sha512-Sr7+HODu8luwO/+gmIxhyIFjccm989PduMwuDFpG0uWYiLYyEAUSwV/fbvyq6uSh87ilK8h50xVBHrm6mJ+4aQ==, tarball: file:projects/storage-file-datalake.tgz} + resolution: {integrity: sha512-q4/t9o5UB6HFD0FJChpZV/Q0Br0qlCGxkm1KmDIjn1lTciF6epHU4GpXs4HXjtNYfPFfhaEejoz8MxDd1/vXYA==, tarball: file:projects/storage-file-datalake.tgz} name: '@rush-temp/storage-file-datalake' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -13735,10 +14049,11 @@ packages: dev: false file:projects/storage-file-share.tgz: - resolution: {integrity: sha512-n08wfQWGO/07zKGppKf8+b7e20zlSjq7pct+R3ddqPfq9GY6rVUBuIoAhJSFLxv0I3f+EVXdEAZuFXV8mOdspg==, tarball: file:projects/storage-file-share.tgz} + resolution: {integrity: sha512-kZ1SIL/CgYyfEoIQLLRl/G0CdM1mSPGpW04YA3iIi2APu8Y963BS0BEpqfp+ohT9zIJDIh6CTkZq9afnTx+IOA==, tarball: file:projects/storage-file-share.tgz} name: '@rush-temp/storage-file-share' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -13796,10 +14111,11 @@ packages: dev: false file:projects/storage-internal-avro.tgz: - resolution: {integrity: sha512-KN+RE64HAWY63T4RBSoWQHMq29VDP/vBfze61HZS3iyEau4lH3TOVk7/XP3A/5+M6+naviCu6ROPuRN4WQuI5A==, tarball: file:projects/storage-internal-avro.tgz} + resolution: {integrity: sha512-v6xXQ7nuIUe8RA71sQkVdNURlVWnd8NaJvQ/0pzm7PwKZH1L/UCLSUXNzcoPZkUKz1GuCgk2qBdltQtMGph0uQ==, tarball: file:projects/storage-internal-avro.tgz} name: '@rush-temp/storage-internal-avro' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 @@ -13854,10 +14170,11 @@ packages: dev: false file:projects/storage-queue.tgz: - resolution: {integrity: sha512-9qZesmHPnXr2Sol9VItzfqDQNK0/mX+dbfZeX3k5Nc+9iq9rpwei1ZF1qek7qgXAhekJdQDIEgfFi2BEQSERnw==, tarball: file:projects/storage-queue.tgz} + resolution: {integrity: sha512-tdtbvZ3lHPr/olY1x2Jv+zawhv2R/CSapl3EOUNwf/FlLw6MTuC9fwcnuYJQQ4ZTC3CMnED3d4Yfe6HhqTaJcg==, tarball: file:projects/storage-queue.tgz} name: '@rush-temp/storage-queue' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -13914,10 +14231,11 @@ packages: dev: false file:projects/synapse-access-control-1.tgz: - resolution: {integrity: sha512-oyb6JdMjvR69By0q6MReN1EcWS/Eqa7N/483q2nTvObJt+SF5UY6RST48wChvLDC1J0A3bHnRvw1JbsxulfLlg==, tarball: file:projects/synapse-access-control-1.tgz} + resolution: {integrity: sha512-YM3ZGx1FWtsmNU51/c+pmhX2j/JtkVxHLrPChYTXIBGbyaCeybpMQOBIoPMHAmtz/ucJrhR3s4xN0D4JjC+Q7g==, tarball: file:projects/synapse-access-control-1.tgz} name: '@rush-temp/synapse-access-control-1' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -13966,11 +14284,12 @@ packages: dev: false file:projects/synapse-access-control.tgz: - resolution: {integrity: sha512-XAxW1/ksG3TEBG4kyP/nJnWp+9g8fTRupupZ7mR3O51ObSP4yEMUaoli6n82dO/+X2Xxo9whH1BnA50hku2w4Q==, tarball: file:projects/synapse-access-control.tgz} + resolution: {integrity: sha512-XtBxDd/1zvMjGXUpZGUOL7Hl/PLAV4AZhiox7x4UohcTnE/F1asKHlLDeoKvyrWK1RouLhpNaYbN2zdJYe4sDA==, tarball: file:projects/synapse-access-control.tgz} name: '@rush-temp/synapse-access-control' version: 0.0.0 dependencies: '@azure-rest/core-client': 1.0.0-beta.7 + '@azure-tools/test-recorder': 1.0.2 '@azure/core-util': 1.0.0-beta.1 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -14021,10 +14340,11 @@ packages: dev: false file:projects/synapse-artifacts.tgz: - resolution: {integrity: sha512-yeEkzQdA2yUPfj1+gRhmkzqy9vMKqj9gEaofwGPeO8X7aJBFNDBRNhLKLN4QCkFHwGY3iL+eHTLUJxCwPc9HgA==, tarball: file:projects/synapse-artifacts.tgz} + resolution: {integrity: sha512-O6vd+XiFqMpHC6Xgk9CU0qye3Ktu+vPGPedc+gvqWs7wxfVyZtbM3o4BGr5sqyQjTixwZbwoi8YVJVt8CboGgQ==, tarball: file:projects/synapse-artifacts.tgz} name: '@rush-temp/synapse-artifacts' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -14073,10 +14393,11 @@ packages: dev: false file:projects/synapse-managed-private-endpoints.tgz: - resolution: {integrity: sha512-QH7S+f26cJUZDiVMMwtgwPpggdhBcaVmUU1KKQXAR7+bI7NTX0QrpgK3TKOUbXxlc4EC8u0ihdhPIAx4PnOzXg==, tarball: file:projects/synapse-managed-private-endpoints.tgz} + resolution: {integrity: sha512-MCHOzRbdz90Fgs8XhZCNXbfMbo4sGmsuCBuuO34NV3WWhq0JnPc7nfjEzElBLsBGEGRcEGvEnCxx0wlYfYBVpg==, tarball: file:projects/synapse-managed-private-endpoints.tgz} name: '@rush-temp/synapse-managed-private-endpoints' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -14138,10 +14459,11 @@ packages: dev: false file:projects/synapse-spark.tgz: - resolution: {integrity: sha512-Jm0VBD49BuJPQJemURZTSBzQXtXUJqFFJ/JxQGkb+sEzGRyuUV1Xjs/m3wS3PFWZuYZyTX5y9jd/CnM6FF9Hhw==, tarball: file:projects/synapse-spark.tgz} + resolution: {integrity: sha512-MP9tmIV6E25jjUUD7AsG52B2QukXzsq5neKxAwNAWore5bGykmPAH5lCxw4sQcK+AXkQg3s5HDLPx2cwj4A8gg==, tarball: file:projects/synapse-spark.tgz} name: '@rush-temp/synapse-spark' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 @@ -14183,7 +14505,7 @@ packages: dev: false file:projects/template.tgz: - resolution: {integrity: sha512-bDy20Y4xQvxey3RMED4DOcnIVa4ctjL1tdNxzJXsFqxE1/IYsRFma/p1K6k5peob4Q0Axj+bZIZHfQR7pSuK4w==, tarball: file:projects/template.tgz} + resolution: {integrity: sha512-dr7idlS3XhlJfHn9dU7ISFa/mI5LL0lCHDU/2TkSuQADpj7725wptg7GxiJy+hDjN+wueaxEsiXiuFavHSs8ow==, tarball: file:projects/template.tgz} name: '@rush-temp/template' version: 0.0.0 dependencies: @@ -14231,10 +14553,11 @@ packages: dev: false file:projects/test-credential.tgz: - resolution: {integrity: sha512-iEnhmTWqjqTfunF2+GRxZMJ4OvO2ek3Y0Mb/RcroAYf1B+/FWfYl0k48jg+uby0+EBT5xVa3+SfANBVgQAlN/g==, tarball: file:projects/test-credential.tgz} + resolution: {integrity: sha512-0CVhBlgJ8+DdZ0gcYZssRyRznTjjPu5x/zziSM1TbkQfv23tepvKRLbrNtWcGSrfRApzXCMn6pXsMBQMxTtJHA==, tarball: file:projects/test-credential.tgz} name: '@rush-temp/test-credential' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@types/node': 12.20.40 eslint: 7.32.0 prettier: 2.5.1 @@ -14245,11 +14568,12 @@ packages: - supports-color dev: false - file:projects/test-recorder-new.tgz: - resolution: {integrity: sha512-SaWOH6ubcTbybPLHGNUDpnpejqqcrxBF9x8qJWcH29LqMf9bq2zJaDTjqLnZR+ZFi4geKD99EafcGTYUXQzCEA==, tarball: file:projects/test-recorder-new.tgz} - name: '@rush-temp/test-recorder-new' + file:projects/test-recorder.tgz: + resolution: {integrity: sha512-vgdKX2IsLra7Eg/LXwD+aZgicnNL5Nyx7/JEUE+1XP9cmzqtjcPgDQQENdT80Zu0T7VnOeElTvtKfF3rzCxmvA==, tarball: file:projects/test-recorder.tgz} + name: '@rush-temp/test-recorder' version: 0.0.0 dependencies: + '@azure/core-tracing': 1.0.0-preview.13 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 '@rollup/plugin-node-resolve': 8.4.0_rollup@1.32.1 @@ -14270,65 +14594,6 @@ packages: dotenv: 8.6.0 eslint: 7.32.0 express: 4.17.2 - karma: 6.3.9 - karma-chrome-launcher: 3.1.0 - karma-coverage: 2.1.0 - karma-edge-launcher: 0.4.2_karma@6.3.9 - karma-env-preprocessor: 0.1.1 - karma-firefox-launcher: 1.3.0 - karma-ie-launcher: 1.0.0_karma@6.3.9 - karma-json-preprocessor: 0.3.3_karma@6.3.9 - karma-json-to-file-reporter: 1.0.1 - karma-junit-reporter: 2.0.1_karma@6.3.9 - karma-mocha: 2.0.1 - karma-mocha-reporter: 2.2.5_karma@6.3.9 - karma-sourcemap-loader: 0.3.8 - mocha: 7.2.0 - mocha-junit-reporter: 2.0.2_mocha@7.2.0 - mock-fs: 5.1.2 - mock-require: 3.0.3 - npm-run-all: 4.1.5 - nyc: 15.1.0 - prettier: 2.5.1 - rimraf: 3.0.2 - rollup: 1.32.1 - rollup-plugin-shim: 1.0.0 - rollup-plugin-sourcemaps: 0.4.2_rollup@1.32.1 - rollup-plugin-terser: 5.3.1_rollup@1.32.1 - rollup-plugin-visualizer: 4.2.2_rollup@1.32.1 - ts-node: 9.1.1_typescript@4.2.4 - tslib: 2.3.1 - typescript: 4.2.4 - uuid: 8.3.2 - xhr-mock: 2.5.1 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - dev: false - - file:projects/test-recorder.tgz: - resolution: {integrity: sha512-YVTZKhc57wqPtdwdRvUNEeT7fWFV9Z8biRjF+DNywqCbCLC6QY/gIy7+Cb7Lw9B+MVCPDxqW4r0dU4aFd4rBjA==, tarball: file:projects/test-recorder.tgz} - name: '@rush-temp/test-recorder' - version: 0.0.0 - dependencies: - '@azure/core-tracing': 1.0.0-preview.13 - '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 - '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 - '@rollup/plugin-node-resolve': 8.4.0_rollup@1.32.1 - '@rollup/plugin-replace': 2.4.2_rollup@1.32.1 - '@types/chai': 4.3.0 - '@types/fs-extra': 8.1.2 - '@types/md5': 2.3.1 - '@types/mocha': 7.0.2 - '@types/mock-fs': 4.13.1 - '@types/mock-require': 2.0.0 - '@types/nise': 1.4.0 - '@types/node': 12.20.40 - chai: 4.3.4 - dotenv: 8.6.0 - eslint: 7.32.0 fs-extra: 8.1.0 karma: 6.3.9 karma-chrome-launcher: 3.1.0 @@ -14359,8 +14624,10 @@ packages: rollup-plugin-sourcemaps: 0.4.2_rollup@1.32.1 rollup-plugin-terser: 5.3.1_rollup@1.32.1 rollup-plugin-visualizer: 4.2.2_rollup@1.32.1 + ts-node: 9.1.1_typescript@4.2.4 tslib: 2.3.1 typescript: 4.2.4 + uuid: 8.3.2 xhr-mock: 2.5.1 transitivePeerDependencies: - bufferutil @@ -14397,10 +14664,11 @@ packages: dev: false file:projects/test-utils.tgz: - resolution: {integrity: sha512-CEWMLlxQTpRRiRBCJi1fRyGOlLMmxCgd4C/5JwIekQL4LtpQ6OvRVGyJFSpJ4Y6dYxAwXyYnEF9MWrDWURAtIQ==, tarball: file:projects/test-utils.tgz} + resolution: {integrity: sha512-8lEcd2lqVBnZR7ZIuKzoSoj//1wwRSltl15FQIkg4YkHIJRxfB9fo4xVn3xWQxPGZLsvH1LlRpqBl8EPJ9QGbQ==, tarball: file:projects/test-utils.tgz} name: '@rush-temp/test-utils' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@opentelemetry/api': 1.0.3 @@ -14430,7 +14698,7 @@ packages: dev: false file:projects/testing-recorder-new.tgz: - resolution: {integrity: sha512-05oLLpWqsXhCMszzTOhm+TeCAFXH70Qld5TTY6BdKQBuXU2c60RzuGJRJK4C87u+v3On/mdfTzth6YG/iJmLOQ==, tarball: file:projects/testing-recorder-new.tgz} + resolution: {integrity: sha512-dBKlhKpFsSOTnRvrJrSOvqoEoEfTrrYS1W2JnAZNEg9KhbVzo8bPqryUlo5NiXw7sAUokaubzM2PVm9OXwRlbA==, tarball: file:projects/testing-recorder-new.tgz} name: '@rush-temp/testing-recorder-new' version: 0.0.0 dependencies: @@ -14527,10 +14795,11 @@ packages: dev: false file:projects/web-pubsub-express.tgz: - resolution: {integrity: sha512-XHFDp/PaBzZs7IH85xM5yQNG39ZKE/V6O7FpErS/cBg7QEai5S4yoI5N5C7ma0bXkceE7/6pk+8wUYY1/gMCxg==, tarball: file:projects/web-pubsub-express.tgz} + resolution: {integrity: sha512-/UzdXCww61QdJQEcfbB4/N93BLSu4IdL90y/4JTYHYaLLiGO1SYr1pwowhK7P7dFT/8qWH4r340GZHBc9hEgsg==, tarball: file:projects/web-pubsub-express.tgz} name: '@rush-temp/web-pubsub-express' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 '@types/express': 4.17.13 @@ -14577,10 +14846,11 @@ packages: dev: false file:projects/web-pubsub.tgz: - resolution: {integrity: sha512-NngkpUc6uTF3aIG2nn6OOp+IWQzS0FL4ctOy1SOtG31BaI6HvF9Zd5N9OPgKKe8ivAulscGCE7PC/zVfvj1NWg==, tarball: file:projects/web-pubsub.tgz} + resolution: {integrity: sha512-sC+qd6xpeS2pifzUwkFm3yc6Dd5VBWDhjjX38xqpsEU+x9os3Sz/AlPBIqxLY5ds/vgwqVwYUAh2v8CSYwwhiw==, tarball: file:projects/web-pubsub.tgz} name: '@rush-temp/web-pubsub' version: 0.0.0 dependencies: + '@azure-tools/test-recorder': 1.0.2 '@azure/core-tracing': 1.0.0-preview.13 '@microsoft/api-extractor': 7.19.2 '@types/chai': 4.3.0 diff --git a/rush.json b/rush.json index 8e698bada625..cb6e779ce3c7 100644 --- a/rush.json +++ b/rush.json @@ -716,11 +716,6 @@ "projectFolder": "sdk/test-utils/recorder", "versionPolicyName": "utility" }, - { - "packageName": "@azure-tools/test-recorder-new", - "projectFolder": "sdk/test-utils/recorder-new", - "versionPolicyName": "utility" - }, { "packageName": "@azure-tools/test-credential", "projectFolder": "sdk/test-utils/test-credential", diff --git a/sdk/storage/CONTRIBUTING.md b/sdk/storage/CONTRIBUTING.md index d1f114089fdd..cf2383da9146 100644 --- a/sdk/storage/CONTRIBUTING.md +++ b/sdk/storage/CONTRIBUTING.md @@ -57,7 +57,7 @@ The environment variable **TEST_MODE** controls how the tests are running. - Else If TEST_MODE = "playback" (or if the TEST_MODE is not set or set to an invalid value), - Existing recordings are played back as responses to the HTTP requests in the tests -Please refer to the [guidelines on Record and Playback](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/test-utils/recorder/GUIDELINES.md) for more details. +Please refer to the [guidelines on Record and Playback](https://github.com/Azure/azure-sdk-for-js/blob/c06170fbaf39e81496f8009a0da93d470d8c9f88/sdk/test-utils/recorder/GUIDELINES.md) for more details. ### Emulator Tests @@ -156,7 +156,7 @@ npm run test `npm run test` would run the the tests in both node and the browser. -**Link** - [Guidelines for record and playback - `@azure-tools/test-recorder`](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/test-utils/recorder/GUIDELINES.md) +**Link** - [Guidelines for record and playback - `@azure-tools/test-recorder`](https://github.com/Azure/azure-sdk-for-js/blob/c06170fbaf39e81496f8009a0da93d470d8c9f88/sdk/test-utils/recorder/GUIDELINES.md) ## Pull Requests diff --git a/sdk/tables/perf-tests/data-tables/package.json b/sdk/tables/perf-tests/data-tables/package.json index fafc38c09514..3b63bf0f9fbe 100644 --- a/sdk/tables/perf-tests/data-tables/package.json +++ b/sdk/tables/perf-tests/data-tables/package.json @@ -8,7 +8,7 @@ "author": "", "license": "ISC", "dependencies": { - "@azure/data-tables": "^12.1.2", + "@azure/data-tables": "^13.0.2", "@azure/test-utils-perf": "^1.0.0", "dotenv": "^8.2.0", "uuid": "^8.3.0" diff --git a/sdk/template/template/karma.conf.js b/sdk/template/template/karma.conf.js index 30b25eb3ebe8..370e37e40021 100644 --- a/sdk/template/template/karma.conf.js +++ b/sdk/template/template/karma.conf.js @@ -4,14 +4,8 @@ // https://github.com/karma-runner/karma-chrome-launcher process.env.CHROME_BIN = require("puppeteer").executablePath(); process.env.RECORDINGS_RELATIVE_PATH = - require("@azure-tools/test-recorder-new").relativeRecordingsPath(); + require("@azure-tools/test-recorder").relativeRecordingsPath(); require("dotenv").config(); -const { - jsonRecordingFilterFunction, - isPlaybackMode, - isSoftRecordMode, - isRecordMode, -} = require("@azure-tools/test-recorder"); module.exports = function (config) { config.set({ @@ -32,15 +26,13 @@ module.exports = function (config) { "karma-env-preprocessor", "karma-coverage", "karma-junit-reporter", - "karma-json-to-file-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 }, - ].concat(isPlaybackMode() || isSoftRecordMode() ? ["recordings/browsers/**/*.json"] : []), + ], // list of files / patterns to exclude exclude: [], @@ -49,7 +41,6 @@ module.exports = function (config) { // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { "**/*.js": ["env"], - "recordings/browsers/**/*.json": ["json"], // 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"] @@ -69,7 +60,7 @@ module.exports = function (config) { // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ["mocha", "coverage", "junit", "json-to-file"], + reporters: ["mocha", "coverage", "junit"], coverageReporter: { // specify a common output directory @@ -87,11 +78,6 @@ module.exports = function (config) { properties: {}, // key value pair of properties to add to the section of the report }, - jsonToFileReporter: { - filter: jsonRecordingFilterFunction, - outputPath: ".", - }, - // web server port port: 9876, @@ -126,9 +112,6 @@ module.exports = function (config) { browserNoActivityTimeout: 600000, browserDisconnectTimeout: 10000, browserDisconnectTolerance: 3, - browserConsoleLogOptions: { - terminal: !isRecordMode(), - }, client: { mocha: { diff --git a/sdk/template/template/package.json b/sdk/template/template/package.json index ccea029cf498..8dd73fe1eb36 100644 --- a/sdk/template/template/package.json +++ b/sdk/template/template/package.json @@ -87,7 +87,7 @@ "@azure/dev-tool": "^1.0.0", "@azure/eslint-plugin-azure-sdk": "^3.0.0", "@azure/identity": "^2.0.1", - "@azure-tools/test-recorder": "^1.0.0", + "@azure-tools/test-recorder": "^2.0.0", "@microsoft/api-extractor": "^7.18.11", "@types/chai": "^4.1.6", "@types/chai-as-promised": "^7.1.0", @@ -122,7 +122,6 @@ "source-map-support": "^0.5.9", "typescript": "~4.2.0", "util": "^0.12.1", - "@azure-tools/test-recorder-new": "~1.0.0", "@azure-tools/test-credential": "~1.0.0" }, "//sampleConfiguration": { diff --git a/sdk/template/template/test/public/configurationClient.spec.ts b/sdk/template/template/test/public/configurationClient.spec.ts index 7f57f2f161b9..fd62e50a6d69 100644 --- a/sdk/template/template/test/public/configurationClient.spec.ts +++ b/sdk/template/template/test/public/configurationClient.spec.ts @@ -3,11 +3,8 @@ import { assert } from "chai"; import { Context } from "mocha"; - import { ConfigurationClient } from "../../src"; - -import { env } from "@azure-tools/test-recorder"; -import { Recorder } from "@azure-tools/test-recorder-new"; +import { Recorder, assertEnvironmentVariable } from "@azure-tools/test-recorder"; import { createTestCredential } from "@azure-tools/test-credential"; // When the recorder observes the values of these environment variables in any @@ -25,7 +22,7 @@ const replaceableVariables: Record = { function createConfigurationClient(recorder: Recorder): ConfigurationClient { // Retrieve the endpoint from the environment variable // we saved to the .env file earlier - const endpoint = env.APPCONFIG_ENDPOINT; + const endpoint = assertEnvironmentVariable("APPCONFIG_ENDPOINT"); // We use the createTestCredential helper from the test-credential tools package. // This function returns the special NoOpCredential in playback mode, which @@ -73,8 +70,8 @@ describe("[AAD] ConfigurationClient functional tests", function () { }); it("predetermined setting has expected value", async () => { - const key = env.APPCONFIG_TEST_SETTING_KEY; - const expectedValue = env.APPCONFIG_TEST_SETTING_EXPECTED_VALUE; + const key = assertEnvironmentVariable("APPCONFIG_TEST_SETTING_KEY"); + const expectedValue = assertEnvironmentVariable("APPCONFIG_TEST_SETTING_EXPECTED_VALUE"); const setting = await client.getConfigurationSetting(key); diff --git a/sdk/test-utils/recorder-new/.gitignore b/sdk/test-utils/recorder-new/.gitignore deleted file mode 100644 index 966433321863..000000000000 --- a/sdk/test-utils/recorder-new/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore the recordings directory in this project since they get regenerated every test run -recordings/ diff --git a/sdk/test-utils/recorder-new/CHANGELOG.md b/sdk/test-utils/recorder-new/CHANGELOG.md deleted file mode 100644 index 2bb8b67fce26..000000000000 --- a/sdk/test-utils/recorder-new/CHANGELOG.md +++ /dev/null @@ -1,117 +0,0 @@ -# Release History - -## 1.0.0 (Unreleased) - -## 2021-12-27 - -- Allows passing `undefined` as keys in the sanitizer options so that devs don't have to add additional checks if a certain env variable exists in playback. -- Exports `delay` - - waits for expected time in record/live modes - - no-op in playback - -[#19561](https://github.com/Azure/azure-sdk-for-js/pull/19561) - -## 2021-12-17 - -- Refactoring the test proxy http clients for better clarity for the end users [#19446](https://github.com/Azure/azure-sdk-for-js/pull/19446) - - - Client rename `TestProxyHttpClient` -> `Recorder` - - Removing the separate `TestProxyHttpClientCoreV1` - - Instead of passing the whole client as the `httpClient` for core-v1 sdks, users are now expected to call `recorder.configureClientOptionsCoreV1` on the client options while passing to the respective client(where `recorder` is an instantiation of `Recorder`). This modifies the httpClient in the options to allow redirecting the requests to the test-proxy tool. - - Duplicated helpers from old recorder to not depend on old recorder package.. with an intention to replace the recorder package as 2.0 and not merge since the old recorder is published already - - Makes the following members of `Recorder` private - - `httpClient` - - `redirectRequest` - - `recorderHttpPolicy` - - `createHttpClientCoreV1` - -- Moving `NoOpCredential` to the new `@azure-tools/test-credential` package. - -## 2021-12-15 - -- Change the API for using variables. Variables can now be accessed using the syntax: - - ```ts - const variable = recorder.variable(, ); - ``` - - A shorthand is also added for when accessing a variable a second time in - the same test, which does not require an initial value be set, e.g.: - - ```ts - recorder.variable("variableName", ); - - // later on in your code - const value = recorder.variable("variableName"); - ``` - - [#19388](https://github.com/Azure/azure-sdk-for-js/pull/19388) - -## 2021-12-14 - -- Add `configureClient` method to the `TestProxytHttpClient` to allow instrumenting the client with the recorder policy which helps in enabling the recorder to redirect the requests of your tests to the proxy tool. - - Also un-exports `recorderHttpPolicy` function. - [#19362](https://github.com/Azure/azure-sdk-for-js/pull/19362) - -## 2021-12-13 - -- Add support for `setMatcher`, which can be used to instruct the proxy tool to ignore headers (using `HeaderlessMatcher`) or the request body (using `BodilessMatcher`) when matching requests to recordings. - - Example: - - ```ts - await recorder.setMatcher("HeaderlessMatcher"); - ``` - -## 2021-12-10 - -- Loads the .env file using with the help of "dotenv" by default. - [#19139](https://github.com/Azure/azure-sdk-for-js/pull/19139) - -## 2021-11-30 - -- Adds NoOp AAD Credential for playback `NoOpCredential`. Using this as your AAD credential in playback mode would avoid the AAD traffic in playback. - [#18904](https://github.com/Azure/azure-sdk-for-js/pull/18904) - -## 2021-11-08 - -- Allows storing dynamically created variables in record mode. The recorder registers the variables as part of the recording and stores their values in the recording file. Using the `variables` in playback mode produces the key-value pairs that were stored in the recording file. - - Example: - - ```ts - if (!isPlaybackMode()) { - recorder.variables["random-1"] = `random-${Math.ceil(Math.random() * 1000 + 1000)}`; - } - ``` - - Use `recorder.variables["random-1"]` to access the value of the variable after setting it. The variable can be accessed in all three modes -- record, playback, and live -- as long as it is set in record mode before it is accessed. - -## 2021-10-15 - -[#17379](https://github.com/Azure/azure-sdk-for-js/pull/17379) - -- Added `addSanitizers` method to support - - BodyKeySanitizer - - BodyRegexSanitizer - - GeneralRegexSanitizer - - HeaderRegexSanitizer - - RemoveHeaderSanitizer - - UriRegexSanitizer - - UriSubscriptionIdSanitizer - - Connection String sanitizer -- Adds `SanitizerOptions`, env setup for playback as the options of the `start()` method - - Applies `generalRegexSanitizers` on the env setup for playback options by default to eliminate any plain text secrets in the recordings -- Testing - All the tests are run in the `recorder-new` folder run in all three modes - "record", "playback" and "live" everytime the test commands are run - -## 2021-09-27 - -[#17388](https://github.com/Azure/azure-sdk-for-js/pull/17388) - -- `TestProxyClient` now takes the test context to determine the location of the recordings. -- Adds a server for the tests, to play the role of an actual service to be able to test the proxy-tool end-to-end. - -## 2021-07-17 - -- Building the unified recorder prototype leveraging the proxy-tool, works for both core-v1 and core-v2 SDKs. Shows data-tables and storage-queue as examples for core-v2 and core-v1 respectively. - [#15826](https://github.com/Azure/azure-sdk-for-js/pull/15826) diff --git a/sdk/test-utils/recorder-new/README.md b/sdk/test-utils/recorder-new/README.md deleted file mode 100644 index 82c5d0d2b8f9..000000000000 --- a/sdk/test-utils/recorder-new/README.md +++ /dev/null @@ -1,54 +0,0 @@ -## Azure @azure-tools/test-recorder-new library for JavaScript - -This is an experimental tool to record and playback the tests in the JS repo by leveraging the unified out-of-process test proxy server. This library is still under construction. -Feature work is being tracked at [#15829](https://github.com/Azure/azure-sdk-for-js/issues/15829) - -## Resources - -- [Azure SDK Tools Test Proxy](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy) -- [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) - -## Running the test-proxy tool - -### With the `docker run` command - -- Run this command - - > `docker run -v /workspaces/azure-sdk-for-js/:/srv/testproxy -p 5001:5001 -p 5000:5000 azsdkengsys.azurecr.io/engsys/testproxy-lin:latest` - - Map the root directory of the azure-sdk-for-js repo to `/srv/testproxy` inside the container for an accurate location while generating recordings. - - (Eventually, recorder will trigger this for you!) - - Add `--add-host host.docker.internal:host-gateway` for linux to access host's network(to access `localhost`) through `host.docker.internal`. - Docker for Windows and Mac support `host.docker.internal` as a functioning alias for localhost. - - If the above command doesn't work directly, try [Troubleshooting Access to Public Container Registry](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#troubleshooting-access-to-public-container-registry). - - Reference: [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) - -### (OR) With the `dotnet tool` - -- Install [.Net 5.0](https://dotnet.microsoft.com/download) -- Install test-proxy - > `dotnet tool install azure.sdk.tools.testproxy --global --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json --version 1.0.0-dev*` -- After successful installation, run the tool: - - > `test-proxy --storage-location ` - - [ `root-of-the-repo example` - `/workspaces/azure-sdk-for-js` if you're on codespaces] - -## Run the tests using recorder-new at `test-utils/testing-recorder-new` - -- Navigate to the test-utils\testing-recorder-new folder -- Run `rush update && rush build -t .` -- Run `rushx test:node` -- Run `rushx test:browser` - -## Copying the recordings saved in the container - -For some reason, the volume mapping did not work for you, copy the recordings manually. - -- `docker cp :/srv/testproxy/ temp-location` - - [This will be fixed eventually [#Issue-17138](https://github.com/Azure/azure-sdk-for-js/issues/17138)] diff --git a/sdk/test-utils/recorder-new/karma.conf.js b/sdk/test-utils/recorder-new/karma.conf.js deleted file mode 100644 index b2f1f58d3228..000000000000 --- a/sdk/test-utils/recorder-new/karma.conf.js +++ /dev/null @@ -1,126 +0,0 @@ -// https://github.com/karma-runner/karma-chrome-launcher -const { relativeRecordingsPath } = 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-edge-launcher", - "karma-firefox-launcher", - "karma-ie-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", "PROXY_MANUAL_START"], - - // 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-new/package.json b/sdk/test-utils/recorder-new/package.json deleted file mode 100644 index 2f5992cd4091..000000000000 --- a/sdk/test-utils/recorder-new/package.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "name": "@azure-tools/test-recorder-new", - "version": "1.0.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/test/utils/server.js": "./dist-esm/test/utils/server.browser.js" - }, - "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 . && cross-env ONLY_NODE=true rollup -c 2>&1", - "build:samples": "echo Skipped.", - "build:test": "tsc -p .", - "build": "npm run clean && tsc -p . && rollup -c 2>&1", - "check-format": "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", - "extract-api": "echo skipped", - "format": "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", - "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", - "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", - "unit-test:browser": "npm run integration-test:browser", - "unit-test:node": "npm run integration-test:node", - "unit-test": "npm run unit-test:node && npm run unit-test:browser", - "test:browser": "npm run clean && npm run build && npm run integration-test:browser", - "test:node": "npm run clean && npm run build:test && npm run integration-test:node", - "test": "npm run clean && npm run build:test && npm run unit-test", - "docs": "echo Skipped." - }, - "files": [ - "dist/", - "dist-esm/src/", - "types/src", - "README.md", - "LICENSE" - ], - "repository": "github:Azure/azure-sdk-for-js", - "keywords": [ - "azure", - "recording", - "cloud", - "playback" - ], - "author": "Microsoft Corporation", - "license": "MIT", - "bugs": { - "url": "https://github.com/Azure/azure-sdk-for-js/issues" - }, - "engines": { - "node": ">=8.0.0" - }, - "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/test-utils/recorder-new/", - "sideEffects": false, - "private": true, - "dependencies": { - "@azure/core-http": "^2.0.0", - "@azure/core-rest-pipeline": "^1.1.0", - "@azure/test-utils": "^1.0.0", - "@azure/core-auth": "^1.3.2" - }, - "devDependencies": { - "@azure/core-client": "^1.0.0", - "@azure/dev-tool": "^1.0.0", - "@azure/eslint-plugin-azure-sdk": "^3.0.0", - "@rollup/plugin-commonjs": "11.0.2", - "@rollup/plugin-multi-entry": "^3.0.0", - "@rollup/plugin-node-resolve": "^8.0.0", - "@rollup/plugin-replace": "^2.2.0", - "@types/express": "^4.16.0", - "@types/fs-extra": "^8.0.0", - "@types/chai": "^4.1.6", - "@types/md5": "^2.2.0", - "@types/mocha": "^7.0.2", - "@types/nise": "^1.4.0", - "@types/node": "^12.0.0", - "@types/mock-require": "~2.0.0", - "@types/mock-fs": "^4.13.1", - "@types/uuid": "^8.3.1", - "chai": "^4.2.0", - "concurrently": "^6.2.1", - "cross-env": "7.0.3", - "dotenv": "^8.2.0", - "eslint": "^7.15.0", - "express": "^4.16.3", - "karma": "^6.2.0", - "karma-chrome-launcher": "^3.0.0", - "karma-coverage": "^2.0.0", - "karma-edge-launcher": "^0.4.2", - "karma-env-preprocessor": "^0.1.1", - "karma-firefox-launcher": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-preprocessor": "^0.3.3", - "karma-json-to-file-reporter": "^1.0.1", - "karma-junit-reporter": "^2.0.1", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-sourcemap-loader": "^0.3.8", - "mocha": "^7.1.1", - "mocha-junit-reporter": "^2.0.0", - "mock-fs": "^5.1.2", - "mock-require": "^3.0.3", - "npm-run-all": "^4.1.5", - "nyc": "^15.0.0", - "prettier": "^2.5.1", - "rimraf": "^3.0.0", - "rollup": "^1.16.3", - "rollup-plugin-shim": "^1.0.0", - "rollup-plugin-sourcemaps": "^0.4.2", - "rollup-plugin-terser": "^5.1.1", - "rollup-plugin-visualizer": "^4.0.4", - "tslib": "^2.2.0", - "ts-node": "^9.0.0", - "typescript": "~4.2.0", - "uuid": "^8.3.2", - "xhr-mock": "^2.4.1" - } -} diff --git a/sdk/test-utils/recorder-new/recordings/browsers/proxy_tool_sanitizers/recording_continuationsanitizer.json b/sdk/test-utils/recorder-new/recordings/browsers/proxy_tool_sanitizers/recording_continuationsanitizer.json deleted file mode 100644 index de2d2aacc679..000000000000 --- a/sdk/test-utils/recorder-new/recordings/browsers/proxy_tool_sanitizers/recording_continuationsanitizer.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "Entries": [ - { - "RequestUri": "http://host.docker.internal:8080/api/sample_uuid_in_header", - "RequestMethod": "GET", - "RequestHeaders": { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-US", - "Connection": "keep-alive", - "Referer": "http://localhost:9328/", - "sec-ch-ua": "", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-site", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "e3f7327d-b119-41a7-8bfa-77ff01d06d5c", - "x-ms-useragent": "core-rest-pipeline/1.3.3 OS/Linuxx86_64" - }, - "RequestBody": null, - "StatusCode": 200, - "ResponseHeaders": { - "Connection": "keep-alive", - "Content-Length": "0", - "Date": "Fri, 10 Dec 2021 00:20:44 GMT", - "Keep-Alive": "timeout=5", - "X-Powered-By": "Express", - "your_uuid": "e03003cb-6dee-450e-af09-cccd60f4b841" - }, - "ResponseBody": null - }, - { - "RequestUri": "http://host.docker.internal:8080/sample_response", - "RequestMethod": "GET", - "RequestHeaders": { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-US", - "Connection": "keep-alive", - "If-None-Match": "W/\u0022d-ua9UN3rfp1JzmLYtPUOjtDq\u002B3co\u0022", - "Referer": "http://localhost:9328/", - "sec-ch-ua": "", - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "", - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-site", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "c18e8b0e-c22b-48de-8860-007e6b38c716", - "x-ms-useragent": "core-rest-pipeline/1.3.3 OS/Linuxx86_64", - "your_uuid": "e03003cb-6dee-450e-af09-cccd60f4b841" - }, - "RequestBody": null, - "StatusCode": 304, - "ResponseHeaders": { - "Connection": "keep-alive", - "Date": "Fri, 10 Dec 2021 00:20:44 GMT", - "ETag": "W/\u0022d-ua9UN3rfp1JzmLYtPUOjtDq\u002B3co\u0022", - "Keep-Alive": "timeout=5", - "X-Powered-By": "Express" - }, - "ResponseBody": null - } - ], - "Variables": {} -} diff --git a/sdk/test-utils/recorder-new/recordings/node/proxy_tool_sanitizers/recording_continuationsanitizer.json b/sdk/test-utils/recorder-new/recordings/node/proxy_tool_sanitizers/recording_continuationsanitizer.json deleted file mode 100644 index 5f537b17a82d..000000000000 --- a/sdk/test-utils/recorder-new/recordings/node/proxy_tool_sanitizers/recording_continuationsanitizer.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "Entries": [ - { - "RequestUri": "http://host.docker.internal:8080/api/sample_uuid_in_header", - "RequestMethod": "GET", - "RequestHeaders": { - "Accept-Encoding": "gzip,deflate", - "Connection": "keep-alive", - "User-Agent": "core-rest-pipeline/1.3.3 Node/v16.13.0 OS/(x64-Linux-5.10.60.1-microsoft-standard-WSL2)", - "x-ms-client-request-id": "51bc7d43-960c-451a-8ff9-bf4c522cdd99" - }, - "RequestBody": null, - "StatusCode": 200, - "ResponseHeaders": { - "Connection": "keep-alive", - "Content-Length": "0", - "Date": "Fri, 10 Dec 2021 00:19:55 GMT", - "Keep-Alive": "timeout=5", - "X-Powered-By": "Express", - "your_uuid": "a0def98a-1e6c-4fa0-8055-30284fd2a930" - }, - "ResponseBody": null - }, - { - "RequestUri": "http://host.docker.internal:8080/sample_response", - "RequestMethod": "GET", - "RequestHeaders": { - "Accept-Encoding": "gzip,deflate", - "Connection": "keep-alive", - "User-Agent": "core-rest-pipeline/1.3.3 Node/v16.13.0 OS/(x64-Linux-5.10.60.1-microsoft-standard-WSL2)", - "x-ms-client-request-id": "95e98b42-96c8-42a3-aa74-806cec4c08c9", - "your_uuid": "a0def98a-1e6c-4fa0-8055-30284fd2a930" - }, - "RequestBody": null, - "StatusCode": 200, - "ResponseHeaders": { - "Connection": "keep-alive", - "Content-Length": "13", - "Content-Type": "application/json; charset=utf-8", - "Date": "Fri, 10 Dec 2021 00:19:55 GMT", - "ETag": "W/\u0022d-ua9UN3rfp1JzmLYtPUOjtDq\u002B3co\u0022", - "Keep-Alive": "timeout=5", - "X-Powered-By": "Express" - }, - "ResponseBody": { - "val": "abc" - } - } - ], - "Variables": {} -} diff --git a/sdk/test-utils/recorder-new/rollup.config.js b/sdk/test-utils/recorder-new/rollup.config.js deleted file mode 100644 index 5d7deee44c14..000000000000 --- a/sdk/test-utils/recorder-new/rollup.config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { makeConfig } from "@azure/dev-tool/shared-config/rollup"; - -export default makeConfig(require("./package.json")); diff --git a/sdk/test-utils/recorder-new/src/index.ts b/sdk/test-utils/recorder-new/src/index.ts deleted file mode 100644 index a97a6a739053..000000000000 --- a/sdk/test-utils/recorder-new/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export { Recorder } from "./recorder"; -export { relativeRecordingsPath } from "./utils/relativePathCalculator"; -export { - SanitizerOptions, - RecorderStartOptions, - isLiveMode, - isPlaybackMode, - isRecordMode, -} from "./utils/utils"; -export { env } from "./utils/env"; -export { delay } from "./utils/delay"; diff --git a/sdk/test-utils/recorder-new/src/recorder.ts b/sdk/test-utils/recorder-new/src/recorder.ts deleted file mode 100644 index ece194d10ea5..000000000000 --- a/sdk/test-utils/recorder-new/src/recorder.ts +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - createDefaultHttpClient, - createPipelineRequest, - HttpClient, - HttpMethods, - Pipeline, - PipelinePolicy, - PipelineRequest, - PipelineResponse, - SendRequest, -} from "@azure/core-rest-pipeline"; -import { - ensureExistence, - getTestMode, - isLiveMode, - isPlaybackMode, - isRecordMode, - once, - RecorderError, - RecorderStartOptions, - RecordingStateManager, -} from "./utils/utils"; -import { Test } from "mocha"; -import { sessionFilePath } from "./utils/sessionFilePath"; -import { SanitizerOptions } from "./utils/utils"; -import { paths } from "./utils/paths"; -import { Sanitizer } from "./sanitizer"; -import { handleEnvSetup } from "./utils/envSetupForPlayback"; -import { Matcher, setMatcher } from "./matcher"; -import { - DefaultHttpClient, - HttpClient as HttpClientCoreV1, - HttpOperationResponse, - WebResource, - WebResourceLike, -} from "@azure/core-http"; - -/** - * This client manages the recorder life cycle and interacts with the proxy-tool to do the recording, - * eventually save them in record mode and playing them back in playback mode. - * - * For Core V2 SDKs, - * - Use the `configureClient` method to add recorder policy on your client. - * For core-v1 SDKs, - * - Use the `configureClientOptionsCoreV1` method to modify the httpClient on your client options - * - * Other than configuring your clients, use `start`, `stop`, `addSanitizers` methods to use the recorder. - */ -export class Recorder { - private url = "http://localhost:5000"; - public recordingId?: string; - private stateManager = new RecordingStateManager(); - private httpClient?: HttpClient; - private sessionFile?: string; - private sanitizer?: Sanitizer; - private variables: Record; - - constructor(private testContext?: Test | undefined) { - if (isRecordMode() || isPlaybackMode()) { - if (this.testContext) { - this.sessionFile = sessionFilePath(this.testContext); - this.httpClient = createDefaultHttpClient(); - } else { - throw new Error( - "Unable to determine the recording file path, testContext provided is not defined." - ); - } - this.sanitizer = new Sanitizer(this.url, this.httpClient); - } - this.variables = {}; - } - - /** - * redirectRequest updates the request in record and playback modes to hit the proxy-tool with appropriate headers. - * Works for both core-v1 and core-v2 - * - * - WebResource -> core-v1 - * - PipelineRequest -> core-v2 (recorderHttpPolicy calls this method on the request to modify and hit the proxy-tool with appropriate headers.) - */ - private redirectRequest(request: WebResource | PipelineRequest): void { - if (!isLiveMode() && !request.headers.get("x-recording-id")) { - if (this.recordingId === undefined) { - throw new RecorderError("Recording ID must be defined to redirect a request"); - } - - request.headers.set("x-recording-id", this.recordingId); - request.headers.set("x-recording-mode", getTestMode()); - - const upstreamUrl = new URL(request.url); - const redirectedUrl = new URL(request.url); - const providedUrl = new URL(this.url); - - redirectedUrl.host = providedUrl.host; - redirectedUrl.port = providedUrl.port; - redirectedUrl.protocol = providedUrl.protocol; - request.headers.set("x-recording-upstream-base-uri", upstreamUrl.toString()); - request.url = redirectedUrl.toString(); - - if (!(request instanceof WebResource)) { - // for core-v2 - request.allowInsecureConnection = true; - } - } - } - - /** - * addSanitizers adds the sanitizers for the current recording which will be applied on it before being saved. - * - * Takes SanitizerOptions as the input, passes on to the proxy-tool. - */ - async addSanitizers(options: SanitizerOptions): Promise { - // If check needed because we only sanitize when the recording is being generated, and we need a recording to apply the sanitizers on. - if (isRecordMode() && ensureExistence(this.sanitizer, "this.sanitizer")) { - return this.sanitizer.addSanitizers(options); - } - } - - /** - * Call this method to ping the proxy-tool with a start request - * signalling to start recording in the record mode - * or to start playing back in the playback mode. - * - * Takes RecorderStartOptions as the input, which will get used in record and playback modes. - * Includes - * - envSetupForPlayback - The key-value pairs will be used as the environment variables in playback mode. If the env variables are present in the recordings as plain strings, they will be replaced with the provided values. - * - sanitizerOptions - Generated recordings are updated by the "proxy-tool" based on the sanitizer options provided. - */ - async start(options: RecorderStartOptions): Promise { - if (isLiveMode()) return; - this.stateManager.state = "started"; - if (this.recordingId === undefined) { - const startUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${ - paths.start - }`; - const req = this._createRecordingRequest(startUri); - - if (ensureExistence(this.httpClient, "TestProxyHttpClient.httpClient")) { - const rsp = await this.httpClient.sendRequest({ - ...req, - allowInsecureConnection: true, - }); - if (rsp.status !== 200) { - throw new RecorderError("Start request failed."); - } - const id = rsp.headers.get("x-recording-id"); - if (!id) { - throw new RecorderError("No recording ID returned for a successful start request."); - } - this.recordingId = id; - if (isPlaybackMode()) { - this.variables = rsp.bodyAsText ? JSON.parse(rsp.bodyAsText) : {}; - } - if (ensureExistence(this.sanitizer, "TestProxyHttpClient.sanitizer")) { - // Setting the recordingId in the sanitizer, - // the sanitizers added will take the recording id and only be part of the current test - this.sanitizer.setRecordingId(this.recordingId); - await handleEnvSetup(options.envSetupForPlayback, this.sanitizer); - } - // Sanitizers to be added only in record mode - if (isRecordMode() && options.sanitizerOptions) { - // Makes a call to the proxy-tool to add the sanitizers for the current recording id - // Recordings of the current test will be influenced by the sanitizers that are being added here - await this.addSanitizers(options.sanitizerOptions); - } - } - } - } - - /** - * Call this method to ping the proxy-tool with a stop request, this helps saving the recording in record mode. - */ - async stop(): Promise { - if (isLiveMode()) return; - this.stateManager.state = "stopped"; - if (this.recordingId !== undefined) { - const stopUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${paths.stop}`; - const req = this._createRecordingRequest(stopUri); - req.headers.set("x-recording-save", "true"); - - if (isRecordMode()) { - req.headers.set("Content-Type", "application/json"); - req.body = JSON.stringify(this.variables); - } - if (ensureExistence(this.httpClient, "TestProxyHttpClient.httpClient")) { - const rsp = await this.httpClient.sendRequest({ - ...req, - allowInsecureConnection: true, - }); - if (rsp.status !== 200) { - throw new RecorderError("Stop request failed."); - } - } - } else { - throw new RecorderError("Bad state, recordingId is not defined when called stop."); - } - } - - /** - * Sets the matcher for the current recording to the matcher specified. - */ - async setMatcher(matcher: Matcher): Promise { - if (isPlaybackMode()) { - if (!this.httpClient) { - throw new RecorderError("httpClient should be defined in playback mode"); - } - - await setMatcher(this.url, this.httpClient, matcher, this.recordingId); - } - } - - /** - * Adds the recording file and the recording id headers to the requests that are sent to the proxy tool. - * These are required to appropriately save the recordings in the record mode and picking them up in playback. - */ - private _createRecordingRequest(url: string, method: HttpMethods = "POST") { - const req = createPipelineRequest({ url, method }); - if (ensureExistence(this.sessionFile, "sessionFile")) { - req.headers.set("x-recording-file", this.sessionFile); - } - if (this.recordingId !== undefined) { - req.headers.set("x-recording-id", this.recordingId); - } - return req; - } - - /** - * For core-v2 - libraries depending on core-rest-pipeline. - * This method adds the recording policy to the input client's pipeline. - * - * Helps in redirecting the requests to the proxy tool instead of directly going to the service. - */ - public configureClient(client: { pipeline: Pipeline }): void { - if (isLiveMode()) return; - client.pipeline.addPolicy(this.recorderHttpPolicy()); - } - - /** - * For core-v1 - libraries depending on core-http. - * This method adds the custom httpClient to the client options. - * - * Helps in redirecting the requests to the proxy tool instead of directly going to the service. - */ - public configureClientOptionsCoreV1< - T extends { - httpClient?: HttpClientCoreV1; - } - >(options: T): T { - if (isLiveMode()) return options; - return { ...options, httpClient: once(() => this.createHttpClientCoreV1())() }; - } - - /** - * recorderHttpPolicy that can be added as a pipeline policy for any of the core-v2 SDKs(SDKs depending on core-rest-pipeline) - */ - private recorderHttpPolicy(): PipelinePolicy { - return { - name: "recording policy", - sendRequest: async ( - request: PipelineRequest, - next: SendRequest - ): Promise => { - this.redirectRequest(request); - return next(request); - }, - }; - } - - /** - * Creates a client that supports redirecting the requests to the proxy-tool. - * Needed for the core-v1 SDKs(SDKs depending on core-http) - */ - private createHttpClientCoreV1(): HttpClientCoreV1 { - const client = new DefaultHttpClient(); - return { - sendRequest: async (request: WebResourceLike): Promise => { - this.redirectRequest(request); - return client.sendRequest(request); - }, - }; - } - - /** - * Register a variable to be stored with the recording. The behavior of this function - * depends on whether the recorder is in record/live mode or in playback mode. - * - * In record or live mode, the function will store the value provided with the recording - * as a variable and return that value. - * - * In playback mode, the function will fetch the value from the variables stored as part of the recording - * and return the retrieved variable, throwing an error if it is not found. - * - * @param name - the name of the variable to be stored in the recording - * @param value - the value of the variable. In record mode, this value will be stored - * with the recording; in playback mode, this parameter is ignored. - * @returns in record and live mode, `value` without modification. - * In playback mode, the variable's value from the recording. - */ - variable(name: string, value: string): string; - - /** - * Convenience overload in case you want to reference the same variable multiple times in a test without - * declaring a variable of your own, or if you know you're in playback mode and don't want to specify an - * initial value. Throws an error in record and live mode if a call to variable(name, value) has not been - * made previously. - * - * @param name - the name of the variable stored in the recording - * @returns the value of the variable -- in record and live mode, the value set - * in a previous call to variable(name, value). In playback mode, the variable's - * value from the recording. - */ - variable(name: string): string; - - variable(name: string, value: string | undefined = undefined): string { - if (isPlaybackMode()) { - const recordedValue = this.variables[name]; - - if (recordedValue === undefined) { - throw new RecorderError( - `Tried to access a variable in playback that was not set in recording: ${name}` - ); - } - - return recordedValue; - } - - if (!this.variables[name]) { - if (value === undefined) { - throw new RecorderError( - `Tried to access uninitialized variable: ${name}. You must initialize it with a value before using it.` - ); - } - - this.variables[name] = value; - } - - return this.variables[name]; - } -} diff --git a/sdk/test-utils/recorder-new/tsconfig.json b/sdk/test-utils/recorder-new/tsconfig.json deleted file mode 100644 index c9af1d87f55c..000000000000 --- a/sdk/test-utils/recorder-new/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../../tsconfig.package", - "compilerOptions": { - "declarationDir": "./types", - "outDir": "./dist-esm", - "lib": ["dom", "esnext"] - }, - "include": ["./src/**/*.ts", "./test/**/*.ts"] -} diff --git a/sdk/test-utils/recorder/.gitignore b/sdk/test-utils/recorder/.gitignore index 147ba76eef90..966433321863 100644 --- a/sdk/test-utils/recorder/.gitignore +++ b/sdk/test-utils/recorder/.gitignore @@ -1,2 +1,2 @@ -recordings/**/*.js -recordings/**/*.json \ No newline at end of file +# Ignore the recordings directory in this project since they get regenerated every test run +recordings/ diff --git a/sdk/test-utils/recorder/.nycrc b/sdk/test-utils/recorder/.nycrc deleted file mode 100644 index b61728d904d1..000000000000 --- a/sdk/test-utils/recorder/.nycrc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "include": [ - "dist-esm/src/**/*.js" - ], - "exclude": [ - "**/*.d.ts" - ], - "reporter": [ - "text-summary", - "html", - "cobertura" - ], - "exclude-after-remap":false, - "sourceMap": true, - "instrument": true, - "all": true - } diff --git a/sdk/test-utils/recorder/CHANGELOG.md b/sdk/test-utils/recorder/CHANGELOG.md index 962136ef8008..46e731205c25 100644 --- a/sdk/test-utils/recorder/CHANGELOG.md +++ b/sdk/test-utils/recorder/CHANGELOG.md @@ -1,116 +1,124 @@ # Release History -## 1.0.2 (2022-01-04) +## 2.0.0 (Unreleased) -- Fixed double replacements in case the source value and the replacement happened to remain the same after encoding. -- Adds a default customization on the recordings to manipulate the `retry-after` header to have "0" value so that playback tests run faster. - [#19501](https://github.com/Azure/azure-sdk-for-js/pull/19501) +## 2022-01-06 -## 1.0.1 (2021-09-01) +- Renaming the package `@azure-tools/test-recorder-new@1.0.0` as `@azure-tools/test-recorder@2.0.0`. + [#19561](https://github.com/Azure/azure-sdk-for-js/pull/19561) -Fix types in the published package. +## 1.0.0 (Unreleased) -## 1.0.0 (2021-08-30) +## 2021-12-27 -Marks the first release of the @azure-tools/test-recorder package. -This is not for the public consumption, releasing for the convenience of certain public pipelines. -Read more at [#17127](https://github.com/Azure/azure-sdk-for-js/pull/17127). +- Allows passing `undefined` as keys in the sanitizer options so that devs don't have to add additional checks if a certain env variable exists in playback. +- Exports `delay` + - waits for expected time in record/live modes + - no-op in playback -## 2021-07-13 +[#19561](https://github.com/Azure/azure-sdk-for-js/pull/19561) -- `pluginForClientSecretCredentialTests` now is exported, and takes an optional `tenantId` parameter with a default value of `env.AZURE_TENANT_ID` to allow handling other tenant Ids. +## 2021-12-17 -## 2021-05-12 +- Refactoring the test proxy http clients for better clarity for the end users [#19446](https://github.com/Azure/azure-sdk-for-js/pull/19446) -- Extended the `requestBodyTransformations` from [#14897](https://github.com/Azure/azure-sdk-for-js/pull/14897) to handle browser tests as well. - - Adds a default transformation to replace the scope URL in the request body with the string `"https://sanitized/"` for browser recordings. - - Adds a default customization on recording to replace the scope URL in the request body for node recordings. - - Removed `requestBodyTransformations` property from `RecorderEnvironmentSetup` since Nock doesn't support multiple `.filteringRequestBody` patches in the recordings. + - Client rename `TestProxyHttpClient` -> `Recorder` + - Removing the separate `TestProxyHttpClientCoreV1` + - Instead of passing the whole client as the `httpClient` for core-v1 sdks, users are now expected to call `recorder.configureClientOptionsCoreV1` on the client options while passing to the respective client(where `recorder` is an instantiation of `Recorder`). This modifies the httpClient in the options to allow redirecting the requests to the test-proxy tool. + - Duplicated helpers from old recorder to not depend on old recorder package.. with an intention to replace the recorder package as 2.0 and not merge since the old recorder is published already + - Makes the following members of `Recorder` private + - `httpClient` + - `redirectRequest` + - `recorderHttpPolicy` + - `createHttpClientCoreV1` -## 2021-05-09 +- Moving `NoOpCredential` to the new `@azure-tools/test-credential` package. -- Mocks msal auth for access_token by not matching the request body since it is too dynamic and has changes between versions. - - Exported a new `pluginForIdentitySDK` method for the tests in the identity SDK - - Added a new property called `onLoadCallbackForPlayback` in the `RecorderEnvironmentSetup` as a means by which identity SDK can mock differently(when compared to the regular SDKs) to cater all of its credentials. - - Also, removed the default request transformation of modifying the `client-request-id` since it is redundant if the request body itself is not matched. - [#14993](https://github.com/Azure/azure-sdk-for-js/pull/14993) +## 2021-12-15 -## 2021-04-19 +- Change the API for using variables. Variables can now be accessed using the syntax: -- Helper method added for the transformations to be applied on the requestBody in record mode to be able to filter the requests in playback. - Extends the `RecorderEnvironmentSetup` with `requestBodyTransformations` property which takes the transformation callbacks to be applied on the request body. - [#14897](https://github.com/Azure/azure-sdk-for-js/pull/14897) - - Adds a default transformation to strip out the client-request-id in the request body with the string "client-request-body" - -## 2021-04-07 - -- Relaxing `maskAccessTokenInBrowserRecording` method to ignore the access_token replacement if the response is not JSON parsable. - [#14793](https://github.com/Azure/azure-sdk-for-js/pull/14793) + ```ts + const variable = recorder.variable(, ); + ``` -## 2021-03-17 + A shorthand is also added for when accessing a variable a second time in + the same test, which does not require an initial value be set, e.g.: -- Adds an internal helper method to mask "access_token" in the recorder rather than expecting users to provide it through the custom config `customizationsOnRecordings` from `RecorderEnvironmentSetup`. [#12759](https://github.com/Azure/azure-sdk-for-js/pull/12759) - With this change, users don't have to add callbacks(to mask `access_token`) in the `customizationsOnRecordings` array such as below. ```ts - customizationsOnRecordings: [ - (recording) => recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`) - ]; + recorder.variable("variableName", ); + + // later on in your code + const value = recorder.variable("variableName"); ``` -## 2020-01-28 + [#19388](https://github.com/Azure/azure-sdk-for-js/pull/19388) -- Single quotes can be present in the url path though unusual. When the url path has single quotes, the fixture generated by "nock" is incorrect leading to invalid recordings. [#13474](https://github.com/Azure/azure-sdk-for-js/pull/13474) introduces a workaround to be applied as part of the default customizations done on the generated recordings to fix the issue. - (Nock Bug 🐛: https://github.com/nock/nock/issues/2136) +## 2021-12-14 -## 2020-12-02 +- Add `configureClient` method to the `TestProxytHttpClient` to allow instrumenting the client with the recorder policy which helps in enabling the recorder to redirect the requests of your tests to the proxy tool. + - Also un-exports `recorderHttpPolicy` function. + [#19362](https://github.com/Azure/azure-sdk-for-js/pull/19362) -- Refactored the code to enable `"browser"` module replacement when used with bundlers. Previously, even browser bundled copies of the recorder would carry dependencies on several Node packages, which would lead to unresolved dependency warnings in Rollup regarding node builtins. With this change, the recorder's browser mappings will avoid this issue. -- Initialized `dotenv` by default in the NockRecorder, since its use is ubiquitous. Clients using the recorder in Node no longer have to import and run `dotenv`, though packages that do import `dotenv` may continue to do so as always. +## 2021-12-13 -## 2020-08-27 +- Add support for `setMatcher`, which can be used to instruct the proxy tool to ignore headers (using `HeaderlessMatcher`) or the request body (using `BodilessMatcher`) when matching requests to recordings. -- [Bug Fix] When the responses have binary content, "nock" library saves the fixture by converting them into hex values. [#10048](https://github.com/Azure/azure-sdk-for-js/pull/10048) fixed the issue if the status code in the response is 200. Now, allowing the rest of the "successful" status codes(200-299). - [#10892](https://github.com/Azure/azure-sdk-for-js/pull/10892) + Example: -## 2020-08-21 + ```ts + await recorder.setMatcher("HeaderlessMatcher"); + ``` + +## 2021-12-10 -- [Bug Fix] When the responses have binary content, "nock" library saves the fixture by converting them into hex values. Fixtures are now overridden before being saved as recordings by decoding the hex values into the buffer as expected if the "Content-Type" is "avro/binary". - [#10048](https://github.com/Azure/azure-sdk-for-js/pull/10048) +- Loads the .env file using with the help of "dotenv" by default. + [#19139](https://github.com/Azure/azure-sdk-for-js/pull/19139) -## 2020-07-10 +## 2021-11-30 -- [Bug Fix] Fixed an issue where the browser-recording file is saved before all the recorded requests are pushed to the array of recordings(request-response pairs). `await recorder.stop()` - stop() call is expected to be await-ed from now on. - [#10013](https://github.com/Azure/azure-sdk-for-js/pull/10013) +- Adds NoOp AAD Credential for playback `NoOpCredential`. Using this as your AAD credential in playback mode would avoid the AAD traffic in playback. + [#18904](https://github.com/Azure/azure-sdk-for-js/pull/18904) -## 2020-04-30 +## 2021-11-08 -- Since Mocha 7.0.0, Mocha behaves as follows: "When conditionally skipping in the `it` test, related afterEach hooks are now executed" - ([Source](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md#700--2020-01-05)), now the `recorder.stop()` calls in the `afterEach()` will always be executed. Calling `recorder.skip` should not call `recorder.stop()` anymore. +- Allows storing dynamically created variables in record mode. The recorder registers the variables as part of the recording and stores their values in the recording file. Using the `variables` in playback mode produces the key-value pairs that were stored in the recording file. -## 2020-02-06 + Example: -- If any URLs are meant to be replaced in the recordings with `replaceableVariables`, hostname from the URLs will be independently matched and replaced. [#7204](https://github.com/Azure/azure-sdk-for-js/issues/7204) - - Example - ENV_VAR `ACCOUNT_URL=https://azureaccount.com/` is supposed to be replaced with `https://endpoint/`, `azureaccount.com` will be independently matched and replaced with `endpoint` too. -- Added the "soft-record" mode, which allows users to only record the tests that have changed. [#7213](https://github.com/Azure/azure-sdk-for-js/issues/7213) -- Added TestContext, TestContextInterface, TestContextTest in exchange of Mocha.Context. They're compatible types, but ours are easier to test. -- Added windowLens, a lens to modify and retrieve properties from the Window object. Useful for future refactorings. + ```ts + if (!isPlaybackMode()) { + recorder.variables["random-1"] = `random-${Math.ceil(Math.random() * 1000 + 1000)}`; + } + ``` -- [BUG FIX] Tests leveraging `coreHttp.requestOptions.timeout` with empty recordings(no nock scopes with request-responses defined in the recording) fail during the playback mode when executed along with other tests. Fixed the issue by resetting nock's global state. More info - [#7264](https://github.com/Azure/azure-sdk-for-js/issues/7264) + Use `recorder.variables["random-1"]` to access the value of the variable after setting it. The variable can be accessed in all three modes -- record, playback, and live -- as long as it is set in record mode before it is accessed. -## 2020-01-30 +## 2021-10-15 -- [BUG FIX] Fixed a bug where the replacements aren't properly filtered in the JSON recordings. [#7175](https://github.com/Azure/azure-sdk-for-js/issues/7175) +[#17379](https://github.com/Azure/azure-sdk-for-js/pull/17379) -## 2020-01-29 +- Added `addSanitizers` method to support + - BodyKeySanitizer + - BodyRegexSanitizer + - GeneralRegexSanitizer + - HeaderRegexSanitizer + - RemoveHeaderSanitizer + - UriRegexSanitizer + - UriSubscriptionIdSanitizer + - Connection String sanitizer +- Adds `SanitizerOptions`, env setup for playback as the options of the `start()` method + - Applies `generalRegexSanitizers` on the env setup for playback options by default to eliminate any plain text secrets in the recordings +- Testing - All the tests are run in the `recorder-new` folder run in all three modes - "record", "playback" and "live" everytime the test commands are run -- [BREAKING] `replaceInRecordings` attribute of `RecorderEnvironmentSetup` interface has been renamed to `customizationsOnRecordings`. +## 2021-09-27 -## 2020-01-28 +[#17388](https://github.com/Azure/azure-sdk-for-js/pull/17388) -- [BUG FIX] Fixed a bug where the replacements provided aren't being replaced in the query-param parts of recordings, which is an expectation for playback. This caused a few test failures during playback for new recordings. More info [#7108](https://github.com/Azure/azure-sdk-for-js/issues/7108) +- `TestProxyClient` now takes the test context to determine the location of the recordings. +- Adds a server for the tests, to play the role of an actual service to be able to test the proxy-tool end-to-end. -- [BREAKING] `setReplaceableVariables`, `setReplacements`, `skipQueryParams` methods of the recorder are no longer exposed. +## 2021-07-17 - - Equivalent `RecorderEnvironmentSetup` type is being exported instead. An object of this type is expected to be passed in the `record()` call. - - `record(this)` changes to `record(this, recorderEnvSetup)`, where `recorderEnvSetup` is of type `RecorderEnvironmentSetup`. - - [PR #7083](https://github.com/Azure/azure-sdk-for-js/pull/7083) +- Building the unified recorder prototype leveraging the proxy-tool, works for both core-v1 and core-v2 SDKs. Shows data-tables and storage-queue as examples for core-v2 and core-v1 respectively. + [#15826](https://github.com/Azure/azure-sdk-for-js/pull/15826) diff --git a/sdk/test-utils/recorder/GUIDELINES.md b/sdk/test-utils/recorder/GUIDELINES.md deleted file mode 100644 index 291d40c04a40..000000000000 --- a/sdk/test-utils/recorder/GUIDELINES.md +++ /dev/null @@ -1,313 +0,0 @@ -# GUIDELINES FOR RECORD AND PLAYBACK - -## Setup for record and playback - -**New env variable for recordings - TEST_MODE** [Supposed to be added in the environment(or `.env` file if the package uses `dotenv` as a devDependency) to be able to do record and playback] - -- If TEST_MODE = "record", - - Tests hit the live-service - - [Nock](https://www.npmjs.com/package/nock)/[Nise](https://www.npmjs.com/package/nise) are leveraged for recording the request-responses for future use - - If recordings are already present, forces re-recording - - The recorded files contain a hash of the test that executed that recording. - - Some recorded files might not have the hash if they were generated by an older version of the recorder. -- If TEST_MODE = "soft-record", - - Tests will be skipped if they haven't changed. `soft-record` mode is isomorphous to `record` mode in all other aspects. - - We determine whether a test has changed or not by doing a MD5 hash of the test's source code. - - If the hash is not stored in the recordings, they will be re-recorded to include the hash. -- Else If TEST_MODE = "live", - - Tests hit the live-service, we don't record the requests/responses -- Else If TEST_MODE = "playback" (or if the TEST_MODE is neither "record" nor "live"), - - Existing recordings are played back as responses to the HTTP requests in the tests - -Add `@azure-tools/test-recorder` as a devDependency of your sdk. - -## Adding a new test/test-suite - -- `recorder` package assumes that the tests in the sdk are leveraging [mocha](https://mochajs.org/) and [rollup](https://rollupjs.org/guide/en/) (and [karma](https://karma-runner.github.io/latest/index.html) test runner for browser tests) as suggested by the [template](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/template/template) package in the repo. - -- `record` from `@azure-tools/test-recorder` package should be imported in the test files. - -### Recording the tests - -- `recorder = record(this, recorderEnvSetup);` initiates recording the HTTP requests and when `await recorder.stop();` is called, the recording stops - and all the HTTP requests recorded in between the two calls are saved as part of the recording in the `"record"` mode. - In the same way, existing recordings are leveraged and played back in the `"playback"` mode when `recorder = record(this, recorderEnvSetup);` is invoked. - [Has no effect if the `TEST_MODE` is `"live"`, tests hit the live-service, we don't record the requests/responses] - -- Follow the below template for adding a new test. `beforeEach` and `afterEach` sections are compulsory. - - ```typescript - import { record } from "@azure-tools/test-recorder"; - - describe("", () => { - beforeEach(async function() { - recorder = record(this, recorderEnvSetup); - /*Place your code here*/ - }); - - afterEach(async () => { - /*Place your code here*/ - await recorder.stop(); - }); - - it("", async () => { - /*Place your code here*/ - }); - }); - ``` - -- Consider the `beforeEach`, `afterEach` and `it` blocks in the above test-suite. - - - `recorder = record(this, recorderEnvSetup);` is invoked in the `beforeEach` section and `await recorder.stop();` in the `afterEach` section. - - All the HTTP requests recorded in between the two calls are saved as part of the test(`it` block) recording in the `"record"` mode. - - Existing test recording is played back when invoked `recorder = record(this, recorderEnvSetup);` in the `"playback"` mode.= - - Any function call that affects http requests and is not in the `it`-block and belongs to a `describe`-block must go in one of the `beforeEach` or `afterEach` sections. - -### ENV Setup - `recorderEnvSetup` - -- Required `recorderEnvSetup` has to be defined before invoking the recording - - ```typescript - interface RecorderEnvironmentSetup { - replaceableVariables: { [ENV_VAR: string]: string }; - customizationsOnRecordings: Array<(recording: string) => string>; - queryParametersToSkip: Array; - } - ``` - -- `RecorderEnvironmentSetup` type is being exposed from the `@azure-tools/test-recorder` package. - Environment variables for the tests in the sdk can be managed with the setup on a per-test basis. - -- `replaceableVariables: { [ENV_VAR: string]: string };` - - - Used in both record and playback modes. - - `replaceableVariables` is a dictionary with key-value pairs. - - The key-value pairs will be used as the environment variables in playback mode. - - If the env variables are present in the recordings as plain strings, they will be replaced with the provided values in record mode. - -- `customizationsOnRecordings: Array<(recording: string) => string>;` - - - Used only in record mode. - - Array of callback functions provided to customize the generated recordings in record mode - - Example with one callback function - - ```typescript - // `sig` param of SAS Token is being filtered here from the recordings.. - (recording: string): string => - recording.replace(new RegExp(env.ACCOUNT_SAS.match("(.*)&sig=(.*)")[2], "g"), "aaaaa"); - ``` - -- `queryParametersToSkip: Array;` - - - Used in record and playback modes - - Array of query parameters provided will be filtered from the requests - -- With the above attributes, declare the `recorderEnvSetup` to use it below. - -### Saving the recordings - -- Any potential plain secrets in the recordings are replaced with dummy values provided at `replaceableVariables` and `customizationsOnRecordings`. - -- `Mocha.Context` is being leveraged to obtain the test title and other required information to save and replay the recordings. Recordings corresponding to `beforeEach`, `it` and `afterEach` sections are saved as a single test recording(`recordings/{node|browsers}//recording_.{js|json}`). - -### Member functions getUniqueName() and newDate() of the `Recorder` - -- Retaining (randomly) generated info of the test run during recording is crucial in order to replay the http requests for the test during `"playback"` mode. - -- `Recorder` returned by the `record()` method has `getUniqueName()` and `newDate()` member functions along with the `stop()`. - - ```typescript - recorder = record(this, recorderEnvSetup); - - const randomName = recorder.getUniqueName("random"); - const tmr = recorder.newDate("tmr"); - ``` - -- When `await recorder.stop()` is called for a test, we save that unique information corresponding to the test run along with the test recording in the `record` mode. - -- In `playback` mode, the saved unique information is pulled out from the existing recording in order to replay the http requests. - -- `newDate: (label: string) => Date;` - - - In live test mode, `new Date()` is returned. - - - In record mode, `new Date()` is returned, and is saved in the recordings by assigning the `label`. - - - In playback mode, the date in the recordings associated to the `label` is returned. - - - [IMPORTANT] Same label cannot be used more than once for a test. If re-used, the new value will overwrite the existing value and the playback would fail. - -- `getUniqueName: (prefix: string, label?: string) => string` - - - In live test mode, random string is generated, appended to `prefix` and returned. - - - In record mode, random string is generated, appended to `prefix` and returned, and is saved in the test-recordings by assigning the provided `label`. - - - In playback mode, the string in the recordings associated to the `label` is returned. - - - If the `label`(optional param) is not provided, `prefix` is used as the label. - - - [IMPORTANT] Same label cannot be used more than once for a test. If re-used, the new value will overwrite the existing value and the playback would fail. - -- Any unique information of the test run that is important for playing back the http request must be saved along with the recordings in the record mode. - -- If a new test/test-suite is added, execute the test/test-suite(or all the tests) by setting the env variable `TEST_MODE = record` and **commit** the generated recording files. - -### Importing `delay` from `@azure-tools/test-recorder` - -- This `delay` has no effect if the `TEST_MODE` is `"playback"`. -- `delay` works as expected(`await delay()`) if the `TEST_MODE` is not `"playback"`. - ---- - -## Updating an existing test/test-suite - -- Recordings are saved in `./recordings/` folder. -- If a test is modified, we might need to record it again in order to equip the recording to accommodate any updates in the function calls that invoke http requests. - - - execute the test/test-suite script(or all the tests) by setting the env variable `TEST_MODE = record` - -- If only the test title is updated, - - - we can either execute that specific test again to generate the recording - - or - - - we can just update the file name of the recording accordingly - -- **Commit** the generated recordings. - ---- - -## Recordings - -- Recordings are being saved in one folder for each describe-block test suite - recording file structure - - `recordings/node//recording_.js` for node tests and - - `recordings/browsers//recording_.json` for browser tests. -- The file name of the recording for a test preserves the title of the `describe`-block and the corresponding `it`-block (with special characters appropriated). The recordings for node tests go into the `./recordings/node/` folder(Similarly, browser recordings in the `./recordings/browsers/` folder). - - For example - - ```typescript - describe("Some Random Test Suite", () => { - beforeEach(async function() { - recorder = record(this, recorderEnvSetup); - /*Place your code here*/ - }); - - afterEach(async () => { - /*Place your code here*/ - await recorder.stop(); - }); - - it("should abort when abort() is called", async () => { - ... - ... - }); - }); - ``` - - (Node Test) Recording corresponding to the above test is placed at - `./recordings/node/some_random_test_suite/recording_should_abort_when_abort_is_called.js` - - [ Following this rule - `./recordings/node//recording_.js` ] - -- In node recordings(Nock), the query parameters are not being stored and the SAS Token query parameters are not being stored in browser recordings(Nise). - -## Skipping a test - -- Currently, some tests (in storage packages) are being skipped because record and playback does not work as expected while executing them, the reasons are listed below. - - - **Abort:** browser testing unexpectedly finishes when a request is aborted during playback (unknown reason; probably related to the way `nise` handles it) - - **Character:** there are characters in the message that are not supported in browser logging or in ECMAScript - - **Progress:** Nock does not record a request if it's aborted in a 'progress' callback - - **Size:** the generated recording file is too big and would considerably increase the size of the package - - **Tempfile:** the request makes use of a random tempfile created locally, and the recorder does not support recording it as unique information - - **UUID:** a UUID is randomly generated within the SDK and used in an HTTP request, resulting in Nock being unable to recognize it - -- We leverage mocha's `.skip()` functionality to skip the test - `this.skip()` - https://mochajs.org/#inclusive-tests. - -- `{recorder.skip(runtime?: "node" | "browser", reason?: string)}` will skip the test in node or browser runtimes based on the `{runtime}` argument. If the `{runtime}` is undefined, the test will be skipped in both the node and browser runtimes. Has no effect if the `TEST_MODE` is `"live"`. - ---- - -## Setting up karma.conf.js file in the SDK - -### Karma.conf.js - -- Install and add the plugins `"karma-json-to-file-reporter", "karma-json-preprocessor"` as `devDependencies`. - - ```javascript - plugins: ["karma-json-to-file-reporter", "karma-json-preprocessor"], - ``` - -- Import recordings in playback mode - ```javascript - files: [].concat(process.env.TEST_MODE === "playback" ? ["recordings/browsers/**/*.json"] : []), - ``` -- Preprocessor for converting JSON files into JS variables - ```javascript - preprocessors: {"recordings/browsers/**/*.json": ["json"]}; - ``` -- Load `TEST_MODE` along with other variables - ```javascript - envPreprocessor: ["TEST_MODE"], - ``` -- Saving the recordings of browser tests in the `recordings/browsers` folder. - - Import `jsonRecordingFilterFunction` from `"@azure-tools/test-recorder"` as shown below. - - ```javascript - const { jsonRecordingFilterFunction } = require("@azure-tools/test-recorder"); - ``` - - jsonToFileReporter in karma.conf.js filters the JSON strings in console.logs - - ```javascript - reporters: ["json-to-file"], - - jsonToFileReporter: { - // required - to save the recordings of browser tests - filter: jsonRecordingFilterFunction, - outputPath: "." - }, - - browserConsoleLogOptions: { - terminal: process.env.TEST_MODE !== "record" - }, - - ``` - - In browser, once the content to be recorded is ready, recordings are supposed to be sent to the appropriate karma reporter in order to generate the corresponding recording file. The way of doing this is by printing the recordings to `console.log()` and filter the console.logs with karma plugins. - - Console logs with `.writeFile` property are captured and are written to a file(as test recordings). Any other console statements are captured and printed normally. - - - Example - `console.warn("hello"); -> console.log({ warn: "hello" });` - - Example - `console.log("hello"); -> console.log({ log: "hello" });` - ---- - -# More Information - -## NOCK [for node tests] - -- [nock](https://www.npmjs.com/package/nock)-package is being leveraged to test modules that perform HTTP requests. -- To mock an existing live system, we record and playback the HTTP calls using `nock.recorder`. -- Recording relies on intercepting real requests and responses and then persisting them for later use. - ---- - -## NISE [for browser tests] - -- Nock has no support for browsers. [nise](https://www.npmjs.com/package/nise)-package is being leveraged for browser tests. -- Nise works in a way similar to Nock, intercepting HTTP requests and mocking responses. -- Unlike Nock, Nise does not have a native record/playback feature. -- Some Nise functions are being overwritten to enable record and playback. -- karma.conf.js is supposed to be updated with the new Karma plugins which are required to access the disk and write/read recording files ([karma-json-to-file-reporter](https://www.npmjs.com/package/karma-json-to-file-reporter) to write and [karma-json-preprocessor](https://www.npmjs.com/package/karma-json-preprocessor) to read). - ---- - -## References - -- https://github.com/Azure/azure-sdk-for-js/pull/2227 diff --git a/sdk/test-utils/recorder/LICENSE b/sdk/test-utils/recorder/LICENSE deleted file mode 100644 index ea8fb1516028..000000000000 --- a/sdk/test-utils/recorder/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2020 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sdk/test-utils/recorder/README.md b/sdk/test-utils/recorder/README.md index 44369063a6a1..82c5d0d2b8f9 100644 --- a/sdk/test-utils/recorder/README.md +++ b/sdk/test-utils/recorder/README.md @@ -1,553 +1,54 @@ -## Azure test-recorder SDK client library for JavaScript +## Azure @azure-tools/test-recorder-new library for JavaScript -### **Note: This project is a test utility that assits with testing the packages maintained at the Azure SDK for JavaScript repository. This is not intended for the public utilization.** +This is an experimental tool to record and playback the tests in the JS repo by leveraging the unified out-of-process test proxy server. This library is still under construction. +Feature work is being tracked at [#15829](https://github.com/Azure/azure-sdk-for-js/issues/15829) -The Azure SDK for JavaScript is composed of a multitude of repositories that -attempt to deliver a common, homogenous SDK to make use of all of the services -that Azure can provide. Among the challenges of such a goal, we have some that -are specific to tests, some of which we can summarize in the following -questions: +## Resources -- How to write live tests that can work as unit tests? -- How to ensure that tests are as fast as they can be? -- How to avoid writing mocked versions of our HTTP API? -- How to protect sensitive data from our live tests? -- How to write tests that support parallelism? -- How to write isomorphic tests for NodeJS and the Browsers? +- [Azure SDK Tools Test Proxy](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy) +- [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) -Our non-published package `@azure-tools/test-recorder` attempts to provide an -answer for those questions, as you'll be able to see throughout this README. +## Running the test-proxy tool -This library provides interfaces and helper methods to equip the SDKs in the -azure-sdk-for-js repo with the recording and playback capabilities for the -tests, it targets HTTP requests in both Node.js and the Browsers. +### With the `docker run` command -test-recorder, as part of the Test Utils available in this repository, it -is supposed to be added only as a devDependency and should be used only for the -tests of an sdk. +- Run this command -## Index + > `docker run -v /workspaces/azure-sdk-for-js/:/srv/testproxy -p 5001:5001 -p 5000:5000 azsdkengsys.azurecr.io/engsys/testproxy-lin:latest` -- [Key concepts](#key-concepts). -- [Getting started](#getting-started). - - [Installing the package](#installing-the-package). - - [Configuring your project](#configuring-your-project). -- [Examples](#examples). - - [How to record](#how-to-record). - - [How to playback](#how-to-playback). - - [Updating existing recordings](#updating-existing-recordings). - - [Skipping tests](#skipping-tests). - - [Securing sensitive data](#securing-sensitive-data). - - [Ever-changing tests](#ever-changing-tests). - - [Supporting parallelism](#supporting-parallelism). - - [Isomorphic tests](#isomorphic-tests). -- [Troubleshooting](#troubleshooting). -- [Next steps](#next-steps). -- [Contributing](#contributing). + Map the root directory of the azure-sdk-for-js repo to `/srv/testproxy` inside the container for an accurate location while generating recordings. -## Key concepts + (Eventually, recorder will trigger this for you!) -- To **record** means to intercept any HTTP request (specifically, every - http.request call and browser's XMLHttpRequest calls), store it in a file, - then store the response received from the live resource that was originally - targeted. We use a couple of mechanisms to do this for NodeJS and the - browser. The output files are stored in `recordings/node/*` and in - `recordings/browser/*`, which are relative to the root of the project you're - working on. -- To **playback** means to intercept any HTTP request and to respond it with the - stored response of a previously recorded matching request. -- **Sensitive information** means content that should not be shared publicly. - Content like passwords, unique identifiers or personal information should be - cleaned up from the recordings. Some functionality is provided to fix this - problem. You can read more at [Securing sensitive data](#securing-sensitive-data). + Add `--add-host host.docker.internal:host-gateway` for linux to access host's network(to access `localhost`) through `host.docker.internal`. + Docker for Windows and Mac support `host.docker.internal` as a functioning alias for localhost. -## Getting started + If the above command doesn't work directly, try [Troubleshooting Access to Public Container Registry](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#troubleshooting-access-to-public-container-registry). -We're about to go through how to set up your project to use the -`@azure-tools/test-recorder` package. + Reference: [Using Test Proxy with docker container](https://github.com/Azure/azure-sdk-tools/tree/main/tools/test-proxy/docker#build-and-run) -This document assumes familiarity with [git](https://git-scm.com) and [rush](https://rushjs.io). -You can read more about how we use rush in the following links: +### (OR) With the `dotnet tool` -- Rush used for [Project Orchestration](https://github.com/sadasant/azure-sdk-for-js/blob/master/CONTRIBUTING.md#project-orchestration). -- [Rush for NPM users](https://github.com/sadasant/azure-sdk-for-js/blob/master/CONTRIBUTING.md#rush-for-npm-users). +- Install [.Net 5.0](https://dotnet.microsoft.com/download) +- Install test-proxy + > `dotnet tool install azure.sdk.tools.testproxy --global --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json --version 1.0.0-dev*` +- After successful installation, run the tool: -Keep in mind that `@azure-tools/test-recorder` is not a published package. It -is only intended to be used by the libraries in the azure-sdk-for-js repository -(at least for now). + > `test-proxy --storage-location ` -### Installing the package + [ `root-of-the-repo example` - `/workspaces/azure-sdk-for-js` if you're on codespaces] -To install the `@azure-tools/test-recorder` package, you'll need to start by -cloning our azure-sdk-for-js repository. One way of doing this is by using the -git command line interface, as follows: +## Run the tests using recorder-new at `test-utils/testing-recorder-new` -```bash -cd /path/to/my/github/repositories -git clone https://github.com/Azure/azure-sdk-for-js/ -``` +- Navigate to the test-utils\testing-recorder-new folder +- Run `rush update && rush build -t .` +- Run `rushx test:node` +- Run `rushx test:browser` -Having cloned this repository, let's set it up by running the following rush commands: +## Copying the recordings saved in the container -```bash -cd azure-sdk-for-js -rush update -rush install -rush build -``` +For some reason, the volume mapping did not work for you, copy the recordings manually. -This will optimistically assume you're in a fresh clone. +- `docker cp :/srv/testproxy/ temp-location` -From this point forward, we'll assume that you're developing (perhaps -contributing!) to one of the azure-sdk-for-js's libraries. So, your next step -is to change directory to the path relevant to your project. Let's say you want -to add the `@azure-tools/test-recorder` package to `@azure/keyvault-keys` (it -already uses test-recorder, but bear with us), you'll be doing the -following: - -```bash -cd sdk/keyvault/keyvault-keys -``` - -Once there, you can add the test-recorder package by changing your package.json -to include the following line in the devDependencies: - -```bash -{ - // ... your package.json properties - "devDependencies": { - // ... your devDependencies - "@azure-tools/test-recorder": "1.0.0", - // ... more of your devDependencies - }, - // ... more of your package.json properties -} -``` - -After that, we recommend you to update rush and install the dependencies again, as follows: - -```bash -rush update && rush install -``` - -And you're ready! Now you can use the common recorder in your code, as shown below: - -```typescript -import * as commonRecorder from "@azure-tools/test-recorder"; -``` - -Or, if you know what functions you want to import, you can also do the following: - -```typescript -import { record, env, delay } from "@azure-tools/test-recorder"; -``` - -The common recorder provides the following public methods and properties: - -- `record()`: Which deals with recording and playing back the network requests, - depending on the value assigned to the `TEST_MODE` environment variable. If - `TEST_MODE` equals to `record` or `soft-record`, it will automatically store network requests - in a plain text file in the folder `recordings` at the root of your - repository (which for our example case is the root of the - `@azure/keyvault/keyvault-keys` repository). - This package assumes that the tests in the sdk are leveraging - [mocha](https://mochajs.org/) and [rollup](https://rollupjs.org/guide/en/) - (and [karma](https://karma-runner.github.io/latest/index.html) test runner - for browser tests) as suggested by the [template](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/template/template) - package in the repo. It also returns an object with a method `stop()`, which - will allow you to control when you want the recorder to stop re-routing your - http requests. -- `Recorder`: The return of the `record()` method is going to be an instance of the - `Recorder`, which has some useful functions: `stop`, `skip`, `getUniqueName`, - and `newDate`. `stop` will stop the recorder from storing a copy of the HTTP - requests and responses. `skip` will pause the recorder only during the test - from which skip was called. `getUniqueName` allows you to get unique strings, - which you can use to create unique resources, so that you can run the same - set of live tests in parallel without colliding. And `newDate` will allow you - to generate dates that don't change during playback. -- `env`, which exposes the environment variable's object from either NodeJS or - the browser (useful on isomorphic tests). -- `delay`, which is an asynchronous function that will resolve once the given milliseconds have elapsed, - but only while the `TEST_MODE` is not `playback`, since you want to make sure you can run the playback tests - as fast as possible. -- `isRecordMode`, which is a shorthand for checking if the environment variable - `TEST_MODE` is set to `record` or `soft-record`. -- `isSoftRecordMode`, which is a shorthand for checking if the environment - variable `TEST_MODE` is set to `soft-record` only. -- `isPlaybackMode`, which is a shorthand for checking if the environment - variable `TEST_MODE` is set to `playback`. -- `setReplaceableVariables`, which will allow you to hide sensitive content - from the environment variables (more on that later). -- `setReplacements`, which will allow you to hide sensitive content by doing - pattern matching in the output files. -- `skipQueryParams`, since query parameters may contain sensitive information, - the array provided to method will signal what query parameters to remove from - the recordings. - -### Configuring your project - -Having the common recorder as a devDependency means that you'll be able to start -recording tests right away by using the exported method `record()`. We'll get -into the details further down this document. This function will do recordings, -or will play back previous recordings, depending on an environment variable: -`TEST_MODE`. If the environment variable `TEST_MODE` is empty, `record()` (and most -of the functions provided by test-recorder) won't be doing anything. You'll need -to set the `TEST_MODE` environment variable to `record` (or `soft-record`) to start recording, and then to -`playback` to play the recordings back at your code. - -#### package.json scripts - -You can write scripts in your `package.json` to -make it easier to switch from record mode to playback mode, on a meaningful context, as follows: - -```json -{ - // ... your package.json properties - "integration-test:node": "mocha myNodeTests.js", - "unit-test:node": "TEST_MODE=playback npm run integration-test:node", - "test:node:record": "TEST_MODE=record npm run integration-test:node" - // ... more of your package.json properties -} -``` - -Once your tests run, new files will be created in the `recordings/*` folder. -These files will have names that are relative to the tests that you have. -There might be cases in which the recordings get outdated with the test files, so you might also want to -add a way to clear the recordings on your `package.json`, like the following one: - -```json -{ - // ... your package.json properties - "clear-recordings": "rm -fr recordings" - // ... more of your package.json properties -} -``` - -#### Environment variables - -Since we make use of the `TEST_MODE` environment variable, we recommend you to -take control of how you deal with environment variables for your tests. If you -don't want to set environment variables, you can use a tool like -[dotenv](https://www.npmjs.com/package/dotenv) to set them for you. Remember to -do one or the other, not both, as dotenv will not overwrite your existing -environment variables. - -#### Karma configuration - -To make sure you're able to do record and playback on your browser tests, make -sure to configure Karma properly. - -The recordings are separated between NodeJS recordings and browser recordings, -so to use them on Karma, You'll at least need to add the recordings to your -`files` array in your `karma.conf.js`, as follows: - -```typescript -config.set({ - // ... more configuration properties here - files: [ - // ... you might have other things here. Keep them. - "recordings/browsers/**/*.json" - ] - // ... more configuration properties here -}); -``` - -Same goes on the `preprocessors` array: - -```typescript -config.set({ - // ... more configuration properties here - preprocessors: { - // ... you might have other things here. Keep them. - "recordings/browsers/**/*.json": ["json"] - } - // ... more configuration properties here -}); -``` - -You will also need to set the following configuration so that Karma passes the -correct environment variables to the browser runtime: - -```typescript -config.set({ - // ... more configuration properties here - envPreprocessor: [ - // ... you might have other things here. Keep them. - "TEST_MODE" - ] - // ... more configuration properties here -}); -``` - -As an optional step, you can also tell Karma to hide console.logs in during the -playbacks in the browsers by adding the following configuration property: - -```typescript -config.set({ - // ... more configuration properties here - browserConsoleLogOptions: { - terminal: process.env.TEST_MODE !== "record" - } - // ... more configuration properties here -}); -``` - -For a more detailed and opinionated approach, please check out the following -section of our guidelines: -[Setting up karma.conf.js file in the SDK](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/test-utils/recorder/GUIDELINES.md#setting-up-karmaconfjs-file-in-the-sdk). - -## Examples - -### How to record - -To record your tests, make sure to set the environment variable `TEST_MODE` to -`record` or `soft-record`, then in your code, call to the `record()` function exported from -`@azure-tools/test-recorder`, then call it before the http request you want to -make. In the following example, we'll invoke the `record()` method before -authenticating our KeyVault client: - -```typescript -import { env, record, Recorder } from "@azure-tools/test-recorder"; -import { KeysClient } from "@azure/keyvault-keys"; - -describe("My test", () => { - let recorder: Recorder; - let client: KeysClient; - - beforeEach(async function() { - recorder = record(this); - - // This is an example of how the environment variables are used - const credential = await new ClientSecretCredential( - env.AZURE_TENANT_ID, - env.AZURE_CLIENT_ID, - env.AZURE_CLIENT_SECRET - ); - - // This example also shows that HTTP requests must be made after the record() method is called. - const keyVaultUrl = "https://myKeyVault.vault.azure.net"; - client = new KeysClient(keyVaultUrl, credential); - }); - - afterEach(async function() { - await recorder.stop(); - }); -}); -``` - -After running this test with the `TEST_MODE` environment variable set to -`record`, the common recorder will create a recording file located in -`recordings/node/my_test/recording_before_each_hook.js` with the contents of -the HTTP request as well as the contents of the HTTP response. - -If `TEST_MODE` is set to `soft-record` instead, the recorder will only create -this recording file if the test has changed from a previous execution. - -You'll see in the code above that we're invoking `recorder.stop`. This is so -that, after each test, we can stop recording and the test file can be -generated. We recommend creating new recorders on `beforeEach` and stopping the -recorder on `afterEach` to make sure that the generated files are smaller and -easier to understand than by having them all in one chunk. - -> **Note:** By this point you might have noticed that the values in the environment variables will be stored in the recordings. -> We'll make sure this doesn't happen in the [Securing sensitive data](#securing-sensitive-data) example. - -To add recorded tests, feel free to extend this file with as many subsequent -`it()` calls as you need. Any HTTP request in these tests will be added to the -recordings. - -```typescript - beforeEach(async function() { - recorder = record(that); - const credential = await new ClientSecretCredential( - env.AZURE_TENANT_ID, - env.AZURE_CLIENT_ID, - env.AZURE_CLIENT_SECRET - ); - - const keyVaultUrl = "https://myKeyVault.vault.azure.net"; - client = new KeysClient(keyVaultUrl, credential); - }); - - afterEach(async function () { - await recorder.stop(); - }); - - it("my first test", async function() { - const result = await client.createKey("First key", "RSA"); - assert.equal(result.name, "First key"); - }); - - it("my second test", async function() { - const result = await client.createKey("Second key", "RSA"); - assert.equal(result.name, "Second key"); - }); - - // And so on... -}); -``` - -### How to playback - -Once you have recorded something, you can run your tests again with `TEST_MODE` -set to `playback`. You'll notice how they pass much faster. This time, they -will not reach out to the remote address, but instead they will respond every -request according to their matching copy stored in the recordings. - -### Update existing recordings - -Once you have your recorded files, to update them after changing one of the tests, simply -re-run the tests with `TEST_MODE` set to `record`. This will overwrite previously existing files. -Or re run the tests with `TEST_MODE` set to `soft-record` to only overwrite the files related to -tests that have changed. - -> **Note:** If you rename the file of the test, or the name of the test, the -> path of the recording will change. Make sure to delete the recordings -> corresponding to the deleted tests. If at any point in time you lose your -> recordings, don't worry. Running your tests with `TEST_MODE=recording` will -> re-generate them. - -### Skipping tests - -Writing live tests can take considerable time, specially since each time you -want to check that everything works fine, you potentially need to run again -every test. With the common recorder, you can specify what test to run by following Mocha's -approach of setting certain tests to `it.only`, and also to skip specific tests -with `it.skip`. If you launch the recorder in record mode with some of these -changes (and given that you activate the recorder on `beforeEach`), only the -files that relate to the changed tests will be updated. Skipped tests won't -update their recordings. This way, you can focus on fixing a specific set of -test with `.only`, then remove all the `.only` calls and trust that the playback will -keep confirming that the unaffected tests are fine and green. - -You can also skip specific tests with `recorder.skip(runtime?: "node" | "browser")`, -which will skip the test in node or browser runtimes based on the `{runtime}` -argument. If the `{runtime}` is undefined, the test will be skipped in both the -node and browser runtimes. This method has no effect if the TEST_MODE -environment variable is neither "record" nor "playback". You can read more -about this feature [here](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/test-utils/recorder/GUIDELINES.md#skipping-a-test). - -### Securing sensitive data - -Live tests need to do sensitive operations, like authenticating with your Azure -credentials. To protect this information from the recordings, -`@azure-tools/test-recorder` provides the following functions: - -- `setReplaceableVariables`, which will allow you to hide sensitive content - from the environment variables (more on that later). -- `setReplacements`, which will allow you to hide sensitive content by doing - pattern matching in the recordings of the tests' HTTP requests. -- `skipQueryParams`, since query parameters may contain sensitive information, - the array provided to method will signal what query parameters to remove from - the recordings. - -Let's see some examples. - -#### setReplaceableVariables - -This method will allow you to specify what environment variables have sensitive information. -Give to this function a plain object composed of the names of the environment -variables that can't be stored in the recordings, with the values you want to -store instead. To solve this issue in our previous example test, you may do the following: - -```typescript -// You'll be most certainly importing more than this method. -import { setReplaceableVariables } from "@azure-tools/test-recorder"; - -// Do this inside of the beforeEach, before you start recording. -setReplaceableVariables({ - AZURE_CLIENT_ID: "azure_client_id", - AZURE_CLIENT_SECRET: "azure_client_secret", - AZURE_TENANT_ID: "azure_tenant_id", - KEYVAULT_URI: "https://keyvault_name.vault.azure.net" -}); -``` - -If you record the tests with that change, you should see the values of those -environment variables replaced with their safe counterpart. - -#### setReplacements - -A more generic way to clean up your recordings is by providing you with the -final string of the recording, to which you'll be able to make arbitrary -changes. The `setReplacements` method expects to receive an array of functions, all of which will receive -the recorded string and return a string. - -Let's say your project can't include the word `mango`. You will be able to get rid of it with the following code inside your `beforeEach`: - -```typescript -setReplacements([(recording: string): string => recording.replace(/mango/g, "bananas")]); -``` - -This lets you have control over the generated recordings and filter any -sensitive information even before checking them in a pull request. - -#### skipQueryParams - -Some HTTP requests might have parameters with sensitive information. To get -them out of your recordings, you can call to `skipQueryParams` with an array of strings -where you specify the names of the query parameter you want to remove. - -For example, given that we find this query parameters in our recordings: -`?sv=2018-11-09&sr=c&sig=&sktid=&skv=2018-11-09&se=2019-08-07T07%3A00%3A00Z&sp=rwdl`, -if we don't want the parameters "sr", "sig" and "sp" to appear in these files, we can do the following: - -```typescript -skipQueryParams(["sr", "sig", "sp"]); -``` - -### Ever-changing tests - -Sometimes, your tests will do requests with unpredictable code. -The best way to ensure that your ever-changing tests pass on playback mode is to -use `setReplacements` or `skipQueryParams` to avoid having the changing contents -as part of your recordings. - -### Supporting parallelism - -A common issue while running integration tests is that, sometimes two persons -or machines might try to run the same set of tests against the same resource. -This is not directly related to the `@azure-tools/test-recorder` package, but -if you're getting into issues because of concurrent conflicting requests, we -understand, and we might be able to help by providing you with the following -suggestions: - -1. Use randomly generated strings as prefixes or suffixes for the resources you - create. This will help you, but it will also only work so far, since new - resources are likely to get accumulated, even if you set code to delete them in - between tests, since some tests will eventually fail and crash your program. -2. Set up a separate program as part of your CI to automatically create and - destroy new resources each time you run a test. It doesn't sound easy, but it - might be a better solution. You'll need to make sure to clear resources in - case this program fails. - -These ideas come with their own issue and things to consider, so please take them -as ideas. We understand that might not be an easy problem to fix. - -### Isomorphic tests - -`@azure-tools/test-recorder` does support running tests in the browser. If you -use Karma, as long as your karma configuration is correct, your tests should -work both on NodeJS and in the browsers! - -## Troubleshooting - -Besides the usual debugging of your code and tests, if you ever encounter a -problem while recording your tests, make sure to read the output in the -recordings. If the output is not what you expected, please follow up the -[contributing](#contributing) guidelines on how to write an issue for us. We'll -make sure to handle it as soon as we find the time. - -## Next steps - -The common recorder might not be used yet in each one of the libraries in the -azure-sdk-for-js repository (we're working on it). In the mean time, an easy -way to find where we're using this package is by going through the following -search link: - - -## Contributing - -If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/main/CONTRIBUTING.md) to learn more about how to build and test the code. - -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-js%2Fsdk%2Ftest-utils%2Frecorder%2FREADME.png) + [This will be fixed eventually [#Issue-17138](https://github.com/Azure/azure-sdk-for-js/issues/17138)] diff --git a/sdk/test-utils/recorder/karma.conf.js b/sdk/test-utils/recorder/karma.conf.js index af556dcdd187..b2f1f58d3228 100644 --- a/sdk/test-utils/recorder/karma.conf.js +++ b/sdk/test-utils/recorder/karma.conf.js @@ -1,7 +1,10 @@ // https://github.com/karma-runner/karma-chrome-launcher +const { relativeRecordingsPath } = 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) @@ -46,7 +49,7 @@ module.exports = function (config) { // 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: [], + envPreprocessor: ["RECORDINGS_RELATIVE_PATH", "PROXY_MANUAL_START"], // test results reporter to use // possible values: 'dots', 'progress' @@ -111,10 +114,6 @@ module.exports = function (config) { browserNoActivityTimeout: 600000, browserDisconnectTimeout: 10000, browserDisconnectTolerance: 3, - browserConsoleLogOptions: { - // We would usually hide the logs from the tests, but we don't need to do this inside of the recorder package because we are not recording the tests. - // // terminal: process.env.TEST_MODE !== "record" - }, client: { mocha: { diff --git a/sdk/test-utils/recorder/package.json b/sdk/test-utils/recorder/package.json index ce1ed09867ae..1245e6902314 100644 --- a/sdk/test-utils/recorder/package.json +++ b/sdk/test-utils/recorder/package.json @@ -1,38 +1,41 @@ { "name": "@azure-tools/test-recorder", - "version": "1.0.2", + "version": "2.0.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/basekarma.conf.js": "./dist-esm/src/basekarma.conf.browser.js", - "./dist-esm/src/createRecorder.js": "./dist-esm/src/createRecorder.browser.js", - "./dist-esm/src/utils/recordings.js": "./dist-esm/src/utils/recordings.browser.js" + "./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/test/utils/server.js": "./dist-esm/test/utils/server.browser.js" }, - "types": "./types/src/index.d.ts", "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": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c 2>&1", + "build:browser": "echo skipped", "build:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c 2>&1", "build:samples": "echo Skipped.", - "build:test": "tsc -p . && rollup -c 2>&1", + "build:test": "tsc -p .", "build": "npm run clean && tsc -p . && rollup -c 2>&1", "check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "clean": "rimraf dist dist-* types *.tgz *.log", + "clean": "rimraf dist dist-esm test-dist typings *.tgz *.log", "extract-api": "echo skipped", "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "integration-test:browser": "karma start --single-run", - "integration-test:node": "nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 5000000 --full-trace \"dist-esm/test/*.spec.js\" \"dist-esm/test/node/*.spec.js\"", + "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", "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", "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 -f html -o recorder-lintReport.html || exit 0", + "lint": "eslint --no-eslintrc -c ../../.eslintrc.internal.json package.json src test --ext .ts", "pack": "npm pack 2>&1", - "unit-test:browser": "karma start --single-run", - "unit-test:node": "mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 5000000 --full-trace --exclude \"dist-esm/**/browser/**/*.spec.js\" \"dist-esm/**/*.spec.js\"", + "unit-test:browser": "npm run integration-test:browser", + "unit-test:node": "npm run integration-test:node", "unit-test": "npm run unit-test:node && npm run unit-test:browser", - "test:browser": "npm run clean && npm run build:test && npm run unit-test:browser", - "test:node": "npm run clean && npm run build:test && npm run unit-test:node", + "test:browser": "npm run clean && npm run build && npm run integration-test:browser", + "test:node": "npm run clean && npm run build:test && npm run integration-test:node", "test": "npm run clean && npm run build:test && npm run unit-test", "docs": "echo Skipped." }, @@ -60,33 +63,28 @@ }, "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/test-utils/recorder/", "sideEffects": false, + "private": true, "dependencies": { "@azure/core-http": "^2.0.0", - "@azure/core-tracing": "1.0.0-preview.13", - "fs-extra": "^8.1.0", - "nise": "^4.0.3", - "nock": "^12.0.3", - "tslib": "^2.2.0", - "md5": "^2.2.1" + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/test-utils": "^1.0.0", + "@azure/core-auth": "^1.3.2" }, "devDependencies": { + "@azure/core-client": "^1.0.0", "@azure/dev-tool": "^1.0.0", "@azure/eslint-plugin-azure-sdk": "^3.0.0", - "@rollup/plugin-commonjs": "11.0.2", - "@rollup/plugin-multi-entry": "^3.0.0", - "@rollup/plugin-node-resolve": "^8.0.0", - "@rollup/plugin-replace": "^2.2.0", - "@types/fs-extra": "^8.0.0", + "@types/express": "^4.16.0", "@types/chai": "^4.1.6", - "@types/md5": "^2.2.0", "@types/mocha": "^7.0.2", - "@types/nise": "^1.4.0", "@types/node": "^12.0.0", - "@types/mock-require": "~2.0.0", - "@types/mock-fs": "^4.13.1", + "@types/uuid": "^8.3.1", "chai": "^4.2.0", + "concurrently": "^6.2.1", + "cross-env": "7.0.3", "dotenv": "^8.2.0", "eslint": "^7.15.0", + "express": "^4.16.3", "karma": "^6.2.0", "karma-chrome-launcher": "^3.0.0", "karma-coverage": "^2.0.0", @@ -94,26 +92,19 @@ "karma-env-preprocessor": "^0.1.1", "karma-firefox-launcher": "^1.1.0", "karma-ie-launcher": "^1.0.0", - "karma-json-preprocessor": "^0.3.3", - "karma-json-to-file-reporter": "^1.0.1", "karma-junit-reporter": "^2.0.1", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.8", "mocha": "^7.1.1", "mocha-junit-reporter": "^2.0.0", - "mock-fs": "^5.1.2", - "mock-require": "^3.0.3", - "npm-run-all": "^4.1.5", "nyc": "^15.0.0", "prettier": "^2.5.1", "rimraf": "^3.0.0", "rollup": "^1.16.3", - "rollup-plugin-shim": "^1.0.0", - "rollup-plugin-sourcemaps": "^0.4.2", - "rollup-plugin-terser": "^5.1.1", - "rollup-plugin-visualizer": "^4.0.4", + "tslib": "^2.2.0", + "ts-node": "^9.0.0", "typescript": "~4.2.0", - "xhr-mock": "^2.4.1" + "uuid": "^8.3.2" } } diff --git a/sdk/test-utils/recorder/recordings/.gitkeep b/sdk/test-utils/recorder/recordings/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/sdk/test-utils/recorder/src/baseRecorder.ts b/sdk/test-utils/recorder/src/baseRecorder.ts deleted file mode 100644 index 45d6c5388001..000000000000 --- a/sdk/test-utils/recorder/src/baseRecorder.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -import { defaultCustomizationsOnRecordings } from "./defaultCustomizations"; -import { - TestInfo, - RecorderEnvironmentSetup, - filterSecretsFromStrings, - filterSecretsRecursivelyFromJSON, - generateTestRecordingFilePath, -} from "./utils"; -import { - defaultRequestBodyTransforms, - RequestBodyTransformsType, -} from "./utils/requestBodyTransform"; - -type InternalRecorderEnvironmentSetup = RecorderEnvironmentSetup & { - /** - * Used in record and playback modes - * - * Array of callback functions provided to customize the request body - * - Record mode: These callbacks will be applied on the request body before the recording is saved - * - Playback mode: These callbacks will be applied on the request body of the new requests - * - * // Nock doesn't support multiple `.filteringRequestBody` patches in the recordings, - * // ..hence not exporting `requestBodyTransformations` to the users until we find an alternative - * // TODO: Best alternative would be to migrate to JSON recordings for node tests - */ - requestBodyTransformations: Required; -}; -/** - * Loads the environment variables in both node and browser modes corresponding to the key-value pairs provided. - * - * Example- - * - * Suppose `replaceableVariables` is { ACCOUNT_NAME: "my_account_name", ACCOUNT_KEY: "fake_secret" }, - * `setEnvironmentVariables` loads the ACCOUNT_NAME and ACCOUNT_KEY in the environment accordingly. - * @export - * @param {{ [key: string]: string }} replaceableVariables - */ -export function setEnvironmentVariables(env: any, replaceableVariables: { [key: string]: string }) { - Object.keys(replaceableVariables).map((key) => { - env[key] = replaceableVariables[key]; - }); -} - -export abstract class BaseRecorder { - // relative file path of the test recording inside the `recordings` folder - // Example - node/some_random_test_suite/recording_first_test.js - protected readonly relativeTestRecordingFilePath: string; - public uniqueTestInfo: TestInfo = { uniqueName: {}, newDate: {} }; - public environmentSetup: InternalRecorderEnvironmentSetup = { - replaceableVariables: {}, - customizationsOnRecordings: [], - queryParametersToSkip: [], - requestBodyTransformations: defaultRequestBodyTransforms, - }; - protected hash: string; - private defaultCustomizationsOnRecordings = defaultCustomizationsOnRecordings; - - constructor( - platform: "node" | "browsers", - hash: string, - testSuiteTitle: string, - testTitle: string - ) { - this.hash = hash; - this.relativeTestRecordingFilePath = generateTestRecordingFilePath( - platform, - testSuiteTitle, - testTitle - ); - } - - /** - * Additional layer of security to avoid unintended/accidental occurrences of secrets in the recordings. - * If the content is a string, a filtered string is returned. - * If the content is a JSON object, a filtered JSON object is returned. - * - * @protected - * @param content - * @memberof BaseRecorder - */ - protected filterSecrets(content: any): any { - let updatedContent = content; - if (typeof content !== "string") { - // For the recording as a whole... - // Methods such as maskAccessTokenInBrowserRecording may have effects here - for (const customization of this.defaultCustomizationsOnRecordings) { - updatedContent = customization(updatedContent); - } - } - - const recordingFilterMethod = - typeof updatedContent === "string" - ? filterSecretsFromStrings - : filterSecretsRecursivelyFromJSON; - - return recordingFilterMethod( - updatedContent, - this.environmentSetup.replaceableVariables, - this.defaultCustomizationsOnRecordings.concat( - this.environmentSetup.customizationsOnRecordings - ) - ); - } - - public init(environmentSetup: RecorderEnvironmentSetup) { - this.environmentSetup = { - replaceableVariables: { - ...this.environmentSetup.replaceableVariables, - ...environmentSetup.replaceableVariables, - }, - customizationsOnRecordings: [ - ...this.environmentSetup.customizationsOnRecordings, - ...environmentSetup.customizationsOnRecordings, - ], - queryParametersToSkip: [ - ...this.environmentSetup.queryParametersToSkip, - ...environmentSetup.queryParametersToSkip, - ], - requestBodyTransformations: { - // TODO: Concat with the requestBodyTransformations once exposed - stringTransforms: this.environmentSetup.requestBodyTransformations?.stringTransforms, - jsonTransforms: this.environmentSetup.requestBodyTransformations?.jsonTransforms, - }, - }; - } - - public abstract record(environmentSetup: RecorderEnvironmentSetup): void; - /** - * Finds the recording for the corresponding test and replays the saved responses from the recording. - * - * @abstract - * @param {string} filePath Test file path (can be obtained from the mocha's context object - Mocha.Context.currentTest) - * @memberof BaseRecorder - */ - public abstract playback(environmentSetup: RecorderEnvironmentSetup, filePath: string): void; - public abstract stop(): Promise; -} diff --git a/sdk/test-utils/recorder/src/basekarma.conf.browser.ts b/sdk/test-utils/recorder/src/basekarma.conf.browser.ts deleted file mode 100644 index 7059fed331ba..000000000000 --- a/sdk/test-utils/recorder/src/basekarma.conf.browser.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export function jsonRecordingFilterFunction() { - // If you get this error, it's because you tried to use this config in a browser. - // It is meant to be imported in the karma configuration file and used to configure - // the browser launch specifications, not to be used within the browser itself. - throw new Error("Attempted to use the base karma configuration in a browser."); -} diff --git a/sdk/test-utils/recorder/src/basekarma.conf.ts b/sdk/test-utils/recorder/src/basekarma.conf.ts deleted file mode 100644 index 75aa488ccb2f..000000000000 --- a/sdk/test-utils/recorder/src/basekarma.conf.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { isRecordMode } from "./utils"; -import fs from "fs-extra"; - -// - jsonToFileReporter filters the JSON strings in console.logs. -// - Console logs with `.writeFile` property are captured and are written to a file(recordings). -// - The other console statements are captured and printed normally. -// - Example - console.warn("hello"); -> console.log({ warn: "hello" }); -// - Example - console.log("hello"); -> console.log({ log: "hello" }); - -/** - * When `jsonRecordingFilterFunction` is passed as a filter to `jsonToFileReporter` in karma.conf.js, - * it captures the recordings(as JSON strings) from the console.logs. - * - * More Info - - * 1. JSON objects with `writeFile` property are captured and saved as recordings as per the `path` property. - * 2. If the captured object doesn't have the `writeFile` property, the object will be logged directly to the console. - * - * @param {{ - * writeFile: boolean; - * path: string; - * content: string; - * }} browserRecordingJsonObject - */ -export const jsonRecordingFilterFunction = function (browserRecordingJsonObject: { - writeFile: boolean; - path: string; - content: string; -}) { - if (isRecordMode()) { - if (browserRecordingJsonObject.writeFile) { - // Create the directories recursively incase they don't exist - try { - // Stripping away the filename from the file path and retaining the directory structure - fs.ensureDirSync( - browserRecordingJsonObject.path.substring( - 0, - browserRecordingJsonObject.path.lastIndexOf("/") + 1 - ) - ); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } - fs.writeFile( - browserRecordingJsonObject.path, - JSON.stringify(browserRecordingJsonObject.content, null, " "), - (err: any) => { - if (err) { - throw err; - } - } - ); - } else { - console.log(browserRecordingJsonObject); - } - } -}; diff --git a/sdk/test-utils/recorder/src/createRecorder.browser.ts b/sdk/test-utils/recorder/src/createRecorder.browser.ts deleted file mode 100644 index 2b5982f05875..000000000000 --- a/sdk/test-utils/recorder/src/createRecorder.browser.ts +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { BaseRecorder } from "./baseRecorder"; - -import nise from "nise"; -import { - parseUrl, - blobToString, - RecorderEnvironmentSetup, - windowLens, - isRecordMode, - isPlaybackMode, -} from "./utils"; -import { customConsoleLog } from "./customConsoleLog"; -import { - applyRequestBodyTransformations, - applyRequestBodyTransformationsOnFixture, -} from "./utils/requestBodyTransform"; - -// To better understand how this class works, it's necessary to comprehend how HTTP async requests are made: -// A new request object is created -// let req = new XMLHttpRequest(); -// The request is opened with some important information -// req.open(method, url, async, user, password); -// Since we're dealing with an async request, we must set a way to know when the response is ready -// req.onreadystatechange = function() { -// if (req.readyState === 4) do_something; -// } -// Finally, the request is sent to the server -// req.send(data); - -// Nise module does not have a native implementation of record/playback like Nock does -// This class overrides requests' 'open', 'send' and 'onreadystatechange' functions, adding our own code to them to deal with requests -export class NiseRecorder extends BaseRecorder { - private recordings: Record[] = []; - private recordingInFlight: Promise[] = []; - private xhr: nise.FakeXMLHttpRequestStatic | undefined; - - constructor(hash: string, testSuiteTitle: string, testTitle: string) { - super("browsers", hash, testSuiteTitle, testTitle); - } - - // Inserts a request/response pair into the recordings array - private async recordRequest(request: any, data: any): Promise { - const responseHeaders: any = {}; - const responseHeadersPairs = request.getAllResponseHeaders().split("\r\n"); - for (const pair of responseHeadersPairs) { - const [key, value] = pair.split(": "); - responseHeaders[key] = value; - } - - // We're not storing Query Parameters because they may contain sensitive information - // We're ignoring the "_" parameter as well because it's not being added by our code - // More info on "_": https://stackoverflow.com/questions/3687729/who-add-single-underscore-query-parameter - const parsedUrl = parseUrl(request.url); - const query: any = {}; - for (const param in parsedUrl.query) { - if (!this.environmentSetup.queryParametersToSkip.includes(param) && param !== "_") { - query[param] = parsedUrl.query[param]; - } - } - - this.recordings.push({ - method: request.method, - url: parsedUrl.url, - query: query, - requestBody: data instanceof Blob ? await blobToString(data) : data, - status: request.status, - response: - request.response instanceof Blob ? await blobToString(request.response) : request.response, - responseHeaders: responseHeaders, - }); - } - - // Checks whether a recording matches a request or not (we're not matching request headers) - private matchRequest(recording: any, request: any): boolean { - // Every parameter in the recording must be present and have the same value in the request - for (const param in recording.query) { - if (recording.query[param] !== request.query[param]) { - return false; - } - } - - // There shouldn't be parameters in the request that are not present in the recording (except for queryParametersToSkip and "_") - for (const param in request.query) { - if ( - recording.query[param] === undefined && - !this.environmentSetup.queryParametersToSkip.includes(param) && - param !== "_" - ) { - return false; - } - } - - return ( - recording.method === request.method && - recording.url === request.url && - // For backward compatibility, calling `applyRequestBodyTransformations` on - // - the request-body in the recording - // and - // - the request-body of the new request - // - // Once all the browser recordings are regenerated, L.H.S can be updated to `recording.requestBody` - // since the `applyRequestBodyTransformations` would have been applied before saving the recording - applyRequestBodyTransformations( - recording.requestBody, - this.environmentSetup.requestBodyTransformations - ) === - applyRequestBodyTransformations( - request.requestBody, - this.environmentSetup.requestBodyTransformations - ) - ); - } - - // When recording, we want to hit the server and intercept requests/responses - // Nise does not allow us to intercept requests if they're sent to the server, so we need to override its behavior - public record(recorderEnvironmentSetup: RecorderEnvironmentSetup): void { - super.init(recorderEnvironmentSetup); - const self = this; - const xhr = nise.fakeXhr.useFakeXMLHttpRequest(); - this.xhr = xhr; - - // The following filter allows every request to be sent to the server without being mocked - xhr.useFilters = true; - xhr.addFilter(() => true); - - // 'onCreate' function is called when a new fake XMLHttpRequest object (req) is created - // Our intent is to override the request's 'onreadystatechange' function so we can create a recording once the response is ready - // We can only override 'onreadystatechange' AFTER the 'send' function is called because we need to make sure our implementation won't be overridden by the client - // But we can only override 'send' AFTER the 'open' function is called because the filter we set above makes Nise override it in 'open' body - xhr.onCreate = function (req: any) { - // We'll override the 'open' function, so we need to store a handle to its original implementation - const reqOpen = req.open; - req.open = function () { - // Here we are calling the original 'open' function to make sure everything is set up correctly (HTTP method, url, filters) - reqOpen.apply(req, arguments); - - // We'll override the 'send' function, so we need to store a handle to its original implementation - // We can already override it because we know 'open' has already been called - const reqSend = req.send; - req.send = function (data: any) { - // We'll override the 'onreadystatechange' function, so we need to store a handle to its original implementation - // Now we can finally override 'onreadystatechange' because 'send' has already been called - const reqStateChange = req.onreadystatechange; - req.onreadystatechange = function () { - // .readyState property returns the state an XMLHttpRequest client is in - // readyState = 4 refers to the completion of the operation. - // This could mean that either the data transfer has been completed successfully or failed. - // More info on readyState - https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState - if (req.readyState === 4) { - // Record the request once the response is obtained - self.recordingInFlight.push(self.recordRequest(req, data)); - } - // Sometimes the client doesn't implement an 'onreadystatechange' function, so we need to make sure it exists before calling the original implementation - if (reqStateChange) { - reqStateChange.apply(null, arguments); - } - }; - - // Now that we have overridden 'onreadystatechange', we can send the request to the server - reqSend.apply(req, arguments); - }; - }; - }; - } - - // When playing back, we want to intercept requests, find a corresponding match in our recordings and respond to it with the recorded data - // We must override the request's 'send' function because all the request information (body, url, method, queries) will be ready when it's called - public playback(recorderEnvironmentSetup: RecorderEnvironmentSetup): void { - super.init(recorderEnvironmentSetup); - const self = this; - const xhr = nise.fakeXhr.useFakeXMLHttpRequest(); - this.xhr = xhr; - - // 'karma-json-preprocessor' helps us to retrieve recordings - this.recordings = windowLens.get([ - "__json__", - "recordings/" + this.relativeTestRecordingFilePath, - "recordings", - ]); - this.uniqueTestInfo = windowLens.get([ - "__json__", - "recordings/" + this.relativeTestRecordingFilePath, - "uniqueTestInfo", - ]); - - // 'onCreate' function is called when a new fake XMLHttpRequest object (req) is created - xhr.onCreate = function (req: any) { - // We'll override the 'send' function, so we need to store a handle to its original implementation - const reqSend = req.send; - req.send = async function (data: any) { - // Here we're calling the original send method. Nise will make the request wait for a mock response that we'll send later - reqSend.call(req, data); - - // formattedRequest contains all the necessary information to look for a match in our recordings - const parsedUrl = parseUrl(req.url); - const formattedRequest = { - method: req.method, - url: parsedUrl.url, - query: parsedUrl.query, - requestBody: data instanceof Blob ? await blobToString(data) : data, - }; - - // We look through our recordings to find a match to the current request - // If we find a match, we remove it from the recordings list so we don't match it again by accident - let recordingFound = false; - for (let i = 0; !recordingFound && i < self.recordings.length; i++) { - if (self.matchRequest(self.recordings[i], formattedRequest)) { - const status = self.recordings[i].status; - const responseHeaders = self.recordings[i].responseHeaders; - const response = self.recordings[i].response; - - // We are dealing with async requests so we're responding to them asynchronously - setTimeout(() => { - if (!req.aborted) { - req.respond(status, responseHeaders, response); - } - }); - self.recordings.splice(i, 1); - recordingFound = true; - } - } - - // If we can't find a match, we throw an error - // Some tests expect errors to happen and, if a matching error is thrown in one of these tests, it may be captured in a catch block by accident, - // resulting in unexpected behavior. For this reason we're printing it to the console as well - if (!recordingFound) { - const err = new Error( - "No match for request " + JSON.stringify(formattedRequest, null, " ") - ); - console.log(err); - throw err; - } - }; - }; - } - - public async stop(): Promise { - if (isRecordMode()) { - await Promise.all(this.recordingInFlight); - // recordings at this point are in the JSON format. - this.recordings = this.filterSecrets(this.recordings); - - this.recordings = this.recordings.map((singleRecording) => - applyRequestBodyTransformationsOnFixture( - "browser", - singleRecording, - this.environmentSetup.requestBodyTransformations - ) - ); - - // We're sending the recordings to the 'karma-json-to-file-reporter' via console.log - console.log( - JSON.stringify({ - writeFile: true, - path: "./recordings/" + this.relativeTestRecordingFilePath, - content: { - recordings: this.recordings, - uniqueTestInfo: this.uniqueTestInfo, - hash: this.hash, - }, - }) - ); - } else if (isPlaybackMode()) { - // TO DO - playback cleanup if any necessary - } - - // Resetting the XHR behavior to it's original state. - // Necessary if any code wants to use the browser outside of the recorder once the recorder is stopped. - if (this.xhr) { - this.xhr.useFilters = false; - this.xhr.restore(); - } - } -} - -/** - * Creates an instance of the recorder that is appropriate for the current - * environment. - */ -export function createRecorder( - currentHash: string, - testHierarchy: string, - testTitle: string -): BaseRecorder { - if (isRecordMode()) { - customConsoleLog(); - } - - return new NiseRecorder(currentHash, testHierarchy, testTitle); -} diff --git a/sdk/test-utils/recorder/src/createRecorder.ts b/sdk/test-utils/recorder/src/createRecorder.ts deleted file mode 100644 index 5f55cb045136..000000000000 --- a/sdk/test-utils/recorder/src/createRecorder.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { BaseRecorder } from "./baseRecorder"; -import { RecorderEnvironmentSetup, isRecordMode, isPlaybackMode } from "./utils"; -import { nodeRequireRecordingIfExists } from "./utils/recordings"; - -import { config as readEnvFile } from "dotenv"; -import fs from "fs-extra"; -import { applyRequestBodyTransformationsOnFixture } from "./utils/requestBodyTransform"; -import { mockMsalAuth, NockType } from "./utils/msalAuth.node"; -import { isNode } from "@azure/core-http"; - -let nock: NockType; - -export class NockRecorder extends BaseRecorder { - constructor(hash: string, testSuiteTitle: string, testTitle: string) { - super("node", hash, testSuiteTitle, testTitle); - } - - public record(recorderEnvironmentSetup: RecorderEnvironmentSetup): void { - super.init(recorderEnvironmentSetup); - nock.recorder.rec({ - dont_print: true, - }); - } - - public playback(recorderEnvironmentSetup: RecorderEnvironmentSetup, testFilePath: string): void { - super.init(recorderEnvironmentSetup); - /** - * `@azure-tools/test-recorder` package is used for both the browser and node tests - * - * During the playback mode, - * `path` module is leveraged to import the node test recordings and `path` module can't be imported in the browser. - * So, instead of `import`-ing the `path` library, `require` is being used and this code path is never executed in the browser. - * - * [A different strategy is in place to import recordings for browser tests by leveraging `karma` plugins.] - */ - this.uniqueTestInfo = nodeRequireRecordingIfExists( - this.relativeTestRecordingFilePath, - testFilePath - ).testInfo; - - if (isNode) { - // The following call provides the fake access_token in playback for the msal /oauth2/v2.0/token requests by not matching the request body - mockMsalAuth(nock, recorderEnvironmentSetup.onLoadCallbackForPlayback); - } - } - - public async stop(): Promise { - if (isRecordMode()) { - // Importing "nock" library in the recording and appending the testInfo part in the recording - const importNockStatement = - "let nock = require('nock');\n" + - "\n" + - `module.exports.hash = "${this.hash}";\n` + - "\n" + - "module.exports.testInfo = " + - JSON.stringify(this.uniqueTestInfo) + - "\n"; - - const fixtures = nock.recorder.play() as string[]; // We know it is an array of strings at this point - - // Create the directories recursively incase they don't exist - try { - // Stripping away the filename from the filepath and retaining the directory structure - fs.ensureDirSync( - "./recordings/" + - this.relativeTestRecordingFilePath.substring( - 0, - this.relativeTestRecordingFilePath.lastIndexOf("/") + 1 - ) - ); - } catch (err) { - if (err.code !== "EEXIST") throw err; - } - - const file = fs.createWriteStream("./recordings/" + this.relativeTestRecordingFilePath, { - flags: "w", - }); - - // Some tests expect errors to happen and, if a writing error is thrown in one of these tests, it may be captured in a catch block by accident, - // resulting in unexpected behavior. For this reason we're printing it to the console as well - file.on("error", (err: any) => { - console.log(err); - throw err; - }); - - file.write(importNockStatement); - - // Saving the recording to the file - for (const fixture of fixtures) { - let updatedFixture = fixture; - // Applying any requestBody transformations that are provided - updatedFixture = applyRequestBodyTransformationsOnFixture( - "node", - fixture, - this.environmentSetup.requestBodyTransformations - ); - - // We're not matching query string parameters because they may contain sensitive information, and Nock does not allow us to customize it easily - updatedFixture = updatedFixture.toString().replace(/\.query\(.*\)/, ".query(true)"); - file.write(this.filterSecrets(updatedFixture) + "\n"); - } - - file.end(); - - nock.recorder.clear(); - nock.restore(); - } else if (isPlaybackMode()) { - nock.restore(); - nock.cleanAll(); - nock.enableNetConnect(); - } - } -} - -/** - * Creates an instance of the recorder that is appropriate for the current - * environment. - */ -export function createRecorder( - currentHash: string, - testHierarchy: string, - testTitle: string -): BaseRecorder { - // Initialize the environment - readEnvFile(); - - if (isRecordMode() || isPlaybackMode()) { - nock = require("nock"); - if (!nock.isActive()) { - // Nock's restore will also remove the http interceptor itself. - // We need to run nock.activate() to re-activate the http interceptor. Without re-activation, nock will not intercept any calls. - nock.activate(); - } - } - - return new NockRecorder(currentHash, testHierarchy, testTitle); -} diff --git a/sdk/test-utils/recorder/src/customConsoleLog.ts b/sdk/test-utils/recorder/src/customConsoleLog.ts deleted file mode 100644 index 97552b0ea80d..000000000000 --- a/sdk/test-utils/recorder/src/customConsoleLog.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -import { isBrowser } from "./utils"; - -// Converting content corresponding to all the console statements -// into (JSON.stringify)-ed content in record mode for browser tests. -// -// In browser, once the content to be recorded is ready, recordings -// are supposed to be sent to the appropriate karma reporter(jsonToFileReporter) -// in order to generate the corresponding recording file. -// The way to do this is by printing the recordings as JSON strings to `console.log()`. -// As a result, the console gets filled with lots of prints while recording. -// -// We solve this issue by -// - disabling the console.logs from karma and -// - by adding a custom console.log() which converts all the console statements into -// console.log() with stringified JSON objects. -// - Handle all the console.logs with stringified JSON objects in karma.conf.js -// as explained below. -// -// Karma.conf.js -// - jsonToFileReporter in karma.conf.js filters the JSON strings in console.logs. -// - Console logs with `.writeFile` property are captured and are written to a file(recordings). -// - The other console statements are captured and printed normally. -// - Example - console.warn("hello"); -> console.log({ warn: "hello" }); -// - Example - console.log("hello"); -> console.log({ log: "hello" }); - -export let consoleLog: (msg: any, ...args: any[]) => void; - -if (isBrowser()) { - consoleLog = window.console.log; -} - -export function setConsoleLogForTesting(func: (msg: any, ...args: any[]) => void) { - consoleLog = func; -} - -/** - * Converts content corresponding to all the console statements into (JSON.stringify)-ed content in record mode for browser tests. - * This allows filtering certain console.logs to generate the recordings for browser tests. - */ -export function customConsoleLog() { - for (const method in window.console) { - if ( - window.console.hasOwnProperty(method) && - typeof (window.console as any)[method] === "function" - ) { - (window.console as any)[method] = function (obj: any) { - try { - if (!JSON.parse(obj).writeFile) { - // If the JSON string doesn't contain `.writeFile` property, - // we wrap the object as a JSON object and apply JSON.stringify() - // Example - console.warn("hello"); -> console.log({ warn: "hello" }); - const newObj: any = {}; - newObj[method] = obj; - consoleLog(JSON.stringify(newObj)); - } else { - // If the JSON strings contain `.writeFile` property, - // use the console.log as it is. - consoleLog(obj); - } - } catch (error) { - // If the object is not a JSON string, the try block fails and - // we wrap the object as a JSON object and apply JSON.stringify() - // (same as the if block in try) - const newObj: any = {}; - newObj[method] = obj; - consoleLog(JSON.stringify(newObj)); - } - }; - } - } -} diff --git a/sdk/test-utils/recorder/src/defaultCustomizations.ts b/sdk/test-utils/recorder/src/defaultCustomizations.ts deleted file mode 100644 index 755637e026e6..000000000000 --- a/sdk/test-utils/recorder/src/defaultCustomizations.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - decodeHexEncodingIfExistsInNockFixture, - handleSingleQuotesInUrlPath, - isBrowser, - maskAccessTokenInBrowserRecording, - maskAccessTokenInNockFixture, - sanitizeScopeUrl, - setDefaultRetryAfterIntervalInNockFixture, -} from "./utils"; - -export const defaultCustomizationsForNodeRecordings = [ - // Decodes "hex" strings in the response from the recorded fixture if any exists. - decodeHexEncodingIfExistsInNockFixture, - // Nock bug: Single quotes in the path of the url are not handled by nock. - // (Link to the bug 🐛: https://github.com/nock/nock/issues/2136) - // The following is the workaround we use in the recorder until nock fixes it. - handleSingleQuotesInUrlPath, - // Masks "access_token"s in the json response from the recording if any exists. - maskAccessTokenInNockFixture, - // Sanitizes the scope url in the request bodies to clean false positives in cred-scan reports - sanitizeScopeUrl, - // Make Retry-After interval to be zero - setDefaultRetryAfterIntervalInNockFixture, -]; - -export const defaultCustomizationsForBrowserRecordings = [ - // Masks "access_token"s in the json response from the recording if any exists. - maskAccessTokenInBrowserRecording, -]; - -/** - * Provides the default customizations that need to be applied on the generated recordings - */ -export const defaultCustomizationsOnRecordings: ((fixture: any) => any)[] = !isBrowser() - ? defaultCustomizationsForNodeRecordings - : defaultCustomizationsForBrowserRecordings; diff --git a/sdk/test-utils/recorder/src/index.ts b/sdk/test-utils/recorder/src/index.ts index 3e8c0435bf1b..55c8baa3d4dd 100644 --- a/sdk/test-utils/recorder/src/index.ts +++ b/sdk/test-utils/recorder/src/index.ts @@ -1,18 +1,15 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. -export { record, Recorder, TestContext, TestContextInterface, TestContextTest } from "./recorder"; +export { Recorder } from "./recorder"; +export { relativeRecordingsPath } from "./utils/relativePathCalculator"; export { - env, - delay, + SanitizerOptions, + RecorderStartOptions, + isLiveMode, isPlaybackMode, isRecordMode, - isLiveMode, - isSoftRecordMode, - RecorderEnvironmentSetup, -} from "./utils"; -export { pluginForIdentitySDK, pluginForClientSecretCredentialTests } from "./utils/msalAuth.node"; -export { jsonRecordingFilterFunction } from "./basekarma.conf"; -export { generateTestRecordingFilePath } from "./utils/recordingPath"; -export { findRecordingsFolderPath } from "./utils/recordings"; -export { setEnvironmentVariables } from "./baseRecorder"; + assertEnvironmentVariable, +} from "./utils/utils"; +export { env } from "./utils/env"; +export { delay } from "./utils/delay"; diff --git a/sdk/test-utils/recorder-new/src/matcher.ts b/sdk/test-utils/recorder/src/matcher.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/matcher.ts rename to sdk/test-utils/recorder/src/matcher.ts diff --git a/sdk/test-utils/recorder/src/recorder.ts b/sdk/test-utils/recorder/src/recorder.ts index 8dfb17bb7e53..ece194d10ea5 100644 --- a/sdk/test-utils/recorder/src/recorder.ts +++ b/sdk/test-utils/recorder/src/recorder.ts @@ -1,235 +1,341 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. import { - getUniqueName, - isBrowser, - isRecordMode, + createDefaultHttpClient, + createPipelineRequest, + HttpClient, + HttpMethods, + Pipeline, + PipelinePolicy, + PipelineRequest, + PipelineResponse, + SendRequest, +} from "@azure/core-rest-pipeline"; +import { + ensureExistence, + getTestMode, + isLiveMode, isPlaybackMode, - RecorderEnvironmentSetup, - env, - isSoftRecordMode, - testHasChanged, - stripNewLines, -} from "./utils"; -import { setEnvironmentVariables } from "./baseRecorder"; -import { createRecorder } from "./createRecorder"; -import MD5 from "md5"; + isRecordMode, + once, + RecorderError, + RecorderStartOptions, + RecordingStateManager, +} from "./utils/utils"; +import { Test } from "mocha"; +import { sessionFilePath } from "./utils/sessionFilePath"; +import { SanitizerOptions } from "./utils/utils"; +import { paths } from "./utils/paths"; +import { Sanitizer } from "./sanitizer"; +import { handleEnvSetup } from "./utils/envSetupForPlayback"; +import { Matcher, setMatcher } from "./matcher"; +import { + DefaultHttpClient, + HttpClient as HttpClientCoreV1, + HttpOperationResponse, + WebResource, + WebResourceLike, +} from "@azure/core-http"; /** - * @export - * An interface that allows recording and playback capabilities for the tests in Azure TS SDKs. + * This client manages the recorder life cycle and interacts with the proxy-tool to do the recording, + * eventually save them in record mode and playing them back in playback mode. + * + * For Core V2 SDKs, + * - Use the `configureClient` method to add recorder policy on your client. + * For core-v1 SDKs, + * - Use the `configureClientOptionsCoreV1` method to modify the httpClient on your client options + * + * Other than configuring your clients, use `start`, `stop`, `addSanitizers` methods to use the recorder. */ -export interface Recorder { +export class Recorder { + private url = "http://localhost:5000"; + public recordingId?: string; + private stateManager = new RecordingStateManager(); + private httpClient?: HttpClient; + private sessionFile?: string; + private sanitizer?: Sanitizer; + private variables: Record; + + constructor(private testContext?: Test | undefined) { + if (isRecordMode() || isPlaybackMode()) { + if (this.testContext) { + this.sessionFile = sessionFilePath(this.testContext); + this.httpClient = createDefaultHttpClient(); + } else { + throw new Error( + "Unable to determine the recording file path, testContext provided is not defined." + ); + } + this.sanitizer = new Sanitizer(this.url, this.httpClient); + } + this.variables = {}; + } + /** - * `stop()` method is supposed to be called at the end of the test, stops and saves the recording in the "record" mode. - * Has no effect in the playback/live test modes. + * redirectRequest updates the request in record and playback modes to hit the proxy-tool with appropriate headers. + * Works for both core-v1 and core-v2 + * + * - WebResource -> core-v1 + * - PipelineRequest -> core-v2 (recorderHttpPolicy calls this method on the request to modify and hit the proxy-tool with appropriate headers.) */ - stop(): Promise; + private redirectRequest(request: WebResource | PipelineRequest): void { + if (!isLiveMode() && !request.headers.get("x-recording-id")) { + if (this.recordingId === undefined) { + throw new RecorderError("Recording ID must be defined to redirect a request"); + } + + request.headers.set("x-recording-id", this.recordingId); + request.headers.set("x-recording-mode", getTestMode()); + + const upstreamUrl = new URL(request.url); + const redirectedUrl = new URL(request.url); + const providedUrl = new URL(this.url); + + redirectedUrl.host = providedUrl.host; + redirectedUrl.port = providedUrl.port; + redirectedUrl.protocol = providedUrl.protocol; + request.headers.set("x-recording-upstream-base-uri", upstreamUrl.toString()); + request.url = redirectedUrl.toString(); + + if (!(request instanceof WebResource)) { + // for core-v2 + request.allowInsecureConnection = true; + } + } + } + /** - * `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes respectively. - * If the `{runtime}` is `{undefined}`, the test will be skipped in both the node and browser runtimes. - * Has no effect in the live test mode. + * addSanitizers adds the sanitizers for the current recording which will be applied on it before being saved. + * + * Takes SanitizerOptions as the input, passes on to the proxy-tool. */ - skip(runtime?: "node" | "browser", reason?: string): void; + async addSanitizers(options: SanitizerOptions): Promise { + // If check needed because we only sanitize when the recording is being generated, and we need a recording to apply the sanitizers on. + if (isRecordMode() && ensureExistence(this.sanitizer, "this.sanitizer")) { + return this.sanitizer.addSanitizers(options); + } + } + /** - * In live test mode, random string is generated, appended to `prefix` and returned. - * - * In record mode, random string is generated, appended to `prefix` and returned, and is saved in the recordings by assigning the `label`. - * - * In playback mode, the string in the recordings associated to the `label` is returned. - * - * If the `label`(optional param) is not provided, `prefix` is used as the `label`. + * Call this method to ping the proxy-tool with a start request + * signalling to start recording in the record mode + * or to start playing back in the playback mode. * - * @param {string} [prefix] Prefix for the generated random string - * @param {string} [label] (Optional) Label to be assigned for the generated string [necessary for playing back the recordings]. If label is not provided, prefix is assumed as the label - * @returns {string} + * Takes RecorderStartOptions as the input, which will get used in record and playback modes. + * Includes + * - envSetupForPlayback - The key-value pairs will be used as the environment variables in playback mode. If the env variables are present in the recordings as plain strings, they will be replaced with the provided values. + * - sanitizerOptions - Generated recordings are updated by the "proxy-tool" based on the sanitizer options provided. */ - getUniqueName: (prefix: string, label?: string) => string; + async start(options: RecorderStartOptions): Promise { + if (isLiveMode()) return; + this.stateManager.state = "started"; + if (this.recordingId === undefined) { + const startUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${ + paths.start + }`; + const req = this._createRecordingRequest(startUri); + + if (ensureExistence(this.httpClient, "TestProxyHttpClient.httpClient")) { + const rsp = await this.httpClient.sendRequest({ + ...req, + allowInsecureConnection: true, + }); + if (rsp.status !== 200) { + throw new RecorderError("Start request failed."); + } + const id = rsp.headers.get("x-recording-id"); + if (!id) { + throw new RecorderError("No recording ID returned for a successful start request."); + } + this.recordingId = id; + if (isPlaybackMode()) { + this.variables = rsp.bodyAsText ? JSON.parse(rsp.bodyAsText) : {}; + } + if (ensureExistence(this.sanitizer, "TestProxyHttpClient.sanitizer")) { + // Setting the recordingId in the sanitizer, + // the sanitizers added will take the recording id and only be part of the current test + this.sanitizer.setRecordingId(this.recordingId); + await handleEnvSetup(options.envSetupForPlayback, this.sanitizer); + } + // Sanitizers to be added only in record mode + if (isRecordMode() && options.sanitizerOptions) { + // Makes a call to the proxy-tool to add the sanitizers for the current recording id + // Recordings of the current test will be influenced by the sanitizers that are being added here + await this.addSanitizers(options.sanitizerOptions); + } + } + } + } + /** - * In live test mode, `new Date();` is returned. - * - * In record mode, `new Date();` is returned, and is saved in the recordings by assigning the `label`. - * - * In playback mode, the date in the recordings associated to the `label` is returned. - * - * @param {string} [label] Label to be assigned for the date [necessary for playing back the recordings] - * @returns {Date} + * Call this method to ping the proxy-tool with a stop request, this helps saving the recording in record mode. */ - newDate: (label: string) => Date; -} + async stop(): Promise { + if (isLiveMode()) return; + this.stateManager.state = "stopped"; + if (this.recordingId !== undefined) { + const stopUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${paths.stop}`; + const req = this._createRecordingRequest(stopUri); + req.headers.set("x-recording-save", "true"); -/** - * An interface representing Mocha's Runnable - */ -export interface TestContextTest { - parent?: { - fullTitle: () => string; - }; - fn?: () => any; - title?: string; - type?: string; - file: string; -} + if (isRecordMode()) { + req.headers.set("Content-Type", "application/json"); + req.body = JSON.stringify(this.variables); + } + if (ensureExistence(this.httpClient, "TestProxyHttpClient.httpClient")) { + const rsp = await this.httpClient.sendRequest({ + ...req, + allowInsecureConnection: true, + }); + if (rsp.status !== 200) { + throw new RecorderError("Stop request failed."); + } + } + } else { + throw new RecorderError("Bad state, recordingId is not defined when called stop."); + } + } -/** - * An interface representing only the public properties of Mocha.Context - */ -export interface TestContextInterface { - test?: TestContextTest; - currentTest?: TestContextTest; - skip: () => void; -} + /** + * Sets the matcher for the current recording to the matcher specified. + */ + async setMatcher(matcher: Matcher): Promise { + if (isPlaybackMode()) { + if (!this.httpClient) { + throw new RecorderError("httpClient should be defined in playback mode"); + } -/** - * A simple class that lets us make fake contexts for tests. - */ -export class TestContext implements TestContextInterface { - public test: TestContextTest | undefined; - public currentTest: TestContextTest | undefined; - public skip() {} - - constructor(test: TestContextTest, currentTest: TestContextTest) { - this.test = test; - this.currentTest = currentTest; + await setMatcher(this.url, this.httpClient, matcher, this.recordingId); + } } -} -/** - * - * @param {Mocha.Context} [testContext] - * @returns {Recorder} - */ -export function record( - testContext: TestContextInterface | Mocha.Context, - recorderEnvironmentSetup: RecorderEnvironmentSetup -): Recorder { - let testHierarchy: string; - let testTitle: string; - - // In a hook ("before all" or "before each"), testContext.test points to the hook, while testContext.currentTest - // points to the individual test that will be run next. A "before all" hook is run once before all tests, - // so the hook itself should be used to identify recordings. However, a "before each" hook is run once before each - // test, so the individual test should be used instead. - if ((testContext as any).test!.type == "hook" && testContext.test!.title!.includes("each")) { - testHierarchy = testContext.currentTest!.parent!.fullTitle(); - testTitle = testContext.currentTest!.title!; - } else { - testHierarchy = testContext.test!.parent!.fullTitle(); - testTitle = testContext.test!.title!; + /** + * Adds the recording file and the recording id headers to the requests that are sent to the proxy tool. + * These are required to appropriately save the recordings in the record mode and picking them up in playback. + */ + private _createRecordingRequest(url: string, method: HttpMethods = "POST") { + const req = createPipelineRequest({ url, method }); + if (ensureExistence(this.sessionFile, "sessionFile")) { + req.headers.set("x-recording-file", this.sessionFile); + } + if (this.recordingId !== undefined) { + req.headers.set("x-recording-id", this.recordingId); + } + return req; } - const stringTest = testContext.currentTest!.fn!.toString(); - // We strip new lines to make it easier for the browser builds to make a predictable output after small changes on the files. - const currentHash = MD5(stripNewLines(stringTest)); - const testAbsolutePath = testContext.currentTest!.file!; - - if ( - isSoftRecordMode() && - !testHasChanged(testHierarchy, testTitle, testAbsolutePath, currentHash) - ) { - testContext.test!.title = `${testContext.test!.title} (Test unchanged since last recording)`; - testContext.skip(); + /** + * For core-v2 - libraries depending on core-rest-pipeline. + * This method adds the recording policy to the input client's pipeline. + * + * Helps in redirecting the requests to the proxy tool instead of directly going to the service. + */ + public configureClient(client: { pipeline: Pipeline }): void { + if (isLiveMode()) return; + client.pipeline.addPolicy(this.recorderHttpPolicy()); } - const recorder = createRecorder(currentHash, testHierarchy, testTitle); - - if (isRecordMode()) { - // If TEST_MODE=record, invokes the recorder, hits the live-service, - // expects that the appropriate environment variables are present - recorder.record(recorderEnvironmentSetup); - } else if (isPlaybackMode()) { - // If TEST_MODE=playback, - // 1. sets up the ENV variables - // 2. invokes the recorder, play the existing test recording. - setEnvironmentVariables(env, recorderEnvironmentSetup.replaceableVariables); - recorder.playback(recorderEnvironmentSetup, testAbsolutePath); + /** + * For core-v1 - libraries depending on core-http. + * This method adds the custom httpClient to the client options. + * + * Helps in redirecting the requests to the proxy tool instead of directly going to the service. + */ + public configureClientOptionsCoreV1< + T extends { + httpClient?: HttpClientCoreV1; + } + >(options: T): T { + if (isLiveMode()) return options; + return { ...options, httpClient: once(() => this.createHttpClientCoreV1())() }; } - // If TEST_MODE=live, hits the live-service and no recordings are generated. - return { - stop: async function () { - // We check wether we're on record or playback inside of the recorder's stop method. - if (recorder) { - await recorder.stop(); - } - }, - /** - * `{recorder.skip("node")}` and `{recorder.skip("browser")}` will skip the test in node.js and browser runtimes respectively. - * `{recorder.skip()}` If the `{runtime}` is undefined, the test will be skipped in both the node and browser runtimes. - * @param runtime Can either be `"node"` or `"browser"` or `undefined` - * @param reason Reason for skipping the test - */ - skip: function (runtime?: "node" | "browser", reason?: string): void { - if (!reason) reason = "Reason to skip the test is not specified"; - // 1. skipping the test only in node - // 2. skipping the test only in browser - // 3. skipping the test in both the node and browser runtimes - if ( - (runtime === "node" && !isBrowser()) || - (runtime === "browser" && isBrowser()) || - !runtime - ) { - // record/playback modes - // - test title is updated with the given reason - // - test is skipped - if (isRecordMode() || isPlaybackMode()) { - testContext.test!.title = testContext.test!.title + ` (${reason})`; - testContext.skip(); - } else { - // live mode - no effect - } - } - }, - getUniqueName: function (prefix: string, label?: string): string { - let name: string; - if (!label) { - label = prefix; - } - if (isRecordMode()) { - name = getUniqueName(prefix); - if (recorder.uniqueTestInfo["uniqueName"][label]) { - throw new Error( - `getUniqueName: function(prefix: string, label?: string), - Label "${label}" is already taken, - please provide a different prefix OR give a new label while keeping the same prefix "${prefix}".` - ); - } else { - recorder.uniqueTestInfo["uniqueName"][label] = name; - } - } else if (isPlaybackMode()) { - if (recorder.uniqueTestInfo["uniqueName"]) { - name = recorder.uniqueTestInfo["uniqueName"][label]; - } else { - name = (recorder.uniqueTestInfo as any)[label]; - } - } else { - name = getUniqueName(prefix); + /** + * recorderHttpPolicy that can be added as a pipeline policy for any of the core-v2 SDKs(SDKs depending on core-rest-pipeline) + */ + private recorderHttpPolicy(): PipelinePolicy { + return { + name: "recording policy", + sendRequest: async ( + request: PipelineRequest, + next: SendRequest + ): Promise => { + this.redirectRequest(request); + return next(request); + }, + }; + } + + /** + * Creates a client that supports redirecting the requests to the proxy-tool. + * Needed for the core-v1 SDKs(SDKs depending on core-http) + */ + private createHttpClientCoreV1(): HttpClientCoreV1 { + const client = new DefaultHttpClient(); + return { + sendRequest: async (request: WebResourceLike): Promise => { + this.redirectRequest(request); + return client.sendRequest(request); + }, + }; + } + + /** + * Register a variable to be stored with the recording. The behavior of this function + * depends on whether the recorder is in record/live mode or in playback mode. + * + * In record or live mode, the function will store the value provided with the recording + * as a variable and return that value. + * + * In playback mode, the function will fetch the value from the variables stored as part of the recording + * and return the retrieved variable, throwing an error if it is not found. + * + * @param name - the name of the variable to be stored in the recording + * @param value - the value of the variable. In record mode, this value will be stored + * with the recording; in playback mode, this parameter is ignored. + * @returns in record and live mode, `value` without modification. + * In playback mode, the variable's value from the recording. + */ + variable(name: string, value: string): string; + + /** + * Convenience overload in case you want to reference the same variable multiple times in a test without + * declaring a variable of your own, or if you know you're in playback mode and don't want to specify an + * initial value. Throws an error in record and live mode if a call to variable(name, value) has not been + * made previously. + * + * @param name - the name of the variable stored in the recording + * @returns the value of the variable -- in record and live mode, the value set + * in a previous call to variable(name, value). In playback mode, the variable's + * value from the recording. + */ + variable(name: string): string; + + variable(name: string, value: string | undefined = undefined): string { + if (isPlaybackMode()) { + const recordedValue = this.variables[name]; + + if (recordedValue === undefined) { + throw new RecorderError( + `Tried to access a variable in playback that was not set in recording: ${name}` + ); } - return name; - }, - newDate: function (label: string): Date { - let date: Date; - if (isRecordMode()) { - date = new Date(); - if (recorder.uniqueTestInfo["newDate"][label]) { - throw new Error( - `newDate: function(label: string), - Label "${label}" is already taken, please provide a new label.` - ); - } else { - recorder.uniqueTestInfo["newDate"][label] = date.toISOString(); - } - } else if (isPlaybackMode()) { - if (recorder.uniqueTestInfo["newDate"]) { - date = new Date(recorder.uniqueTestInfo["newDate"][label]); - } else { - date = new Date((recorder.uniqueTestInfo as any)[label]); - } - } else { - date = new Date(); + + return recordedValue; + } + + if (!this.variables[name]) { + if (value === undefined) { + throw new RecorderError( + `Tried to access uninitialized variable: ${name}. You must initialize it with a value before using it.` + ); } - return date; - }, - }; + + this.variables[name] = value; + } + + return this.variables[name]; + } } diff --git a/sdk/test-utils/recorder-new/src/sanitizer.ts b/sdk/test-utils/recorder/src/sanitizer.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/sanitizer.ts rename to sdk/test-utils/recorder/src/sanitizer.ts diff --git a/sdk/test-utils/recorder-new/src/utils/connectionStringHelpers.ts b/sdk/test-utils/recorder/src/utils/connectionStringHelpers.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/connectionStringHelpers.ts rename to sdk/test-utils/recorder/src/utils/connectionStringHelpers.ts diff --git a/sdk/test-utils/recorder-new/src/utils/delay.ts b/sdk/test-utils/recorder/src/utils/delay.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/delay.ts rename to sdk/test-utils/recorder/src/utils/delay.ts diff --git a/sdk/test-utils/recorder-new/src/utils/env.browser.ts b/sdk/test-utils/recorder/src/utils/env.browser.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/env.browser.ts rename to sdk/test-utils/recorder/src/utils/env.browser.ts diff --git a/sdk/test-utils/recorder-new/src/utils/env.ts b/sdk/test-utils/recorder/src/utils/env.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/env.ts rename to sdk/test-utils/recorder/src/utils/env.ts diff --git a/sdk/test-utils/recorder-new/src/utils/envSetupForPlayback.ts b/sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/envSetupForPlayback.ts rename to sdk/test-utils/recorder/src/utils/envSetupForPlayback.ts diff --git a/sdk/test-utils/recorder-new/src/utils/filePathGenerator.ts b/sdk/test-utils/recorder/src/utils/filePathGenerator.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/filePathGenerator.ts rename to sdk/test-utils/recorder/src/utils/filePathGenerator.ts diff --git a/sdk/test-utils/recorder/src/utils/index.ts b/sdk/test-utils/recorder/src/utils/index.ts deleted file mode 100644 index c896bd2d30f4..000000000000 --- a/sdk/test-utils/recorder/src/utils/index.ts +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { URLBuilder } from "@azure/core-http"; - -export { testHasChanged } from "./recordings"; - -export { generateTestRecordingFilePath } from "./recordingPath"; - -export { windowLens } from "./windowLens"; - -export interface TestInfo { - uniqueName: { [x: string]: string }; - newDate: { [x: string]: string }; -} - -export type ReplacementMap = Map; -/** - * Interface to setup environment necessary for the test run. - * - * @export - * @interface RecorderEnvironmentSetup - */ -export interface RecorderEnvironmentSetup { - /** - * Used in record and playback modes - * - * 1. The key-value pairs will be used as the environment variables in playback mode. - * 2. If the env variables are present in the recordings as plain strings, they will be replaced with the provided values in record mode - * - * @type {{ [ENV_VAR: string]: string }} - * @memberof RecorderEnvironmentSetup - */ - replaceableVariables: { [ENV_VAR: string]: string }; - /** - * Used in record mode - * - * Array of callback functions provided to customize the generated recordings in record mode - * - * Example with one callback function - - * `sig` param of SAS Token is being filtered here from the recordings.. - * [ (recording: string): string => recording.replace(new RegExp(env.ACCOUNT_SAS.match("(.*)&sig=(.*)")[2], "g"), "aaaaa") ] - * - * @type {Array<(content: string) => string>} - * @memberof RecorderEnvironmentSetup - */ - customizationsOnRecordings: Array<(content: string) => string>; - /** - * Used in record and playback modes - * - * Array of query parameters provided will be filtered from the requests - * - * @type {Array} - * @memberof RecorderEnvironmentSetup - */ - queryParametersToSkip: Array; - /** - * Used in playback mode - * - * [Only in Node] - * - * Callback that is run at the time of loading the recording. - * Introduced only to handle special cases of identity SDK, not meant for the SDK developers to use. - */ - onLoadCallbackForPlayback?: () => void; -} - -export const env = isBrowser() ? (window as any).__env__ : process.env; - -export function isRecordMode() { - // It should be safe to assume that these two can be considered being in record mode. - // For more specific distinctions, one can use isSoftRecordMode. - return env.TEST_MODE === "record" || isSoftRecordMode(); -} - -export function isSoftRecordMode() { - return env.TEST_MODE === "soft-record"; -} - -export function isLiveMode() { - return env.TEST_MODE === "live"; -} - -export function isPlaybackMode() { - return !isRecordMode() && !isLiveMode(); -} - -/** - * Encodes a string as a URI component, but also taking in consideration the RFC 3986 specification. - * JavaScript's encodeURIComponent method doesn't take in consideration the characters: !, ', (, ), * - * @param str The string that needs to be encoded. - */ -export function encodeRFC3986(str: string): string { - return encodeURIComponent(str).replace( - /[!'()*]/g, - (x) => `%${x.charCodeAt(0).toString(16).toUpperCase()}` - ); -} - -/** - * Escapes all of the valid RegExp characters of a string. - * @param str The string that needs to be escaped. - */ -export function escapeRegExp(str: string): string { - return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); -} - -/** - * Replaces all occurrences of a pattern in a string with a given replacement. - * @param string Target of the replacements. - * @param pattern String used to match and find what to replace. - * @param replacement Replacement of the matched string. - */ -function replaceAll(string: string, pattern: string, replacement: string) { - return string.replace(new RegExp(escapeRegExp(pattern), "g"), replacement); -} - -/** - * Looks for the environment variables based on the keys of the given map, - * then replaces the values found with each value from the same map. - * @param replacements A map of string keys and string values. - * @param content The content that has the text to be replaced. - */ -export function applyReplacementMap( - env: NodeJS.ProcessEnv, - replacements: ReplacementMap, - content: string -): string { - let updated = content; - replacements.forEach((replacement: string, key: string) => { - if (env[key]) { - const value = env[key]!; - const [encodedValue, encodedReplacement] = [value, replacement].map(encodeRFC3986); - if (value !== encodedValue || replacement !== encodedReplacement) { - updated = replaceAll(updated, encodedValue, encodedReplacement); - } - updated = replaceAll(updated, value, replacement); - if ( - value.startsWith("http") && - replacement.startsWith("http") && - URLBuilder.parse(value).getHost() - ) { - // If an ENV variable and its replacement start with `http` with a valid hostname, replace the hostname - // with the one provided in the replacement. This has no effect incase the URI is already replaced in the previous step. - updated = replaceAll( - updated, - URLBuilder.parse(value).getHost()!, - URLBuilder.parse(replacement).getHost()! - ); - } - } - }); - return updated; -} - -/** - * Passes the given content as the parameter to the first function of the array, - * then reduces the remaining functions of the array with the result of the previous function. - * @param replacements An array of replacement functions. - * @param content The input used to apply the replacements. - */ -export function applyReplacementFunctions( - replacements: Array<(content: string) => string>, - content: string -): string { - let updated = content; - for (const map of replacements) { - updated = map(updated); - } - return updated; -} - -/** - * Method to avoid unintended/accidental occurrences of secrets in the recordings. - * - * Takes in the content(recording), replaceableVariables and replacements(callback functions). - * Returns the recording after the updates as per the provided replaceableVariables, and the replacement functions. - * @export - * @param {string} content - * @param { [ENV_VAR: string]: string } replaceableVariables - * @param {ReplacementFunctions} replacements - */ -export function filterSecretsFromStrings( - content: string, - replaceableVariables: { [ENV_VAR: string]: string }, - customizations: Array<(content: string) => string> -) { - const result = applyReplacementMap(env, new Map(Object.entries(replaceableVariables)), content); - return applyReplacementFunctions(customizations, result); -} - -/** - * Method to avoid unintended/accidental occurrences of secrets in the recordings. - * - * Takes in the content(recording), replaceableVariables and replacements(callback functions). - * Returns the recording after the updates as per the provided replaceableVariables, and the replacement functions. - * @export - * @param {any} content - * @param { [ENV_VAR: string]: string } replaceableVariables - * @param {ReplacementFunctions} replacements - */ -export function filterSecretsRecursivelyFromJSON( - content: any, - replaceableVariables: { [ENV_VAR: string]: string }, - customizations: Array<(content: string) => string> -) { - let updatedContent = content; - if (typeof updatedContent === "string") { - // strings - updatedContent = filterSecretsFromStrings(updatedContent, replaceableVariables, customizations); - } else if (Array.isArray(updatedContent)) { - // arrays - updatedContent = updatedContent.map((item) => - filterSecretsRecursivelyFromJSON(item, replaceableVariables, customizations) - ); - } else { - // json objects - for (const i of Object.keys(updatedContent)) { - if (typeof updatedContent[i] === "string") { - updatedContent[i] = filterSecretsFromStrings( - updatedContent[i], - replaceableVariables, - customizations - ); - } else if (updatedContent[i] !== null && typeof updatedContent[i] === "object") { - updatedContent[i] = filterSecretsRecursivelyFromJSON( - updatedContent[i], - replaceableVariables, - customizations - ); - } - } - // last resort to capture any left over secrets - updatedContent = JSON.parse( - filterSecretsFromStrings(JSON.stringify(updatedContent), replaceableVariables, customizations) - ); - } - return updatedContent; -} - -/** - * @returns {Promise} - */ -export async function blobToString(blob: Blob): Promise { - const fileReader = new FileReader(); - return new Promise((resolve, reject) => { - fileReader.onloadend = (ev: any) => { - resolve(ev.target!.result); - }; - fileReader.onerror = reject; - fileReader.readAsText(blob); - }); -} - -/** - * String.prototype.padStart() - * - * @param {string} currentString - * @param {number} targetLength - * @param {string} [padString=" "] - * @returns {string} - */ -function padStart(currentString: string, targetLength: number, padString: string = " "): string { - // TS doesn't know this code needs to run downlevel sometimes. - // @ts-expect-error - if (String.prototype.padStart) { - return currentString.padStart(targetLength, padString); - } - - padString = padString || " "; - if (currentString.length > targetLength) { - return currentString; - } else { - targetLength = targetLength - currentString.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); - } - return padString.slice(0, targetLength) + currentString; - } -} - -/** - * @returns {string} - */ -export function getUniqueName(prefix: string): string { - return `${prefix}${new Date().getTime()}${padStart( - Math.floor(Math.random() * 10000).toString(), - 5, - "00000" - )}`; -} - -/** - * @returns {boolean} - */ -export function isBrowser(): boolean { - return typeof window !== "undefined"; -} - -/** - * Usage - `await delay()` - * This `delay` has no effect if the `TEST_MODE` is `"playback"`. - * If the `TEST_MODE` is not `"playback"`, `delay` is a wrapper for setTimeout that resolves a promise after t milliseconds. - * - * @param {number} milliseconds The number of milliseconds to be delayed. - * @returns {Promise} Resolved promise - */ -export function delay(milliseconds: number): Promise | null { - return isPlaybackMode() ? null : new Promise((resolve) => setTimeout(resolve, milliseconds)); -} - -/** - * Usage - `parseUrl()` - * - * @param {string} url The URL you want to parse - * @returns {any} An object with the url without parameters, and a query object with all the query properties. - */ -export function parseUrl(url: string): any { - const [cleanUrl, ...queryParts] = url.split(/[?&]/); - const query = queryParts.reduce((query: { [key: string]: any }, part) => { - const [name, value] = part.split(/=/); - query[name] = decodeURIComponent(value.replace(/\+/g, " ")); - return query; - }, {}); - return { - url: cleanUrl, - query, - }; -} - -/** - * Removes new lines from a string. - * @param str String with new lines - */ -export function stripNewLines(str: string): string { - return str.replace(/(\r\n|\n|\r)/gm, ""); -} - -/** - * Returns true if the given string is a hexadecimal value - * - * @export - * @param {string} value - * @returns {boolean} - */ -export function isHex(value: string): boolean { - if (/^[0-9a-fA-F]+$/.test(value)) { - return true; - } - return false; -} - -/** - * Meant for node recordings only! - * Returns true if the content-type in the `fixture` matches with - * any of the strings provided in the expected content types. - * - * @private - * @param {string} fixture - * @param {string} expectedContentTypes - * @returns {boolean} - */ -export function isContentTypeInNockFixture( - fixture: string, - expectedContentTypes: string[] -): boolean { - for (const contentType of expectedContentTypes) { - if (fixture.replace(/(\r\n|\n|\r|\s)/gm, "").includes(`'Content-Type','${contentType}'`)) { - return true; - } - } - return false; -} - -/** - * Meant for browser recordings only! - * - * Returns true if the content-type in the `fixture` matches with - * any of the strings provided in the expected content types. - * - * @private - */ -export function isContentTypeInBrowserRecording( - fixture: any, - expectedContentTypes: string[] -): boolean { - for (const contentType of expectedContentTypes) { - if (fixture.responseHeaders?.["content-type"]?.replace(/\s/, "") === contentType) { - return true; - } - } - return false; -} - -/** - * Meant for node recordings only! - * Decodes "hex" strings in the response from the recorded fixture if any exists. - * For example, the following part of the nock fixture/recording would be updated. - * from `.reply(200, "4f626a01", [` - * to `.reply(200, Buffer.from("4f626a01", "hex"), [` - * - * @private - * @param {string} fixture - */ -export function decodeHexEncodingIfExistsInNockFixture(fixture: string): string { - // Replaces only if the content-type is binary(Currently, "avro/binary" is considered) - if (!isBrowser() && isContentTypeInNockFixture(fixture, binaryContentTypes)) { - // Matching with 200-206 status codes (Successful codes) - const matches = fixture.match(/\.reply\((.*), "(.*)", .*/); - if (matches) { - const statusCode = Number(matches[1]); - // Success status codes >=200 & < 300 - if (statusCode >= 200 && statusCode < 300 && isHex(matches[2])) { - fixture = fixture.replace(`"${matches[2]}"`, `Buffer.from("${matches[2]}", "hex")`); - } - } - } - return fixture; -} - -/** - * Meant for node recordings only! - * - * Single quotes can be present in the url path though unusual. - * When the url path has single quotes, the fixture generated by "nock" is incorrect - * since it doesn't consider the case. (Nock Bug 🐛: https://github.com/nock/nock/issues/2136) - * Examples below: - * .delete('/Tables('node')') - * .get('/Tables('node')') - * .post('/Tables('node')', {"TableName":"testTablenode"}) - * - * The above problem results in invalid recordings. - * - * To avoid this problem, we replace the single quotes surrounding the url-path in the recording - * with backticks(`). This would fix the invalid recordings. - * - * @private - * @param {string} fixture - */ -export function handleSingleQuotesInUrlPath(fixture: string): string { - let updatedFixture = fixture; - if (!isBrowser()) { - // Fixtures would contain url-path as shown below - // Case-1: .{method}('{url-path}') - // Case-2: .{method}('{url-path}', {json-object}) - // Examples: - // .get('/Tables('node')') - // .post('/Tables('node')', {"TableName":"node"}) - // .post('/Tables('node')', "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1')") - - // Case-1 - const matches = fixture.match(/\.(get|put|post|delete)\(\'(.*)\'\)\n\s*(.query\(true\))/); - if (matches && matches[2]) { - const match = matches[2]; // Extracted url-path - // If the url-path contains a single quote - if (match.search("'") !== -1) { - // Replace the occurrence of surrounding single quotes with backticks - updatedFixture = fixture.replace("'" + match + "'", "`" + match + "`"); - } - } - - // Case-2 - // TODO: To handle the presence of request bodies - } - return updatedFixture; -} - -/** - * Meant for node recordings only! - * - * Manipulates the `retry-after` header to have "0" value so that playback tests run faster. - */ -export function setDefaultRetryAfterIntervalInNockFixture(fixture: string) { - if (isBrowser()) { - throw new Error( - `"setDefaultRetryAfterIntervalInNockFixture" method is not meant to be used in the browsers` - ); - } - const matches = fixture.match(/'Retry-After',\n\s*'([0-9]*)',\n/); - if (!matches) { - return fixture; - } - return fixture.replace(matches[0], `'Retry-After',\n '0',\n`); -} - -/** - * Meant for node recordings only! - * - * Masks access tokens in the json response from nock fixtures. - * For example, the following part of the nock fixture/recording would be updated. - * from `.reply(200, {"token_type":"Bearer","expires_in":86399,"access_token":"e6z-9_g"}, [` - * to `.reply(200, {"token_type":"Bearer","expires_in":86399,"access_token":"access_token"}, [` - * - * @param {string} fixture - */ -export function maskAccessTokenInNockFixture(fixture: string): string { - if (isBrowser()) { - throw new Error( - `"maskAccessTokenInNockFixture" method is not meant to be used in the browsers` - ); - } - // Replaces only if the content-type is json - if (isContentTypeInNockFixture(fixture, jsonContentTypes)) { - // Matches the nock's reply from the fixture such as below - // `.reply(200, {"token_type":"Bearer","expires_in":86399,"access_token":"e6z-9_g"}, [` - const matches = fixture.match(/\.reply\((.*), (.*), .*/); - if (matches && matches[2]) { - return fixture.replace(/"access_token"\s*:\s*"(.+?)"/, `"access_token":"access_token"`); - } - } - return fixture; -} - -/** - * Meant for browser recordings only! - * - * Masks access tokens in the json recordings of the browser. - * For example, the following part of the nock fixture/recording would be updated. - * from `"response": "{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}",` - * to `"response": "{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}",` - * - */ -export function maskAccessTokenInBrowserRecording(fixtures: string): string { - if (!isBrowser()) { - throw new Error( - `"maskAccessTokenInBrowserRecording" method is meant to be used in the browsers only` - ); - } - - // fixture is supposed to be an array of JSON recordings at this point - for (let i = 0; i < fixtures.length; i++) { - // Replaces only if the content-type is json - if (isContentTypeInBrowserRecording(fixtures[i], jsonContentTypes)) { - if ((fixtures[i] as any).response) { - try { - const parsedResponse = JSON.parse((fixtures[i] as any).response); - if (parsedResponse["access_token"]) { - parsedResponse["access_token"] = "access_token"; - (fixtures[i] as any).response = JSON.stringify(parsedResponse); - } - } catch (_) { - // Skip for non-JSON parsable content - } - } - } - } - return fixtures; -} - -/** - * Sanitizes the scope url in the request bodies [Meant for cleaning the false positives in cred-scan reports] - */ -export function sanitizeScopeUrl(body: string) { - return body.replace(/scope=https%3A%2F%2F[^&"]*/g, "scope=https%3A%2F%2Fsanitized%2F"); -} - -/** - * List of binary content types. - * Currently, "avro/binary" is the only one present. - */ -export const binaryContentTypes = ["avro/binary"]; - -/** - * List of json content types. - * // TODO: Add anything else to the list?? - */ -export const jsonContentTypes = ["application/json;charset=utf-8"]; diff --git a/sdk/test-utils/recorder/src/utils/msalAuth.node.ts b/sdk/test-utils/recorder/src/utils/msalAuth.node.ts deleted file mode 100644 index c87026a7ba8c..000000000000 --- a/sdk/test-utils/recorder/src/utils/msalAuth.node.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { env } from "."; - -export type NockType = typeof import("nock"); -let nock: NockType; - -/** - * msal auth requests where an access_token is returned are dynamic - * - client_request_id is generated everytime we make a new request which makes the requestBody dynamic - * - requestBody also has more properties such as the type/make of the OS which makes the requests different in different machines - * - * This method provides the fake token during playback by not matching the request body at all - */ -export function mockMsalAuth(importNock: NockType, plugin: (() => void) | undefined) { - nock = importNock; - if (!plugin) { - pluginForClientSecretCredentialTests(); - } else { - plugin(); - } -} - -/** - * This method is enough for any test that uses ClientSecretCredential from identity to provide the fake access_token (as per the current msal configurations). - * - * If msal ever changes its behavior, this needs to change - for example path/url/reply have to be updated accordingly - */ -export const pluginForClientSecretCredentialTests = (tenantId: string = env.AZURE_TENANT_ID) => { - if (tenantId) { - nock("https://login.microsoftonline.com:443") - .persist() - .post(`/${tenantId}/oauth2/v2.0/token`) - .reply(200, { - token_type: "Bearer", - expires_in: 86399, - ext_expires_in: 86399, - access_token: "access_token", - }); - } -}; - -/** - * This method is required for testing the credentials in the identity SDK to provide the fake access_token (as per the current msal configurations). - * - * If msal ever changes its behavior, this needs to change - for example path/url/reply have to be updated accordingly - */ -export const pluginForIdentitySDK = () => { - nock("https://login.microsoftonline.com:443") - .persist() - .post((uri: string) => uri.includes("/oauth2/v2.0/token")) // Path can either be "{tenant-id}/oauth2/v2.0/token" or "/organizations/oauth2/v2.0/token" - .reply(200, { - token_type: "Bearer", - expires_in: 86399, - ext_expires_in: 86399, - access_token: "access_token", - refresh_token: "refresh_token", - id_token: - "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFMVE16YWtpaGlSbGFfOHoyQkVKVlhlV01xbyJ9.eyJ2ZXIiOiIyLjAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vOTE4ODA0MGQtNmM2Ny00YzViLWIxMTItMzZhMzA0YjY2ZGFkL3YyLjAiLCJzdWIiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFJa3pxRlZyU2FTYUZIeTc4MmJidGFRIiwiYXVkIjoiNmNiMDQwMTgtYTNmNS00NmE3LWI5OTUtOTQwYzc4ZjVhZWYzIiwiZXhwIjoxNTM2MzYxNDExLCJpYXQiOjE1MzYyNzQ3MTEsIm5iZiI6MTUzNjI3NDcxMSwibmFtZSI6IkFiZSBMaW5jb2xuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiQWJlTGlAbWljcm9zb2Z0LmNvbSIsIm9pZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC02NmYzLTMzMzJlY2E3ZWE4MSIsInRpZCI6IjMzMzgwNDBkLTZjNjctNGM1Yi1iMTEyLTM2YTMwNGI2NmRhZCIsIm5vbmNlIjoiMTIzNTIzIiwiYWlvIjoiRGYyVVZYTDFpeCFsTUNXTVNPSkJjRmF0emNHZnZGR2hqS3Y4cTVnMHg3MzJkUjVNQjVCaXN2R1FPN1lXQnlqZDhpUURMcSFlR2JJRGFreXA1bW5PcmNkcUhlWVNubHRlcFFtUnA2QUlaOGpZIn0=.1AFWW-Ck5nROwSlltm7GzZvDwUkqvhSQpm55TQsmVo9Y59cLhRXpvB8n-55HCr9Z6G_31_UbeUkoz612I2j_Sm9FFShSDDjoaLQr54CreGIJvjtmS3EkK9a7SJBbcpL1MpUtlfygow39tFjY7EVNW9plWUvRrTgVk7lYLprvfzw-CIqw3gHC-T7IK_m_xkr08INERBtaecwhTeN4chPC4W3jdmw_lIxzC48YoQ0dB1L9-ImX98Egypfrlbm0IBL5spFzL6JDZIRRJOu8vecJvj1mq-IUhGt0MacxX8jdxYLP-KUu2d9MbNKpCKJuZ7p8gwTL5B7NlUdh_dmSviPWrw", - client_info: "eyJ1aWQiOiIxMjMtdGVzdC11aWQiLCJ1dGlkIjoiNDU2LXRlc3QtdXRpZCJ9", - }); - // Above id_token and client_info are straight from the msal tests - // Reference - https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/926f1c2ba0598575e23dfd8cdd8b79fa3a3d19ff/lib/msal-browser/test/utils/BrowserProtocolUtils.spec.ts#L73 -}; diff --git a/sdk/test-utils/recorder-new/src/utils/paths.ts b/sdk/test-utils/recorder/src/utils/paths.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/paths.ts rename to sdk/test-utils/recorder/src/utils/paths.ts diff --git a/sdk/test-utils/recorder/src/utils/recordingPath.ts b/sdk/test-utils/recorder/src/utils/recordingPath.ts deleted file mode 100644 index a3a1ece91be5..000000000000 --- a/sdk/test-utils/recorder/src/utils/recordingPath.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export function formatPath(path: string): string { - return path - .toLowerCase() - .replace(/ /g, "_") - .replace(/<=/g, "lte") - .replace(/>=/g, "gte") - .replace(//g, "gt") - .replace(/=/g, "eq") - .replace(/\W/g, ""); -} - -/** - * Generates a file path with the following structure: - * - * `{node|browsers}//recording_.{js|json}` - * - * @param platform A string, either "node" or "browsers". - * @param testSuiteTitle The title of the test suite. - * @param testTitle The title of the specific test we're running. - */ -export function generateTestRecordingFilePath( - platform: "node" | "browsers", - testSuiteTitle: string, - testTitle: string, - extension?: "js" | "json" -): string { - // File Extension - // nock recordings for node tests - .js extension - // recordings are saved in json format for browser tests - .json extension - const ext = extension ?? (platform === "node" ? "js" : "json"); - return `${platform}/${formatPath(testSuiteTitle)}/recording_${formatPath(testTitle)}.${ext}`; -} diff --git a/sdk/test-utils/recorder/src/utils/recordings.browser.ts b/sdk/test-utils/recorder/src/utils/recordings.browser.ts deleted file mode 100644 index 9be27d0e556c..000000000000 --- a/sdk/test-utils/recorder/src/utils/recordings.browser.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { windowLens } from "./windowLens"; -import { generateTestRecordingFilePath } from "./recordingPath"; - -/** - * Checks if a test hasn't changed from the last time it was recorded. - * @param testContext - * @param testSuiteTitle - * @param testTitle - * @param currentHash - */ -export function testHasChanged( - testSuiteTitle: string, - testTitle: string, - _testAbsolutePath: string, - currentHash: string -): boolean { - const recordingPath: string = generateTestRecordingFilePath( - "browsers", - testSuiteTitle, - testTitle - ); - - let previousHash: string = ""; - - if (windowLens.get(["__json__", "recordings/" + recordingPath])) { - previousHash = windowLens.get(["__json__", "recordings/" + recordingPath, "hash"]); - } - - if (!previousHash) { - return true; - } - - return previousHash !== currentHash; -} - -export function findRecordingsFolderPath() { - throw new Error( - "Attempted to use the method `findRecordingsFolderPath`(meant for node) in a browser" - ); -} diff --git a/sdk/test-utils/recorder/src/utils/recordings.ts b/sdk/test-utils/recorder/src/utils/recordings.ts deleted file mode 100644 index ef89c1dcb50d..000000000000 --- a/sdk/test-utils/recorder/src/utils/recordings.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { generateTestRecordingFilePath } from "./recordingPath"; - -import fs from "fs-extra"; -import path from "path"; - -/** - * ONLY WORKS IN THE NODE.JS ENVIRONMENT - * - * Meant to be called during the playback for the node tests. - * 1. Takes the test filePath as argument. - * 2. Looks for the `recordings` folder in its hierarchical path. - * 3. Returns the full path of the `recordings` folder - * - * While running the tests, `filePath` can vary depending on location of the test files, examples below - * - * 1. If roll-up generated bundle files are being leveraged to run the tests - * filePath = `\azure-sdk-for-js\sdk\storage\storage-blob\dist-test\index.node.js` - * 2. If ts complied dist-esm files are being used to run the tests - * filePath = `\azure-sdk-for-js\sdk\storage\storage-blob\dist-esm\test\utils.spec.js` - * filePath = `\azure-sdk-for-js\sdk\storage\storage-blob\dist-esm\test\node\utils.spec.js` - * 3. If `.spec.ts` test files are being used directly - * filePath = `\azure-sdk-for-js\sdk\storage\storage-blob\test\utils.spec.ts` - * filePath = `\azure-sdk-for-js\sdk\storage\storage-blob\test\node\utils.spec.ts` - * In the above examples, no matter where the test files are, - * the recordings are located at `\azure-sdk-for-js\sdk\storage\storage-blob\recordings\`. - * In order to playback the tests, exact location of the recordings is to be found, - * this is done by checking the parent(s) folders until the `recordings` folder is found. - * - * @export - * @param {string} filePath - * @returns {string} location of the `recordings` folder - */ -export function findRecordingsFolderPath(filePath: string): string { - // Stripping away the file name - let currentPath = path.resolve(filePath, ".."); - // File/folder path of a closest child of `currentPath` in the folder hierarchy of `filePath` - let lastPath = filePath; - try { - // While loop to find the `recordings` folder - while (!fs.existsSync(path.resolve(currentPath, "recordings/"))) { - if ( - fs.existsSync(path.resolve(currentPath, "package.json")) && - fs.existsSync(path.resolve(currentPath, "..", "..", "sdk/")) && - fs.existsSync(path.resolve(currentPath, "..", "..", "..", "rush.json")) - ) { - // package.json of the SDK is found but not the `recordings` folder - // which is supposed to be present at the same level as package.json - throw new Error(`'recordings' folder is not found at ${currentPath}`); - } else if (lastPath === currentPath) { - throw new Error( - `'recordings' folder is not found at ${currentPath} (reached the root directory)` - ); - } else { - lastPath = currentPath; - currentPath = path.resolve(currentPath, ".."); - } - } - return path.resolve(currentPath, "recordings/"); - } catch (error) { - throw new Error( - `Unable to locate the 'recordings' folder anywhere in the hierarchy of the file path ${filePath}\n ${error}` - ); - } -} - -/** - * Requires a file if it exists. Only works on NodeJS. - */ -export function nodeRequireRecordingIfExists(recordingPath: string, testAbsolutePath: string): any { - const path = require("path"); - - // Get the full path of the `recordings` folder by navigating through the hierarchy of the test file path. - const recordingsFolderPath = findRecordingsFolderPath(testAbsolutePath); - const absoluteRecordingPath = path.resolve(recordingsFolderPath, recordingPath); - - if (fs.existsSync(absoluteRecordingPath)) { - return require(absoluteRecordingPath); - } else { - throw new Error(`The recording ${recordingPath} was not found in ${recordingsFolderPath}`); - } -} - -/** - * Checks if a test hasn't changed from the last time it was recorded. - * @param testContext - * @param testSuiteTitle - * @param testTitle - * @param currentHash - */ -export function testHasChanged( - testSuiteTitle: string, - testTitle: string, - testAbsolutePath: string, - currentHash: string -): boolean { - const recordingPath: string = generateTestRecordingFilePath("node", testSuiteTitle, testTitle); - - let previousHash: string = ""; - - try { - previousHash = nodeRequireRecordingIfExists(recordingPath, testAbsolutePath).hash; - } catch (e) {} - - if (!previousHash) { - return true; - } - - return previousHash !== currentHash; -} diff --git a/sdk/test-utils/recorder-new/src/utils/relativePathCalculator.browser.ts b/sdk/test-utils/recorder/src/utils/relativePathCalculator.browser.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/relativePathCalculator.browser.ts rename to sdk/test-utils/recorder/src/utils/relativePathCalculator.browser.ts diff --git a/sdk/test-utils/recorder-new/src/utils/relativePathCalculator.ts b/sdk/test-utils/recorder/src/utils/relativePathCalculator.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/relativePathCalculator.ts rename to sdk/test-utils/recorder/src/utils/relativePathCalculator.ts diff --git a/sdk/test-utils/recorder/src/utils/requestBodyTransform.ts b/sdk/test-utils/recorder/src/utils/requestBodyTransform.ts deleted file mode 100644 index bc273536dca9..000000000000 --- a/sdk/test-utils/recorder/src/utils/requestBodyTransform.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { isBrowser, sanitizeScopeUrl } from "."; - -/** - * Callbacks to be applied on the generated recordings - * - stringTransforms - callbacks to be applied on the string based request body - * - jsonTransforms - callbacks to be applied on the json based request body - */ -export type RequestBodyTransformsType = { - stringTransforms?: Array<(body: string) => string>; - jsonTransforms?: Array<(body: { [x: string]: unknown }) => { [x: string]: unknown }>; -}; - -/** - * Provides the default RequestBodyTransforms that need to be applied on the generated recordings - */ -export const defaultRequestBodyTransforms: Required = { - stringTransforms: [(body: string) => (isBrowser() ? sanitizeScopeUrl(body) : body)], - jsonTransforms: [], -}; - -/** - * Transformations to be applied on the requestBody in record mode for "string" fixtures to be able to filter the requests in playback. - */ -export function applyRequestBodyTransformationsOnFixture( - runtime: "node" | "browser", - fixture: string, - requestBodyTransformations: Required -): string; - -/** - * Transformations to be applied on the requestBody in record mode for "JSON" fixtures to be able to filter the requests in playback. - */ -export function applyRequestBodyTransformationsOnFixture( - runtime: "node" | "browser", - fixture: { [x: string]: unknown }, - requestBodyTransformations: Required -): { [x: string]: unknown }; - -/** - * Transformations to be applied on the requestBody in record mode to be able to filter the requests in playback. - * - * Example: - * Input: - * nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - * .post('/azuretenantid/oauth2/v2.0/token', "client-request-id=11111111-1111-1111-1111-111111111111&client_secret=azure_client_secret") - * .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}, [ - * ... - * ]); - * Output: - * nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - * .filteringRequestBody((body) => body.replace(/client-request-id=[^&]/g, "client-request-id=client-request-id")) - * .post('/azuretenantid/oauth2/v2.0/token', "client-request-id=client-request-id&client_secret=azure_client_secret") - * .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}, [ - * ... - * ]); - */ -export function applyRequestBodyTransformationsOnFixture( - runtime: "node" | "browser", - fixture: string | { [x: string]: unknown }, - requestBodyTransformations: Required -): string | { [x: string]: unknown } { - if (!requestBodyTransformations) { - return fixture; - } - if (runtime === "node" && typeof fixture === "string") { - // Modify the request body - let updatedFixture = fixture; - - // TODO: PUT and PATCH may also have request bodies, currently focusing only on POST - can be extended as needed - - // Matching the following at this point - // .post('/azuretenantid/oauth2/v2.0/token', "client-request-id=11111111-1111-1111-1111-111111111111&client_secret=azure_client_secret") - // .reply(200,.... - let matches = fixture.match(/\.post\((.*)\, (.*)\)\n\s*.reply\(/); - if ( - matches?.[2] && - typeof matches[2] === "string" && - requestBodyTransformations.stringTransforms - ) { - const updatedBody = applyRequestBodyTransformations(matches[2], requestBodyTransformations); // Must be string - either normal or JSON-stringified - // TODO: Handle JSON stringified bodies - not required as of now - - // Updated fixture with the new request body - // Example: - // .post('/azuretenantid/oauth2/v2.0/token', "client-request-id=client-request-id&client_secret=azure_client_secret") - // .reply(200,.... - updatedFixture = updatedFixture.replace(matches[2], updatedBody); - } - - if (updatedFixture === fixture) { - // No need to update the fixture with filtering method since the body didn't change - return fixture; - } - // Modify the updated fixture with `.filteringRequestBody` method to be able to match the request in playback - matches = updatedFixture.match(/\.post\((.*)\, (.*)\)\n\s*.reply\(/); - if (matches?.[0] && requestBodyTransformations.stringTransforms) { - for (const transformation of requestBodyTransformations.stringTransforms) { - // Add .filteringRequestBody method with the transformation in the recording - // Example: `.filteringRequestBody((body) => body.replace(/client-request-id=[^&]/g, "client-request-id=client-request-id"))` - // - // Recording would look like the following - // nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - // .filteringRequestBody((body) => body.replace(/client-request-id=[^&]/g, "client-request-id=client-request-id")) - // .post('/azuretenantid/oauth2/v2.0/token', "client-request-id=client-request-id&client_secret=azure_client_secret") - // ... - // ]); - updatedFixture = updatedFixture.replace( - matches[0], - `.filteringRequestBody(${transformation.toString()})\n ` + matches[0] - ); - } - } - return updatedFixture; - } else if (runtime === "browser" && typeof fixture !== "string") { - if (fixture?.requestBody) { - if (typeof fixture.requestBody === "string") { - const updatedFixture = { - ...fixture, - requestBody: applyRequestBodyTransformations( - fixture.requestBody, - requestBodyTransformations - ), - }; - return updatedFixture; - } else { - // TODO: If the request body is not string - can be null or JSON - // Not implemented yet - } - } - } - - return fixture; -} - -/** - * Transformations to be applied on the requestBody in record mode for "string" bodies to be able to filter the requests in playback. - */ -export function applyRequestBodyTransformations( - body: string, - requestBodyTransformations: RequestBodyTransformsType -): string; - -/** - * Transformations to be applied on the requestBody in record mode for "JSON" bodies to be able to filter the requests in playback. - */ -export function applyRequestBodyTransformations( - body: { [x: string]: unknown }, - requestBodyTransformations: RequestBodyTransformsType -): { [x: string]: unknown }; - -/** - * Transformations to be applied on the requestBody in record mode to be able to filter the requests in playback. - * - * Example: - * Input: - * "client-request-id=11111111-1111-1111-1111-111111111111&client_secret=azure_client_secret" - * with - * (body: string) => body.replace(/client-request-id=[^&]*g, "client-request-id=client-request-id") - * Output: - * "client-request-id=client-request-id&client_secret=azure_client_secret") - */ -export function applyRequestBodyTransformations( - body: string | { [x: string]: unknown }, - requestBodyTransformations: RequestBodyTransformsType -): string | { [x: string]: unknown } { - if (typeof body === "string") { - if (!requestBodyTransformations.stringTransforms) { - return body; - } - let updatedBody = body; - for (const transformation of requestBodyTransformations.stringTransforms) { - updatedBody = transformation(updatedBody); - } - return updatedBody; - } else if (typeof body === "object") { - // TODO: Expecting JSON object or null - Yet to be implemented - } - return body; -} diff --git a/sdk/test-utils/recorder-new/src/utils/sessionFilePath.ts b/sdk/test-utils/recorder/src/utils/sessionFilePath.ts similarity index 100% rename from sdk/test-utils/recorder-new/src/utils/sessionFilePath.ts rename to sdk/test-utils/recorder/src/utils/sessionFilePath.ts diff --git a/sdk/test-utils/recorder-new/src/utils/utils.ts b/sdk/test-utils/recorder/src/utils/utils.ts similarity index 97% rename from sdk/test-utils/recorder-new/src/utils/utils.ts rename to sdk/test-utils/recorder/src/utils/utils.ts index 3a9209348c14..8e9d8dcc99b8 100644 --- a/sdk/test-utils/recorder-new/src/utils/utils.ts +++ b/sdk/test-utils/recorder/src/utils/utils.ts @@ -316,3 +316,12 @@ export function setEnvironmentVariables(variables: { [key: string]: string }) { env[key] = value; } } + +/** + * Returns the environment variable. Throws error if not defined. + */ +export function assertEnvironmentVariable(variable: string): string { + const value = env[variable]; + if (!value) throw new Error(`${variable} is not defined`); + return value; +} diff --git a/sdk/test-utils/recorder/src/utils/windowLens.ts b/sdk/test-utils/recorder/src/utils/windowLens.ts deleted file mode 100644 index 9b2d63e08a4b..000000000000 --- a/sdk/test-utils/recorder/src/utils/windowLens.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * A method that allows us to alter and retrieve from the Window object. - * This will help us clean up the code later. - */ -export const windowLens: { - get: (propertyPath: string[], root?: any) => any; - set: (propertyPath: string[], propertyValue: any, root?: any) => void; -} = { - get(propertyPath: string[], root = window): any { - if (propertyPath.length === 1) { - return root[propertyPath[0]]; - } - if (!root[propertyPath[0]]) { - return; - } - return this.get(propertyPath.slice(1), root[propertyPath[0]]); - }, - set(propertyPath: string[], propertyValue: any, root = window): void { - if (propertyPath.length === 1) { - root[propertyPath[0]] = propertyValue; - return; - } - if (!root[propertyPath[0]]) { - root[propertyPath[0]] = {}; - } - return this.set(propertyPath.slice(1), propertyValue, root[propertyPath[0]]); - }, -}; diff --git a/sdk/test-utils/recorder/test/browser/recorder.spec.ts b/sdk/test-utils/recorder/test/browser/recorder.spec.ts deleted file mode 100644 index aca9e10fea84..000000000000 --- a/sdk/test-utils/recorder/test/browser/recorder.spec.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { RecorderEnvironmentSetup, windowLens } from "../../src/utils"; -import { record, TestContextInterface, TestContext, TestContextTest } from "../../src"; -import { consoleLog, setConsoleLogForTesting } from "../../src/customConsoleLog"; - -import { expect } from "chai"; -import MD5 from "md5"; -import xhrMock from "xhr-mock"; - -const expectedHttpResponse = "Hello World!"; - -async function helloWorldRequest(): Promise { - return new Promise((resolve) => { - function reqListener(this: any) { - resolve(this.responseText); - } - - const req = new XMLHttpRequest(); - req.addEventListener("load", reqListener); - req.open("GET", windowLens.get(["__env__", "PATH"])); - req.send(); - }); -} - -const recorderEnvSetup: RecorderEnvironmentSetup = { - replaceableVariables: { - PATH: "/replaced", - }, - customizationsOnRecordings: [], - queryParametersToSkip: [], -}; - -/** - * A function that generates another function with a predictable shape, even after compiling it to the browser. - */ -const getNoOpFunction = () => { - /* istanbul ignore next */ - return () => {}; -}; - -/** - * Another function that generates another function with a predictable shape, even after compiling it to the browser. - */ -const getAnotherNoOpFunction = () => { - /* istanbul ignore next */ - return () => 1; -}; - -describe("The recorder's public API, on a browser", () => { - afterEach(() => { - windowLens.set(["__env__", "TEST_MODE"], undefined); - windowLens.set(["__env__", "PATH"], undefined); - windowLens.set(["__json__"], undefined); - }); - - it("should record a simple test", async function () { - // Setting up the record mode, and the PATH environment variable. - windowLens.set(["__env__", "TEST_MODE"], "record"); - windowLens.set(["__env__", "PATH"], "/to/replace"); - - // We can't use Nise's FakeServer since the recorder ends up sending the request through the original XHR anyway. - xhrMock.setup(); - xhrMock.get("/to/replace", { - status: 200, - body: expectedHttpResponse, - }); - - // Before starting the recorder, we need to make a copy of the original XHR object, so that we can - // restore it before doing the recorder do its magic, in order for the HTTP request to hit the xhr-mock instance. - const originalXHR = XMLHttpRequest; - - // The recorder outputs files into the console, - // so we need to mock the console.log function to capture and test the recorder output. - const originalConsoleLog = consoleLog; - const savedConsoleLogParams: any[] = []; - setConsoleLogForTesting((...params: any[]) => { - if (params && params.length > 0) { - try { - if (JSON.parse(params[0]).writeFile) { - savedConsoleLogParams.push(params); - } - } catch (err) {} - } - }); - - // The recorder should start in the beforeEach call. - // We have to do this to emulate that. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: "test/recorder.browser.spec.ts", - // For this test, we don't care what's the content of the recorded function. - fn: getNoOpFunction(), - }); - - const recorder = record(fakeThis, recorderEnvSetup); - - // Restoring the XHR, otherwise we can't test this. - windowLens.set(["XMLHttpRequest"], originalXHR); - - const response = await helloWorldRequest(); - - // This test's request reached the server and received the expected response. - expect(response).to.equal(expectedHttpResponse); - - // Cleaning everything before we continue verifying the results. - xhrMock.teardown(); - await recorder.stop(); - setConsoleLogForTesting(originalConsoleLog); - - // Here we confirm that the recorder generated an expected output on the console.logs. - // This output is used to generate the recording files in the filesystem, though here we're only - // checking what was that the recorded emitted to the standard output. - expect(savedConsoleLogParams[0][0]).to.equal( - // The recordings here are empty because we hijacked the XHR. - // See the playback test for an example of a properly constructed recording object. - // TODO: Find a way to capture the complete output. - JSON.stringify({ - writeFile: true, - path: "./recordings/browsers/the_recorders_public_api_on_a_browser/recording_should_record_a_simple_test.json", - content: { - recordings: [], - uniqueTestInfo: { - uniqueName: {}, - newDate: {}, - }, - hash: MD5(getNoOpFunction().toString()), - }, - }) - ); - }); - - it("should playback a simple test", async function () { - // Setting up the playback mode. - // The PATH environment variable is not needed on playback. - // The recorder will assume that it is in playback mode by default. - // windowLens.set(["__env__", "TEST_MODE"], "playback"); - - // This is to emulate what 'karma-json-preprocessor' does for us during the real scenarios. - windowLens.set( - [ - "__json__", - "recordings/browsers/the_recorders_public_api_on_a_browser/recording_should_playback_a_simple_test.json", - ], - { - recordings: [ - { - method: "GET", - url: "/replaced", - response: expectedHttpResponse, - }, - ], - uniqueTestInfo: { uniqueName: {}, newDate: {} }, - } - ); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: "test/recorder.browser.spec.ts", - // For this test, we don't care what's the content of the recorded function. - fn: getNoOpFunction(), - }); - - const recorder = record(fakeThis, recorderEnvSetup); - - const response = await helloWorldRequest(); - - // The playback code served the appropriate response based on the recordings. - expect(response).to.equal(expectedHttpResponse); - - await recorder.stop(); - }); - - it("soft-record should re-record a simple outdated test", async function () { - // Setting up the playback mode. - // The PATH environment variable is not needed on playback. - windowLens.set(["__env__", "TEST_MODE"], "soft-record"); - windowLens.set(["__env__", "PATH"], "/to/replace"); - - // This is to emulate what 'karma-json-preprocessor' does for us during the real scenarios. - windowLens.set( - [ - "__json__", - "recordings/browsers/the_recorders_public_api_on_a_browser/recording_softrecord_should_rerecord_a_simple_outdated_test.json", - ], - { - recordings: [ - { - method: "GET", - url: "/replaced", - response: expectedHttpResponse, - }, - ], - uniqueTestInfo: { uniqueName: {}, newDate: {} }, - hash: "fake old hash", - } - ); - - // We can't use Nise's FakeServer since the recorder ends up sending the request through the original XHR anyway. - xhrMock.setup(); - xhrMock.get("/to/replace", { - status: 200, - body: expectedHttpResponse, - }); - - // Before starting the recorder, we need to make a copy of the original XHR object, so that we can - // restore it before doing the recorder do its magic, in order for the HTTP request to hit the xhr-mock instance. - const originalXHR = XMLHttpRequest; - - // The recorder outputs files into the console, - // so we need to override the consoleLog function to capture and test the recorder output. - const originalConsoleLog = consoleLog; - const savedConsoleLogParams: any[] = []; - setConsoleLogForTesting((...params: any[]) => { - if (params && params.length > 0) { - try { - if (JSON.parse(params[0]).writeFile) { - savedConsoleLogParams.push(params); - } - } catch (err) {} - } - }); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: "test/recorder.browser.spec.ts", - // The hash in our expected recording is made out of an empty function. - // This function has something inside, which means it has changed. - fn: getAnotherNoOpFunction(), - }); - - const recorder = record(fakeThis, recorderEnvSetup); - - // Restoring the XHR, otherwise we can't test this. - windowLens.set(["XMLHttpRequest"], originalXHR); - - const response = await helloWorldRequest(); - - // This test's request reached the server and received the expected response. - expect(response).to.equal(expectedHttpResponse); - - // Cleaning everything before we continue verifying the results. - xhrMock.teardown(); - await recorder.stop(); - setConsoleLogForTesting(originalConsoleLog); - - // Now we check the hash has changed in the recorded console.log output. - - // Here we confirm that the recorder generated an expected output on the console.logs. - // This output is used to generate the recording files in the filesystem, though here we're only - // checking what was that the recorded emitted to the standard output. - expect(savedConsoleLogParams[0][0]).to.equal( - // The recordings here are empty because we hijacked the XHR. - // See the playback test for an example of a properly constructed recording object. - // TODO: Find a way to capture the complete output. - JSON.stringify({ - writeFile: true, - path: "./recordings/browsers/the_recorders_public_api_on_a_browser/recording_softrecord_should_rerecord_a_simple_outdated_test.json", - content: { - recordings: [], - uniqueTestInfo: { - uniqueName: {}, - newDate: {}, - }, - hash: MD5(getAnotherNoOpFunction().toString()), - }, - }) - ); - }); - - it("soft-record should skip a simple unchanged test", async function () { - // Setting up the playback mode. - // The PATH environment variable is not needed on playback. - windowLens.set(["__env__", "TEST_MODE"], "soft-record"); - - // This is to emulate what 'karma-json-preprocessor' does for us during the real scenarios. - windowLens.set( - [ - "__json__", - "recordings/browsers/the_recorders_public_api_on_a_browser/recording_softrecord_should_skip_a_simple_unchanged_test.json", - ], - { - recordings: [ - { - method: "GET", - url: "/replaced", - response: expectedHttpResponse, - }, - ], - uniqueTestInfo: { uniqueName: {}, newDate: {} }, - // This is the expected hash - hash: MD5(getNoOpFunction().toString()), - } - ); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - let skipped = false; - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: "test/recorder.browser.spec.ts", - // The hash in our expected recording is made out of an empty function. - // This function is empty, which means it remains the same. - fn: getNoOpFunction(), - }); - - // We have to mock this.skip in order to confirm that the recorder has called it. - // We'll make a fake this. - fakeThis.skip = () => { - skipped = true; - throw new Error("Emulating mocha's skip"); - }; - - try { - record(fakeThis, recorderEnvSetup); - } catch (e) { - if (e.message !== "Emulating mocha's skip") { - throw e; - } - } - - expect(skipped).to.true; - }); -}); diff --git a/sdk/test-utils/recorder/test/browser/utils.spec.ts b/sdk/test-utils/recorder/test/browser/utils.spec.ts deleted file mode 100644 index 1de8811fbb83..000000000000 --- a/sdk/test-utils/recorder/test/browser/utils.spec.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { - testHasChanged, - generateTestRecordingFilePath, - stripNewLines, - windowLens, - isContentTypeInBrowserRecording, - maskAccessTokenInBrowserRecording, -} from "../../src/utils"; -import { expect } from "chai"; -import { - applyRequestBodyTransformationsOnFixture, - defaultRequestBodyTransforms, -} from "../../src/utils/requestBodyTransform"; - -describe("Browser utils", () => { - describe("windowLens", () => { - it("should set and set at one level of depth", () => { - windowLens.set(["A"], "A"); - expect(windowLens.get(["A"])).to.equal("A"); - // Cleaning what we just did. - windowLens.set(["A"], undefined); - }); - - it("should set and set at more than one level of depth", () => { - windowLens.set(["A", "B", "C"], "ABC"); - expect(windowLens.get(["A", "B", "C"])).to.equal("ABC"); - // Cleaning what we just did. - windowLens.set(["A", "B", "C"], undefined); - windowLens.set(["A", "B"], undefined); - windowLens.set(["A"], undefined); - }); - }); - - describe("testHasChanged", () => { - it("Should not crash if the recorded file doesn't exist", function () { - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - - windowLens.set(["__json__"], {}); - - // We won't be testing whether MD5 works or not. - const newHash = "new hash"; - - expect(testHasChanged(testSuiteTitle, testTitle, "test/myTest.spec.ts", newHash)).to.equal( - true - ); - }); - - it("Should return true if the older hash doesn't exist", function () { - const platform = "browsers"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - - windowLens.set(["__json__"], { - ["recordings/" + filePath]: {}, - }); - - // We won't be testing whether MD5 works or not. - const newHash = "new hash"; - - expect(testHasChanged(testSuiteTitle, testTitle, "test/myTest.spec.ts", newHash)).to.equal( - true - ); - }); - }); - - describe("stripNewLines", () => { - it("should remove new lines", () => { - const targetString = "a\r\nb\nc\rd"; - expect(stripNewLines(targetString)).to.equal("abcd"); - }); - }); - - describe("Mask access tokens in browser recordings", () => { - [ - { - name: "keyvault-keys example", - input: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"eyJ0eXAiOiJKV1QiL"}', - responseHeaders: { - "cache-control": "no-store, no-cache", - "content-length": "1315", - "content-type": "application/json; charset=utf-8", - date: "Mon, 22 Mar 2021 18:34:56 GMT", - expires: "-1", - p3p: 'CP="DSP CUR OTPi IND OTRi ONL FIN"', - pragma: "no-cache", - "referrer-policy": "strict-origin-when-cross-origin", - "strict-transport-security": "max-age=31536000; includeSubDomains", - "x-content-type-options": "nosniff", - "x-ms-ests-server": "2.1.11562.10 - SCUS ProdSlices", - "x-ms-request-id": "a81f6417-0fc8-4fd4-80ea-9c9e58f9d600", - }, - }, - ], - output: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}', - responseHeaders: { - "cache-control": "no-store, no-cache", - "content-length": "1315", - "content-type": "application/json; charset=utf-8", - date: "Mon, 22 Mar 2021 18:34:56 GMT", - expires: "-1", - p3p: 'CP="DSP CUR OTPi IND OTRi ONL FIN"', - pragma: "no-cache", - "referrer-policy": "strict-origin-when-cross-origin", - "strict-transport-security": "max-age=31536000; includeSubDomains", - "x-content-type-options": "nosniff", - "x-ms-ests-server": "2.1.11562.10 - SCUS ProdSlices", - "x-ms-request-id": "a81f6417-0fc8-4fd4-80ea-9c9e58f9d600", - }, - }, - ], - }, - { - name: `mask "access_token"s in json response`, - input: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "application/json; charset=utf-8", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - ], - output: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}', - responseHeaders: { - "content-length": "1315", - "content-type": "application/json; charset=utf-8", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - ], - }, - { - name: `doesn't mask "access_token"s in json response since the content-type is not application/json`, - input: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "something; charset=utf-8", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - ], - output: [ - { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "something; charset=utf-8", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - ], - }, - { - name: `no impact on other recordings`, - input: [ - { - method: "PUT", - url: "https://fakestorageaccount.blob.core.windows.net/container159218753534504901", - query: { - restype: "container", - }, - requestBody: null, - status: 201, - response: "", - responseHeaders: {}, - }, - ], - output: [ - { - method: "PUT", - url: "https://fakestorageaccount.blob.core.windows.net/container159218753534504901", - query: { - restype: "container", - }, - requestBody: null, - status: 201, - response: "", - responseHeaders: {}, - }, - ], - }, - { - name: `no impact on the recording with json content type but no json response`, - input: [ - { - method: "GET", - url: "https://managed_hsm.azure.net/keys/cryptography-client-test1e909341f12eab0", - query: { "api-version": "7.2" }, - requestBody: "", - status: 401, - response: "OK", - responseHeaders: { - "content-security-policy": "default-src 'self'", - "content-type": "application/json; charset=utf-8", - "strict-transport-security": "max-age=31536000; includeSubDomains", - }, - }, - ], - output: [ - { - method: "GET", - url: "https://managed_hsm.azure.net/keys/cryptography-client-test1e909341f12eab0", - query: { "api-version": "7.2" }, - requestBody: "", - status: 401, - response: "OK", - responseHeaders: { - "content-security-policy": "default-src 'self'", - "content-type": "application/json; charset=utf-8", - "strict-transport-security": "max-age=31536000; includeSubDomains", - }, - }, - ], - }, - ].forEach((test) => { - it(test.name, () => { - expect(maskAccessTokenInBrowserRecording(test.input as any)).to.deep.equal( - test.output, - `Unexpected result - access_token is not masked` - ); - }); - }); - }); - - describe("isContentTypeInBrowserRecording", () => { - [ - { - name: `"avro/binary" matches`, - input: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "avro/binary", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - expectedContentTypes: ["avro/binary"], - output: true, - }, - { - name: `"avro/binary" matches with an array of expected content types`, - input: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "avro/binary", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - expectedContentTypes: ["avro/binary", "application/xml"], - output: true, - }, - { - name: `"text/plain" should not match with an array of different content types`, - input: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "text/plain", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - expectedContentTypes: ["avro/binary", "application/xml"], - output: false, - }, - { - name: "application/json; charset=utf-8", - input: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - query: {}, - requestBody: - "response_type=token&grant_type=client_credentials&client_id=azure_client_id&client_secret=azure_client_secret&scope=https%3A%2F%2Fvault.azure.net%2F.default", - status: 200, - response: - '{"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}', - responseHeaders: { - "content-length": "1315", - "content-type": "application/json; charset=utf-8", - date: "Tue, 16 Feb 2021 18:21:34 GMT", - }, - }, - expectedContentTypes: ["application/json;charset=utf-8"], - output: true, - }, - ].forEach((test) => { - it(test.name, () => { - expect(isContentTypeInBrowserRecording(test.input, test.expectedContentTypes)).to.equal( - test.output, - `Unexpected result - content types ${test.expectedContentTypes} ${ - test.output ? "do not match" : "matched" - }` - ); - }); - }); - }); - - describe("applyRequestBodyTransformationsOnFixture", () => { - [ - { - title: "scope at the end of the request body gets replaced", - recording: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - requestBody: - "response_type=token&client_secret=azure_client_secret&scope=https%3A%2F%2Fattest.azure.net%2F.default", - response: '{"token_type":"Bearer","access_token":"access_token"}', - responseHeaders: { - "content-length": "1317", - date: "Fri, 21 May 2021 20:27:39 GMT", - }, - }, - finalRequestBody: - "response_type=token&client_secret=azure_client_secret&scope=https%3A%2F%2Fsanitized%2F", - }, - { - title: "scope at the middle of the request body gets replaced", - recording: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - requestBody: - "response_type=token&client_secret=azure_client_secret&scope=https%3A%2F%2Fattest.azure.net%2F.default&abc=123", - response: '{"token_type":"Bearer","access_token":"access_token"}', - responseHeaders: { - "content-length": "1317", - date: "Fri, 21 May 2021 20:27:39 GMT", - }, - }, - finalRequestBody: - "response_type=token&client_secret=azure_client_secret&scope=https%3A%2F%2Fsanitized%2F&abc=123", - }, - { - title: "unchanged for body with no scope", - recording: { - method: "POST", - url: "https://login.microsoftonline.com/azure_tenant_id/oauth2/v2.0/token", - requestBody: "response_type=token&client_secret=azure_client_secret", - response: '{"token_type":"Bearer","access_token":"access_token"}', - responseHeaders: { - "content-length": "1317", - date: "Fri, 21 May 2021 20:27:39 GMT", - }, - }, - finalRequestBody: "response_type=token&client_secret=azure_client_secret", - }, - ].forEach((testCase) => { - it(`${testCase.title}`, () => { - expect( - applyRequestBodyTransformationsOnFixture( - "browser", - testCase.recording, - defaultRequestBodyTransforms - ).requestBody - ).to.equal(testCase.finalRequestBody, "Unexpected request body"); - }); - }); - }); -}); diff --git a/sdk/test-utils/recorder/test/node/recorder.spec.ts b/sdk/test-utils/recorder/test/node/recorder.spec.ts deleted file mode 100644 index 0945870f3779..000000000000 --- a/sdk/test-utils/recorder/test/node/recorder.spec.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { RecorderEnvironmentSetup, delay, stripNewLines } from "../../src/utils"; -import { record, TestContext, TestContextInterface, TestContextTest } from "../../src"; - -import { expect } from "chai"; -import MD5 from "md5"; - -const expectedHttpResponse = "Hello World!"; -const emptyFunction = () => {}; -const expectedHash = MD5(emptyFunction.toString()); -const expectedRecording = `let nock = require('nock'); - -module.exports.hash = "${expectedHash}"; - -module.exports.testInfo = {"uniqueName":{},"newDate":{}} - -nock('http://127.0.0.1:1337', {"encodedQueryParams":true}) - .get('/') - .reply(200, "Hello World!", [ - 'Date', - 'DATE', - 'Connection', - 'close', - 'Transfer-Encoding', - 'chunked' -]); -`; - -/** - * helloWorldRequest makes a get request to the env.SERVER_ADDRESS - * and returns a promise that resolves when the server responds. - */ -async function helloWorldRequest(): Promise { - return new Promise((resolve) => { - const http = require("http"); - http.get(process.env.SERVER_ADDRESS!, (res: any) => { - let data = ""; - res.on("data", (chunk: string) => { - data += chunk; - }); - res.on("end", () => { - resolve(data); - }); - }); - }); -} - -const recorderEnvSetup: RecorderEnvironmentSetup = { - replaceableVariables: { - SERVER_ADDRESS: "http://127.0.0.1:1337", - }, - customizationsOnRecordings: [], - queryParametersToSkip: [], -}; - -describe("The recorder's public API, on NodeJS", () => { - beforeEach(function () { - // These tests do make files in the recordings folder. - // For that reason, we make sure these files are deleted before testing. - const fs = require("fs"); - const path = require("path"); - const directory = path.resolve("./recordings/node/the_recorders_public_api_on_nodejs"); - try { - const files = fs.readdirSync(directory); - for (const file of files) { - fs.unlinkSync(path.join(directory, file)); - } - } catch (e) {} - }); - - afterEach(() => { - delete process.env.TEST_MODE; - delete process.env.SERVER_ADDRESS; - }); - - it("should record a simple test", async function () { - process.env.TEST_MODE = "record"; - process.env.SERVER_ADDRESS = "http://127.0.0.1:8080"; - - // We create a very simple HTTP server that serves some content at a specific port. - const http = require("http"); - const server = http.createServer(function (_: any, res: any) { - res.write(expectedHttpResponse); - res.end(); - }); - server.listen(8080); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: __filename, - fn: emptyFunction, - }); - - const recorder = record(fakeThis, recorderEnvSetup); - - const response = await helloWorldRequest(); - - // This test's request reached the server and received the expected response. - expect(response).to.equal(expectedHttpResponse); - - // Cleaning everything before we continue verifying the results. - server.close(); - await recorder.stop(); - - // The recorder takes some time to finish writing the output file. - // It's not a second, but we're being pessimists. - await delay(1000); - const fs = require("fs"); - const recording = fs.readFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_should_record_a_simple_test.js", - { encoding: "utf-8" } - ); - - // Nock does store the date of the request. Let's strip that from the response. - const recordingWithoutDate = recording.replace(/Date',\n[^\n]*\n/, "Date',\n 'DATE',\n"); - - // Removing non-alphanumeric characters because of inconsistencies for this specific test on CI. - expect(stripNewLines(recordingWithoutDate).replace(/[^a-zA-Z0-9]+/g, " ")).to.equal( - stripNewLines(expectedRecording).replace(/[^a-zA-Z0-9]+/g, " ") - ); - }); - - it("should playback a simple test", async function () { - // The recorder will assume that it is in playback mode by default. - // process.env.TEST_MODE = "playback"; - - // Making sure the expected recording actually exists before running playback. - const fs = require("fs"); - fs.writeFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_should_playback_a_simple_test.js", - expectedRecording - ); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: __filename, - fn: emptyFunction, - }); - - const recorder = record(fakeThis, recorderEnvSetup); - const response = await helloWorldRequest(); - - // The playback code served the appropriate response based on the recordings. - expect(response).to.equal(expectedHttpResponse); - - await recorder.stop(); - }); - - it("soft-record should re-record a simple outdated test", async function () { - process.env.TEST_MODE = "soft-record"; - process.env.SERVER_ADDRESS = "http://127.0.0.1:8080"; - - // Making sure the expected recording actually exists before running playback. - const fs = require("fs"); - fs.writeFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_softrecord_should_rerecord_a_simple_outdated_test.js", - expectedRecording - ); - - // We create a very simple HTTP server that serves some content at a specific port. - const http = require("http"); - const server = http.createServer(function (_: any, res: any) { - res.write(expectedHttpResponse); - res.end(); - }); - server.listen(8080); - - const changedTestFunction = () => { - let the_contents_have_changed = true; - return the_contents_have_changed; - }; - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: __filename, - fn: changedTestFunction, - }); - - const recorder = record(fakeThis, recorderEnvSetup); - - const response = await helloWorldRequest(); - - // This test's request reached the server and received the expected response. - expect(response).to.equal(expectedHttpResponse); - - // Cleaning everything before we continue verifying the results. - server.close(); - await recorder.stop(); - - // The recorder takes some time to finish writing the output file. - // It's not a second, but we're being pessimists. - await delay(1000); - const recording = fs.readFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_softrecord_should_rerecord_a_simple_outdated_test.js", - { encoding: "utf-8" } - ); - - // Nock does store the date of the request. Let's strip that from the response. - const recordingWithoutDate = recording.replace(/Date',\n[^\n]*\n/, "Date',\n 'DATE',\n"); - - // Let's make a new expected recording variable, - // this time with the new hash. - const expectedRecordingWithUpdatedHash = recordingWithoutDate.replace( - expectedHash, - MD5(changedTestFunction.toString()) - ); - - // The hash has changed in the recorded file. - expect(recordingWithoutDate).to.equal(expectedRecordingWithUpdatedHash); - }); - - it("soft-record should skip a simple unchanged test", async function () { - process.env.TEST_MODE = "soft-record"; - - // Making sure the expected recording actually exists before running playback. - const fs = require("fs"); - fs.writeFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_softrecord_should_skip_a_simple_unchanged_test.js", - expectedRecording - ); - - // The recorder should start in the beforeEach call. - // To emulate that behavior while keeping the test code as contained as possible, - // we're compensating with this. - let skipped = false; - const fakeThis: TestContextInterface = new TestContext(this.test! as TestContextTest, { - ...this.currentTest, - file: __filename, - // The hash in our expected recording is made out of an empty function. - // This function is empty, which means it remains the same. - fn: emptyFunction, - }); - - // We have to mock this.skip in order to confirm that the recorder has called it. - fakeThis.skip = () => { - skipped = true; - throw new Error("Emulating mocha's skip"); - }; - - try { - record(fakeThis, recorderEnvSetup); - } catch (e) { - if (e.message !== "Emulating mocha's skip") { - throw e; - } - } - - expect(skipped).to.true; - - // The file shouldn't have changed, but just in case. - // The recorder takes some time to finish writing the output file. - // It's not a second, but we're being pessimists. - await delay(1000); - const recording = fs.readFileSync( - "./recordings/node/the_recorders_public_api_on_nodejs/recording_softrecord_should_skip_a_simple_unchanged_test.js", - { encoding: "utf-8" } - ); - - // Nock does store the date of the request. Let's strip that from the response. - const recordingWithoutDate = recording.replace(/Date',\n[^\n]*\n/, "Date',\n 'DATE',\n"); - - // We confirm that the file hasn't changed. - expect(recordingWithoutDate).to.equal(expectedRecording); - }); -}); diff --git a/sdk/test-utils/recorder/test/node/utils.spec.ts b/sdk/test-utils/recorder/test/node/utils.spec.ts deleted file mode 100644 index a882d42cc067..000000000000 --- a/sdk/test-utils/recorder/test/node/utils.spec.ts +++ /dev/null @@ -1,863 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - generateTestRecordingFilePath, - isBrowser, - testHasChanged, - isContentTypeInNockFixture, - decodeHexEncodingIfExistsInNockFixture, - handleSingleQuotesInUrlPath, - setDefaultRetryAfterIntervalInNockFixture, -} from "../../src/utils"; -import { nodeRequireRecordingIfExists, findRecordingsFolderPath } from "../../src/utils/recordings"; -import chai, { expect } from "chai"; -import { defaultCustomizationsOnRecordings } from "../../src/defaultCustomizations"; - -describe("NodeJS utils", () => { - describe("nodeRequireRecordingIfExists", () => { - it("should be able to load the contents of a recording file if the file exists", function () { - const mockFs = require("mock-fs"); - const mockRequire = require("mock-require"); - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - // Our lazy require doesn't use the fs module internally. - "recordings/recording.json": "", - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - // If rollup bundle is used to execute the tests - `dist-test/index.node.js`, `recordings` folder is present one level above. - mockRequire("../recordings/recording.json", { - property: "value", - }); - - // If the dist-esm files are used to execute the tests - `dist-esm/test/node/utils.spec.js`, `recordings` folder is present three levels above. - mockRequire("../../../recordings/recording.json", { - property: "value", - }); - - expect(nodeRequireRecordingIfExists("recording.json", testAbsolutePath).property).to.equal( - "value" - ); - - mockFs.restore(); - mockRequire.stopAll(); - }); - - it("should throw if the file at a given recording path doesn't exist", function () { - if (isBrowser()) { - this.skip(); - } - - // Require shouldn't be mocked in this test since we should be preventing require from being reached. - - const mockFs = require("mock-fs"); - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - recordings: {}, - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - let error: Error | undefined; - - try { - nodeRequireRecordingIfExists("recording.json", testAbsolutePath); - } catch (e) { - error = e; - } - - expect(error!.message).to.equal( - `The recording recording.json was not found in ${findRecordingsFolderPath( - "recording.json" - )}` - ); - - mockFs.restore(); - }); - }); - - describe("testHasChanged", () => { - it("should not crash if the recorded file doesn't exist", function () { - const mockFs = require("mock-fs"); - const mockRequire = require("mock-require"); - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - // Our lazy require doesn't use the fs module internally. - recordings: {}, - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - // We won't be testing whether MD5 works or not. - const newHash = "new hash"; - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true); - - mockFs.restore(); - mockRequire.stopAll(); - }); - - it("should return true if the older hash doesn't exist", function () { - const mockFs = require("mock-fs"); - const mockRequire = require("mock-require"); - const platform = "node"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - // Our lazy require doesn't use the fs module internally. - [`recordings/${filePath}`]: "", - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - mockRequire(`../recordings/${filePath}`, {}); - - // We won't be testing whether MD5 works or not. - const newHash = "new hash"; - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true); - - mockFs.restore(); - mockRequire.stopAll(); - }); - - it("should return false if the older hash is the same as the new hash", function () { - const mockFs = require("mock-fs"); - const mockRequire = require("mock-require"); - const platform = "node"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - // Our lazy require doesn't use the fs module internally. - [`recordings/${filePath}`]: "", - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - // If rollup bundle is used to execute the tests - `dist-test/index.node.js`, `recordings` folder is present one level above. - mockRequire(`../recordings/${filePath}`, { - // We won't be testing whether MD5 works or not. - hash: "same old hash", - }); - - // If the dist-esm files are used to execute the tests - `dist-esm/test/node/utils.spec.js`, `recordings` folder is present three levels above. - mockRequire(`../../../recordings/${filePath}`, { - // We won't be testing whether MD5 works or not. - hash: "same old hash", - }); - - // We won't be testing whether MD5 works or not. - const newHash = "same old hash"; - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(false); - - mockFs.restore(); - mockRequire.stopAll(); - }); - - it("should return true if the older hash is different than the new hash", function () { - const mockFs = require("mock-fs"); - const mockRequire = require("mock-require"); - const platform = "node"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const filePath = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - - // This needs to change if findRecordingsFolderPath changes. - mockFs({ - // Our lazy require doesn't use the fs module internally. - [`recordings/${filePath}`]: "", - "test/myTest.spec.ts": "", - "../../sdk/": {}, - "../../../rush.json": "", - }); - - mockRequire(`../recordings/${filePath}`, { - // We won't be testing whether MD5 works or not. - hash: "old hash", - }); - - // We won't be testing whether MD5 works or not. - const newHash = "new hash"; - - const path = require("path"); - const testAbsolutePath = path.resolve("test/myTest.spec.ts"); - - expect(testHasChanged(testSuiteTitle, testTitle, testAbsolutePath, newHash)).to.equal(true); - - mockFs.restore(); - mockRequire.stopAll(); - }); - }); - - describe("decodeHexEncodingIfExistsInNockFixture", () => { - [ - { - name: `Hex encoding decodes for "avro/binary" content type`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, Buffer.from("4f626a0131c2", "hex"), [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - }, - { - name: `Hex encoding is not decoded for "something/else" content type`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'something/else', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'something/else', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - }, - { - name: `Hex encoding decodes for "avro/binary" content type even for partial success(status code 206)`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "") - .query(true) - .reply(206, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "") - .query(true) - .reply(206, Buffer.from("4f626a0131c2", "hex"), [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - }, - { - name: `Hex encoding is not decoded for non-successful status codes`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(400, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(400, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - }, - ].forEach((test) => { - it(test.name, () => { - chai.assert.equal( - decodeHexEncodingIfExistsInNockFixture(test.input), - test.output, - `Unexpected output` - ); - }); - }); - }); - - describe("isContentTypeInNockFixture", () => { - [ - { - name: `"avro/binary" matches`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', -]);`, - expectedContentTypes: ["avro/binary"], - output: true, - }, - { - name: `"avro/binary" matches with an array of expected content types`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'avro/binary', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', - ]);`, - expectedContentTypes: ["avro/binary", "application/xml"], - output: true, - }, - { - name: `"text/plain" should not match with an array of different content types`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'text/plain', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', - ]);`, - expectedContentTypes: ["avro/binary", "application/xml"], - output: false, - }, - ].forEach((test) => { - it(test.name, () => { - chai.assert.equal( - isContentTypeInNockFixture(test.input, test.expectedContentTypes), - test.output, - `Unexpected result - content types ${test.expectedContentTypes} ${ - test.output ? "do not match" : "matched" - }` - ); - }); - }); - }); - - describe("defaultCustomizationsOnRecordings for nock fixtures", () => { - [ - { - name: `mask "access_token"s in json response and modify scope url`, - input: `nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - .post('/aaaaa/oauth2/v2.0/token', "response_type=token&grant_type=client_credentials&client_id=aaaaa&client_secret=aaaaa&scope=https%3A%2F%2Fstorage.azure.com%2F.default") - .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"e6z-9_g"}, [ - 'Cache-Control', - 'no-store, no-cache', - 'Pragma', - 'no-cache', - 'Content-Length', - '1318', - 'Content-Type', - 'application/json; charset=utf-8', - 'Expires', - '-1', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'X-Content-Type-Options' - ]);`, - output: `nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - .post('/aaaaa/oauth2/v2.0/token', "response_type=token&grant_type=client_credentials&client_id=aaaaa&client_secret=aaaaa&scope=https%3A%2F%2Fsanitized%2F") - .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_token":"access_token"}, [ - 'Cache-Control', - 'no-store, no-cache', - 'Pragma', - 'no-cache', - 'Content-Length', - '1318', - 'Content-Type', - 'application/json; charset=utf-8', - 'Expires', - '-1', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'X-Content-Type-Options' - ]);`, - }, - { - name: `modify scope url and do nothing else for json response without access_token`, - input: `nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - .post('/aaaaa/oauth2/v2.0/token', "response_type=token&grant_type=client_credentials&client_id=aaaaa&client_secret=aaaaa&scope=https%3A%2F%2Fstorage.azure.com%2F.default") - .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_string":"e6z-9_g"}, [ - 'Cache-Control', - 'no-store, no-cache', - 'Pragma', - 'no-cache', - 'Content-Length', - '1318', - 'Content-Type', - 'application/json; charset=utf-8', - 'Expires', - '-1', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'X-Content-Type-Options' - ]);`, - output: `nock('https://login.microsoftonline.com:443', {"encodedQueryParams":true}) - .post('/aaaaa/oauth2/v2.0/token', "response_type=token&grant_type=client_credentials&client_id=aaaaa&client_secret=aaaaa&scope=https%3A%2F%2Fsanitized%2F") - .reply(200, {"token_type":"Bearer","expires_in":86399,"ext_expires_in":86399,"access_string":"e6z-9_g"}, [ - 'Cache-Control', - 'no-store, no-cache', - 'Pragma', - 'no-cache', - 'Content-Length', - '1318', - 'Content-Type', - 'application/json; charset=utf-8', - 'Expires', - '-1', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'X-Content-Type-Options' - ]);`, - }, - { - name: `do nothing for (unrelated) non JSON response`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'text/plain', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', - ]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/path', "select * from BlobStorage") - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'text/plain', - 'Last-Modified', - 'Thu, 20 Aug 2020 09:22:11 GMT', - ]);`, - }, - { - name: "do nothing for unrelated XML response", - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/', "2019-12-03T06:12:38Z2019-12-04T05:12:38Z") - .query(true) - .reply(200, "324ed67c-1c74-4563-816e-c4be5f675ef172f988bf-86f1-41af-91ab-2d7cd011db472019-12-03T06:12:38Z2019-12-04T05:12:38Zb2019-02-028MsGAT04kfgnEnSiawpJDNcTyJ/HcSmKC8O01InfMj4=", [ 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'application/xml', - 'Server', - 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0', - 'x-ms-request-id', - '30efa223-901e-000a-7397-a95c17000000', - 'x-ms-client-request-id', - '63b64c0c-b0fe-498d-b530-0ceede8e8888', - 'x-ms-version', - '2019-02-02', - 'Date', - 'Tue, 03 Dec 2019 05:06:39 GMT' ]); - `, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .post('/', "2019-12-03T06:12:38Z2019-12-04T05:12:38Z") - .query(true) - .reply(200, "324ed67c-1c74-4563-816e-c4be5f675ef172f988bf-86f1-41af-91ab-2d7cd011db472019-12-03T06:12:38Z2019-12-04T05:12:38Zb2019-02-028MsGAT04kfgnEnSiawpJDNcTyJ/HcSmKC8O01InfMj4=", [ 'Transfer-Encoding', - 'chunked', - 'Content-Type', - 'application/xml', - 'Server', - 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0', - 'x-ms-request-id', - '30efa223-901e-000a-7397-a95c17000000', - 'x-ms-client-request-id', - '63b64c0c-b0fe-498d-b530-0ceede8e8888', - 'x-ms-version', - '2019-02-02', - 'Date', - 'Tue, 03 Dec 2019 05:06:39 GMT' ]); - `, - }, - ].forEach((test) => { - it(test.name, () => { - let updatedFixture = test.input; - for (const customization of defaultCustomizationsOnRecordings) { - updatedFixture = customization(updatedFixture); - } - chai.assert.equal( - updatedFixture, - test.output, - `Unexpected result - updatedFixture is not as expected` - ); - }); - }); - }); - - describe("setDefaultRetryAfterIntervalInNockFixture", () => { - [ - { - name: `Default RetryAfter interval is set if retry-after is > 0`, - input: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - '60', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - output: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - '0', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - }, - { - name: `has no effect if retry-after is absent`, - input: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - output: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - }, - { - name: `unchanged if retry-after is 0 already`, - input: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - '0', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - output: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - '0', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - }, - { - name: `unchanged if the value after the retry-after is not a number`, - input: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - 'AB-CD', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - output: `nock('https://management.azure.com:443', {"encodedQueryParams":true}) - .get('/subscriptions/azure_subscription_id/resourceGroups/myjstest/providers/Microsoft.ApiManagement/service/myserviceyyy2/operationresults/ZWFzdHVzOm15c2VydmljZXl5eTJfQWN0X2M5ZTUxY2Ri') - .query(true) - .reply(202, "", [ - 'Cache-Control', - 'no-cache', - 'Pragma', - 'no-cache', - 'Retry-After', - 'AB-CD', - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains', - 'x-ms-request-id', - '8fc80de9-0d11-4570-a62b-3e90ae6fd07c', -]);`, - }, - ].forEach((test) => { - it(test.name, () => { - chai.assert.equal( - setDefaultRetryAfterIntervalInNockFixture(test.input), - test.output, - `Output from "setDefaultRetryAfterIntervalInNockFixture" did not match the expected output` - ); - }); - }); - }); - - describe("handleSingleQuotesInUrlPath", () => { - [ - // { - // name: `single quotes in get request in the fixture with request body`, - // input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .get('/'path'', "select * from BlobStorage") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);`, - // output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .get(\`/'path'\`, "select * from BlobStorage") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);` - // }, - { - name: `single quotes in get request in the fixture with no request body`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .get('/'path'') - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .get(\`/'path'\`) - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - }, - { - name: `single quotes in delete request in the fixture with no request body`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete('/'path'pathx') - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete(\`/'path'pathx\`) - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - }, - { - name: `no single quotes in get request in the fixture`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete('/path') - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete('/path') - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - }, - { - name: `more than two single quotes in delete request in the fixture`, - input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete('/p'''a't'h') - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - .delete(\`/p'''a't'h\`) - .query(true) - .reply(200, "4f626a0131c2", [ - 'Transfer-Encoding', - 'chunked' - ]);`, - }, - // { - // name: `no single quotes in the path and single quotes in the request body should not affect`, - // input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post('/$batch', "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);`, - // output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post('/$batch', "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);` - // }, - // { - // name: `single quotes in the path and the request body should work`, - // input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post('/$batch'hello'', "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);`, - // output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post(\`/$batch'hello'\`, "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);` - // }, - // { - // name: `single quotes in the path and the request body should work - without space`, - // input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post('/$batch'hello'',"--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);`, - // output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post(\`/$batch'hello'\`,"--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);` - // }, - // { - // name: `single quotes in the path and the request body should work`, - // input: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post('/$batch'hello'', "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);`, - // output: `nock('https://fakestorageaccount.blob.core.windows.net:443', {"encodedQueryParams":true}) - // .post(\`/$batch'hello'\`, "--batch_fakeId\\r\\nDELETE https://endpoint.net/node(key='batchTest',RowKey='1') HTTP/1.1\\r\\nAccept: application/json\\r\\n") - // .query(true) - // .reply(200, "4f626a0131c2", [ - // 'Transfer-Encoding', - // 'chunked' - // ]);` - // } - ].forEach((test) => { - it(test.name, () => { - chai.assert.equal( - handleSingleQuotesInUrlPath(test.input), - test.output, - `Output from "handleSingleQuotesInUrlPath" did not match the expected output` - ); - }); - }); - }); -}); diff --git a/sdk/test-utils/recorder-new/test/sanitizers.spec.ts b/sdk/test-utils/recorder/test/sanitizers.spec.ts similarity index 100% rename from sdk/test-utils/recorder-new/test/sanitizers.spec.ts rename to sdk/test-utils/recorder/test/sanitizers.spec.ts diff --git a/sdk/test-utils/recorder-new/test/testProxyClient.spec.ts b/sdk/test-utils/recorder/test/testProxyClient.spec.ts similarity index 100% rename from sdk/test-utils/recorder-new/test/testProxyClient.spec.ts rename to sdk/test-utils/recorder/test/testProxyClient.spec.ts diff --git a/sdk/test-utils/recorder-new/test/testProxyTests.spec.ts b/sdk/test-utils/recorder/test/testProxyTests.spec.ts similarity index 100% rename from sdk/test-utils/recorder-new/test/testProxyTests.spec.ts rename to sdk/test-utils/recorder/test/testProxyTests.spec.ts diff --git a/sdk/test-utils/recorder/test/utils.spec.ts b/sdk/test-utils/recorder/test/utils.spec.ts deleted file mode 100644 index ea4b4e6a9abf..000000000000 --- a/sdk/test-utils/recorder/test/utils.spec.ts +++ /dev/null @@ -1,479 +0,0 @@ -import { - applyReplacementMap, - ReplacementMap, - applyReplacementFunctions, - encodeRFC3986, - filterSecretsFromStrings, - env, - filterSecretsRecursivelyFromJSON, - generateTestRecordingFilePath, - isHex, -} from "../src/utils"; -import { setEnvironmentVariables } from "../src/baseRecorder"; - -import { expect } from "chai"; - -describe("utils", () => { - describe("encodeRFC3986", () => { - // From https://tools.ietf.org/html/rfc3986 - // Also useful: https://en.wikipedia.org/wiki/Percent-encoding - - it("Should encode the reserved characters", () => { - const genDelims = [":", "/", "?", "#", "[", "]", "@"]; - const encodedGenDelims = genDelims.map(encodeRFC3986); - expect(encodedGenDelims).to.deep.equal(["%3A", "%2F", "%3F", "%23", "%5B", "%5D", "%40"]); - - const subDelims = ["!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="]; - const encodedSubDelims = subDelims.map(encodeRFC3986); - expect(encodedSubDelims).to.deep.equal([ - "%21", - "%24", - "%26", - "%27", - "%28", - "%29", - "%2A", - "%2B", - "%2C", - "%3B", - "%3D", - ]); - }); - - it("Should not encode unreserved characters", () => { - const unreservedCharacters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; - const result = encodeRFC3986(unreservedCharacters); - expect(result).to.deep.equal(unreservedCharacters); - }); - }); - - describe("applyReplacementMap", () => { - it("Should filter URI encoded secrets", () => { - const env: NodeJS.ProcessEnv = { - SECRET: "(SECRET)", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("SECRET", "HIDDEN_SECRET"); - - const recording = "azure.com/url/%28SECRET%29"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("azure.com/url/HIDDEN_SECRET"); - }); - - it("Should filter hostname of the plain URI", () => { - const env: NodeJS.ProcessEnv = { - ENDPOINT: "https://azureaccount.net/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("ENDPOINT", "https://endpoint/"); - - const recording = "https://azureaccount.net/"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("https://endpoint/"); - }); - - it("Should filter hostname of the URI irrespective of `/` at the end", () => { - const env: NodeJS.ProcessEnv = { - ENDPOINT: "https://azureaccount.net/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("ENDPOINT", "https://endpoint/"); - - const recording = "https://azureaccount.net"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("https://endpoint"); - }); - - it("Should filter hostname of the URI irrespective of the content succeeding the hostname", () => { - const env: NodeJS.ProcessEnv = { - ENDPOINT: "https://azureaccount.net/queue/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("ENDPOINT", "https://endpoint/blob/"); - - const recording = "https://azureaccount.net"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("https://endpoint"); - }); - - it("Should filter raw secrets", () => { - const env: NodeJS.ProcessEnv = { - ENDPOINT: "azure.com/url/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("ENDPOINT", "default.com/path/"); - - const recording = "azure.com/url/%28SECRET%29"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("default.com/path/%28SECRET%29"); - }); - - it("Should filter both, raw and URI encoded secrets", () => { - const env: NodeJS.ProcessEnv = { - SECRET: "(SECRET)", - ENDPOINT: "azure.com/url/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("SECRET", "HIDDEN_SECRET"); - replacementMap.set("ENDPOINT", "default.com/path/"); - - const recording = "azure.com/url/%28SECRET%29"; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal("default.com/path/HIDDEN_SECRET"); - }); - - it("Should not apply unnecessary repeated replacements", () => { - const env: NodeJS.ProcessEnv = { - AZURE_USERNAME: "username", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("AZURE_USERNAME", "azure_username"); - - const recording = `.post('/tenant/oauth2/v2.0/token', "username=username")`; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - // It could be because the replacement is a superset of the original value, - // Or perhaps because other weird occurrences in the file, - // in either case, double replacements should not happen. - expect(appliedMap.indexOf("azure_azure_username")).to.equal(-1); - }); - - it("Should work with recordings of several lines", () => { - const env: NodeJS.ProcessEnv = { - SECRET: "(SECRET)", - ENDPOINT: "azure.com/url/", - }; - - const replacementMap: ReplacementMap = new Map(); - replacementMap.set("SECRET", "HIDDEN_SECRET"); - replacementMap.set("ENDPOINT", "default.com/path/"); - - const recording = ` -All the combinations: -azure.com/url/%28SECRET%29 -ultramarine.com/url/%28SECRET%29 -azure.com/url/PUBLIC -ultramarine.com/url/PUBLIC -`; - const appliedMap = applyReplacementMap(env, replacementMap, recording); - - expect(appliedMap).to.equal( - ` -All the combinations: -default.com/path/HIDDEN_SECRET -ultramarine.com/url/HIDDEN_SECRET -default.com/path/PUBLIC -ultramarine.com/url/PUBLIC -` - ); - }); - }); - - describe("applyReplacementFunctions", () => { - it("Should apply one replacement function", () => { - const replacements: Array<(content: string) => string> = [ - (source: string): string => { - return source.replace(/banana/i, "Bonobo's"); - }, - ]; - const recording = "Banana Split"; - const appliedFunctions = applyReplacementFunctions(replacements, recording); - expect(appliedFunctions).to.equal("Bonobo's Split"); - }); - - it("Should apply several replacement functions", () => { - const replacements: Array<(content: string) => string> = [ - (source: string): string => { - return source.replace(/banana/i, "Bonobo's"); - }, - (source: string): string => { - return source.replace(/split/i, "Flex"); - }, - ]; - const recording = "Banana Split"; - const appliedFunctions = applyReplacementFunctions(replacements, recording); - expect(appliedFunctions).to.equal("Bonobo's Flex"); - }); - - it("Should work with recordings of several lines", () => { - const replacements = [ - (source: string): string => { - return source.replace(/azure.com/g, "default.com"); - }, - (source: string): string => { - return source.replace(/%28SECRET%29/g, "HIDDEN_SECRET"); - }, - ]; - const recording = ` -All the combinations: -azure.com/url/%28SECRET%29 -ultramarine.com/url/%28SECRET%29 -azure.com/url/PUBLIC -ultramarine.com/url/PUBLIC -`; - const appliedFunctions = applyReplacementFunctions(replacements, recording); - expect(appliedFunctions).to.equal( - ` -All the combinations: -default.com/url/HIDDEN_SECRET -ultramarine.com/url/HIDDEN_SECRET -default.com/url/PUBLIC -ultramarine.com/url/PUBLIC -` - ); - }); - }); - - describe("filter secrets from content", () => { - function verifyFilterFunctionForJson( - recording: any, - replacementMap: { [ENV_VAR: string]: string }, - replacements: Array<(content: string) => string>, - expectedFilteredOutput: any - ) { - const updatedRecording = filterSecretsRecursivelyFromJSON( - recording, - replacementMap, - replacements - ); - expect(updatedRecording).to.deep.equal(expectedFilteredOutput); - } - - it("Should work for strings", () => { - env.SECRET = "SECRET"; - const replaceableVariables = { SECRET: "FAKE_IT" }; - - const recording = "HERE_IS_THE_FLAG-SECRET"; - const updatedRecording = filterSecretsFromStrings(recording, replaceableVariables, []); - expect(updatedRecording).to.equal("HERE_IS_THE_FLAG-FAKE_IT"); - }); - - it("Should work for JSON content #1 - secret is present in the query attributes, part of the xml response string", () => { - env.ACCOUNT_NAME = "azureaccount"; - const replaceableVariables = { ACCOUNT_NAME: "fakestorageaccount" }; - verifyFilterFunctionForJson( - { - recordings: [ - { - method: "GET", - url: "https://azureaccount.net", - query: { - marker: "/azureaccount/queue156816850373302116x2", - maxresults: "1", - }, - response: - 'queue156816850373302116/azureaccount/queue156816850373302116x21queue156816850373302116x2val', - responseHeaders: { - server: "Windows-Azure-Queue/1.0 Microsoft-HTTPAPI/2.0", - }, - }, - ], - }, - replaceableVariables, - [], - { - recordings: [ - { - method: "GET", - url: "https://fakestorageaccount.net", - query: { - marker: "/fakestorageaccount/queue156816850373302116x2", - maxresults: "1", - }, - response: - 'queue156816850373302116/fakestorageaccount/queue156816850373302116x21queue156816850373302116x2val', - responseHeaders: { - server: "Windows-Azure-Queue/1.0 Microsoft-HTTPAPI/2.0", - }, - }, - ], - } - ); - }); - - it("Should work for JSON content #2 - secret is present as part of a JSON lookalike response string ", () => { - verifyFilterFunctionForJson( - { - recording: [ - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiwN"}', - }, - ], - }, - {}, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - { - recording: [ - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"access_token"}', - }, - ], - } - ); - }); - - it("Should work for JSON content #3 - array of JSON objects", () => { - env.ACCOUNT_NAME = "azureaccount"; - const replaceableVariables = { ACCOUNT_NAME: "fakestorageaccount" }; - verifyFilterFunctionForJson( - [ - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiwN"}', - }, - { url: "http://bing.com" }, - { ACCOUNT_NAME: "azureaccount" }, - ], - replaceableVariables, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - [ - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"access_token"}', - }, - { url: "http://bing.com" }, - { ACCOUNT_NAME: "fakestorageaccount" }, - ] - ); - }); - - it("Should work for JSON content #4 - JSON content with key-value pair strings", () => { - verifyFilterFunctionForJson( - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eXAiOiwN"}', - }, - {}, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - { - response: - '{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"access_token"}', - } - ); - }); - - it("Should work for JSON content #5 - regex to be replaced is present as a key-value pair in the JSON content", () => { - verifyFilterFunctionForJson( - { access_token: "eyJ0eXA75E_Q" }, - {}, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - { access_token: "access_token" } - ); - }); - - it("Should work for JSON content #6 - JSON.stringify-ed content with regex to be replaced is present as a key-value pair at the top level in the JSON content", () => { - verifyFilterFunctionForJson( - JSON.stringify({ access_token: "eyJ0eXA75E_Q" }), - {}, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - JSON.stringify({ access_token: "access_token" }) - ); - }); - - it("Should work for JSON content #7 - JSON.stringify-ed content - regex to be replaced is present as a key-value pair somewhere inside the tree in the JSON content", () => { - verifyFilterFunctionForJson( - JSON.stringify({ - recording: [{ access_token: "eyJ0eXA75E_Q" }], - }), - {}, - [ - (recording: any): any => - recording.replace(/"access_token":"[^"]*"/g, `"access_token":"access_token"`), - ], - JSON.stringify({ - recording: [{ access_token: "access_token" }], - }) - ); - }); - }); - - describe("set environment variables", () => { - it("Should not fail if the dictionary is empty", () => { - env.SECRET = "SECRET"; - const replaceableVariables = {}; - - setEnvironmentVariables(env, replaceableVariables); - }); - - it("Should succeed if the dictionary has one key-value pair", () => { - const replaceableVariables = { SECRET: "FAKE_IT" }; - - setEnvironmentVariables(env, replaceableVariables); - expect(env.SECRET).to.equal("FAKE_IT"); - }); - - it("Should succeed if the dictionary has multiple key-value pairs", () => { - const replaceableVariables = { ACCOUNT_NAME: "fake_account_name", SECRET: "FAKE IT" }; - - setEnvironmentVariables(env, replaceableVariables); - expect(env.SECRET).to.equal("FAKE IT"); - expect(env.ACCOUNT_NAME).to.equal("fake_account_name"); - }); - }); - - describe("generateTestRecordingFilePath", () => { - it("Should generate a properly formatted path on platform: Node", function () { - const platform = "node"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const result = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - expect(result).to.equal( - `${platform}/utils_generatetestrecordingfilepath/recording_should_generate_a_properly_formatted_path_on_platform_node.js` - ); - }); - - it("Should generate a properly formatted path on platform: Browsers", function () { - const platform = "browsers"; - const testSuiteTitle = this.test!.parent!.fullTitle(); - const testTitle = this.test!.title; - const result = generateTestRecordingFilePath(platform, testSuiteTitle, testTitle); - expect(result).to.equal( - `${platform}/utils_generatetestrecordingfilepath/recording_should_generate_a_properly_formatted_path_on_platform_browsers.json` - ); - }); - }); - - describe("isHex", () => { - ["abc", "1ab", "2b"].forEach((val) => { - it(`isHex(${val}) returns true`, () => { - expect(isHex(val)).to.equal(true, `Valid hex didn't match - ${val}`); - }); - }); - ["abct", ""].forEach((val) => { - it(`isHex(${val}) returns false`, () => { - expect(isHex(val as string)).to.equal(false, `Invalid hex matches - ${val}`); - }); - }); - }); -}); diff --git a/sdk/test-utils/recorder-new/test/utils/server.browser.ts b/sdk/test-utils/recorder/test/utils/server.browser.ts similarity index 100% rename from sdk/test-utils/recorder-new/test/utils/server.browser.ts rename to sdk/test-utils/recorder/test/utils/server.browser.ts diff --git a/sdk/test-utils/recorder-new/test/utils/server.ts b/sdk/test-utils/recorder/test/utils/server.ts similarity index 96% rename from sdk/test-utils/recorder-new/test/utils/server.ts rename to sdk/test-utils/recorder/test/utils/server.ts index fa2faea4d289..bfb58761e31e 100644 --- a/sdk/test-utils/recorder-new/test/utils/server.ts +++ b/sdk/test-utils/recorder/test/utils/server.ts @@ -9,6 +9,7 @@ const port = 8080; const TEST_SERVER_URL = `http://localhost:${port}`; app.use(express.json()); app.use(express.text()); +app.set("etag", false); // turn off app.get("/", (_, res) => { res.send("Hello world!"); diff --git a/sdk/test-utils/recorder-new/test/utils/utils.ts b/sdk/test-utils/recorder/test/utils/utils.ts similarity index 100% rename from sdk/test-utils/recorder-new/test/utils/utils.ts rename to sdk/test-utils/recorder/test/utils/utils.ts diff --git a/sdk/test-utils/recorder/tsconfig.json b/sdk/test-utils/recorder/tsconfig.json index 7a1844090cc9..cc27adb959de 100644 --- a/sdk/test-utils/recorder/tsconfig.json +++ b/sdk/test-utils/recorder/tsconfig.json @@ -2,10 +2,8 @@ "extends": "../../../tsconfig.package", "compilerOptions": { "declarationDir": "./types", - "target": "es2017", "outDir": "./dist-esm", - "lib": ["dom", "es5", "es6", "es7", "esnext"] + "lib": ["dom"] }, - "exclude": ["node_modules", "./types/**/*.d.ts", "./samples/**/*.ts"], "include": ["./src/**/*.ts", "./test/**/*.ts"] } diff --git a/sdk/test-utils/testing-recorder-new/karma.conf.js b/sdk/test-utils/testing-recorder-new/karma.conf.js index 22f1de375f8e..2fd2b7042da3 100644 --- a/sdk/test-utils/testing-recorder-new/karma.conf.js +++ b/sdk/test-utils/testing-recorder-new/karma.conf.js @@ -1,5 +1,5 @@ // https://github.com/karma-runner/karma-chrome-launcher -const { relativeRecordingsPath } = require("@azure-tools/test-recorder-new"); +const { relativeRecordingsPath } = require("@azure-tools/test-recorder"); process.env.CHROME_BIN = require("puppeteer").executablePath(); require("dotenv").config({ path: "./.env" }); diff --git a/sdk/test-utils/testing-recorder-new/package.json b/sdk/test-utils/testing-recorder-new/package.json index d1ddb0f22c43..94e2ab4bcf30 100644 --- a/sdk/test-utils/testing-recorder-new/package.json +++ b/sdk/test-utils/testing-recorder-new/package.json @@ -60,7 +60,7 @@ "devDependencies": { "@azure/core-util": "^1.0.0-beta.1", "@azure/identity": "^2.0.1", - "@azure-tools/test-recorder-new": "^1.0.0", + "@azure-tools/test-recorder": "^2.0.0", "@azure-tools/test-credential": "^1.0.0", "@azure/core-auth": "^1.3.2", "@azure/storage-queue": "^12.6.0", @@ -102,7 +102,7 @@ "typescript": "~4.2.0", "xhr-mock": "^2.4.1", "uuid": "^8.3.0", - "@azure/data-tables": "^12.1.2", + "@azure/data-tables": "^13.0.2", "@types/uuid": "^8.0.0" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v1_tests/recording_storagequeue_create_queue.json b/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v1_tests/recording_storagequeue_create_queue.json index 7b144b1703ab..da53502e3c43 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v1_tests/recording_storagequeue_create_queue.json +++ b/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v1_tests/recording_storagequeue_create_queue.json @@ -1,7 +1,7 @@ { "Entries": [ { - "RequestUri": "https://account_name.queue.core.windows.net/queue-1836?sv=2020-08-04\u0026ss=bfqt\u0026srt=sco\u0026sp=rwdlacuptfx\u0026se=2026-07-10T07:00:24Z\u0026st=2021-07-09T23:00:24Z\u0026spr=https\u0026sig=fake_sig\u0026timeout=30", + "RequestUri": "https://account_name.queue.core.windows.net/queue-1002?sv=2020-08-04\u0026ss=bfqt\u0026srt=sco\u0026sp=rwdlacuptfx\u0026se=2026-07-10T07:00:24Z\u0026st=2021-07-09T23:00:24Z\u0026spr=https\u0026sig=fake_sig\u0026timeout=30", "RequestMethod": "PUT", "RequestHeaders": { "Accept": "application/xml", @@ -9,7 +9,6 @@ "Accept-Language": "en-US", "Connection": "keep-alive", "Content-Length": "0", - "Origin": "http://localhost:9328", "Referer": "http://localhost:9328/", "sec-ch-ua": "", "sec-ch-ua-mobile": "?0", @@ -18,28 +17,26 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "ed753236-f065-4292-912d-db14a14f03ab", + "x-ms-client-request-id": "d235fefc-829a-49f8-ae29-2fbbd3317668", "x-ms-version": "2020-10-02" }, "RequestBody": null, "StatusCode": 201, "ResponseHeaders": { - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,Content-Length,Date,Transfer-Encoding", "Content-Length": "0", - "Date": "Tue, 09 Nov 2021 00:35:21 GMT", + "Date": "Fri, 14 Jan 2022 01:54:16 GMT", "Server": [ "Windows-Azure-Queue/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "ed753236-f065-4292-912d-db14a14f03ab", - "x-ms-request-id": "2f6fe14f-4003-003e-0a01-d5d0dd000000", + "x-ms-client-request-id": "d235fefc-829a-49f8-ae29-2fbbd3317668", + "x-ms-request-id": "b83e075e-2003-0043-69e9-084c15000000", "x-ms-version": "2020-10-02" }, "ResponseBody": null } ], "Variables": { - "queue-name": "queue-1836" + "queue-name": "queue-1002" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v2_tests/recording_datatables_create_entity.json b/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v2_tests/recording_datatables_create_entity.json index 08cb5a28a034..b4bb44defd35 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v2_tests/recording_datatables_create_entity.json +++ b/sdk/test-utils/testing-recorder-new/recordings/browsers/core_v2_tests/recording_datatables_create_entity.json @@ -10,8 +10,7 @@ "Connection": "keep-alive", "Content-Length": "25", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "Origin": "http://localhost:9328", + "DataServiceVersion": "3.0", "Referer": "http://localhost:9328/", "sec-ch-ua": "", "sec-ch-ua-mobile": "?0", @@ -20,37 +19,35 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "abacc0eb-947c-417e-954a-7ffc7eb177c2", - "x-ms-useragent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 OS/Linuxx86_64", + "x-ms-client-request-id": "a7aeba48-6cd1-4cab-aeca-b19c54a7950c", + "x-ms-useragent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 OS/Linuxx86_64", "x-ms-version": "2019-02-02" }, "RequestBody": { - "TableName": "table1099" + "TableName": "table1662" }, "StatusCode": 201, "ResponseHeaders": { - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,X-Content-Type-Options,Cache-Control,Location,Content-Type,Content-Length,Date,Transfer-Encoding", "Cache-Control": "no-cache", "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", - "Date": "Tue, 09 Nov 2021 00:35:21 GMT", - "Location": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1099\u0027)", + "Date": "Fri, 14 Jan 2022 01:54:17 GMT", + "Location": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1662\u0027)", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "Transfer-Encoding": "chunked", - "x-ms-client-request-id": "abacc0eb-947c-417e-954a-7ffc7eb177c2", - "x-ms-request-id": "0fb4188a-0002-0010-6a01-d5501a000000", + "x-ms-client-request-id": "a7aeba48-6cd1-4cab-aeca-b19c54a7950c", + "x-ms-request-id": "29276732-2002-0043-18e9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": { "odata.metadata": "https://fakeaccountname.table.core.windows.net/$metadata#Tables/@Element", - "TableName": "table1099" + "TableName": "table1662" } }, { - "RequestUri": "https://fakeaccountname.table.core.windows.net/table1099?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestUri": "https://fakeaccountname.table.core.windows.net/table1662?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", "RequestMethod": "POST", "RequestHeaders": { "Accept": "application/json;odata=minimalmetadata", @@ -59,9 +56,8 @@ "Connection": "keep-alive", "Content-Length": "366", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "Origin": "http://localhost:9328", - "prefer": "return-no-content", + "DataServiceVersion": "3.0", + "Prefer": "return-no-content", "Referer": "http://localhost:9328/", "sec-ch-ua": "", "sec-ch-ua-mobile": "?0", @@ -70,8 +66,8 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "4c8bce23-dac7-4910-b422-6d023fe1f84d", - "x-ms-useragent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 OS/Linuxx86_64", + "x-ms-client-request-id": "049b6375-7870-4e04-aec2-5267726f3853", + "x-ms-useragent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 OS/Linuxx86_64", "x-ms-version": "2019-02-02" }, "RequestBody": { @@ -87,34 +83,31 @@ }, "StatusCode": 204, "ResponseHeaders": { - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,X-Content-Type-Options,Cache-Control,Preference-Applied,Location,DataServiceId,ETag,Content-Length,Date,Transfer-Encoding", "Cache-Control": "no-cache", "Content-Length": "0", - "DataServiceId": "https://fakeaccountname.table.core.windows.net/table1099(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", - "Date": "Tue, 09 Nov 2021 00:35:22 GMT", - "ETag": "W/\u0022datetime\u00272021-11-09T00%3A35%3A22.767648Z\u0027\u0022", - "Location": "https://fakeaccountname.table.core.windows.net/table1099(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/table1662(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", + "Date": "Fri, 14 Jan 2022 01:54:17 GMT", + "ETag": "W/\u0022datetime\u00272022-01-14T01%3A54%3A17.7211956Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/table1662(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", "Preference-Applied": "return-no-content", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "4c8bce23-dac7-4910-b422-6d023fe1f84d", - "x-ms-request-id": "0fb4189c-0002-0010-7a01-d5501a000000", + "x-ms-client-request-id": "049b6375-7870-4e04-aec2-5267726f3853", + "x-ms-request-id": "29276745-2002-0043-29e9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null }, { - "RequestUri": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1099\u0027)?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestUri": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1662\u0027)?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", "RequestMethod": "DELETE", "RequestHeaders": { "Accept": "application/json", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US", "Connection": "keep-alive", - "Origin": "http://localhost:9328", "Referer": "http://localhost:9328/", "sec-ch-ua": "", "sec-ch-ua-mobile": "?0", @@ -123,30 +116,28 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "48071f12-8913-4acc-b6f9-414d0b5df23a", - "x-ms-useragent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 OS/Linuxx86_64", + "x-ms-client-request-id": "d747bb36-2443-4c76-a7d4-9c33b391ca81", + "x-ms-useragent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 OS/Linuxx86_64", "x-ms-version": "2019-02-02" }, "RequestBody": null, "StatusCode": 204, "ResponseHeaders": { - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "x-ms-request-id,x-ms-client-request-id,Server,x-ms-version,X-Content-Type-Options,Cache-Control,Content-Length,Date,Transfer-Encoding", "Cache-Control": "no-cache", "Content-Length": "0", - "Date": "Tue, 09 Nov 2021 00:35:22 GMT", + "Date": "Fri, 14 Jan 2022 01:54:17 GMT", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "48071f12-8913-4acc-b6f9-414d0b5df23a", - "x-ms-request-id": "0fb418af-0002-0010-0b01-d5501a000000", + "x-ms-client-request-id": "d747bb36-2443-4c76-a7d4-9c33b391ca81", + "x-ms-request-id": "2927675e-2002-0043-40e9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null } ], "Variables": { - "table-name": "table1099" + "table-name": "table1662" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/browsers/noop_credential_with_tables/recording_should_create_new_table_then_delete.json b/sdk/test-utils/testing-recorder-new/recordings/browsers/noop_credential_with_tables/recording_should_create_new_table_then_delete.json index 60888ca95608..68400c1393a5 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/browsers/noop_credential_with_tables/recording_should_create_new_table_then_delete.json +++ b/sdk/test-utils/testing-recorder-new/recordings/browsers/noop_credential_with_tables/recording_should_create_new_table_then_delete.json @@ -11,8 +11,7 @@ "Connection": "keep-alive", "Content-Length": "25", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "prefer": "return-content", + "DataServiceVersion": "3.0", "Referer": "http://localhost:9328/", "sec-ch-ua": "", "sec-ch-ua-mobile": "?0", @@ -21,37 +20,36 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "73505eb1-249a-4907-a923-31a56ecb6ab2", - "x-ms-useragent": "azsdk-js-data-tables/12.1.2 core-rest-pipeline/1.3.2 OS/Linuxx86_64", + "x-ms-client-request-id": "0a10863d-74b6-40fb-8baf-883b54ee71df", + "x-ms-useragent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 OS/Linuxx86_64", "x-ms-version": "2019-02-02" }, "RequestBody": { - "TableName": "table1678" + "TableName": "table1883" }, "StatusCode": 201, "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", - "Date": "Wed, 01 Dec 2021 03:33:59 GMT", - "Location": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1678\u0027)", - "Preference-Applied": "return-content", + "Date": "Fri, 14 Jan 2022 01:54:17 GMT", + "Location": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1883\u0027)", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "Transfer-Encoding": "chunked", "X-Content-Type-Options": "nosniff", - "x-ms-client-request-id": "73505eb1-249a-4907-a923-31a56ecb6ab2", - "x-ms-request-id": "f4a38afa-9002-0015-7164-e64326000000", + "x-ms-client-request-id": "0a10863d-74b6-40fb-8baf-883b54ee71df", + "x-ms-request-id": "cb9608e3-2002-0103-49e9-08c4ed000000", "x-ms-version": "2019-02-02" }, "ResponseBody": { "odata.metadata": "https://fakeaccount.table.core.windows.net/$metadata#Tables/@Element", - "TableName": "table1678" + "TableName": "table1883" } }, { - "RequestUri": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1678\u0027)", + "RequestUri": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1883\u0027)", "RequestMethod": "DELETE", "RequestHeaders": { "Accept": "application/json", @@ -67,8 +65,8 @@ "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/93.0.4577.0 Safari/537.36", - "x-ms-client-request-id": "4a1c3793-6746-42a4-9c4c-24957e49217d", - "x-ms-useragent": "azsdk-js-data-tables/12.1.2 core-rest-pipeline/1.3.2 OS/Linuxx86_64", + "x-ms-client-request-id": "7acbf357-ecb5-4d35-af32-46d20567abdb", + "x-ms-useragent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 OS/Linuxx86_64", "x-ms-version": "2019-02-02" }, "RequestBody": null, @@ -76,20 +74,20 @@ "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Length": "0", - "Date": "Wed, 01 Dec 2021 03:33:59 GMT", + "Date": "Fri, 14 Jan 2022 01:54:17 GMT", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "X-Content-Type-Options": "nosniff", - "x-ms-client-request-id": "4a1c3793-6746-42a4-9c4c-24957e49217d", - "x-ms-request-id": "f4a38b1c-9002-0015-1164-e64326000000", + "x-ms-client-request-id": "7acbf357-ecb5-4d35-af32-46d20567abdb", + "x-ms-request-id": "cb9608ec-2002-0103-50e9-08c4ed000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null } ], "Variables": { - "table-name": "table1678" + "table-name": "table1883" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/node/core_v1_tests/recording_storagequeue_create_queue.json b/sdk/test-utils/testing-recorder-new/recordings/node/core_v1_tests/recording_storagequeue_create_queue.json index b90db01b2cbe..8ced97705a07 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/node/core_v1_tests/recording_storagequeue_create_queue.json +++ b/sdk/test-utils/testing-recorder-new/recordings/node/core_v1_tests/recording_storagequeue_create_queue.json @@ -1,7 +1,7 @@ { "Entries": [ { - "RequestUri": "https://account_name.queue.core.windows.net/queue-1391?sv=2020-08-04\u0026ss=bfqt\u0026srt=sco\u0026sp=rwdlacuptfx\u0026se=2026-07-10T07:00:24Z\u0026st=2021-07-09T23:00:24Z\u0026spr=https\u0026sig=fake_sig\u0026timeout=30", + "RequestUri": "https://account_name.queue.core.windows.net/queue-1323?sv=2020-08-04\u0026ss=bfqt\u0026srt=sco\u0026sp=rwdlacuptfx\u0026se=2026-07-10T07:00:24Z\u0026st=2021-07-09T23:00:24Z\u0026spr=https\u0026sig=fake_sig\u0026timeout=30", "RequestMethod": "PUT", "RequestHeaders": { "Accept": "application/xml", @@ -9,27 +9,27 @@ "Connection": "keep-alive", "Content-Length": "0", "Cookie": "", - "User-Agent": "azsdk-js-storagequeue/12.7.1 (NODE-VERSION v14.17.6; Linux 5.4.0-1062-azure)", - "x-ms-client-request-id": "2b314843-fb30-456e-bb5a-d4b0fe43a4ee", + "User-Agent": "azsdk-js-storagequeue/12.7.0 (NODE-VERSION v14.17.6; Linux 5.4.0-1065-azure)", + "x-ms-client-request-id": "4efd04b3-672a-49d6-a242-a159d9ff671e", "x-ms-version": "2020-10-02" }, "RequestBody": null, - "StatusCode": 201, + "StatusCode": 204, "ResponseHeaders": { "Content-Length": "0", - "Date": "Tue, 09 Nov 2021 00:35:20 GMT", + "Date": "Fri, 14 Jan 2022 01:53:25 GMT", "Server": [ "Windows-Azure-Queue/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "2b314843-fb30-456e-bb5a-d4b0fe43a4ee", - "x-ms-request-id": "2f6fe0a7-4003-003e-7d01-d5d0dd000000", + "x-ms-client-request-id": "4efd04b3-672a-49d6-a242-a159d9ff671e", + "x-ms-request-id": "1a03e05a-2003-002e-39e9-08e63b000000", "x-ms-version": "2020-10-02" }, "ResponseBody": null } ], "Variables": { - "queue-name": "queue-1391" + "queue-name": "queue-1323" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/node/core_v2_tests/recording_datatables_create_entity.json b/sdk/test-utils/testing-recorder-new/recordings/node/core_v2_tests/recording_datatables_create_entity.json index fd55a9961c75..3c93e293d0d1 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/node/core_v2_tests/recording_datatables_create_entity.json +++ b/sdk/test-utils/testing-recorder-new/recordings/node/core_v2_tests/recording_datatables_create_entity.json @@ -9,36 +9,36 @@ "Connection": "keep-alive", "Content-Length": "25", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "User-Agent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 Node/v14.17.6 OS/(x64-Linux-5.4.0-1062-azure)", - "x-ms-client-request-id": "9d900c8f-b6fb-436e-9636-5e541f356018", + "DataServiceVersion": "3.0", + "User-Agent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 Node/v14.17.6 OS/(x64-Linux-5.4.0-1065-azure)", + "x-ms-client-request-id": "3094197f-0e2e-4aef-861c-b28ee9315896", "x-ms-version": "2019-02-02" }, "RequestBody": { - "TableName": "table1472" + "TableName": "table1676" }, "StatusCode": 201, "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", - "Date": "Tue, 09 Nov 2021 00:35:20 GMT", - "Location": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1472\u0027)", + "Date": "Fri, 14 Jan 2022 01:53:26 GMT", + "Location": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1676\u0027)", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "Transfer-Encoding": "chunked", - "x-ms-client-request-id": "9d900c8f-b6fb-436e-9636-5e541f356018", - "x-ms-request-id": "0fb416a2-0002-0010-4801-d5501a000000", + "x-ms-client-request-id": "3094197f-0e2e-4aef-861c-b28ee9315896", + "x-ms-request-id": "2927327d-2002-0043-59e9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": { "odata.metadata": "https://fakeaccountname.table.core.windows.net/$metadata#Tables/@Element", - "TableName": "table1472" + "TableName": "table1676" } }, { - "RequestUri": "https://fakeaccountname.table.core.windows.net/table1472?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestUri": "https://fakeaccountname.table.core.windows.net/table1676?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", "RequestMethod": "POST", "RequestHeaders": { "Accept": "application/json;odata=minimalmetadata", @@ -46,10 +46,10 @@ "Connection": "keep-alive", "Content-Length": "366", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "prefer": "return-no-content", - "User-Agent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 Node/v14.17.6 OS/(x64-Linux-5.4.0-1062-azure)", - "x-ms-client-request-id": "870cf487-ebba-4668-91e7-7c7fefbe8444", + "DataServiceVersion": "3.0", + "Prefer": "return-no-content", + "User-Agent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 Node/v14.17.6 OS/(x64-Linux-5.4.0-1065-azure)", + "x-ms-client-request-id": "cfb2810f-5dc8-4951-9af4-5ebf61d16a86", "x-ms-version": "2019-02-02" }, "RequestBody": { @@ -67,30 +67,30 @@ "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Length": "0", - "DataServiceId": "https://fakeaccountname.table.core.windows.net/table1472(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", - "Date": "Tue, 09 Nov 2021 00:35:20 GMT", - "ETag": "W/\u0022datetime\u00272021-11-09T00%3A35%3A21.2615087Z\u0027\u0022", - "Location": "https://fakeaccountname.table.core.windows.net/table1472(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", + "DataServiceId": "https://fakeaccountname.table.core.windows.net/table1676(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", + "Date": "Fri, 14 Jan 2022 01:53:26 GMT", + "ETag": "W/\u0022datetime\u00272022-01-14T01%3A53%3A27.3093406Z\u0027\u0022", + "Location": "https://fakeaccountname.table.core.windows.net/table1676(PartitionKey=\u0027simpleEntity\u0027,RowKey=\u002703590009-4169-46ce-9e33-d011dbaf308c\u0027)", "Preference-Applied": "return-no-content", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "870cf487-ebba-4668-91e7-7c7fefbe8444", - "x-ms-request-id": "0fb416bd-0002-0010-5e01-d5501a000000", + "x-ms-client-request-id": "cfb2810f-5dc8-4951-9af4-5ebf61d16a86", + "x-ms-request-id": "29273294-2002-0043-6de9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null }, { - "RequestUri": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1472\u0027)?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", + "RequestUri": "https://fakeaccountname.table.core.windows.net/Tables(\u0027table1676\u0027)?st=2021-08-03T08:52:15Z\u0026spr=https\u0026sig=fakesigval", "RequestMethod": "DELETE", "RequestHeaders": { "Accept": "application/json", "Accept-Encoding": "gzip,deflate", "Connection": "keep-alive", - "User-Agent": "azsdk-js-data-tables/12.2.0 core-rest-pipeline/1.3.3 Node/v14.17.6 OS/(x64-Linux-5.4.0-1062-azure)", - "x-ms-client-request-id": "f6f82804-36f3-46ae-99ea-be0a3a3af5c6", + "User-Agent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 Node/v14.17.6 OS/(x64-Linux-5.4.0-1065-azure)", + "x-ms-client-request-id": "44932b30-c8c5-48ce-b8b3-202d5c49dd2d", "x-ms-version": "2019-02-02" }, "RequestBody": null, @@ -98,19 +98,19 @@ "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Length": "0", - "Date": "Tue, 09 Nov 2021 00:35:20 GMT", + "Date": "Fri, 14 Jan 2022 01:53:27 GMT", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], - "x-ms-client-request-id": "f6f82804-36f3-46ae-99ea-be0a3a3af5c6", - "x-ms-request-id": "0fb416d1-0002-0010-7101-d5501a000000", + "x-ms-client-request-id": "44932b30-c8c5-48ce-b8b3-202d5c49dd2d", + "x-ms-request-id": "292732a6-2002-0043-7de9-084c15000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null } ], "Variables": { - "table-name": "table1472" + "table-name": "table1676" } } diff --git a/sdk/test-utils/testing-recorder-new/recordings/node/noop_credential_with_tables/recording_should_create_new_table_then_delete.json b/sdk/test-utils/testing-recorder-new/recordings/node/noop_credential_with_tables/recording_should_create_new_table_then_delete.json index 270ce20aab69..88665b857840 100644 --- a/sdk/test-utils/testing-recorder-new/recordings/node/noop_credential_with_tables/recording_should_create_new_table_then_delete.json +++ b/sdk/test-utils/testing-recorder-new/recordings/node/noop_credential_with_tables/recording_should_create_new_table_then_delete.json @@ -10,47 +10,45 @@ "Connection": "keep-alive", "Content-Length": "25", "Content-Type": "application/json;odata=nometadata", - "dataserviceversion": "3.0", - "prefer": "return-content", - "User-Agent": "azsdk-js-data-tables/12.1.2 core-rest-pipeline/1.3.2 Node/v14.17.6 OS/(x64-Linux-5.4.0-1063-azure)", - "x-ms-client-request-id": "32ba8e90-d7e1-493f-96b3-8f4e85e0683b", + "DataServiceVersion": "3.0", + "User-Agent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 Node/v14.17.6 OS/(x64-Linux-5.4.0-1065-azure)", + "x-ms-client-request-id": "0d698f5f-1240-4362-a28a-f3d6cc9e5749", "x-ms-version": "2019-02-02" }, "RequestBody": { - "TableName": "table1869" + "TableName": "table1209" }, "StatusCode": 201, "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Type": "application/json; odata=minimalmetadata; streaming=true; charset=utf-8", - "Date": "Wed, 01 Dec 2021 03:33:54 GMT", - "Location": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1869\u0027)", - "Preference-Applied": "return-content", + "Date": "Fri, 14 Jan 2022 01:53:28 GMT", + "Location": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1209\u0027)", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "Transfer-Encoding": "chunked", "X-Content-Type-Options": "nosniff", - "x-ms-client-request-id": "32ba8e90-d7e1-493f-96b3-8f4e85e0683b", - "x-ms-request-id": "76817f31-4002-007a-8064-e6ebf2000000", + "x-ms-client-request-id": "0d698f5f-1240-4362-a28a-f3d6cc9e5749", + "x-ms-request-id": "cb95d0c1-2002-0103-49e9-08c4ed000000", "x-ms-version": "2019-02-02" }, "ResponseBody": { "odata.metadata": "https://fakeaccount.table.core.windows.net/$metadata#Tables/@Element", - "TableName": "table1869" + "TableName": "table1209" } }, { - "RequestUri": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1869\u0027)", + "RequestUri": "https://fakeaccount.table.core.windows.net/Tables(\u0027table1209\u0027)", "RequestMethod": "DELETE", "RequestHeaders": { "Accept": "application/json", "Accept-Encoding": "gzip,deflate", "Authorization": "Sanitized", "Connection": "keep-alive", - "User-Agent": "azsdk-js-data-tables/12.1.2 core-rest-pipeline/1.3.2 Node/v14.17.6 OS/(x64-Linux-5.4.0-1063-azure)", - "x-ms-client-request-id": "1289757b-56ab-4e6f-87a7-eee3c49f498b", + "User-Agent": "azsdk-js-data-tables/13.0.2 core-rest-pipeline/1.4.1 Node/v14.17.6 OS/(x64-Linux-5.4.0-1065-azure)", + "x-ms-client-request-id": "6896a9bc-ab06-4a86-be2a-23d77ca7f035", "x-ms-version": "2019-02-02" }, "RequestBody": null, @@ -58,20 +56,20 @@ "ResponseHeaders": { "Cache-Control": "no-cache", "Content-Length": "0", - "Date": "Wed, 01 Dec 2021 03:33:54 GMT", + "Date": "Fri, 14 Jan 2022 01:53:28 GMT", "Server": [ "Windows-Azure-Table/1.0", "Microsoft-HTTPAPI/2.0" ], "X-Content-Type-Options": "nosniff", - "x-ms-client-request-id": "1289757b-56ab-4e6f-87a7-eee3c49f498b", - "x-ms-request-id": "76817f46-4002-007a-1464-e6ebf2000000", + "x-ms-client-request-id": "6896a9bc-ab06-4a86-be2a-23d77ca7f035", + "x-ms-request-id": "cb95d20e-2002-0103-7be9-08c4ed000000", "x-ms-version": "2019-02-02" }, "ResponseBody": null } ], "Variables": { - "table-name": "table1869" + "table-name": "table1209" } } diff --git a/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts b/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts index 597e094edcdb..32d9f0396cc7 100644 --- a/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts +++ b/sdk/test-utils/testing-recorder-new/test/core-v1-test.spec.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { QueueServiceClient } from "@azure/storage-queue"; -import { Recorder, RecorderStartOptions } from "@azure-tools/test-recorder-new"; +import { Recorder, RecorderStartOptions } from "@azure-tools/test-recorder"; import { assertEnvironmentVariable } from "./utils/utils"; const fakeSASUrl = diff --git a/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts b/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts index 19e2a90de759..99293a53e441 100644 --- a/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts +++ b/sdk/test-utils/testing-recorder-new/test/core-v2-test.spec.ts @@ -2,9 +2,8 @@ // Licensed under the MIT license. import { TableEntity, TableClient } from "@azure/data-tables"; -import { Recorder, RecorderStartOptions, env } from "@azure-tools/test-recorder-new"; +import { Recorder, RecorderStartOptions, env, SanitizerOptions } from "@azure-tools/test-recorder"; import { createSimpleEntity, assertEnvironmentVariable } from "./utils/utils"; -import { SanitizerOptions } from "@azure-tools/test-recorder-new"; const fakeConnString = "TableEndpoint=https://fakeaccountname.table.core.windows.net/;SharedAccessSignature=st=2021-08-03T08:52:15Z&spr=https&sig=fakesigval"; diff --git a/sdk/test-utils/testing-recorder-new/test/noOpCredentialTest.spec.ts b/sdk/test-utils/testing-recorder-new/test/noOpCredentialTest.spec.ts index 598f76e053ad..a34afb135df0 100644 --- a/sdk/test-utils/testing-recorder-new/test/noOpCredentialTest.spec.ts +++ b/sdk/test-utils/testing-recorder-new/test/noOpCredentialTest.spec.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { RecorderStartOptions, Recorder, env } from "@azure-tools/test-recorder-new"; +import { RecorderStartOptions, Recorder, env } from "@azure-tools/test-recorder"; import { createTestCredential } from "@azure-tools/test-credential"; import { TokenCredential } from "@azure/core-auth"; import { TableServiceClient } from "@azure/data-tables"; diff --git a/sdk/test-utils/testing-recorder-new/test/utils/utils.ts b/sdk/test-utils/testing-recorder-new/test/utils/utils.ts index 34c092d73ab5..bcac68dfbfdb 100644 --- a/sdk/test-utils/testing-recorder-new/test/utils/utils.ts +++ b/sdk/test-utils/testing-recorder-new/test/utils/utils.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { TableEntity } from "@azure/data-tables"; -import { env } from "@azure-tools/test-recorder-new"; +import { env } from "@azure-tools/test-recorder"; const stringValue = "This is a string";