@elench/testkit 0.1.77 → 0.1.78
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/README.md +24 -1
- package/lib/app/doctor.mjs +43 -0
- package/lib/config-api/index.d.ts +189 -16
- package/lib/config-api/index.mjs +14 -170
- package/lib/config-api/index.test.mjs +282 -4
- package/lib/config-api/profiles.mjs +640 -0
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -365,8 +365,31 @@ Named HTTP profiles live in `testkit.config.ts` and can be referenced by name:
|
|
|
365
365
|
|
|
366
366
|
```ts
|
|
367
367
|
import { defineHttpSuite } from "@elench/testkit";
|
|
368
|
+
import { defineConfig, profiles } from "@elench/testkit/config";
|
|
368
369
|
|
|
369
|
-
|
|
370
|
+
export default defineConfig({
|
|
371
|
+
profiles: {
|
|
372
|
+
http: {
|
|
373
|
+
defaultAuth: profiles.localJson({
|
|
374
|
+
password: "password",
|
|
375
|
+
identities: {
|
|
376
|
+
primary: {
|
|
377
|
+
email: "test@example.com",
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
session: {
|
|
381
|
+
authCookie: "session",
|
|
382
|
+
},
|
|
383
|
+
headers: {
|
|
384
|
+
contentTypeJson: true,
|
|
385
|
+
forwardedFor: "deterministic",
|
|
386
|
+
},
|
|
387
|
+
}).session(),
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const suite = defineHttpSuite({ profile: "defaultAuth" }, ({ req, setupData }) => {
|
|
370
393
|
req("GET", "/api/auth/session", setupData);
|
|
371
394
|
});
|
|
372
395
|
```
|
package/lib/app/doctor.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import ts from "typescript";
|
|
|
4
4
|
import { discoverTests } from "../discovery/index.mjs";
|
|
5
5
|
import { loadConfigContext } from "../config/index.mjs";
|
|
6
6
|
import { runTestkitTypecheck } from "./typecheck.mjs";
|
|
7
|
+
import { findConfigFile } from "../config/config-loader.mjs";
|
|
7
8
|
|
|
8
9
|
export async function runDoctor(options = {}) {
|
|
9
10
|
const checks = [];
|
|
@@ -39,6 +40,17 @@ export async function runDoctor(options = {}) {
|
|
|
39
40
|
details: playwrightViolations,
|
|
40
41
|
});
|
|
41
42
|
|
|
43
|
+
const configImportViolations = findConfigImportViolations(productDir);
|
|
44
|
+
checks.push({
|
|
45
|
+
code: "config-import-hygiene",
|
|
46
|
+
level: configImportViolations.length === 0 ? "pass" : "fail",
|
|
47
|
+
message:
|
|
48
|
+
configImportViolations.length === 0
|
|
49
|
+
? "Repo config does not import __testkit__ helper modules"
|
|
50
|
+
: `Found ${configImportViolations.length} repo config import violation(s)`,
|
|
51
|
+
details: configImportViolations,
|
|
52
|
+
});
|
|
53
|
+
|
|
42
54
|
const hasBrowserOrNextWork = discovery.files.some((entry) => entry.selectionType === "pw");
|
|
43
55
|
if (hasBrowserOrNextWork) {
|
|
44
56
|
const nodeCount = discovery.coverageGraph?.nodes?.length || 0;
|
|
@@ -113,6 +125,31 @@ function findPlaywrightRuntimeImportViolations(productDir) {
|
|
|
113
125
|
return violations;
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
function findConfigImportViolations(productDir) {
|
|
129
|
+
const configFile = findConfigFile(productDir);
|
|
130
|
+
if (!configFile || !fs.existsSync(configFile)) return [];
|
|
131
|
+
|
|
132
|
+
const sourceText = fs.readFileSync(configFile, "utf8");
|
|
133
|
+
const sourceFile = ts.createSourceFile(configFile, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
134
|
+
const violations = [];
|
|
135
|
+
|
|
136
|
+
for (const statement of sourceFile.statements) {
|
|
137
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
138
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
139
|
+
const specifier = statement.moduleSpecifier.text;
|
|
140
|
+
if (!isRepoLocalConfigImportViolation(specifier)) continue;
|
|
141
|
+
const position = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
142
|
+
violations.push({
|
|
143
|
+
file: path.relative(productDir, configFile).split(path.sep).join("/"),
|
|
144
|
+
line: position.line + 1,
|
|
145
|
+
specifier,
|
|
146
|
+
snippet: statement.getText(sourceFile),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return violations;
|
|
151
|
+
}
|
|
152
|
+
|
|
116
153
|
function collectFiles(rootDir, out = []) {
|
|
117
154
|
if (!fs.existsSync(rootDir)) return out;
|
|
118
155
|
for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
|
@@ -129,6 +166,12 @@ function collectFiles(rootDir, out = []) {
|
|
|
129
166
|
return out.sort((left, right) => left.localeCompare(right));
|
|
130
167
|
}
|
|
131
168
|
|
|
169
|
+
function isRepoLocalConfigImportViolation(specifier) {
|
|
170
|
+
if (typeof specifier !== "string") return false;
|
|
171
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) return false;
|
|
172
|
+
return specifier.includes("__testkit__");
|
|
173
|
+
}
|
|
174
|
+
|
|
132
175
|
function relativeViolation(productDir, absolutePath, sourceFile, statement) {
|
|
133
176
|
const position = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
134
177
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpSuiteConfig, RuntimeEnv, RuntimeOptions } from "../index";
|
|
2
2
|
|
|
3
3
|
export interface DatabaseTemplateConfig {
|
|
4
4
|
inputs?: string[];
|
|
@@ -192,6 +192,187 @@ export interface TestkitFileMetadata {
|
|
|
192
192
|
skip?: string | { reason: string };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
export interface ProfileRequestContext {
|
|
196
|
+
actor: string;
|
|
197
|
+
actorIndex: number;
|
|
198
|
+
env: RuntimeEnv;
|
|
199
|
+
phase: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export type ProfileValueFactory<TValue> =
|
|
203
|
+
| TValue
|
|
204
|
+
| ((context: ProfileRequestContext) => TValue);
|
|
205
|
+
|
|
206
|
+
export interface ProfileRequestConfig {
|
|
207
|
+
body?: ProfileValueFactory<unknown>;
|
|
208
|
+
contentTypeJson?: boolean;
|
|
209
|
+
expect?: number | number[];
|
|
210
|
+
headers?: ProfileValueFactory<Record<string, string>>;
|
|
211
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
212
|
+
path: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface SessionAuthSourceConfig {
|
|
216
|
+
key: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface SessionAuthHeaderConfig {
|
|
220
|
+
header?: string;
|
|
221
|
+
prefix?: string;
|
|
222
|
+
source: SessionAuthSourceConfig;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface SessionCaptureConfig {
|
|
226
|
+
auth?: SessionAuthHeaderConfig;
|
|
227
|
+
cookies?: Record<string, string>;
|
|
228
|
+
fields?: Record<string, string>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface SessionActorConfig {
|
|
232
|
+
bootstrap?: ProfileRequestConfig | ProfileRequestConfig[];
|
|
233
|
+
login: ProfileRequestConfig;
|
|
234
|
+
session: SessionCaptureConfig;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface ProfileHeaderContext<TSession = unknown> {
|
|
238
|
+
actor: string | null;
|
|
239
|
+
env: RuntimeEnv;
|
|
240
|
+
session: TSession | null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface ProfileSessionHeaderConfig {
|
|
244
|
+
actor?: string;
|
|
245
|
+
field: string;
|
|
246
|
+
header: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface DeterministicForwardedForConfig {
|
|
250
|
+
actor?: string;
|
|
251
|
+
header?: string;
|
|
252
|
+
seed?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface ProfileHeaderConfig<TSession = unknown> {
|
|
256
|
+
contentTypeJson?: boolean;
|
|
257
|
+
forwardedFor?: "deterministic" | DeterministicForwardedForConfig;
|
|
258
|
+
fromSession?: ProfileSessionHeaderConfig[];
|
|
259
|
+
values?: Record<string, string> | ((context: ProfileHeaderContext<TSession>) => Record<string, string>);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface RawHttpProfileOptions {
|
|
263
|
+
env?: RuntimeEnv;
|
|
264
|
+
headers?: ProfileHeaderConfig;
|
|
265
|
+
options?: RuntimeOptions;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface SessionHttpProfileOptions {
|
|
269
|
+
actor: SessionActorConfig;
|
|
270
|
+
env?: RuntimeEnv;
|
|
271
|
+
headers?: ProfileHeaderConfig<Record<string, unknown>>;
|
|
272
|
+
options?: RuntimeOptions;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface MultiActorHttpProfileOptions {
|
|
276
|
+
actors: Record<string, SessionActorConfig>;
|
|
277
|
+
env?: RuntimeEnv;
|
|
278
|
+
headers?: ProfileHeaderConfig<Record<string, Record<string, unknown>>>;
|
|
279
|
+
options?: RuntimeOptions;
|
|
280
|
+
primaryActor?: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface LocalJsonIdentityContext {
|
|
284
|
+
actor: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export type LocalJsonIdentityValue = string | ((context: LocalJsonIdentityContext) => string);
|
|
288
|
+
|
|
289
|
+
export interface LocalJsonActorIdentityConfig {
|
|
290
|
+
email?: LocalJsonIdentityValue;
|
|
291
|
+
loginBody?: Record<string, unknown>;
|
|
292
|
+
name?: LocalJsonIdentityValue;
|
|
293
|
+
organizationName?: LocalJsonIdentityValue;
|
|
294
|
+
password?: LocalJsonIdentityValue;
|
|
295
|
+
signupBody?: Record<string, unknown>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface LocalJsonSessionAuthOptions {
|
|
299
|
+
header?: string;
|
|
300
|
+
prefix?: string;
|
|
301
|
+
sourceKey?: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface LocalJsonSessionOptions {
|
|
305
|
+
auth?: LocalJsonSessionAuthOptions;
|
|
306
|
+
authCookie?: string;
|
|
307
|
+
cookies?: Record<string, string>;
|
|
308
|
+
fields?: Record<string, string>;
|
|
309
|
+
organizationIdPath?: string;
|
|
310
|
+
refreshCookie?: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface LocalJsonOrganizationHeaderConfig {
|
|
314
|
+
actor?: string;
|
|
315
|
+
field?: string;
|
|
316
|
+
header?: string;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export interface LocalJsonHeaderOptions {
|
|
320
|
+
contentTypeJson?: boolean;
|
|
321
|
+
forwardedFor?: "deterministic" | DeterministicForwardedForConfig;
|
|
322
|
+
organization?: false | string | LocalJsonOrganizationHeaderConfig;
|
|
323
|
+
values?: Record<string, string> | ((context: ProfileHeaderContext<unknown>) => Record<string, string>);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface LocalJsonSignupOptions {
|
|
327
|
+
enabled?: boolean;
|
|
328
|
+
expect?: number | number[];
|
|
329
|
+
path?: string;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface LocalJsonLoginOptions {
|
|
333
|
+
expect?: number | number[];
|
|
334
|
+
path?: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface LocalJsonProfileOptions {
|
|
338
|
+
env?: RuntimeEnv;
|
|
339
|
+
headers?: LocalJsonHeaderOptions;
|
|
340
|
+
identities?: Record<string, LocalJsonActorIdentityConfig>;
|
|
341
|
+
login?: LocalJsonLoginOptions;
|
|
342
|
+
options?: RuntimeOptions;
|
|
343
|
+
password?: LocalJsonIdentityValue;
|
|
344
|
+
session?: LocalJsonSessionOptions;
|
|
345
|
+
signup?: false | LocalJsonSignupOptions;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface LocalJsonSessionProfileOptions {
|
|
349
|
+
actor?: string;
|
|
350
|
+
env?: RuntimeEnv;
|
|
351
|
+
headers?: LocalJsonHeaderOptions;
|
|
352
|
+
identity?: LocalJsonActorIdentityConfig;
|
|
353
|
+
options?: RuntimeOptions;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export interface LocalJsonMultiActorProfileOptions {
|
|
357
|
+
actors?: string[] | Record<string, LocalJsonActorIdentityConfig>;
|
|
358
|
+
env?: RuntimeEnv;
|
|
359
|
+
headers?: LocalJsonHeaderOptions;
|
|
360
|
+
options?: RuntimeOptions;
|
|
361
|
+
primaryActor?: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface LocalJsonRawProfileOptions {
|
|
365
|
+
env?: RuntimeEnv;
|
|
366
|
+
headers?: LocalJsonHeaderOptions;
|
|
367
|
+
options?: RuntimeOptions;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export interface LocalJsonProfileBuilder {
|
|
371
|
+
multiActor(options?: LocalJsonMultiActorProfileOptions): HttpSuiteConfig<Record<string, Record<string, unknown>>>;
|
|
372
|
+
raw(options?: LocalJsonRawProfileOptions): HttpSuiteConfig<any>;
|
|
373
|
+
session(options?: LocalJsonSessionProfileOptions): HttpSuiteConfig<Record<string, unknown>>;
|
|
374
|
+
}
|
|
375
|
+
|
|
195
376
|
export interface NodeAppOptions extends Omit<ServiceConfig, "local" | "runtime" | "env"> {
|
|
196
377
|
baseUrl?: string;
|
|
197
378
|
build?: BuildConfig | null;
|
|
@@ -246,7 +427,6 @@ export interface TestkitConfig {
|
|
|
246
427
|
}
|
|
247
428
|
|
|
248
429
|
export declare function defineConfig<T extends TestkitConfig>(config: T): T;
|
|
249
|
-
export declare function defineHttpProfile<T extends HttpSuiteConfig>(profile: T): T;
|
|
250
430
|
export declare function defineFile<T extends TestkitFileMetadata>(metadata: T): T;
|
|
251
431
|
export declare const app: {
|
|
252
432
|
node(options: NodeAppOptions): ServiceConfig;
|
|
@@ -269,20 +449,13 @@ export declare const database: {
|
|
|
269
449
|
export declare const toolchain: {
|
|
270
450
|
node(options?: NodeToolchainConfig): NodeToolchainConfig;
|
|
271
451
|
};
|
|
272
|
-
export declare
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
cookieName?: string;
|
|
280
|
-
headers?: Record<string, string>;
|
|
281
|
-
loginPath?: string;
|
|
282
|
-
passwordEnv?: string;
|
|
283
|
-
successStatus?: number;
|
|
284
|
-
usernameEnv?: string;
|
|
285
|
-
}): HttpSuiteConfig;
|
|
452
|
+
export declare const profiles: {
|
|
453
|
+
custom<T extends HttpSuiteConfig>(profile: T): T;
|
|
454
|
+
localJson(options?: LocalJsonProfileOptions): LocalJsonProfileBuilder;
|
|
455
|
+
multiActor(options: MultiActorHttpProfileOptions): HttpSuiteConfig<Record<string, Record<string, unknown>>>;
|
|
456
|
+
raw(options?: RawHttpProfileOptions): HttpSuiteConfig<any>;
|
|
457
|
+
session(options: SessionHttpProfileOptions): HttpSuiteConfig<Record<string, unknown>>;
|
|
458
|
+
};
|
|
286
459
|
|
|
287
460
|
export declare function registerRepoConfig(config: unknown): void;
|
|
288
461
|
export declare function getRepoConfig(): unknown;
|
package/lib/config-api/index.mjs
CHANGED
|
@@ -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
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
}
|