Skip to content

Latest commit

 

History

History
329 lines (231 loc) · 15.2 KB

MIGRATION.md

File metadata and controls

329 lines (231 loc) · 15.2 KB

Migration Guide

This document outlines key differences between the legacy recorder and the new Unified Recorder client. The Unified Recorder replaces the older nock/nise-based recorder(v1) with a solution that uses the language-agnostic test proxy server.

Advantages of migration to v3

  • Recorder v3 handles secrets in the recordings better than v1 leveraging the new sanitizers/transformations approach.
  • There are maintenance benefits to recorder v3, partly owing to the new test-proxy tool that is built to support recorders in various languages.
  • Will allow us to run the tests in parallel in the future.
  • All the recordings will be saved as JSON files instead of being JS files.
  • The new recorder allows tests to imitate the live service more accurately during playback, since real HTTP requests are being made.
  • The new recorder's API is more transparent and is easier to use compared to the old recorder.
  • Leverages the asset-sync workflow to enable you push recordings out of the JS repo.

Upgrading to the Unified Recorder

The new recorder is version 3.x.y of the @azure-tools/test-recorder package. Update the test-recorder dependency in your package.json file as follows:

{
  // ...
  "devDependencies": {
    // ...
    "@azure-tools/test-credential": "^1.0.0", // If you're using `@azure/identity` in your tests
    "@azure-tools/test-recorder": "^3.0.0"
  }
}

Once you've updated the dependency version, run rush update and you are ready to start using the new recorder. The new recorder's API is similar to the legacy recorder. Differences will be discussed below.

Changes to NPM scripts

For the unified recorder client library to work, the test proxy server must be active while you are running your tests. Helpers have been added to the dev-tool package which manage starting and stopping the test proxy server before and after your tests are run.

Update your test scripts based on the following examples:

Script Before migration After migration
unit-test:browser karma start --single-run dev-tool run test:browser
unit-test:node mocha -r esm -r ts-node/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 1200000 --full-trace --exclude \"test/**/browser/*.spec.ts\" \"test/**/*.spec.ts\" dev-tool run test:node-ts-input -- --timeout 1200000 --exclude 'test/**/browser/*.spec.ts' 'test/**/*.spec.ts'
integration-test:browser karma start --single-run dev-tool run test:browser
integration-test:node nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 5000000 \"dist-esm/test/{,!(browser)/**/}*.spec.js\" dev-tool run test:node-js-input -- --timeout 5000000 'dist-esm/test/**/*.spec.js'

Note the difference between the dev-tool node-ts-input and node-js-input commands:

  • node-ts-input runs the tests using ts-node, without code coverage.
  • node-js-input runs the tests using the built JavaScript output, and generates coverage reporting using nyc.

Compare with the older test runs to make sure you're running all the tests/files as before.

Initializing the recorder

The recorder is implemented as a custom policy which should be attached to your client's pipeline. Firstly, initialize the recorder:

import { Recorder } from "@azure-tools/test-recorder";

let recorder: Recorder;

/*
 * Note the use of function() instead of the arrow syntax. We need access to `this` so we
 * can pass test information from Mocha to the recorder.
 */
beforeEach(function (this: Context) {
  recorder = new Recorder(this.currentTest);
});

To enable the recorder, you should then initialize your SDK client as normal and use the recorder's configureClientOptions method. This method will add the necessary policies to the client options' additionalPolicies array for the recording to be enabled. Note that for this method to work, the additionalPolicies options has to be part of the client options.

const client = new MyServiceClient(
  /* ... insert options here ... */,
  recorder.configureClientOptions({ /* any additional options to pass through */ }),
);

Starting and stopping the recorder

The way that the recorder is started and stopped has changed slightly. At the beginning of your test (or in a beforeEach block), start the recorder as follows:

await recorder.start({
  envSetupForPlayback: {
    // Your environment variables (equivalent to the old recorder's replaceableVariables option). See the section on environment variables below for detail
  },
  // Other options, e.g. sanitizers (which replace the customizationsOnRecordings option)
});

And at the end of your test (or in an afterEach block), stop the recorder:

await recorder.stop();

It is important that recorder.stop() is called, or otherwise the next test will throw an error when trying to start the already started recorder. Additionally, it is important that both the start and stop calls are awaited, for similar reasons.

XHRHttpClient

The legacy recorder didn't support recording browser calls made through the Fetch API. As a result, when fetch API was introduced in core-rest-pipeline a workaround was applied to the libraries not yet migrated to the new recorder, this workaround needs to be removed as part of the migration. Make the following changes, typically located in utils/recordedClient.ts

- const httpClient = isNode || isLiveMode() ? undefined : createXhrHttpClient();
- recorder.configureClientOptions({ httpClient, ...options })
+ recorder.configureClientOptions(options)

Environment variables

In the legacy recorder, the replaceableVariables option could be used to specify environment variables that would be replaced in the recording and set during playback. This could be used to ensure that secrets and user-specific options do not appear in the recording body.

The Unified Recorder client provides this functionality through the use of the envSetupForPlayback option, which is passed when recorder.start is called. Like the legacy recorder, it takes in an object mapping environment variables to what they should be replaced with in the recording. For example:

await recorder.start({
  envSetupForPlayback: {
    TABLES_SAS_CONNECTION_STRING: "fakeConnectionString",
  },
});

Under the hood, this is powered by the Unified Recorder's sanitizer functionality.

⚠️Important: To access environment variables, you must use the env export made available from the new recorder. This ensures that environment variables are sourced from the correct location (using process.env and dotenv in Node, and using window.__env__ via karma in the browser), and also means that the environment variables set in envSetupForPlayback are used in playback mode.

recorder.start() internally sets up the environment variables for playback. So, make sure to have the recorder.start() call before you use any environment variables in your tests.

Recorder variables

If you want to compute a value at record time and re-use it during playback, the Unified Recorder's variable functionality is for you. This API lets you declare variables which are stored with the recording at record time. During playback, instead of computing the variable afresh, the value will be retrieved from the recording. A use case of this might be to set a value randomly during record time that needs to be the same during playback.

Here is an example:

const queueName = recorder.variable("queueName", "queue-${Math.floor(Math.random * 1000)}");
// Assume that we have a client that has a createQueue method.
await client.createQueue(queueName);

In this example, the name of the queue used in the recording is randomized. However, in playback, instead of using the value passed into recorder.variable, the value will be retrieved from the recording file. This means that the name of the queue will be consistent between record and playback modes.

Customizations on recordings

A powerful feature of the legacy recorder was its customizationsOnRecordings option, which allowed for arbitrary replacements to be made to recordings. The new recorder's analog to this is the sanitizer functionality.

General sanitizers

For a simple find/replace, generalSanitizers can be used. For example:

await recorder.addSanitizers({
  generalSanitizers: [
    {
      target: "find", // With `regex` unspecified, this matches a plaintext string
      value: "replace",
    },
    {
      regex: true, // Enable regex matching
      target: "[Rr]egex", // This is a .NET regular expression that will be compiled by the proxy tool.
      value: "replace",
    },
    // add additional sanitizers here as required
  ],
});

This example has two sanitizers:

  • The first sanitizer replaces all instances of "find" in the recording with "replace".
  • The second example demonstrates the use of a regular expression for replacement, where anything matching the .NET regular expression [Rr]egex (i.e. "Regex" and "regex") would be replaced with "replace".

ConnectionStringSanitizer

A ConnectionStringSanitizer can be used to strip all occurrences of a connection string from a recording. Its usage is very similar to the GeneralRegexSanitizer. For example:

recorder.addSanitizers({
  connectionStringSanitizers: [
    {
      actualConnString: /* the actual connection string to be replaced, usually passed in as an environment variable */,
      fakeConnString: /* a mock connection string to replace actualConnString with */,
    },
  ],
});

RemoveHeaderSanitizer

RemoveHeaderSanitizer can be used to remove specific headers from the recordings as follows:

recorder.addSanitizers({
  removeHeaderSanitizer: {
    headersForRemoval: ["Header1", "Header2" /* ... */],
  },
});

Other sanitizers for more complex use cases are also available.

AAD and the new NoOpCredential

The new recorder does not record AAD traffic at present. As such, tests with clients using AAD should make use of the new @azure-tools/test-credential package.

This package provides a NoOpCredential implementation of TokenCredential which makes no network requests, and should be used in playback mode. The provided createTestCredential helper will handle switching between NoOpCredential in playback and ClientSecretCredential when recording for you:

import { createTestCredential } from "@azure-tools/test-credential";

const credential = createTestCredential();

// Create your client using the test credential.
new MyServiceClient(<endpoint>, credential);

Since AAD traffic is not recorded by the new recorder, there is no longer a need to remove AAD credentials from the recording using a sanitizer.

Browser tests and modifications to Karma configuration

When running browser tests, the recorder relies on an environment variable to determine where to save the recordings. Add this snippet to your karma.conf.js:

const { relativeRecordingsPath } = require("@azure-tools/test-recorder");

process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();

And then, again in karma.conf.js, add the variable to the list of environment variables:

module.exports = function (config) {
  config.set({
    /* ... */

    envPreprocessor: [
      ,
      /* ... */ "RECORDINGS_RELATIVE_PATH", // Add this!
    ],

    /* ... */
  });
};

The following configuration options in karma.config.js are unnecessary and should be removed:

// imports - to be deleted
const {
  jsonRecordingFilterFunction,
  isPlaybackMode,
  isSoftRecordMode,
  isRecordMode,
} = require("@azure-tools/test-recorder");

// plugins - to be removed
      "karma-json-to-file-reporter",
      "karma-json-preprocessor",

// files section - snippet to remove
     .concat(isPlaybackMode() || isSoftRecordMode() ? ["recordings/browsers/**/*.json"] : [])

// preprocessors - to be removed
      "recordings/browsers/**/*.json": ["json"],

// reporters - to be removed
      "json-to-file"

/* ... */
// jsonToFileReporter - to be removed
jsonToFileReporter: {
  filter: jsonRecordingFilterFunction,  outputPath: ".",
}

/* ... */
// log options - to be removed
browserConsoleLogOptions: {
  terminal: !isRecordMode(),
}

Remove the following "devDependencies" from package.json

   "karma-json-preprocessor": "^0.3.3",
   "karma-json-to-file-reporter": "^1.0.1",

Migrating your recordings

Once you have made the necessary code changes, it is time to re-record your tests using the Unified Recorder to complete the migration.

To do this, first delete the directory containing the old recordings (the recordings folder).

Asset Sync - Push recordings to the Azure/azure-sdk-assets repo

Make sure you do have the Powershell installed.

npx dev-tool test-proxy init - to initialize an assets.json file

Then, run your tests with the TEST_MODE environment variable to record.

npx dev-tool test-proxy push - to push the recordings.

If everything succeeds, the new recordings will be made available in the Azure/azure-sdk-assets repo based on the tag present in the assets.json.

Inspect them to make sure everything looks OK (no secrets present, etc.), and then run the tests in playback mode to ensure everything is passing.

If you're running into issues, check out the Troubleshooting section.

Troubleshooting

If you run into issues while migrating your package, some of the following troubleshooting steps may help:

Viewing test proxy log output

dev-tool by default outputs logs from the test proxy to test-proxy-output.log in your package's root directory. These logs can be inspected to see what requests were made to the proxy tool.