load
This commit is contained in:
269
src/__tests__/polly.ts
Normal file
269
src/__tests__/polly.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import path from "path";
|
||||
import NodeHttpAdapter from "@pollyjs/adapter-node-http";
|
||||
import FetchAdapter from "@pollyjs/adapter-fetch";
|
||||
import { Polly, type Headers, type PollyConfig } from "@pollyjs/core";
|
||||
import FSPersister from "@pollyjs/persister-fs";
|
||||
import { afterEach, beforeEach, expect } from "vitest";
|
||||
import merge from "lodash-es/merge";
|
||||
import omit from "lodash-es/omit";
|
||||
import omitDeep from "omit-deep-lodash";
|
||||
import { tryJsonParse, tryIgnore } from "../lib/utils";
|
||||
import { testEnv } from "@/__tests__/test-env.mjs";
|
||||
import { env } from "@/lib/env.mjs";
|
||||
|
||||
declare module "vitest" {
|
||||
export interface TestContext {
|
||||
polly?: Polly;
|
||||
}
|
||||
}
|
||||
|
||||
export const omitPathsFromJson = (paths: string[]) => (input: string) => {
|
||||
return JSON.stringify(omit(JSON.parse(input) as object, paths));
|
||||
};
|
||||
export const omitPathsFromHeaders = (paths: string[]) => (headers: Headers) => {
|
||||
return omit(headers, paths);
|
||||
};
|
||||
|
||||
const HEADERS_BLACKLIST = new Set([
|
||||
"authorization-bearer",
|
||||
"authorization",
|
||||
"saleor-signature",
|
||||
"set-cookie",
|
||||
"x-api-key",
|
||||
"x-stripe-client-user-agent",
|
||||
]);
|
||||
|
||||
const VARIABLES_BLACKLIST = new Set([
|
||||
"csrfToken",
|
||||
"email",
|
||||
"newEmail",
|
||||
"newPassword",
|
||||
"oldPassword",
|
||||
"password",
|
||||
"redirectUrl",
|
||||
"refreshToken",
|
||||
"token",
|
||||
"authorisationToken",
|
||||
]);
|
||||
|
||||
const removeBlacklistedVariables = (
|
||||
obj: {} | undefined | string | null,
|
||||
): {} | undefined | string | null => {
|
||||
if (!obj || typeof obj === "string") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if ("client_secret" in obj) {
|
||||
obj.client_secret = "pi_FAKE_CLIENT_SECRET";
|
||||
}
|
||||
|
||||
return omitDeep(obj, ...VARIABLES_BLACKLIST);
|
||||
};
|
||||
|
||||
/**
|
||||
* This interface is incomplete
|
||||
*/
|
||||
interface PollyRecording {
|
||||
response: {
|
||||
content?: {
|
||||
mimeType: string;
|
||||
text: string;
|
||||
};
|
||||
cookies: string[];
|
||||
headers: Array<{ value: string; name: string }>;
|
||||
};
|
||||
request: {
|
||||
postData?: {
|
||||
text: string;
|
||||
};
|
||||
cookies: string[];
|
||||
headers: Array<{ value: string; name: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
const responseIsJson = (recording: PollyRecording) => {
|
||||
return recording.response.content?.mimeType.includes("application/json");
|
||||
};
|
||||
const requestIsJson = (recording: PollyRecording) => {
|
||||
return recording.request.headers.some(
|
||||
({ name, value }) =>
|
||||
name.toLowerCase() === "content-type" && value.includes("application/json"),
|
||||
);
|
||||
};
|
||||
|
||||
export const setupRecording = (config?: PollyConfig) => {
|
||||
Polly.on("create", (polly) => {
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
polly.server
|
||||
.any()
|
||||
// Hide sensitive data in headers or in body
|
||||
.on("beforePersist", (_req, recording: PollyRecording) => {
|
||||
recording.response.cookies = [];
|
||||
|
||||
recording.response.headers = recording.response.headers.filter(
|
||||
(el: Record<string, string>) => !HEADERS_BLACKLIST.has(el.name),
|
||||
);
|
||||
recording.request.headers = recording.request.headers.filter(
|
||||
(el: Record<string, string>) => !HEADERS_BLACKLIST.has(el.name),
|
||||
);
|
||||
|
||||
if (recording.request.postData?.text) {
|
||||
const requestJson = tryJsonParse(recording.request.postData.text);
|
||||
const filteredRequestJson = removeBlacklistedVariables(requestJson);
|
||||
recording.request.postData.text =
|
||||
typeof filteredRequestJson === "string"
|
||||
? filteredRequestJson
|
||||
: JSON.stringify(filteredRequestJson);
|
||||
}
|
||||
if (recording.response.content?.text) {
|
||||
const responseJson = tryJsonParse(recording.response.content.text);
|
||||
const filteredResponseJson = removeBlacklistedVariables(responseJson);
|
||||
recording.response.content.text =
|
||||
typeof filteredResponseJson === "string"
|
||||
? filteredResponseJson
|
||||
: JSON.stringify(filteredResponseJson);
|
||||
}
|
||||
})
|
||||
// make JSON response and requests more readable
|
||||
// https://github.com/Netflix/pollyjs/issues/322
|
||||
.on("beforePersist", (_req, recording: PollyRecording) => {
|
||||
if (responseIsJson(recording)) {
|
||||
tryIgnore(
|
||||
() =>
|
||||
(recording.response.content!.text = JSON.parse(
|
||||
recording.response.content!.text,
|
||||
) as string),
|
||||
);
|
||||
}
|
||||
|
||||
if (requestIsJson(recording)) {
|
||||
tryIgnore(
|
||||
() =>
|
||||
(recording.request.postData!.text = JSON.parse(
|
||||
recording.request.postData!.text,
|
||||
) as string),
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("beforeReplay", (_req, recording: PollyRecording) => {
|
||||
if (responseIsJson(recording)) {
|
||||
tryIgnore(
|
||||
() =>
|
||||
(recording.response.content!.text = JSON.stringify(recording.response.content!.text)),
|
||||
);
|
||||
}
|
||||
|
||||
if (requestIsJson(recording)) {
|
||||
tryIgnore(
|
||||
() =>
|
||||
(recording.request.postData!.text = JSON.stringify(recording.request.postData!.text)),
|
||||
);
|
||||
}
|
||||
});
|
||||
/* eslint-enable */
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
...getRecordingSettings(),
|
||||
adapters: [FetchAdapter, NodeHttpAdapter],
|
||||
persister: FSPersister,
|
||||
adapterOptions: {
|
||||
fetch: {
|
||||
context: globalThis,
|
||||
},
|
||||
},
|
||||
persisterOptions: {
|
||||
fs: {},
|
||||
keepUnusedRequests: false,
|
||||
},
|
||||
flushRequestsOnStop: true,
|
||||
matchRequestsBy: {
|
||||
url: {
|
||||
protocol: true,
|
||||
username: true,
|
||||
password: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
pathname: true,
|
||||
query: true,
|
||||
hash: false,
|
||||
},
|
||||
body: true,
|
||||
order: false,
|
||||
method: true,
|
||||
headers: {
|
||||
exclude: [
|
||||
"date",
|
||||
"idempotency-key",
|
||||
"original-request",
|
||||
"request-id",
|
||||
"content-length",
|
||||
"x-stripe-client-user-agent",
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async (ctx) => {
|
||||
const { currentTestName } = expect.getState();
|
||||
if (!currentTestName) {
|
||||
throw new Error("This function must be run inside a test case!");
|
||||
}
|
||||
|
||||
const recordingsRoot = path.dirname(expect.getState().testPath || "");
|
||||
const recordingsDirectory = path.join(recordingsRoot, "__recordings__");
|
||||
|
||||
const [, ...names] = currentTestName.split(" > ");
|
||||
const polly = new Polly(
|
||||
names.join("/"),
|
||||
merge(
|
||||
defaultConfig,
|
||||
{
|
||||
persisterOptions: {
|
||||
fs: {
|
||||
recordingsDir: recordingsDirectory,
|
||||
},
|
||||
},
|
||||
},
|
||||
config,
|
||||
),
|
||||
);
|
||||
ctx.polly = polly;
|
||||
});
|
||||
|
||||
afterEach((ctx) => ctx.polly?.flush());
|
||||
afterEach((ctx) => ctx.polly?.stop());
|
||||
};
|
||||
|
||||
const getRecordingSettings = (): Pick<
|
||||
PollyConfig,
|
||||
"mode" | "recordIfMissing" | "recordFailedRequests"
|
||||
> => {
|
||||
// use replay mode by default, override if POLLY_MODE env variable is passed
|
||||
const mode = env.CI ? "replay" : testEnv.POLLY_MODE;
|
||||
|
||||
if (mode === "record") {
|
||||
return {
|
||||
mode: "record",
|
||||
recordIfMissing: true,
|
||||
recordFailedRequests: true,
|
||||
};
|
||||
}
|
||||
if (mode === "record_missing") {
|
||||
return {
|
||||
mode: "replay",
|
||||
recordIfMissing: true,
|
||||
recordFailedRequests: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
mode: "replay",
|
||||
recordIfMissing: false,
|
||||
recordFailedRequests: false,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user