@elench/testkit 0.1.80 → 0.1.82

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 (54) hide show
  1. package/README.md +78 -56
  2. package/lib/cli/args.mjs +2 -14
  3. package/lib/cli/args.test.mjs +1 -17
  4. package/lib/cli/command-helpers.mjs +1 -20
  5. package/lib/cli/entrypoint.mjs +0 -4
  6. package/lib/cli/presentation/colors.mjs +1 -1
  7. package/lib/cli/presentation/failure-presentation.mjs +4 -4
  8. package/lib/cli/presentation/run-reporter.mjs +23 -9
  9. package/lib/cli/presentation/run-reporter.test.mjs +12 -6
  10. package/lib/cli/presentation/summary-box.test.mjs +4 -4
  11. package/lib/cli/viewer.mjs +18 -19
  12. package/lib/config/index.mjs +6 -6
  13. package/lib/config/runtime.mjs +8 -8
  14. package/lib/config-api/auth-fixtures.mjs +762 -0
  15. package/lib/config-api/index.d.ts +96 -112
  16. package/lib/config-api/index.mjs +22 -12
  17. package/lib/config-api/index.test.mjs +61 -222
  18. package/lib/index.d.ts +29 -9
  19. package/lib/package.test.mjs +4 -4
  20. package/lib/{known-failures → regressions}/github.mjs +36 -78
  21. package/lib/regressions/github.test.mjs +324 -0
  22. package/lib/regressions/index.d.ts +189 -0
  23. package/lib/{known-failures → regressions}/index.mjs +90 -93
  24. package/lib/{known-failures → regressions}/index.test.mjs +37 -48
  25. package/lib/runner/formatting.mjs +49 -34
  26. package/lib/runner/formatting.test.mjs +16 -15
  27. package/lib/runner/metadata.mjs +1 -1
  28. package/lib/runner/orchestrator.mjs +7 -9
  29. package/lib/runner/regressions.mjs +304 -0
  30. package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
  31. package/lib/runner/reporting.mjs +2 -2
  32. package/lib/runner/reporting.test.mjs +2 -2
  33. package/lib/runner/run-finalization.mjs +18 -30
  34. package/lib/runner/template-steps.mjs +2 -2
  35. package/lib/runtime/index.d.ts +50 -33
  36. package/lib/runtime/index.mjs +0 -1
  37. package/lib/runtime-src/k6/http-suite-runtime.js +147 -0
  38. package/lib/runtime-src/k6/http.js +80 -41
  39. package/lib/runtime-src/k6/scenario-suite.js +13 -110
  40. package/lib/runtime-src/k6/suite.js +13 -107
  41. package/node_modules/@elench/next-analysis/package.json +1 -1
  42. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  43. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  44. package/node_modules/@elench/ts-analysis/package.json +1 -1
  45. package/package.json +8 -8
  46. package/lib/cli/commands/known-failures/render.mjs +0 -19
  47. package/lib/cli/commands/known-failures/validate.mjs +0 -20
  48. package/lib/cli/known-failures.mjs +0 -164
  49. package/lib/config-api/profiles.mjs +0 -640
  50. package/lib/known-failures/github.test.mjs +0 -512
  51. package/lib/known-failures/index.d.ts +0 -192
  52. package/lib/runner/triage.mjs +0 -221
  53. /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
  54. /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
+ auth,
3
4
  app,
4
5
  database,
5
6
  defineConfig,
6
7
  defineFile,
7
- profiles,
8
8
  toolchain,
9
9
  } from "./index.mjs";
10
10
  import { clearRuntimeContext, registerRuntimeContext } from "./runtime.mjs";
@@ -16,27 +16,7 @@ describe("config api", () => {
16
16
  });
17
17
  });
18
18
 
19
- it("builds raw HTTP profiles with deterministic forwarded headers", () => {
20
- const profile = profiles.raw({
21
- headers: {
22
- contentTypeJson: true,
23
- forwardedFor: "deterministic",
24
- values: { "X-Testkit-Mode": "raw" },
25
- },
26
- });
27
-
28
- const headers = profile.headers?.(null, { env: { BASE: "http://api.test" } });
29
- const rawHeaders = profile.rawHeaders?.(null, { env: { BASE: "http://api.test" } });
30
-
31
- expect(headers).toMatchObject({
32
- "Content-Type": "application/json",
33
- "X-Testkit-Mode": "raw",
34
- });
35
- expect(headers["X-Forwarded-For"]).toMatch(/^10\.\d+\.\d+\.\d+$/);
36
- expect(rawHeaders).toEqual(headers);
37
- });
38
-
39
- it("builds session profiles that perform bootstrap/login and derive auth/session headers", () => {
19
+ it("builds generated auth fixture profiles with actor-aware sessions and headers", () => {
40
20
  const requests = [];
41
21
  registerRuntimeContext({
42
22
  env: {
@@ -45,100 +25,13 @@ describe("config api", () => {
45
25
  },
46
26
  http: {
47
27
  post(url, body, params) {
48
- requests.push({ url, body, params });
49
- if (url.endsWith("/signup")) {
50
- return {
51
- status: 201,
52
- body: JSON.stringify({ data: { organizations: [{ id: "org-signup" }] } }),
53
- headers: {
54
- "set-cookie": ["fixture_session=signup-token; Path=/", "fixture_refresh=signup-refresh; Path=/"],
55
- },
56
- };
57
- }
58
- return {
59
- status: 200,
60
- body: JSON.stringify({ data: { organizations: [{ id: "org-123" }] } }),
61
- headers: {
62
- "set-cookie": ["fixture_session=jwt-123; Path=/", "fixture_refresh=refresh-123; Path=/"],
63
- },
64
- };
65
- },
66
- },
67
- });
68
-
69
- const profile = profiles.session({
70
- actor: {
71
- bootstrap: {
72
- path: "/signup",
73
- expect: [201, 409],
74
- body: ({ actor }) => ({ email: `${actor}@example.com` }),
75
- },
76
- login: {
77
- path: "/login",
78
- expect: 200,
79
- body: ({ actor }) => ({ email: `${actor}@example.com` }),
80
- },
81
- session: {
82
- cookies: {
83
- jwt: "fixture_session",
84
- refreshToken: "fixture_refresh",
85
- },
86
- fields: {
87
- organizationId: "data.organizations[0].id",
88
- },
89
- auth: {
90
- source: { key: "jwt" },
91
- },
92
- },
93
- },
94
- headers: {
95
- contentTypeJson: true,
96
- forwardedFor: "deterministic",
97
- fromSession: [{ header: "X-Organization-Id", field: "organizationId" }],
98
- values: ({ actor }) => ({ "X-Testkit-Actor": actor || "primary" }),
99
- },
100
- });
101
-
102
- const session = profile.auth.setup({
103
- env: { BASE: "http://api.test", routeParams: { "x-route": "route-a" } },
104
- });
105
- const authHeaders = profile.auth.headers(session, { env: { BASE: "http://api.test" } });
106
- const requestHeaders = profile.headers(session, { env: { BASE: "http://api.test" } });
107
-
108
- expect(requests).toHaveLength(2);
109
- expect(requests[0].params.headers["x-route"]).toBe("route-a");
110
- expect(session).toEqual({
111
- jwt: "jwt-123",
112
- refreshToken: "refresh-123",
113
- organizationId: "org-123",
114
- });
115
- expect(authHeaders).toEqual({
116
- Authorization: "Bearer jwt-123",
117
- });
118
- expect(requestHeaders).toMatchObject({
119
- "Content-Type": "application/json",
120
- "X-Organization-Id": "org-123",
121
- "X-Testkit-Actor": "primary",
122
- });
123
- expect(requestHeaders["X-Forwarded-For"]).toMatch(/^10\.\d+\.\d+\.\d+$/);
124
- clearRuntimeContext();
125
- });
126
-
127
- it("builds local-json profile presets that derive session, multi-actor, and raw variants", () => {
128
- const requests = [];
129
- registerRuntimeContext({
130
- env: {
131
- BASE: "http://api.test",
132
- },
133
- http: {
134
- post(url, body) {
135
28
  const payload = JSON.parse(body);
136
- requests.push({ url, payload });
137
- const actor = payload.email.startsWith("primary")
138
- ? "primary"
139
- : payload.email.startsWith("user-a")
140
- ? "userA"
141
- : "userB";
29
+ requests.push({ url, payload, params });
30
+ const actor = payload.email.includes(".user-a@")
31
+ ? "userA"
32
+ : payload.email.includes(".user-b@")
33
+ ? "userB"
34
+ : "primary";
142
35
  return {
143
36
  status: url.endsWith("/signup") ? 201 : 200,
144
37
  body: JSON.stringify({
@@ -157,68 +50,71 @@ describe("config api", () => {
157
50
  },
158
51
  });
159
52
 
160
- const auth = profiles.localJson({
161
- password: "TestkitPass2026",
162
- identities: {
163
- primary: {
164
- email: "primary@example.com",
165
- name: "Primary User",
166
- organizationName: "Primary Org",
167
- },
168
- userA: {
169
- email: "user-a@example.com",
170
- name: "User A",
171
- organizationName: "Org A",
172
- },
173
- userB: {
174
- email: "user-b@example.com",
175
- name: "User B",
176
- organizationName: "Org B",
177
- },
178
- },
179
- session: {
53
+ const fixture = auth.fixture({
54
+ contract: auth.contracts.jsonSession({
180
55
  authCookie: "fixture_session",
181
56
  refreshCookie: "fixture_refresh",
182
57
  organizationIdPath: "data.organizations[0].id",
183
- },
184
- headers: {
185
- contentTypeJson: true,
186
58
  forwardedFor: "deterministic",
187
- organization: "X-Organization-Id",
188
- },
59
+ organizationHeader: "X-Organization-Id",
60
+ }),
61
+ topology: auth.topologies.crossOrg({
62
+ namespace: "fixture",
63
+ actors: {
64
+ primary: { org: "primary" },
65
+ userA: { org: "primary" },
66
+ userB: { org: "secondary" },
67
+ },
68
+ }),
189
69
  });
190
70
 
191
- const defaultProfile = auth.session();
192
- const defaultSetup = defaultProfile.auth.setup({ env: { BASE: "http://api.test" } });
193
- expect(defaultSetup).toEqual({
194
- jwt: "jwt-primary",
195
- refreshToken: "refresh-primary",
196
- organizationId: "org-primary",
71
+ const builtProfiles = fixture.profiles({
72
+ default: auth.profile.actor("primary", {
73
+ headers: {
74
+ values: ({ actor }) => ({ "X-Testkit-Actor": actor || "primary" }),
75
+ },
76
+ }),
77
+ dual: auth.profile.actors({
78
+ primaryActor: "userA",
79
+ actors: ["userA", "userB"],
80
+ }),
81
+ raw: auth.profile.raw({
82
+ headers: {
83
+ values: {
84
+ "X-Testkit-Mode": "raw",
85
+ },
86
+ },
87
+ }),
197
88
  });
198
- expect(defaultProfile.auth.headers(defaultSetup)).toEqual({
89
+
90
+ expect(fixture.topology.actors.primary.email).toBe("testkit+fixture.primary@example.test");
91
+ expect(fixture.topology.actors.userA.name).toBe("Testkit Fixture User A");
92
+ expect(fixture.topology.actors.userB.organizationName).toBe("Testkit Fixture Secondary Org");
93
+
94
+ const defaultSetup = builtProfiles.default.auth.setup({ env: { BASE: "http://api.test", routeParams: { "x-route": "route-a" } } });
95
+ expect(defaultSetup.actors.primary.organizationId).toBe("org-primary");
96
+ expect(builtProfiles.default.auth.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toEqual({
199
97
  Authorization: "Bearer jwt-primary",
200
98
  });
201
- expect(defaultProfile.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toMatchObject({
99
+ expect(builtProfiles.default.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toMatchObject({
202
100
  "Content-Type": "application/json",
203
101
  "X-Organization-Id": "org-primary",
102
+ "X-Testkit-Actor": "primary",
204
103
  });
205
104
 
206
- const dualProfile = auth.multiActor({
207
- primaryActor: "userA",
208
- actors: ["userA", "userB"],
209
- });
210
- const dualSetup = dualProfile.auth.setup({ env: { BASE: "http://api.test" } });
211
- expect(dualSetup.userA.organizationId).toBe("org-userA");
212
- expect(dualSetup.userB.organizationId).toBe("org-userB");
213
- expect(dualProfile.auth.headers(dualSetup)).toEqual({
105
+ const dualSetup = builtProfiles.dual.auth.setup({ env: { BASE: "http://api.test" } });
106
+ expect(dualSetup.actors.userA.organizationId).toBe("org-userA");
107
+ expect(dualSetup.actors.userB.organizationId).toBe("org-userB");
108
+ expect(builtProfiles.dual.auth.headers(dualSetup)).toEqual({
214
109
  Authorization: "Bearer jwt-userA",
215
110
  });
216
111
 
217
- const rawProfile = auth.raw();
218
- expect(rawProfile.rawHeaders(null, { env: { BASE: "http://api.test" } })).toMatchObject({
112
+ expect(builtProfiles.raw.rawHeaders(null, { env: { BASE: "http://api.test" } })).toMatchObject({
219
113
  "Content-Type": "application/json",
114
+ "X-Testkit-Mode": "raw",
220
115
  });
221
116
 
117
+ expect(requests[0].params.headers["x-route"]).toBe("route-a");
222
118
  expect(requests.map((entry) => entry.url)).toEqual([
223
119
  "http://api.test/api/v1/auth/signup",
224
120
  "http://api.test/api/v1/auth/login",
@@ -230,72 +126,15 @@ describe("config api", () => {
230
126
  clearRuntimeContext();
231
127
  });
232
128
 
233
- it("builds multi-actor profiles and derives headers from the primary actor by default", () => {
234
- const responses = {
235
- alpha: {
236
- status: 200,
237
- body: JSON.stringify({ data: { organizations: [{ id: "org-alpha" }] } }),
238
- headers: { "set-cookie": "fixture_session=token-alpha; Path=/" },
239
- },
240
- beta: {
241
- status: 200,
242
- body: JSON.stringify({ data: { organizations: [{ id: "org-beta" }] } }),
243
- headers: { "set-cookie": "fixture_session=token-beta; Path=/" },
244
- },
245
- };
246
- registerRuntimeContext({
247
- env: {
248
- BASE: "http://api.test",
249
- },
250
- http: {
251
- post(_url, body) {
252
- const payload = JSON.parse(body);
253
- return payload.email.startsWith("alpha") ? responses.alpha : responses.beta;
254
- },
255
- },
129
+ it("builds a single-org topology with deterministic defaults", () => {
130
+ const topology = auth.topologies.singleOrg({
131
+ namespace: "sample-app",
132
+ actors: ["primary", "reviewer"],
256
133
  });
257
134
 
258
- const profile = profiles.multiActor({
259
- primaryActor: "userA",
260
- actors: {
261
- userA: {
262
- login: {
263
- path: "/login",
264
- body: () => ({ email: "alpha@example.com" }),
265
- },
266
- session: {
267
- cookies: { jwt: "fixture_session" },
268
- fields: { organizationId: "data.organizations[0].id" },
269
- auth: { source: { key: "jwt" } },
270
- },
271
- },
272
- userB: {
273
- login: {
274
- path: "/login",
275
- body: () => ({ email: "beta@example.com" }),
276
- },
277
- session: {
278
- cookies: { jwt: "fixture_session" },
279
- fields: { organizationId: "data.organizations[0].id" },
280
- auth: { source: { key: "jwt" } },
281
- },
282
- },
283
- },
284
- headers: {
285
- fromSession: [{ header: "X-Organization-Id", field: "organizationId" }],
286
- },
287
- });
288
-
289
- const setupData = profile.auth.setup({ env: { BASE: "http://api.test" } });
290
- expect(setupData.userA.organizationId).toBe("org-alpha");
291
- expect(setupData.userB.organizationId).toBe("org-beta");
292
- expect(profile.auth.headers(setupData)).toEqual({
293
- Authorization: "Bearer token-alpha",
294
- });
295
- expect(profile.headers(setupData, { env: { BASE: "http://api.test" } })).toMatchObject({
296
- "X-Organization-Id": "org-alpha",
297
- });
298
- clearRuntimeContext();
135
+ expect(topology.actors.primary.email).toBe("testkit+sample-app.primary@example.test");
136
+ expect(topology.actors.reviewer.name).toBe("Testkit Sample App Reviewer");
137
+ expect(topology.actors.primary.organizationName).toBe("Testkit Sample App Primary Org");
299
138
  });
300
139
 
301
140
  it("defines file-local metadata plainly", () => {
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ ActorRequestClient,
2
3
  HttpClient,
3
4
  HttpClientConfig,
4
5
  RuntimeDb,
@@ -16,6 +17,7 @@ export interface TestkitSuite<TSetup = unknown> {
16
17
  }
17
18
 
18
19
  export interface HeaderBuilderContext {
20
+ actor?: string | null;
19
21
  env: RuntimeEnv;
20
22
  }
21
23
 
@@ -29,13 +31,31 @@ export interface AuthAdapter<TSetup = unknown> {
29
31
  headers?: HeaderBuilder<TSetup>;
30
32
  }
31
33
 
32
- export interface HttpSuiteContext<TSetup = unknown> {
34
+ export interface SuiteActor<TSession = Record<string, unknown>> {
35
+ email: string | null;
36
+ index: number;
37
+ key: string;
38
+ name: string | null;
39
+ organizationKey: string | null;
40
+ organizationName: string | null;
41
+ req: ActorRequestClient;
42
+ session: TSession | null;
43
+ }
44
+
45
+ export interface SuiteActors<TSession = Record<string, unknown>> {
46
+ readonly names: string[];
47
+ readonly primary: SuiteActor<TSession> | null;
48
+ get(name: string): SuiteActor<TSession>;
49
+ has(name: string): boolean;
50
+ list(): SuiteActor<TSession>[];
51
+ }
52
+
53
+ export interface HttpSuiteContext<TSession = Record<string, unknown>> {
54
+ actor: SuiteActor<TSession> | null;
55
+ actors: SuiteActors<TSession>;
33
56
  env: RuntimeEnv;
34
- req: HttpClient<TSetup>["request"];
57
+ req: HttpClient<TSession>;
35
58
  rawReq: HttpClient["raw"];
36
- getWithHeaders: HttpClient<TSetup>["getWithHeaders"];
37
- setupData: TSetup | null;
38
- session: TSetup | null;
39
59
  }
40
60
 
41
61
  export interface ScenarioStepResult {
@@ -83,14 +103,14 @@ export interface ScenarioRuntime {
83
103
  };
84
104
  }
85
105
 
86
- export interface ScenarioSuiteContext<TSetup = unknown> extends HttpSuiteContext<TSetup> {
106
+ export interface ScenarioSuiteContext<TSession = Record<string, unknown>> extends HttpSuiteContext<TSession> {
87
107
  scenario: ScenarioRuntime;
88
108
  }
89
109
 
90
- export interface HttpSuiteConfig<TSetup = unknown> {
91
- auth?: AuthAdapter<TSetup> | null;
110
+ export interface HttpSuiteConfig<TSession = unknown> {
111
+ auth?: AuthAdapter<TSession> | null;
92
112
  env?: RuntimeEnv;
93
- headers?: HeaderBuilder<TSetup>;
113
+ headers?: HeaderBuilder<TSession>;
94
114
  profile?: string;
95
115
  rawHeaders?: HeaderBuilder<never>;
96
116
  options?: RuntimeOptions;
@@ -42,9 +42,9 @@ describe("package metadata", () => {
42
42
  types: "./lib/discovery/index.d.ts",
43
43
  default: "./lib/discovery/index.mjs",
44
44
  });
45
- expect(packageJson.exports["./known-failures"]).toEqual({
46
- types: "./lib/known-failures/index.d.ts",
47
- default: "./lib/known-failures/index.mjs",
45
+ expect(packageJson.exports["./regressions"]).toEqual({
46
+ types: "./lib/regressions/index.d.ts",
47
+ default: "./lib/regressions/index.mjs",
48
48
  });
49
49
  expect(fs.existsSync(path.join(rootDir, "lib", "index.d.ts"))).toBe(true);
50
50
  expect(fs.existsSync(path.join(rootDir, "lib", "config-api", "index.d.ts"))).toBe(true);
@@ -54,6 +54,6 @@ describe("package metadata", () => {
54
54
  expect(fs.existsSync(path.join(rootDir, "lib", "runtime", "index.d.ts"))).toBe(true);
55
55
  expect(fs.existsSync(path.join(rootDir, "lib", "vitest", "index.d.ts"))).toBe(true);
56
56
  expect(fs.existsSync(path.join(rootDir, "lib", "discovery", "index.d.ts"))).toBe(true);
57
- expect(fs.existsSync(path.join(rootDir, "lib", "known-failures", "index.d.ts"))).toBe(true);
57
+ expect(fs.existsSync(path.join(rootDir, "lib", "regressions", "index.d.ts"))).toBe(true);
58
58
  });
59
59
  });