@gooddata/sdk-e2e-utils 11.46.0-alpha.2 → 11.46.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/goodmock.d.ts CHANGED
@@ -44,14 +44,19 @@ export declare function goodmockMode(): GoodmockMode;
44
44
  * {@link resetMappings} in recording mode.
45
45
  */
46
46
  export declare function startRecording(host: string, backendHost: string): Promise<void>;
47
+ /**
48
+ * @internal
49
+ */
47
50
  export interface IGoodmockMapping {
48
51
  request: {
49
52
  url?: string;
53
+ urlPath?: string;
50
54
  bodyPatterns?: unknown;
51
55
  };
52
56
  response: {
53
57
  headers?: Record<string, string>;
54
- jsonBody?: Map<string, any>;
58
+ jsonBody?: unknown;
59
+ body?: string;
55
60
  };
56
61
  }
57
62
  /**
@@ -61,25 +66,73 @@ export interface IWorkspaceIdMapping {
61
66
  sourceWorkspaceId: string;
62
67
  targetWorkspaceId: string;
63
68
  }
69
+ /**
70
+ * @internal
71
+ * Rewrite of a secret value in saved recordings. Every occurrence of `secret` (request bodies,
72
+ * response bodies, URLs) is replaced with `placeholder`, so tests can type the placeholder at
73
+ * replay time and still match the recorded request exactly.
74
+ */
75
+ export interface ISecretMapping {
76
+ secret: string;
77
+ placeholder: string;
78
+ }
79
+ /**
80
+ * @internal
81
+ * A credential shape that must never appear in a saved recording (checked against the fully
82
+ * serialized output). Provide domain-specific shapes (e.g. a provider's API key format) via
83
+ * {@link ISnapshotAndSaveRecordingOptions.leakPatterns} — this module has no built-in patterns of
84
+ * its own, since it has no knowledge of what a given consumer's traffic considers a secret.
85
+ */
86
+ export interface ILeakPattern {
87
+ label: string;
88
+ pattern: RegExp;
89
+ }
90
+ /**
91
+ * @internal
92
+ */
93
+ export interface ISnapshotAndSaveRecordingOptions {
94
+ /** Source/target workspace ID rewrite(s) applied before mappings are saved. */
95
+ workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
96
+ /**
97
+ * Real backend URL (e.g. "https://example.gooddata.com"). When provided together with
98
+ * baseUrl, all occurrences are replaced in the saved mappings so that recorded responses
99
+ * (e.g. geo tile URLs) resolve correctly during replay.
100
+ */
101
+ backendHost?: string;
102
+ /** App base URL (e.g. "http://kpi-dashboards-ui:9500") that replaces backendHost references. */
103
+ baseUrl?: string;
104
+ /**
105
+ * Secret rewrites. Each secret value is replaced by its placeholder everywhere in the saved
106
+ * mappings (request bodyPatterns included, and masked variants such as "sk-prox***abcd").
107
+ * Saving fails hard if any secret still remains afterwards.
108
+ */
109
+ secretMappings?: ISecretMapping[];
110
+ /**
111
+ * Credential-shaped patterns that must never appear anywhere in the saved output, regardless
112
+ * of which field they came from. A backstop for secrets `secretMappings` doesn't know about
113
+ * (e.g. a value a backend echoed back that the test never typed). Saving fails hard on a match.
114
+ */
115
+ leakPatterns?: ILeakPattern[];
116
+ /**
117
+ * Consumer-supplied hook to redact domain-specific secrets from the collected mappings (e.g.
118
+ * blank a `content.apiKey` field on a settings entity) before they're merged and written out.
119
+ * Runs once, right after the built-in credential handling (login cookie, resolveSettings map
120
+ * tokens) and before workspace-id rewriting.
121
+ */
122
+ sanitizeMappings?: (mappings: IGoodmockMapping[]) => IGoodmockMapping[];
123
+ }
64
124
  /**
65
125
  * @internal
66
126
  * Snapshot goodmock recordings, save the combined mappings to a JSON file
67
127
  * on disk, and sanitize any credentials from the output.
68
128
  *
69
- * This mirrors the Cypress recording flow: two snapshot calls are made —
70
- * one for executionResults URLs (with repeatsAsScenarios) and one for
71
- * everything else then the results are merged and written out.
129
+ * A single snapshot is taken with repeatsAsScenarios enabled: goodmock turns a
130
+ * request into a scenario chain only when its responses differ across the
131
+ * recording, so stateful sequences are preserved and everything else stays flat.
72
132
  *
73
- * @param host -
74
- * @param mappingFilePath -
75
- * @param workspaceIdMappings - Optional source/target workspace ID rewrite(s)
76
- * applied before mappings are saved. Accepts a single mapping or an array.
77
- * @param backendHost - Optional real backend URL (e.g. "https://example.gooddata.com").
78
- * When provided together with baseUrl, all occurrences are replaced in the
79
- * saved mappings so that recorded responses (e.g. geo tile URLs) resolve
80
- * correctly during replay.
81
- * @param baseUrl - Optional app base URL (e.g. "http://kpi-dashboards-ui:9500")
82
- * that replaces backendHost references in the saved mappings.
133
+ * @param host - Goodmock host:port (e.g. "backend-mock:8080")
134
+ * @param mappingFilePath - Absolute path to write the mapping JSON file to
135
+ * @param options - See {@link ISnapshotAndSaveRecordingOptions}.
83
136
  */
84
- export declare function snapshotAndSaveRecording(host: string, mappingFilePath: string, workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[], backendHost?: string, baseUrl?: string): Promise<void>;
137
+ export declare function snapshotAndSaveRecording(host: string, mappingFilePath: string, options?: ISnapshotAndSaveRecordingOptions): Promise<void>;
85
138
  //# sourceMappingURL=goodmock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"goodmock.d.ts","sourceRoot":"","sources":["../src/goodmock.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBvF;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/D;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;AAED;;;GAGG;AACH,oBAAY,YAAY;IACpB,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,KAAK,UAAU;CAClB;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAE3C;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrF;AAWD,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,CAAC;CAC/E;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,EACjE,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAqFf"}
1
+ {"version":3,"file":"goodmock.d.ts","sourceRoot":"","sources":["../src/goodmock.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBvF;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/D;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;AAED;;;GAGG;AACH,oBAAY,YAAY;IACpB,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,KAAK,UAAU;CAClB;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAE3C;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrF;AAWD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACpE,QAAQ,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC7C,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAAC;IAClE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,CAAC;CAC3E;AAqBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC1C,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,OAAO,GAAE,gCAAqC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAkKf"}
package/esm/goodmock.js CHANGED
@@ -139,47 +139,39 @@ function sanitizeWorkspaceId(mappings, sourceWorkspaceId, targetWorkspaceId) {
139
139
  * Snapshot goodmock recordings, save the combined mappings to a JSON file
140
140
  * on disk, and sanitize any credentials from the output.
141
141
  *
142
- * This mirrors the Cypress recording flow: two snapshot calls are made —
143
- * one for executionResults URLs (with repeatsAsScenarios) and one for
144
- * everything else then the results are merged and written out.
142
+ * A single snapshot is taken with repeatsAsScenarios enabled: goodmock turns a
143
+ * request into a scenario chain only when its responses differ across the
144
+ * recording, so stateful sequences are preserved and everything else stays flat.
145
145
  *
146
- * @param host -
147
- * @param mappingFilePath -
148
- * @param workspaceIdMappings - Optional source/target workspace ID rewrite(s)
149
- * applied before mappings are saved. Accepts a single mapping or an array.
150
- * @param backendHost - Optional real backend URL (e.g. "https://example.gooddata.com").
151
- * When provided together with baseUrl, all occurrences are replaced in the
152
- * saved mappings so that recorded responses (e.g. geo tile URLs) resolve
153
- * correctly during replay.
154
- * @param baseUrl - Optional app base URL (e.g. "http://kpi-dashboards-ui:9500")
155
- * that replaces backendHost references in the saved mappings.
146
+ * @param host - Goodmock host:port (e.g. "backend-mock:8080")
147
+ * @param mappingFilePath - Absolute path to write the mapping JSON file to
148
+ * @param options - See {@link ISnapshotAndSaveRecordingOptions}.
156
149
  */
157
- export async function snapshotAndSaveRecording(host, mappingFilePath, workspaceIdMappings, backendHost, baseUrl) {
158
- // Snapshot executionResults with scenarios
159
- const scenariosRes = await fetch(`http://${host}/__admin/recordings/snapshot`, {
150
+ export async function snapshotAndSaveRecording(host, mappingFilePath, options = {}) {
151
+ const { workspaceIdMappings, backendHost, baseUrl, secretMappings, leakPatterns, sanitizeMappings } = options;
152
+ // Snapshot everything with scenarios enabled. goodmock only emits a scenario
153
+ // chain when a given request produced *different* responses across the
154
+ // recording; identical repeats collapse to a single mapping. This captures
155
+ // stateful sequences (e.g. a GET whose result changes after a POST) that the
156
+ // old per-URL dedup path silently dropped, while keeping idempotent traffic flat.
157
+ const snapshotRes = await fetch(`http://${host}/__admin/recordings/snapshot`, {
160
158
  method: "POST",
161
159
  headers: { "Content-Type": "application/json" },
162
160
  body: JSON.stringify({
163
161
  repeatsAsScenarios: true,
164
- filters: { urlPattern: ".*executionResults.*" },
165
162
  persist: false,
166
163
  ...snapshotParams,
167
164
  }),
168
165
  });
169
- const scenariosData = (await scenariosRes.json());
170
- // Snapshot everything else without scenarios
171
- const plainRes = await fetch(`http://${host}/__admin/recordings/snapshot`, {
172
- method: "POST",
173
- headers: { "Content-Type": "application/json" },
174
- body: JSON.stringify({
175
- repeatsAsScenarios: false,
176
- filters: { urlPattern: "((?!executionResults).)*" },
177
- persist: false,
178
- ...snapshotParams,
179
- }),
180
- });
181
- const plainData = (await plainRes.json());
182
- const mappings = [...plainData.mappings, ...scenariosData.mappings];
166
+ const snapshotData = (await snapshotRes.json());
167
+ const mappings = [...snapshotData.mappings];
168
+ // An empty snapshot means the recording journal was lost (e.g. a worker crash re-ran the
169
+ // beforeAll reset mid-spec) or the spec made no requests at all. Never overwrite a previous
170
+ // recording with an empty one, and fail the run so the broken recording cannot go unnoticed.
171
+ if (mappings.length === 0) {
172
+ throw new Error(`Recording snapshot for ${mappingFilePath} is empty — keeping the existing mapping ` +
173
+ `file. Investigate (worker crash mid-spec? goodmock proxy misconfigured?) and re-record.`);
174
+ }
183
175
  // Sanitize credentials
184
176
  for (const mapping of mappings) {
185
177
  if (mapping?.request?.url === "/gdc/account/login") {
@@ -188,37 +180,104 @@ export async function snapshotAndSaveRecording(host, mappingFilePath, workspaceI
188
180
  delete mapping.response.headers["Set-Cookie"];
189
181
  }
190
182
  }
191
- const requestUrl = mapping?.request?.url;
192
- if (typeof requestUrl === "string" &&
193
- /^\/api\/v1\/actions\/workspaces\/[^/]+\/resolveSettings$/.test(requestUrl)) {
183
+ // Sanitize secrets from resolveSettings responses. Match both org-level
184
+ // (/api/v1/actions/resolveSettings) and workspace-level variants, via either `url`
185
+ // (no query) or `urlPath` (with query params).
186
+ const requestPath = mapping?.request?.url ?? mapping?.request?.urlPath;
187
+ if (typeof requestPath === "string" && requestPath.includes("/resolveSettings")) {
194
188
  try {
195
- const parsedBody = mapping?.response?.jsonBody;
196
- if (Array.isArray(parsedBody)) {
197
- for (const setting of parsedBody) {
198
- if ((setting?.id === "agGridToken" || setting?.id === "mapboxToken") &&
199
- setting?.content &&
200
- typeof setting.content === "object") {
201
- setting.content.value = "";
189
+ const body = mapping?.response?.jsonBody;
190
+ const settings = Array.isArray(body)
191
+ ? body
192
+ : Array.isArray(body?.data)
193
+ ? body.data
194
+ : null;
195
+ if (settings) {
196
+ for (const setting of settings) {
197
+ const content = setting?.content;
198
+ if (!content || typeof content !== "object") {
199
+ continue;
200
+ }
201
+ // Map tokens (agGrid, mapbox) are stored under content.value.
202
+ if (setting?.id === "agGridToken" || setting?.id === "mapboxToken") {
203
+ content["value"] = "";
202
204
  }
203
205
  }
204
- mapping.response.jsonBody = parsedBody;
205
206
  }
206
207
  }
207
208
  catch {
208
- console.warn(`sanitizeCredentials – resolveSettings body is not valid JSON for url: ${requestUrl}`);
209
+ console.warn(`sanitizeCredentials – resolveSettings body is not valid JSON for: ${requestPath}`);
209
210
  }
210
211
  }
211
212
  }
213
+ // Consumer-supplied redaction for secrets this module has no knowledge of (e.g. an LLM
214
+ // provider's API key echoed back on a settings entity).
215
+ const sanitizedByCaller = sanitizeMappings ? sanitizeMappings(mappings) : mappings;
212
216
  const mappingsToApply = Array.isArray(workspaceIdMappings)
213
217
  ? workspaceIdMappings
214
218
  : workspaceIdMappings
215
219
  ? [workspaceIdMappings]
216
220
  : [];
217
- const sanitizedMappings = mappingsToApply.reduce((acc, { sourceWorkspaceId, targetWorkspaceId }) => sanitizeWorkspaceId(acc, sourceWorkspaceId, targetWorkspaceId), mappings);
221
+ const sanitizedMappings = mappingsToApply.reduce((acc, { sourceWorkspaceId, targetWorkspaceId }) => sanitizeWorkspaceId(acc, sourceWorkspaceId, targetWorkspaceId), sanitizedByCaller);
218
222
  let output = JSON.stringify({ mappings: sanitizedMappings }, null, 4) + "\n";
219
223
  if (backendHost && baseUrl) {
220
224
  output = output.replaceAll(backendHost, baseUrl);
221
225
  }
226
+ // Replace secrets typed into the UI / sent by API helpers during recording with their
227
+ // replay-time placeholders. JSON-escape both sides so values containing characters that
228
+ // JSON.stringify escapes (quotes, backslashes) are still found in the serialized output.
229
+ // Provider error messages echo keys in a masked form ("sk-prox***********abcd"), so that
230
+ // shape is rewritten too — it still leaks the ends of a real key.
231
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
232
+ // Below this, prefix/suffix would overlap or leave nothing to mask — there's no safe
233
+ // "partial reveal" shape to detect, so such secrets rely on the exact-match replace above.
234
+ const MIN_MASKABLE_SECRET_LENGTH = 6;
235
+ // Masking keeps a variable-length prefix/suffix of the key around the asterisks; scale how
236
+ // much we match by the secret's own length instead of assuming a fixed provider convention
237
+ // (e.g. OpenAI's "sk-prox***********abcd") so shorter secrets and other providers' masking
238
+ // shapes are still caught.
239
+ const buildMaskedFormPattern = (escapedSecret) => {
240
+ if (escapedSecret.length < MIN_MASKABLE_SECRET_LENGTH) {
241
+ return undefined;
242
+ }
243
+ const prefixLength = Math.min(7, Math.floor(escapedSecret.length / 3));
244
+ const suffixLength = Math.min(4, Math.floor(escapedSecret.length / 4));
245
+ return new RegExp(`${escapeRegex(escapedSecret.slice(0, prefixLength))}[^"*\\\\]*[*]+[^"*\\\\]*${escapeRegex(escapedSecret.slice(-suffixLength))}`, "g");
246
+ };
247
+ const effectiveSecretMappings = (secretMappings ?? []).filter(({ secret, placeholder }) => secret && secret !== placeholder);
248
+ for (const { secret, placeholder } of effectiveSecretMappings) {
249
+ const escapedSecret = JSON.stringify(secret).slice(1, -1);
250
+ const escapedPlaceholder = JSON.stringify(placeholder).slice(1, -1);
251
+ output = output.replaceAll(escapedSecret, escapedPlaceholder);
252
+ const maskedForm = buildMaskedFormPattern(escapedSecret);
253
+ if (maskedForm) {
254
+ output = output.replace(maskedForm, escapedPlaceholder);
255
+ }
256
+ }
257
+ // Leak guard: never write a recording that still contains a real secret (full or masked).
258
+ for (const { secret } of effectiveSecretMappings) {
259
+ const escapedSecret = JSON.stringify(secret).slice(1, -1);
260
+ const maskedForm = buildMaskedFormPattern(escapedSecret);
261
+ if (output.includes(escapedSecret) || output.includes(secret) || maskedForm?.test(output)) {
262
+ throw new Error(`Recording for ${mappingFilePath} still contains a secret after sanitization ` +
263
+ `(e.g. in an encoded form). Refusing to save the mapping file.`);
264
+ }
265
+ }
266
+ // Pattern guard: catch credential-shaped values from ANY source (not just the ones the
267
+ // tests typed), e.g. a real key stored in a backend org setting under an unexpected field.
268
+ // Patterns are consumer-supplied — this module has no built-in notion of what a secret
269
+ // looks like for a given domain (see ISnapshotAndSaveRecordingOptions.leakPatterns).
270
+ for (const { label, pattern } of leakPatterns ?? []) {
271
+ // Reset lastIndex in case pattern carries the g/y flag — exec() on a stateful regex
272
+ // would otherwise resume from wherever a previous call (e.g. a prior recording) left off.
273
+ pattern.lastIndex = 0;
274
+ const match = pattern.exec(output);
275
+ if (match) {
276
+ throw new Error(`Recording for ${mappingFilePath} contains a value shaped like a ${label} ` +
277
+ `("${match[0].slice(0, 8)}…"). Refusing to save the mapping file — extend ` +
278
+ `sanitizeMappings / secretMappings so it gets sanitized.`);
279
+ }
280
+ }
222
281
  mkdirSync(dirname(mappingFilePath), { recursive: true });
223
282
  writeFileSync(mappingFilePath, output);
224
283
  // eslint-disable-next-line no-console
package/esm/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { authHeader, authedRouteFetch, injectAuthHeader } from "./auth.js";
2
- export { type IWorkspaceIdMapping, loadMappings, resetMappings, resetScenarios, mockLogRequests, goodmockMode, GoodmockMode, startRecording, snapshotAndSaveRecording, } from "./goodmock.js";
2
+ export { type IGoodmockMapping, type ILeakPattern, type ISecretMapping, type ISnapshotAndSaveRecordingOptions, type IWorkspaceIdMapping, loadMappings, resetMappings, resetScenarios, mockLogRequests, goodmockMode, GoodmockMode, startRecording, snapshotAndSaveRecording, } from "./goodmock.js";
3
3
  export { type BaseTestArgs, type BaseWorkerArgs, type ICreateTestOptions, type IDescribeFunction, type IE2eTest, type IE2eTestDetails, type IFeatureHubEnvironment, type IFeatureHubFeature, type IGoodmockOptions, createTest, } from "./playwright.js";
4
4
  export { clickByBoundingBox, hoverByBoundingBox } from "./helpers/mouse-actions.js";
5
5
  export { GOODMOCK_HOST, API_TOKEN, BACKEND_HOST, getBaseUrl, getEnvWithFallback, getWorkspaceId, getDangerWorkspaceId, } from "./constants.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EACH,KAAK,mBAAmB,EACxB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,wBAAwB,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,UAAU,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EACH,aAAa,EACb,SAAS,EACT,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACvB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gCAAgC,EACrC,KAAK,mBAAmB,EACxB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,wBAAwB,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,UAAU,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EACH,aAAa,EACb,SAAS,EACT,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACvB,MAAM,gBAAgB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { type Fixtures, type PlaywrightTestArgs, type PlaywrightTestOptions, type PlaywrightWorkerArgs, type PlaywrightWorkerOptions, type TestDetails, test } from "@playwright/test";
2
- import { type IWorkspaceIdMapping } from "./goodmock.js";
2
+ import { type IGoodmockMapping, type ILeakPattern, type ISecretMapping, type IWorkspaceIdMapping } from "./goodmock.js";
3
3
  /**
4
4
  * @internal
5
5
  */
@@ -9,6 +9,12 @@ export interface IGoodmockOptions {
9
9
  getMappingPath: (specFile: string) => string;
10
10
  workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
11
11
  baseUrl?: string;
12
+ /** Secret values replaced by placeholders in saved recordings (see {@link ISecretMapping}). */
13
+ secretMappings?: ISecretMapping[];
14
+ /** Credential-shaped patterns that must never appear in saved recordings (see {@link ILeakPattern}). */
15
+ leakPatterns?: ILeakPattern[];
16
+ /** Consumer-supplied redaction hook for domain-specific secrets (see snapshotAndSaveRecording). */
17
+ sanitizeMappings?: (mappings: IGoodmockMapping[]) => IGoodmockMapping[];
12
18
  }
13
19
  /**
14
20
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"playwright.d.ts","sourceRoot":"","sources":["../src/playwright.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"playwright.d.ts","sourceRoot":"","sources":["../src/playwright.ts"],"names":[],"mappings":"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"}
package/esm/playwright.js CHANGED
@@ -106,7 +106,14 @@ export function createTest(options = {}) {
106
106
  });
107
107
  testInstance.afterAll(async () => {
108
108
  if (goodmockMode === GoodmockMode.Record) {
109
- await snapshotAndSaveRecording(gm.host, gm.getMappingPath(specName), gm.workspaceIdMappings, gm.backendHost, gm.baseUrl);
109
+ await snapshotAndSaveRecording(gm.host, gm.getMappingPath(specName), {
110
+ workspaceIdMappings: gm.workspaceIdMappings,
111
+ backendHost: gm.backendHost,
112
+ baseUrl: gm.baseUrl,
113
+ secretMappings: gm.secretMappings,
114
+ leakPatterns: gm.leakPatterns,
115
+ sanitizeMappings: gm.sanitizeMappings,
116
+ });
110
117
  }
111
118
  await resetMappings(gm.host);
112
119
  });
@@ -183,6 +183,22 @@ export declare interface IFeatureHubFeature {
183
183
  v?: string;
184
184
  }
185
185
 
186
+ /**
187
+ * @internal
188
+ */
189
+ export declare interface IGoodmockMapping {
190
+ request: {
191
+ url?: string;
192
+ urlPath?: string;
193
+ bodyPatterns?: unknown;
194
+ };
195
+ response: {
196
+ headers?: Record<string, string>;
197
+ jsonBody?: unknown;
198
+ body?: string;
199
+ };
200
+ }
201
+
186
202
  /**
187
203
  * @internal
188
204
  */
@@ -192,6 +208,24 @@ export declare interface IGoodmockOptions {
192
208
  getMappingPath: (specFile: string) => string;
193
209
  workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
194
210
  baseUrl?: string;
211
+ /** Secret values replaced by placeholders in saved recordings (see {@link ISecretMapping}). */
212
+ secretMappings?: ISecretMapping[];
213
+ /** Credential-shaped patterns that must never appear in saved recordings (see {@link ILeakPattern}). */
214
+ leakPatterns?: ILeakPattern[];
215
+ /** Consumer-supplied redaction hook for domain-specific secrets (see snapshotAndSaveRecording). */
216
+ sanitizeMappings?: (mappings: IGoodmockMapping[]) => IGoodmockMapping[];
217
+ }
218
+
219
+ /**
220
+ * @internal
221
+ * A credential shape that must never appear in a saved recording (checked against the fully
222
+ * serialized output). Provide domain-specific shapes (e.g. a provider's API key format) via
223
+ * {@link ISnapshotAndSaveRecordingOptions.leakPatterns} — this module has no built-in patterns of
224
+ * its own, since it has no knowledge of what a given consumer's traffic considers a secret.
225
+ */
226
+ export declare interface ILeakPattern {
227
+ label: string;
228
+ pattern: RegExp;
195
229
  }
196
230
 
197
231
  /**
@@ -200,6 +234,52 @@ export declare interface IGoodmockOptions {
200
234
  */
201
235
  export declare function injectAuthHeader(page: Page, token: string): Promise<void>;
202
236
 
237
+ /**
238
+ * @internal
239
+ * Rewrite of a secret value in saved recordings. Every occurrence of `secret` (request bodies,
240
+ * response bodies, URLs) is replaced with `placeholder`, so tests can type the placeholder at
241
+ * replay time and still match the recorded request exactly.
242
+ */
243
+ export declare interface ISecretMapping {
244
+ secret: string;
245
+ placeholder: string;
246
+ }
247
+
248
+ /**
249
+ * @internal
250
+ */
251
+ export declare interface ISnapshotAndSaveRecordingOptions {
252
+ /** Source/target workspace ID rewrite(s) applied before mappings are saved. */
253
+ workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[];
254
+ /**
255
+ * Real backend URL (e.g. "https://example.gooddata.com"). When provided together with
256
+ * baseUrl, all occurrences are replaced in the saved mappings so that recorded responses
257
+ * (e.g. geo tile URLs) resolve correctly during replay.
258
+ */
259
+ backendHost?: string;
260
+ /** App base URL (e.g. "http://kpi-dashboards-ui:9500") that replaces backendHost references. */
261
+ baseUrl?: string;
262
+ /**
263
+ * Secret rewrites. Each secret value is replaced by its placeholder everywhere in the saved
264
+ * mappings (request bodyPatterns included, and masked variants such as "sk-prox***abcd").
265
+ * Saving fails hard if any secret still remains afterwards.
266
+ */
267
+ secretMappings?: ISecretMapping[];
268
+ /**
269
+ * Credential-shaped patterns that must never appear anywhere in the saved output, regardless
270
+ * of which field they came from. A backstop for secrets `secretMappings` doesn't know about
271
+ * (e.g. a value a backend echoed back that the test never typed). Saving fails hard on a match.
272
+ */
273
+ leakPatterns?: ILeakPattern[];
274
+ /**
275
+ * Consumer-supplied hook to redact domain-specific secrets from the collected mappings (e.g.
276
+ * blank a `content.apiKey` field on a settings entity) before they're merged and written out.
277
+ * Runs once, right after the built-in credential handling (login cookie, resolveSettings map
278
+ * tokens) and before workspace-id rewriting.
279
+ */
280
+ sanitizeMappings?: (mappings: IGoodmockMapping[]) => IGoodmockMapping[];
281
+ }
282
+
203
283
  /**
204
284
  * @internal
205
285
  */
@@ -243,22 +323,15 @@ export declare function resetScenarios(host: string): Promise<void>;
243
323
  * Snapshot goodmock recordings, save the combined mappings to a JSON file
244
324
  * on disk, and sanitize any credentials from the output.
245
325
  *
246
- * This mirrors the Cypress recording flow: two snapshot calls are made —
247
- * one for executionResults URLs (with repeatsAsScenarios) and one for
248
- * everything else then the results are merged and written out.
326
+ * A single snapshot is taken with repeatsAsScenarios enabled: goodmock turns a
327
+ * request into a scenario chain only when its responses differ across the
328
+ * recording, so stateful sequences are preserved and everything else stays flat.
249
329
  *
250
- * @param host -
251
- * @param mappingFilePath -
252
- * @param workspaceIdMappings - Optional source/target workspace ID rewrite(s)
253
- * applied before mappings are saved. Accepts a single mapping or an array.
254
- * @param backendHost - Optional real backend URL (e.g. "https://example.gooddata.com").
255
- * When provided together with baseUrl, all occurrences are replaced in the
256
- * saved mappings so that recorded responses (e.g. geo tile URLs) resolve
257
- * correctly during replay.
258
- * @param baseUrl - Optional app base URL (e.g. "http://kpi-dashboards-ui:9500")
259
- * that replaces backendHost references in the saved mappings.
260
- */
261
- export declare function snapshotAndSaveRecording(host: string, mappingFilePath: string, workspaceIdMappings?: IWorkspaceIdMapping | IWorkspaceIdMapping[], backendHost?: string, baseUrl?: string): Promise<void>;
330
+ * @param host - Goodmock host:port (e.g. "backend-mock:8080")
331
+ * @param mappingFilePath - Absolute path to write the mapping JSON file to
332
+ * @param options - See {@link ISnapshotAndSaveRecordingOptions}.
333
+ */
334
+ export declare function snapshotAndSaveRecording(host: string, mappingFilePath: string, options?: ISnapshotAndSaveRecordingOptions): Promise<void>;
262
335
 
263
336
  /**
264
337
  * @internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-e2e-utils",
3
- "version": "11.46.0-alpha.2",
3
+ "version": "11.46.0-alpha.4",
4
4
  "description": "GoodData utility functions for Playwright E2E tests",
5
5
  "license": "MIT",
6
6
  "author": "GoodData",
@@ -51,8 +51,8 @@
51
51
  "oxlint": "1.51.0",
52
52
  "oxlint-tsgolint": "0.15.0",
53
53
  "typescript": "5.9.3",
54
- "@gooddata/eslint-config": "11.46.0-alpha.2",
55
- "@gooddata/oxlint-config": "11.46.0-alpha.2"
54
+ "@gooddata/eslint-config": "11.46.0-alpha.4",
55
+ "@gooddata/oxlint-config": "11.46.0-alpha.4"
56
56
  },
57
57
  "scripts": {
58
58
  "_phase:build": "npm run build",