Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions sdk/test-utils/recorder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@

## 2.0.0 (Unreleased)

## 2022-01-27

Add support for the new string sanitizers, including **breaking changes**:

- Removed the `Sanitizer` class, instead making the `addSanitizers` function in `sanitizer.ts` take in a `HttpClient` and recording ID as parameter.
- Refactored the `addSanitizers` function to call smaller functions for each sanitizer (some of which are a bit FP-style) instead of using if statements + special cases. Hopefully this will make things a bit easier to maintain.
- Some other minor refactors (e.g. extracting duplicated `createRecordingRequest` function into a utility).
- Add support for the string sanitizers in what I think is the most logical way, but there is a **breaking change**:
- When calling `addSanitizers`, instead of specifying `generalRegexSanitizers: [...]` etc., you now specify `generalSanitizers: [...]`. Both regex sanitizers and string sanitizers can be used in this way, for example:

```ts
recorder.addSanitizers({
generalSanitizers: [
{
regex: true, // Regex matching is enabled by setting the 'regex' option to true.
target: ".*regex",
value: "sanitized",
},
{
// Note that `regex` defaults to false and doesn't need to be specified when working with bare strings.
// In my experience, this is the most common scenario anyway.
target: "Not a regex",
value: "sanitized",
},
],
});
```

[#19954](https://github.com/Azure/azure-sdk-for-js/pull/19954)

## 2022-01-06

- Renaming the package `@azure-tools/test-recorder-new@1.0.0` as `@azure-tools/test-recorder@2.0.0`.
Expand Down
19 changes: 13 additions & 6 deletions sdk/test-utils/recorder/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The new recorder is version 2.0.0 of the `@azure-tools/test-recorder` package. U
// ...
"devDependencies": {
// ...
"@azure-tools/test-recorder": "^2.0.0",
"@azure-tools/test-recorder": "^2.0.0"
}
}
```
Expand Down Expand Up @@ -152,23 +152,30 @@ In this example, the name of the queue used in the recording is randomized. Howe

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.

### GeneralRegexSanitizer
### General sanitizers

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

```ts
await recorder.addSanitizers({
generalRegexSanitizers: [
generalSanitizers: [
Comment thread
HarshaNalluru marked this conversation as resolved.
{
regex: "find", // This should be a .NET regular expression as it is passed to the .NET proxy tool
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 would replace all instances of `find` in the recording with `replace`.
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

Expand Down
52 changes: 26 additions & 26 deletions sdk/test-utils/recorder/src/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

import {
createDefaultHttpClient,
createPipelineRequest,
HttpClient,
HttpMethods,
Pipeline,
PipelinePolicy,
PipelineRequest,
Expand All @@ -27,7 +25,7 @@ import { Test } from "mocha";
import { sessionFilePath } from "./utils/sessionFilePath";
import { SanitizerOptions } from "./utils/utils";
import { paths } from "./utils/paths";
import { Sanitizer } from "./sanitizer";
import { addSanitizers, transformsInfo } from "./sanitizer";
import { handleEnvSetup } from "./utils/envSetupForPlayback";
import { Matcher, setMatcher } from "./matcher";
import {
Expand All @@ -37,6 +35,7 @@ import {
WebResource,
WebResourceLike,
} from "@azure/core-http";
import { createRecordingRequest } from "./utils/createRecordingRequest";

/**
* This client manages the recorder life cycle and interacts with the proxy-tool to do the recording,
Expand All @@ -55,7 +54,6 @@ export class Recorder {
private stateManager = new RecordingStateManager();
private httpClient?: HttpClient;
private sessionFile?: string;
private sanitizer?: Sanitizer;
private variables: Record<string, string>;

constructor(private testContext?: Test | undefined) {
Expand All @@ -68,7 +66,6 @@ export class Recorder {
"Unable to determine the recording file path, testContext provided is not defined."
);
}
this.sanitizer = new Sanitizer(this.url, this.httpClient);
}
this.variables = {};
}
Expand Down Expand Up @@ -113,8 +110,12 @@ export class Recorder {
*/
async addSanitizers(options: SanitizerOptions): Promise<void> {
// 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);
if (
isRecordMode() &&
ensureExistence(this.httpClient, "this.httpClient") &&
ensureExistence(this.recordingId, "this.recordingId")
) {
return addSanitizers(this.httpClient, this.url, this.recordingId, options);
}
}

Expand All @@ -135,7 +136,7 @@ export class Recorder {
const startUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${
paths.start
}`;
const req = this._createRecordingRequest(startUri);
const req = createRecordingRequest(startUri, this.sessionFile, this.recordingId);

if (ensureExistence(this.httpClient, "TestProxyHttpClient.httpClient")) {
const rsp = await this.httpClient.sendRequest({
Expand All @@ -153,12 +154,14 @@ export class Recorder {
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);
}

await handleEnvSetup(
this.httpClient,
this.url,
this.recordingId,
options.envSetupForPlayback
);

// 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
Expand All @@ -177,7 +180,7 @@ export class Recorder {
this.stateManager.state = "stopped";
if (this.recordingId !== undefined) {
const stopUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${paths.stop}`;
const req = this._createRecordingRequest(stopUri);
const req = createRecordingRequest(stopUri, undefined, this.recordingId);
req.headers.set("x-recording-save", "true");

if (isRecordMode()) {
Expand Down Expand Up @@ -211,19 +214,16 @@ export class Recorder {
}
}

/**
* 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.body = JSON.stringify({ "x-recording-file": this.sessionFile });
async transformsInfo(): Promise<string | null | undefined> {
if (isLiveMode()) {
throw new RecorderError("Cannot call transformsInfo in live mode");
}
if (this.recordingId !== undefined) {
req.headers.set("x-recording-id", this.recordingId);

if (ensureExistence(this.httpClient, "this.httpClient")) {
return await transformsInfo(this.httpClient, this.url, this.recordingId!);
}
return req;

throw new RecorderError("Expected httpClient to be defined");
Comment thread
timovv marked this conversation as resolved.
}

/**
Expand Down
Loading