Skip to content

Commit

Permalink
[Recorder] Add support for session-level sanitizers (Azure#21533)
Browse files Browse the repository at this point in the history
The test proxy has support for adding sanitizers at two levels: at the per-recording level and at the session level. Before this PR, the JS recorder client only allowed for sanitizers to be added at the per-recording level (using the `addSanitizers` instance method on the Recorder class). This PR adds a static `Recorder.addSessionSanitizers(...)` method which allows for sanitizers to be set at the session level. These sanitizers will be applied to every recording following the call.
  • Loading branch information
timovv authored Apr 21, 2022
1 parent 93d2c21 commit 11c2645
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 58 deletions.
2 changes: 2 additions & 0 deletions sdk/test-utils/recorder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Add support for session-level sanitization using the `Recorder.addSessionSanitizers` static method. [#21533](https://github.com/Azure/azure-sdk-for-js/pull/21533)

### Breaking Changes

### Bugs Fixed
Expand Down
47 changes: 38 additions & 9 deletions sdk/test-utils/recorder/src/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { AdditionalPolicyConfig } from "@azure/core-client";
* Other than configuring your clients, use `start`, `stop`, `addSanitizers` methods to use the recorder.
*/
export class Recorder {
private url = "http://localhost:5000";
private static url = "http://localhost:5000";
public recordingId?: string;
private stateManager = new RecordingStateManager();
private httpClient?: HttpClient;
Expand Down Expand Up @@ -81,7 +81,7 @@ export class Recorder {
private redirectRequest(request: WebResource | PipelineRequest): void {
const upstreamUrl = new URL(request.url);
const redirectedUrl = new URL(request.url);
const testProxyUrl = new URL(this.url);
const testProxyUrl = new URL(Recorder.url);

// Sometimes, due to the service returning a redirect or due to the retry policy, redirectRequest
// may be called multiple times. We only want to update the request the second time if the request's
Expand All @@ -104,6 +104,7 @@ export class Recorder {
redirectedUrl.host = testProxyUrl.host;
redirectedUrl.port = testProxyUrl.port;
redirectedUrl.protocol = testProxyUrl.protocol;

request.headers.set("x-recording-upstream-base-uri", upstreamUrl.toString());
request.url = redirectedUrl.toString();

Expand Down Expand Up @@ -134,7 +135,33 @@ export class Recorder {
ensureExistence(this.httpClient, "this.httpClient") &&
ensureExistence(this.recordingId, "this.recordingId")
) {
return addSanitizers(this.httpClient, this.url, this.recordingId, options);
return addSanitizers(this.httpClient, Recorder.url, this.recordingId, options);
}
}

/**
* addSessionSanitizers adds the sanitizers for all the following recordings which will be applied on it before being saved.
* This lets you call addSessionSanitizers once (e.g. in a global before() in your tests). The sanitizers will be applied
* to every subsequent test.
*
* Takes SanitizerOptions as the input, passes on to the proxy-tool.
*
* By default, it applies only to record mode.
*
* If you want this to be applied in a specific mode or in a combination of modes, use the "mode" argument.
*/
static async addSessionSanitizers(
options: SanitizerOptions,
mode: ("record" | "playback")[] = ["record"]
): Promise<void> {
if (isLiveMode()) {
return;
}

const actualTestMode = getTestMode() as "record" | "playback";
if (mode.includes(actualTestMode)) {
const httpClient = createDefaultHttpClient();
return addSanitizers(httpClient, Recorder.url, undefined, options);
}
}

Expand All @@ -144,7 +171,7 @@ export class Recorder {
ensureExistence(this.httpClient, "this.httpClient") &&
ensureExistence(this.recordingId, "this.recordingId")
) {
await addTransform(this.url, this.httpClient, transform, this.recordingId);
await addTransform(Recorder.url, this.httpClient, transform, this.recordingId);
}
}

Expand All @@ -162,7 +189,7 @@ export class Recorder {
if (isLiveMode()) return;
this.stateManager.state = "started";
if (this.recordingId === undefined) {
const startUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${
const startUri = `${Recorder.url}${isPlaybackMode() ? paths.playback : paths.record}${
paths.start
}`;
const req = createRecordingRequest(startUri, this.sessionFile, this.recordingId);
Expand All @@ -186,7 +213,7 @@ export class Recorder {

await handleEnvSetup(
this.httpClient,
this.url,
Recorder.url,
this.recordingId,
options.envSetupForPlayback
);
Expand All @@ -208,7 +235,9 @@ export class Recorder {
if (isLiveMode()) return;
this.stateManager.state = "stopped";
if (this.recordingId !== undefined) {
const stopUri = `${this.url}${isPlaybackMode() ? paths.playback : paths.record}${paths.stop}`;
const stopUri = `${Recorder.url}${isPlaybackMode() ? paths.playback : paths.record}${
paths.stop
}`;
const req = createRecordingRequest(stopUri, undefined, this.recordingId);
req.headers.set("x-recording-save", "true");

Expand Down Expand Up @@ -247,7 +276,7 @@ export class Recorder {
throw new RecorderError("httpClient should be defined in playback mode");
}

await setMatcher(this.url, this.httpClient, matcher, this.recordingId, options);
await setMatcher(Recorder.url, this.httpClient, matcher, this.recordingId, options);
}
}

Expand All @@ -257,7 +286,7 @@ export class Recorder {
}

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

throw new RecorderError("Expected httpClient to be defined");
Expand Down
80 changes: 35 additions & 45 deletions sdk/test-utils/recorder/src/sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
type AddSanitizer<T> = (
httpClient: HttpClient,
url: string,
recordingId: string,
recordingId: string | undefined,
sanitizer: T
) => Promise<void>;

Expand All @@ -32,7 +32,7 @@ type AddSanitizer<T> = (
*/
const pluralize =
<T>(singular: AddSanitizer<T>): AddSanitizer<T[]> =>
async (httpClient: HttpClient, url: string, recordingId: string, sanitizers: T[]) => {
async (httpClient, url, recordingId, sanitizers) => {
await Promise.all(
sanitizers.map((sanitizer) => singular(httpClient, url, recordingId, sanitizer))
);
Expand All @@ -43,12 +43,7 @@ const pluralize =
*/
const makeAddSanitizer =
(sanitizerName: ProxyToolSanitizers): AddSanitizer<Record<string, unknown>> =>
async (
httpClient: HttpClient,
url: string,
recordingId: string,
sanitizer: Record<string, unknown>
) => {
async (httpClient, url, recordingId, sanitizer) => {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: sanitizerName,
body: sanitizer,
Expand All @@ -61,7 +56,7 @@ const makeAddSanitizer =
*/
const makeAddBodilessSanitizer =
(sanitizerName: ProxyToolSanitizers): AddSanitizer<boolean> =>
async (httpClient: HttpClient, url: string, recordingId: string, enable: boolean) => {
async (httpClient, url, recordingId, enable) => {
if (enable) {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: sanitizerName,
Expand All @@ -80,12 +75,7 @@ const makeAddFindReplaceSanitizer =
regexSanitizerName: ProxyToolSanitizers,
stringSanitizerName: ProxyToolSanitizers
): AddSanitizer<FindReplaceSanitizer> =>
async (
httpClient: HttpClient,
url: string,
recordingId: string,
sanitizer: FindReplaceSanitizer
): Promise<void> => {
async (httpClient, url, recordingId, sanitizer): Promise<void> => {
if (isStringSanitizer(sanitizer)) {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: stringSanitizerName,
Expand All @@ -112,12 +102,12 @@ const makeAddFindReplaceSanitizer =
* - each part of the connection string is mapped with its corresponding fake value
* - GeneralStringSanitizer is applied for each of the parts with the real and fake values that are parsed
*/
async function addConnectionStringSanitizer(
httpClient: HttpClient,
url: string,
recordingId: string,
{ actualConnString, fakeConnString }: ConnectionStringSanitizer
): Promise<void> {
const addConnectionStringSanitizer: AddSanitizer<ConnectionStringSanitizer> = async (
httpClient,
url,
recordingId,
{ actualConnString, fakeConnString }
) => {
if (!actualConnString) {
if (!isRecordMode()) return;
throw new RecorderError(
Expand All @@ -134,42 +124,42 @@ async function addConnectionStringSanitizer(
return { value, target: key };
}),
});
}
};

/**
* Adds a ContinuationSanitizer with the given options.
*/
async function addContinuationSanitizer(
httpClient: HttpClient,
url: string,
recordingId: string,
sanitizer: ContinuationSanitizer
) {
const addContinuationSanitizer: AddSanitizer<ContinuationSanitizer> = async (
httpClient,
url,
recordingId,
sanitizer
) => {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: "ContinuationSanitizer",
body: {
...sanitizer,
resetAfterFirst: sanitizer.resetAfterFirst.toString(),
},
});
}
};

/**
* Adds a RemoveHeaderSanitizer with the given options.
*/
async function addRemoveHeaderSanitizer(
httpClient: HttpClient,
url: string,
recordingId: string,
sanitizer: RemoveHeaderSanitizer
) {
const addRemoveHeaderSanitizer: AddSanitizer<RemoveHeaderSanitizer> = async (
httpClient,
url,
recordingId,
sanitizer
) => {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: "RemoveHeaderSanitizer",
body: {
headersForRemoval: sanitizer.headersForRemoval.toString(),
},
});
}
};

/**
* Adds a HeaderRegexSanitizer or HeaderStringSanitizer.
Expand All @@ -178,12 +168,12 @@ async function addRemoveHeaderSanitizer(
* Additionally, the 'target' option is not required. If target is unspecified, the header's value will always
* be replaced.
*/
async function addHeaderSanitizer(
httpClient: HttpClient,
url: string,
recordingId: string,
sanitizer: HeaderSanitizer
) {
const addHeaderSanitizer: AddSanitizer<HeaderSanitizer> = async (
httpClient,
url,
recordingId,
sanitizer
) => {
if (sanitizer.regex || !sanitizer.target) {
await addSanitizer(httpClient, url, recordingId, {
sanitizer: "HeaderRegexSanitizer",
Expand All @@ -204,7 +194,7 @@ async function addHeaderSanitizer(
},
});
}
}
};

const addSanitizersActions: {
[K in keyof SanitizerOptions]: AddSanitizer<Exclude<SanitizerOptions[K], undefined>>;
Expand All @@ -229,7 +219,7 @@ const addSanitizersActions: {
export async function addSanitizers(
httpClient: HttpClient,
url: string,
recordingId: string,
recordingId: string | undefined,
options: SanitizerOptions
): Promise<void> {
await Promise.all(
Expand All @@ -250,7 +240,7 @@ export async function addSanitizers(
async function addSanitizer(
httpClient: HttpClient,
url: string,
recordingId: string,
recordingId: string | undefined,
options: {
sanitizer: ProxyToolSanitizers;
body: Record<string, unknown> | undefined;
Expand Down
Loading

0 comments on commit 11c2645

Please sign in to comment.