@elench/testkit 0.1.77 → 0.1.79

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.
@@ -3,21 +3,23 @@ import {
3
3
  clearRuntimeContext,
4
4
  getRepoConfig,
5
5
  getRuntimeContext,
6
- getRuntimeEnv,
7
6
  registerRepoConfig,
8
7
  registerRuntimeContext,
9
8
  runtimeHttp,
10
9
  runtimeJson,
11
10
  } from "./runtime.mjs";
11
+ import {
12
+ customProfile,
13
+ localJsonProfile,
14
+ multiActorProfile,
15
+ rawProfile,
16
+ sessionProfile,
17
+ } from "./profiles.mjs";
12
18
 
13
19
  export function defineConfig(config) {
14
20
  return config || {};
15
21
  }
16
22
 
17
- export function defineHttpProfile(profile) {
18
- return profile || {};
19
- }
20
-
21
23
  export function defineFile(metadata) {
22
24
  return metadata || {};
23
25
  }
@@ -262,73 +264,13 @@ export const toolchain = {
262
264
  node: nodeToolchain,
263
265
  };
264
266
 
265
- export function clerkSessionProfile(options = {}) {
266
- const apiBase = options.apiBase || "https://api.clerk.com/v1";
267
- const secretKeyEnv = options.secretKeyEnv || "CLERK_SECRET_KEY";
268
- const needsAuth = options.needsAuth !== false;
269
-
270
- return defineHttpProfile({
271
- auth: {
272
- setup() {
273
- return resolveClerkSession({
274
- apiBase,
275
- secretKey: envValue(secretKeyEnv),
276
- needsAuth,
277
- });
278
- },
279
- headers(setupData) {
280
- return getClerkAuthHeaders(setupData, { apiBase });
281
- },
282
- },
283
- });
284
- }
285
-
286
- export function jsonSessionProfile(options = {}) {
287
- const loginPath = options.loginPath || "/api/auth/login";
288
- const cookieName = options.cookieName || "session";
289
- const body =
290
- typeof options.body === "function"
291
- ? options.body
292
- : () => ({
293
- username: envValue(options.usernameEnv || "TESTKIT_USERNAME"),
294
- password: envValue(options.passwordEnv || "TESTKIT_PASSWORD"),
295
- });
296
-
297
- return defineHttpProfile({
298
- auth: {
299
- setup({ env }) {
300
- const requestBody = body({ env });
301
- const res = runtimeHttp.post(`${env.BASE}${loginPath}`, JSON.stringify(requestBody), {
302
- headers: {
303
- "Content-Type": "application/json",
304
- ...(env.routeParams || {}),
305
- ...(options.headers || {}),
306
- },
307
- });
308
-
309
- if (res.status !== (options.successStatus || 200)) {
310
- throw new Error(`Login failed (${res.status}): ${res.body}`);
311
- }
312
-
313
- const cookie = res.cookies?.[cookieName]?.[0]?.value ?? null;
314
- if (!cookie) {
315
- throw new Error(`No ${cookieName} cookie returned from login`);
316
- }
317
-
318
- return {
319
- cookie,
320
- body: safeJson(res),
321
- };
322
- },
323
- headers(session) {
324
- if (!session?.cookie) return {};
325
- return {
326
- Cookie: `${cookieName}=${session.cookie}`,
327
- };
328
- },
329
- },
330
- });
331
- }
267
+ export const profiles = {
268
+ custom: customProfile,
269
+ localJson: localJsonProfile,
270
+ multiActor: multiActorProfile,
271
+ raw: rawProfile,
272
+ session: sessionProfile,
273
+ };
332
274
 
333
275
  export {
334
276
  clearRepoConfig,
@@ -430,101 +372,3 @@ function compiledEntryFromSource(entry, outDir) {
430
372
  const relative = compiled.startsWith("src/") ? compiled.slice(4) : compiled;
431
373
  return `${outDir}/${relative}`.replace(/\/+/g, "/");
432
374
  }
433
-
434
- function envValue(name) {
435
- const env = getRuntimeEnv();
436
- const value = env?.rawEnv?.[name] || env?.[name];
437
- if (!value) {
438
- throw new Error(`Missing required env var "${name}" for testkit config`);
439
- }
440
- return value;
441
- }
442
-
443
- function safeJson(response) {
444
- try {
445
- return runtimeJson(response);
446
- } catch {
447
- return null;
448
- }
449
- }
450
-
451
- function resolveClerkSession({ apiBase, secretKey, needsAuth }) {
452
- if (!needsAuth) {
453
- return { jwt: null };
454
- }
455
-
456
- const headers = { Authorization: `Bearer ${secretKey}` };
457
- const usersRes = runtimeHttp.get(`${apiBase}/users?limit=1&order_by=-created_at`, {
458
- headers,
459
- });
460
- if (usersRes.status !== 200) {
461
- throw new Error(`Clerk list users failed (${usersRes.status}): ${usersRes.body}`);
462
- }
463
-
464
- const users = runtimeJson(usersRes);
465
- if (!users.length) {
466
- throw new Error("No Clerk users found for testkit Clerk auth profile");
467
- }
468
-
469
- const userId = users[0].id;
470
- const sessionsRes = runtimeHttp.get(
471
- `${apiBase}/sessions?user_id=${userId}&status=active&limit=1`,
472
- { headers }
473
- );
474
- if (sessionsRes.status !== 200) {
475
- throw new Error(`Clerk list sessions failed (${sessionsRes.status}): ${sessionsRes.body}`);
476
- }
477
-
478
- const sessions = runtimeJson(sessionsRes);
479
- if (!sessions?.length) {
480
- throw new Error("No active Clerk session found for testkit Clerk auth profile");
481
- }
482
-
483
- const sessionId = sessions[0].id;
484
- const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${sessionId}/tokens`, null, {
485
- headers: {
486
- ...headers,
487
- "Content-Type": "application/json",
488
- },
489
- });
490
- if (tokenRes.status !== 200) {
491
- throw new Error(`Clerk create token failed (${tokenRes.status}): ${tokenRes.body}`);
492
- }
493
-
494
- return {
495
- jwt: runtimeJson(tokenRes).jwt,
496
- sessionId,
497
- secretKey,
498
- };
499
- }
500
-
501
- function getClerkAuthHeaders(setupData, { apiBase }) {
502
- if (!setupData?.jwt) return {};
503
-
504
- if (!setupData.sessionId || !setupData.secretKey) {
505
- return {
506
- Authorization: `Bearer ${setupData.jwt}`,
507
- "Content-Type": "application/json",
508
- };
509
- }
510
-
511
- const tokenRes = runtimeHttp.post(`${apiBase}/sessions/${setupData.sessionId}/tokens`, null, {
512
- headers: {
513
- Authorization: `Bearer ${setupData.secretKey}`,
514
- "Content-Type": "application/json",
515
- },
516
- });
517
- if (tokenRes.status !== 200) {
518
- return {
519
- Authorization: `Bearer ${setupData.jwt}`,
520
- "Content-Type": "application/json",
521
- };
522
- }
523
-
524
- const nextJwt = runtimeJson(tokenRes).jwt;
525
- setupData.jwt = nextJwt;
526
- return {
527
- Authorization: `Bearer ${nextJwt}`,
528
- "Content-Type": "application/json",
529
- };
530
- }
@@ -4,9 +4,10 @@ import {
4
4
  database,
5
5
  defineConfig,
6
6
  defineFile,
7
- defineHttpProfile,
7
+ profiles,
8
8
  toolchain,
9
9
  } from "./index.mjs";
10
+ import { clearRuntimeContext, registerRuntimeContext } from "./runtime.mjs";
10
11
 
11
12
  describe("config api", () => {
12
13
  it("defines repo config plainly", () => {
@@ -15,9 +16,286 @@ describe("config api", () => {
15
16
  });
16
17
  });
17
18
 
18
- it("defines HTTP profiles plainly", () => {
19
- const profile = defineHttpProfile({ headers: () => ({ Authorization: "Bearer token" }) });
20
- expect(typeof profile.headers).toBe("function");
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", () => {
40
+ const requests = [];
41
+ registerRuntimeContext({
42
+ env: {
43
+ BASE: "http://api.test",
44
+ routeParams: { "x-route": "route-a" },
45
+ },
46
+ http: {
47
+ 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
+ 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";
142
+ return {
143
+ status: url.endsWith("/signup") ? 201 : 200,
144
+ body: JSON.stringify({
145
+ data: {
146
+ organizations: [{ id: `org-${actor}` }],
147
+ },
148
+ }),
149
+ headers: {
150
+ "set-cookie": [
151
+ `fixture_session=jwt-${actor}; Path=/`,
152
+ `fixture_refresh=refresh-${actor}; Path=/`,
153
+ ],
154
+ },
155
+ };
156
+ },
157
+ },
158
+ });
159
+
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: {
180
+ authCookie: "fixture_session",
181
+ refreshCookie: "fixture_refresh",
182
+ organizationIdPath: "data.organizations[0].id",
183
+ },
184
+ headers: {
185
+ contentTypeJson: true,
186
+ forwardedFor: "deterministic",
187
+ organization: "X-Organization-Id",
188
+ },
189
+ });
190
+
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",
197
+ });
198
+ expect(defaultProfile.auth.headers(defaultSetup)).toEqual({
199
+ Authorization: "Bearer jwt-primary",
200
+ });
201
+ expect(defaultProfile.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toMatchObject({
202
+ "Content-Type": "application/json",
203
+ "X-Organization-Id": "org-primary",
204
+ });
205
+
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({
214
+ Authorization: "Bearer jwt-userA",
215
+ });
216
+
217
+ const rawProfile = auth.raw();
218
+ expect(rawProfile.rawHeaders(null, { env: { BASE: "http://api.test" } })).toMatchObject({
219
+ "Content-Type": "application/json",
220
+ });
221
+
222
+ expect(requests.map((entry) => entry.url)).toEqual([
223
+ "http://api.test/api/v1/auth/signup",
224
+ "http://api.test/api/v1/auth/login",
225
+ "http://api.test/api/v1/auth/signup",
226
+ "http://api.test/api/v1/auth/login",
227
+ "http://api.test/api/v1/auth/signup",
228
+ "http://api.test/api/v1/auth/login",
229
+ ]);
230
+ clearRuntimeContext();
231
+ });
232
+
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
+ },
256
+ });
257
+
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();
21
299
  });
22
300
 
23
301
  it("defines file-local metadata plainly", () => {