@elench/testkit 0.1.82 → 0.1.84

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.
Files changed (100) hide show
  1. package/README.md +37 -7
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/prompt-builder.mjs +25 -0
  6. package/lib/cli/agents/providers/claude.mjs +74 -0
  7. package/lib/cli/agents/providers/codex.mjs +83 -0
  8. package/lib/cli/agents/providers/shared.mjs +134 -0
  9. package/lib/cli/command-helpers.mjs +53 -25
  10. package/lib/cli/commands/investigate.mjs +87 -0
  11. package/lib/cli/entrypoint.mjs +3 -0
  12. package/lib/cli/presentation/colors.mjs +12 -0
  13. package/lib/cli/presentation/events-reporter.mjs +135 -0
  14. package/lib/cli/presentation/summary-box.mjs +11 -11
  15. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  16. package/lib/cli/tui/run-app.mjs +1 -0
  17. package/lib/cli/tui/run-session-app.mjs +370 -0
  18. package/lib/cli/tui/run-session-state.mjs +481 -0
  19. package/lib/cli/tui/run-tree-state.mjs +1 -0
  20. package/lib/config-api/auth-fixtures.mjs +15 -10
  21. package/lib/discovery/index.mjs +1 -1
  22. package/lib/index.d.ts +5 -1
  23. package/lib/runner/orchestrator.mjs +1 -0
  24. package/lib/runtime/index.d.ts +138 -5
  25. package/lib/runtime/index.mjs +68 -2
  26. package/lib/runtime-src/k6/http-assertions.js +31 -1
  27. package/lib/runtime-src/k6/http-checks.js +120 -0
  28. package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
  29. package/lib/runtime-src/k6/http.js +213 -23
  30. package/lib/runtime-src/shared/error-body.mjs +42 -0
  31. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  32. package/node_modules/@elench/next-analysis/package.json +1 -1
  33. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  34. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  35. package/node_modules/@elench/ts-analysis/package.json +1 -1
  36. package/package.json +7 -6
  37. package/lib/app/configs.test.mjs +0 -34
  38. package/lib/app/typecheck.test.mjs +0 -24
  39. package/lib/bundler/index.test.mjs +0 -164
  40. package/lib/cli/args.test.mjs +0 -110
  41. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  42. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  43. package/lib/cli/presentation/summary-box.test.mjs +0 -43
  44. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  45. package/lib/config/database.test.mjs +0 -29
  46. package/lib/config/discovery.test.mjs +0 -276
  47. package/lib/config/env.test.mjs +0 -40
  48. package/lib/config/index.test.mjs +0 -44
  49. package/lib/config/paths.test.mjs +0 -27
  50. package/lib/config/runtime.test.mjs +0 -82
  51. package/lib/config/skip-config.test.mjs +0 -63
  52. package/lib/config-api/index.test.mjs +0 -344
  53. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  54. package/lib/coverage/backend-discovery.test.mjs +0 -61
  55. package/lib/coverage/evidence.test.mjs +0 -87
  56. package/lib/coverage/index.test.mjs +0 -715
  57. package/lib/coverage/routing.test.mjs +0 -36
  58. package/lib/coverage/shared.test.mjs +0 -72
  59. package/lib/database/fingerprint.test.mjs +0 -99
  60. package/lib/database/index.test.mjs +0 -95
  61. package/lib/database/naming.test.mjs +0 -39
  62. package/lib/database/state.test.mjs +0 -66
  63. package/lib/database/template-steps.test.mjs +0 -43
  64. package/lib/discovery/file-metadata.test.mjs +0 -51
  65. package/lib/discovery/index.test.mjs +0 -182
  66. package/lib/discovery/path-policy.test.mjs +0 -65
  67. package/lib/drizzle/index.test.mjs +0 -33
  68. package/lib/env/index.test.mjs +0 -82
  69. package/lib/history/index.test.mjs +0 -115
  70. package/lib/package.test.mjs +0 -59
  71. package/lib/playwright/index.test.mjs +0 -43
  72. package/lib/regressions/github.test.mjs +0 -324
  73. package/lib/regressions/index.test.mjs +0 -187
  74. package/lib/reporters/playwright.test.mjs +0 -167
  75. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  76. package/lib/runner/execution-config.test.mjs +0 -67
  77. package/lib/runner/failure-details.test.mjs +0 -114
  78. package/lib/runner/formatting.test.mjs +0 -205
  79. package/lib/runner/metadata.test.mjs +0 -52
  80. package/lib/runner/planning.test.mjs +0 -371
  81. package/lib/runner/playwright-config.test.mjs +0 -78
  82. package/lib/runner/processes.test.mjs +0 -21
  83. package/lib/runner/regressions.test.mjs +0 -168
  84. package/lib/runner/reporting.test.mjs +0 -310
  85. package/lib/runner/results.test.mjs +0 -376
  86. package/lib/runner/runtime-manager.test.mjs +0 -252
  87. package/lib/runner/runtime-preparation.test.mjs +0 -141
  88. package/lib/runner/selection.test.mjs +0 -24
  89. package/lib/runner/setup-operations.test.mjs +0 -94
  90. package/lib/runner/state.test.mjs +0 -62
  91. package/lib/runner/suite-selection.test.mjs +0 -49
  92. package/lib/runner/template.test.mjs +0 -272
  93. package/lib/shared/build-config.test.mjs +0 -132
  94. package/lib/shared/configured-steps.test.mjs +0 -102
  95. package/lib/shared/execution-schema.test.mjs +0 -26
  96. package/lib/shared/file-timeout.test.mjs +0 -64
  97. package/lib/shared/test-context.test.mjs +0 -43
  98. package/lib/timing/index.test.mjs +0 -64
  99. package/lib/toolchains/index.test.mjs +0 -168
  100. package/lib/vitest/index.test.mjs +0 -20
@@ -141,9 +141,36 @@ export interface HttpClientConfig<TSetup = unknown> {
141
141
  sessionBundle?: AuthSessionBundle<TSetup> | null;
142
142
  }
143
143
 
144
- export interface ActorRequestClient {
144
+ export interface MultipartFileInput {
145
+ contentType?: string;
146
+ data: unknown;
147
+ field: string;
148
+ filename?: string;
149
+ }
150
+
151
+ export interface MultipartPayload {
152
+ fields?: Record<string, unknown>;
153
+ files?: MultipartFileInput[];
154
+ }
155
+
156
+ export interface MultipartRequestClient {
157
+ patch(path: string, payload: MultipartPayload, extraHeaders?: RuntimeHeaders): RuntimeResponse;
158
+ post(path: string, payload: MultipartPayload, extraHeaders?: RuntimeHeaders): RuntimeResponse;
159
+ put(path: string, payload: MultipartPayload, extraHeaders?: RuntimeHeaders): RuntimeResponse;
160
+ }
161
+
162
+ export interface RawRequestClient {
163
+ (
164
+ method: RuntimeMethod,
165
+ path: string,
166
+ body?: unknown,
167
+ extraHeaders?: RuntimeHeaders
168
+ ): RuntimeResponse;
169
+ as(actorName: string): RawRequestClient;
145
170
  delete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
146
171
  get(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
172
+ headers(extraHeaders?: RuntimeHeaders): RuntimeHeaders;
173
+ multipart: MultipartRequestClient;
147
174
  patch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
148
175
  post(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
149
176
  put(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
@@ -155,24 +182,53 @@ export interface ActorRequestClient {
155
182
  ): RuntimeResponse;
156
183
  }
157
184
 
158
- export interface HttpClient<TSetup = unknown> {
159
- as(actorName: string): ActorRequestClient;
185
+ export interface ActorRequestClient {
186
+ headers(extraHeaders?: RuntimeHeaders): RuntimeHeaders;
187
+ multipart: MultipartRequestClient;
188
+ raw(
189
+ method: RuntimeMethod,
190
+ path: string,
191
+ body?: unknown,
192
+ extraHeaders?: RuntimeHeaders
193
+ ): RuntimeResponse;
194
+ rawDelete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
195
+ rawGet(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
196
+ rawHeaders(extraHeaders?: RuntimeHeaders): RuntimeHeaders;
197
+ rawMultipart: MultipartRequestClient;
198
+ rawPatch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
199
+ rawPost(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
200
+ rawPut(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
201
+ rawReq: RawRequestClient;
160
202
  delete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
161
203
  get(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
162
204
  patch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
163
205
  post(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
164
206
  put(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
165
- raw(
207
+ request(
166
208
  method: RuntimeMethod,
167
209
  path: string,
168
210
  body?: unknown,
169
211
  extraHeaders?: RuntimeHeaders
170
212
  ): RuntimeResponse;
213
+ }
214
+
215
+ export interface HttpClient<TSetup = unknown> {
216
+ as(actorName: string): ActorRequestClient;
217
+ headers(extraHeaders?: RuntimeHeaders): RuntimeHeaders;
218
+ multipart: MultipartRequestClient;
219
+ delete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
220
+ get(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
221
+ patch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
222
+ post(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
223
+ put(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
171
224
  rawDelete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
172
225
  rawGet(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
226
+ rawHeaders(actorName?: string | null, extraHeaders?: RuntimeHeaders): RuntimeHeaders;
227
+ rawMultipart: MultipartRequestClient;
173
228
  rawPatch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
174
229
  rawPost(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
175
230
  rawPut(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
231
+ raw: RawRequestClient;
176
232
  request(
177
233
  method: RuntimeMethod,
178
234
  path: string,
@@ -255,7 +311,7 @@ export declare function makeRawReq(
255
311
  baseUrl: string,
256
312
  routeHeaders?: RuntimeHeaders,
257
313
  getRawHeaders?: (context: HttpHeaderBuildContext<never>) => RuntimeHeaders | void
258
- ): HttpClient["raw"];
314
+ ): RawRequestClient;
259
315
 
260
316
  export declare function expectStatus(
261
317
  response: RuntimeResponse,
@@ -283,6 +339,83 @@ export declare function expectJsonPath(
283
339
  predicate: (value: unknown) => boolean,
284
340
  label?: string | null
285
341
  ): boolean;
342
+ export declare function expectErrorShape(
343
+ response: RuntimeResponse,
344
+ label?: string | null
345
+ ): boolean;
346
+ export declare function expectErrorMessage(
347
+ response: RuntimeResponse,
348
+ label?: string | null
349
+ ): boolean;
350
+ export declare function expectResponse(
351
+ response: RuntimeResponse,
352
+ predicate: (response: RuntimeResponse) => boolean,
353
+ label: string
354
+ ): boolean;
355
+ export declare function expectValue<T>(
356
+ value: T,
357
+ predicate: (value: T) => boolean,
358
+ label: string
359
+ ): boolean;
360
+ export declare function expectCondition(
361
+ predicate: () => boolean,
362
+ label: string
363
+ ): boolean;
364
+
365
+ export declare function runAuthGateChecks(
366
+ rawReq: RawRequestClient,
367
+ scope: string,
368
+ descriptors: {
369
+ delete?: Array<string>;
370
+ get?: Array<string>;
371
+ patch?: Array<string | [string, unknown]>;
372
+ post?: Array<string | [string, unknown]>;
373
+ put?: Array<string | [string, unknown]>;
374
+ validateGetErrorShape?: boolean;
375
+ }
376
+ ): void;
377
+ export declare function runPaginationChecks(
378
+ req: Pick<HttpClient, "get">,
379
+ endpoint: string,
380
+ options?: { auditLogsExtra?: boolean }
381
+ ): void;
382
+
383
+ export interface RuntimeExpectNamespace {
384
+ condition: typeof expectCondition;
385
+ error: {
386
+ message: typeof expectErrorMessage;
387
+ shape: typeof expectErrorShape;
388
+ };
389
+ json: typeof expectJson;
390
+ jsonPath: typeof expectJsonPath;
391
+ notStatus: typeof expectNotStatus;
392
+ response: typeof expectResponse;
393
+ status: typeof expectStatus;
394
+ statusOneOf: typeof expectStatusOneOf;
395
+ value: typeof expectValue;
396
+ }
397
+
398
+ export interface RuntimeParseNamespace {
399
+ cookie(response: Pick<RuntimeResponse, "headers">, cookieName: string): string | null;
400
+ json<T = unknown>(response: Pick<RuntimeResponse, "body">): T;
401
+ safeJson: typeof safeJson;
402
+ sse(body: string): Array<{ event: string | null; data: unknown }>;
403
+ sseEvent<T = unknown>(body: string, eventName: string): T | null;
404
+ }
405
+
406
+ export interface RuntimeChecksNamespace {
407
+ authGate: typeof runAuthGateChecks;
408
+ pagination: typeof runPaginationChecks;
409
+ }
410
+
411
+ export interface RuntimeNetworkNamespace {
412
+ deterministicIp(seed: string | number, offset?: number): string;
413
+ }
414
+
415
+ export declare const expect: RuntimeExpectNamespace;
416
+ export declare const parse: RuntimeParseNamespace;
417
+ export declare const checks: RuntimeChecksNamespace;
418
+ export declare const network: RuntimeNetworkNamespace;
286
419
 
287
420
  declare global {
288
421
  const __ENV: Record<string, string | undefined>;
@@ -1,6 +1,13 @@
1
1
  import rawHttp from "k6/http";
2
2
  import { Rate, Trend } from "k6/metrics";
3
3
  import { fail, sleep } from "k6";
4
+ import { deriveDeterministicIp } from "../runtime-src/shared/error-body.mjs";
5
+ import {
6
+ extractCookie,
7
+ getSseEventData,
8
+ parseJsonBody,
9
+ parseSseEvents,
10
+ } from "../runtime-src/shared/http-parsing.mjs";
4
11
  import {
5
12
  formatWaitForTimeoutError,
6
13
  normalizeWaitIntervalSeconds,
@@ -27,17 +34,26 @@ export {
27
34
  contains,
28
35
  defaultOptions,
29
36
  evaluateCheck,
30
- isSorted,
31
37
  json,
38
+ isSorted,
32
39
  singleIterationOptions,
33
40
  } from "../runtime-src/k6/checks.js";
34
41
  export {
42
+ expectCondition,
43
+ expectErrorMessage,
44
+ expectErrorShape,
35
45
  expectJson,
36
46
  expectJsonPath,
37
47
  expectNotStatus,
48
+ expectResponse,
38
49
  expectStatus,
39
50
  expectStatusOneOf,
51
+ expectValue,
40
52
  } from "../runtime-src/k6/http-assertions.js";
53
+ export {
54
+ runAuthGateChecks,
55
+ runPaginationChecks,
56
+ } from "../runtime-src/k6/http-checks.js";
41
57
  export {
42
58
  createDalContext,
43
59
  openDb,
@@ -48,13 +64,63 @@ export {
48
64
  defaultOptions as httpDefaultOptions,
49
65
  getEnv,
50
66
  getHttpTrace,
51
- makeRawReq,
52
67
  makeReq,
68
+ makeRawReq,
53
69
  safeJson,
54
70
  summarizeHttpTrace,
55
71
  toBodyPreview,
56
72
  } from "../runtime-src/k6/http.js";
57
73
 
74
+ import {
75
+ expectCondition,
76
+ expectErrorMessage,
77
+ expectErrorShape,
78
+ expectJson,
79
+ expectJsonPath,
80
+ expectNotStatus,
81
+ expectResponse,
82
+ expectStatus,
83
+ expectStatusOneOf,
84
+ expectValue,
85
+ } from "../runtime-src/k6/http-assertions.js";
86
+ import {
87
+ runAuthGateChecks,
88
+ runPaginationChecks,
89
+ } from "../runtime-src/k6/http-checks.js";
90
+ import { safeJson } from "../runtime-src/k6/http.js";
91
+
92
+ export const expect = {
93
+ condition: expectCondition,
94
+ error: {
95
+ message: expectErrorMessage,
96
+ shape: expectErrorShape,
97
+ },
98
+ json: expectJson,
99
+ jsonPath: expectJsonPath,
100
+ notStatus: expectNotStatus,
101
+ response: expectResponse,
102
+ status: expectStatus,
103
+ statusOneOf: expectStatusOneOf,
104
+ value: expectValue,
105
+ };
106
+
107
+ export const parse = {
108
+ cookie: extractCookie,
109
+ json: parseJsonBody,
110
+ safeJson,
111
+ sse: parseSseEvents,
112
+ sseEvent: getSseEventData,
113
+ };
114
+
115
+ export const checks = {
116
+ authGate: runAuthGateChecks,
117
+ pagination: runPaginationChecks,
118
+ };
119
+
120
+ export const network = {
121
+ deterministicIp: deriveDeterministicIp,
122
+ };
123
+
58
124
  export function getTestkitContext() {
59
125
  return readTestkitContext(__ENV);
60
126
  }
@@ -1,4 +1,8 @@
1
- import { evaluateCheck, recordFailureDetail } from "./checks.js";
1
+ import { check, evaluateCheck, recordFailureDetail } from "./checks.js";
2
+ import {
3
+ extractErrorMessageFromBody,
4
+ hasStandardErrorShape,
5
+ } from "../shared/error-body.mjs";
2
6
  import { safeJson, summarizeHttpTrace, toBodyPreview } from "./http.js";
3
7
 
4
8
  export function expectStatus(response, expected, label = null) {
@@ -122,6 +126,32 @@ export function expectJsonPath(response, path, predicate, label = null) {
122
126
  );
123
127
  }
124
128
 
129
+ export function expectErrorShape(response, label = "response has standard error shape") {
130
+ return expectJson(response, hasStandardErrorShape, label);
131
+ }
132
+
133
+ export function expectErrorMessage(response, label = "response includes extractable error message") {
134
+ return expectJson(response, (body) => extractErrorMessageFromBody(body) !== null, label);
135
+ }
136
+
137
+ export function expectResponse(response, predicate, label) {
138
+ return check(response, {
139
+ [normalizeLabel(label, "response matches expectation")]: predicate,
140
+ });
141
+ }
142
+
143
+ export function expectValue(value, predicate, label) {
144
+ return check(value, {
145
+ [normalizeLabel(label, "value matches expectation")]: predicate,
146
+ });
147
+ }
148
+
149
+ export function expectCondition(predicate, label) {
150
+ return check(null, {
151
+ [normalizeLabel(label, "condition holds")]: () => Boolean(predicate()),
152
+ });
153
+ }
154
+
125
155
  function buildHttpAssertionDetail({ kind, title, trace, expected, actual, response, message }) {
126
156
  return {
127
157
  kind,
@@ -0,0 +1,120 @@
1
+ import { group } from "./checks.js";
2
+ import {
3
+ expectErrorShape,
4
+ expectNotStatus,
5
+ expectResponse,
6
+ expectStatus,
7
+ expectStatusOneOf,
8
+ } from "./http-assertions.js";
9
+
10
+ const DEFAULT_PAGINATION_CASES = [
11
+ { qs: "limit=0", label: "limit=0", expect400: false },
12
+ { qs: "limit=-1", label: "limit=-1", expect400: true },
13
+ { qs: "limit=999999", label: "limit=999999", expect400: false },
14
+ { qs: "limit=abc", label: "limit=abc", expect400: true },
15
+ { qs: "offset=-1", label: "offset=-1", expect400: true },
16
+ { qs: "offset=1.5", label: "offset=1.5", expect400: true },
17
+ ];
18
+
19
+ const AUDIT_LOGS_PAGINATION_CASES = [
20
+ { qs: "limit=1e3", label: "limit=1e3 (scientific notation)" },
21
+ { qs: "limit=Infinity", label: "limit=Infinity" },
22
+ { qs: "limit=NaN", label: "limit=NaN" },
23
+ { qs: "offset=NaN", label: "offset=NaN" },
24
+ { qs: "limit=", label: "limit= (empty)" },
25
+ { qs: "offset=", label: "offset= (empty)" },
26
+ { qs: "limit=0x10", label: "limit=0x10 (hex)" },
27
+ ];
28
+
29
+ export function runAuthGateChecks(rawReq, scope, descriptors = {}) {
30
+ const {
31
+ get = [],
32
+ post = [],
33
+ patch = [],
34
+ delete: deleteCases = [],
35
+ put = [],
36
+ validateGetErrorShape = false,
37
+ } = descriptors;
38
+
39
+ runMethodAuthGateChecks(rawReq, scope, "GET", get, validateGetErrorShape);
40
+ runMethodAuthGateChecks(rawReq, scope, "POST", post);
41
+ runMethodAuthGateChecks(rawReq, scope, "PATCH", patch);
42
+ runMethodAuthGateChecks(rawReq, scope, "DELETE", deleteCases);
43
+ runMethodAuthGateChecks(rawReq, scope, "PUT", put);
44
+ }
45
+
46
+ export function runPaginationChecks(req, endpoint, options = {}) {
47
+ group(`${endpoint} — pagination abuse`, () => {
48
+ for (const { qs, label, expect400 } of DEFAULT_PAGINATION_CASES) {
49
+ const url = `${endpoint}?${qs}`;
50
+ const response = req.get(url);
51
+
52
+ expectNotStatus(response, 500, `${label} → not 500`);
53
+ if (response.status === 500) {
54
+ expectResponse(response, () => true, `BUG: ${endpoint} crashes on ${label}`);
55
+ }
56
+
57
+ if (expect400) {
58
+ expectStatus(response, 400, `${label} → 400`);
59
+ if (response.status === 200) {
60
+ expectResponse(response, () => true, `BUG: ${endpoint} accepts ${label}`);
61
+ }
62
+ }
63
+
64
+ if (label === "limit=abc" && response.body) {
65
+ expectResponse(response, (value) => !value.body.includes("NaN"), `${label} → no NaN in response`);
66
+ }
67
+ }
68
+
69
+ if (!options.auditLogsExtra) {
70
+ return;
71
+ }
72
+
73
+ for (const { qs, label } of AUDIT_LOGS_PAGINATION_CASES) {
74
+ const url = `${endpoint}?${qs}`;
75
+ const response = req.get(url);
76
+
77
+ expectNotStatus(response, 500, `audit-logs ${label} → not 500`);
78
+ if (response.status === 500) {
79
+ expectResponse(response, () => true, `BUG: audit-logs crashes on ${label}`);
80
+ }
81
+
82
+ expectStatusOneOf(response, [400, 200], `audit-logs ${label} → 400 (not silently accepted)`);
83
+ if (response.status === 200 && response.body) {
84
+ expectResponse(response, (value) => !value.body.includes("NaN"), `audit-logs ${label} → no NaN in response`);
85
+ }
86
+ }
87
+ });
88
+ }
89
+
90
+ function runMethodAuthGateChecks(rawReq, scope, method, cases, validateErrorShape = false) {
91
+ if (!Array.isArray(cases) || cases.length === 0) {
92
+ return;
93
+ }
94
+
95
+ group(`${scope} ${method} endpoints return 401 without auth`, () => {
96
+ for (const entry of cases) {
97
+ const { path, body } = normalizeRequestCase(entry);
98
+ const response = rawReq(method, path, body);
99
+
100
+ expectStatus(response, 401, `${method} ${path} → 401`);
101
+ if (validateErrorShape && method === "GET" && response.status === 401) {
102
+ expectErrorShape(response, `${method} ${path} error shape`);
103
+ }
104
+ }
105
+ });
106
+ }
107
+
108
+ function normalizeRequestCase(entry) {
109
+ if (Array.isArray(entry)) {
110
+ return {
111
+ path: entry[0],
112
+ body: entry[1],
113
+ };
114
+ }
115
+
116
+ return {
117
+ path: entry,
118
+ body: undefined,
119
+ };
120
+ }
@@ -64,14 +64,18 @@ export function createSuiteActors(sessionBundle, client, profileMeta) {
64
64
  const actorNames = profileMeta?.actorNames || Object.keys(sessionBundle?.actors || {});
65
65
  const entries = actorNames.map((actorName) => {
66
66
  const actorRecord = sessionBundle?.actors?.[actorName] || null;
67
+ const actorClient = client.as(actorName);
67
68
  return {
68
69
  email: actorRecord?.email || null,
70
+ headers: actorClient.headers(),
69
71
  index: Number(actorRecord?.actorIndex ?? 0),
70
72
  key: actorName,
71
73
  name: actorRecord?.name || null,
72
74
  organizationKey: actorRecord?.organizationKey || null,
73
75
  organizationName: actorRecord?.organizationName || null,
74
- req: client.as(actorName),
76
+ rawHeaders: actorClient.rawHeaders(),
77
+ rawReq: actorClient.rawReq,
78
+ req: actorClient,
75
79
  session: actorRecord?.session || null,
76
80
  };
77
81
  });