@elench/testkit 0.1.25 → 0.1.27
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 +103 -79
- package/lib/bundler/index.mjs +46 -7
- package/lib/cli/index.mjs +3 -32
- package/lib/config/discovery.mjs +209 -54
- package/lib/config/discovery.test.mjs +57 -28
- package/lib/config/index.mjs +297 -154
- package/lib/config/setup-loader.mjs +98 -0
- package/lib/index.d.ts +93 -0
- package/lib/package.test.mjs +29 -0
- package/lib/runner/index.mjs +3 -0
- package/lib/runner/results.mjs +49 -3
- package/lib/runner/results.test.mjs +24 -1
- package/lib/runner/template.mjs +1 -1
- package/lib/runtime/index.d.ts +183 -0
- package/lib/runtime-src/k6/http.js +1 -0
- package/lib/runtime-src/k6/suite.js +66 -23
- package/lib/setup/index.d.ts +104 -0
- package/lib/setup/index.mjs +292 -0
- package/lib/setup/runtime.mjs +79 -0
- package/package.json +14 -3
- package/lib/config/model.mjs +0 -408
- package/lib/config/model.test.mjs +0 -194
- package/lib/runtime-manager/index.mjs +0 -190
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HttpClient,
|
|
3
|
+
HttpClientConfig,
|
|
4
|
+
RuntimeDb,
|
|
5
|
+
RuntimeDalContext,
|
|
6
|
+
RuntimeEnv,
|
|
7
|
+
RuntimeHeaders,
|
|
8
|
+
RuntimeOptions,
|
|
9
|
+
RuntimeResponse,
|
|
10
|
+
} from "./runtime/index";
|
|
11
|
+
|
|
12
|
+
export interface TestkitSuite<TSetup = unknown> {
|
|
13
|
+
options: RuntimeOptions;
|
|
14
|
+
setup: () => TSetup | null;
|
|
15
|
+
exec: (setupData: TSetup | null) => unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HeaderBuilderContext {
|
|
19
|
+
env: RuntimeEnv;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type HeaderBuilder<TSetup = unknown> = (
|
|
23
|
+
setupData?: TSetup | null,
|
|
24
|
+
context?: HeaderBuilderContext
|
|
25
|
+
) => RuntimeHeaders | void;
|
|
26
|
+
|
|
27
|
+
export interface AuthAdapter<TSetup = unknown> {
|
|
28
|
+
setup?: (context: { env: RuntimeEnv }) => TSetup;
|
|
29
|
+
headers?: HeaderBuilder<TSetup>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface HttpSuiteContext<TSetup = unknown> {
|
|
33
|
+
env: RuntimeEnv;
|
|
34
|
+
req: HttpClient<TSetup>["request"];
|
|
35
|
+
rawReq: HttpClient["raw"];
|
|
36
|
+
getWithHeaders: HttpClient<TSetup>["getWithHeaders"];
|
|
37
|
+
setupData: TSetup | null;
|
|
38
|
+
session: TSetup | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface HttpSuiteConfig<TSetup = unknown> {
|
|
42
|
+
auth?: AuthAdapter<TSetup> | null;
|
|
43
|
+
env?: RuntimeEnv;
|
|
44
|
+
headers?: HeaderBuilder<TSetup>;
|
|
45
|
+
profile?: string;
|
|
46
|
+
rawHeaders?: HeaderBuilder<never>;
|
|
47
|
+
options?: RuntimeOptions;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DalSuiteContext<TSetup = unknown> {
|
|
51
|
+
db: RuntimeDb;
|
|
52
|
+
dal: RuntimeDalContext;
|
|
53
|
+
setupData: TSetup | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DalSuiteConfig<TSetup = unknown> {
|
|
57
|
+
db?: RuntimeDb;
|
|
58
|
+
options?: RuntimeOptions;
|
|
59
|
+
setup?: (context: { db: RuntimeDb; dal: RuntimeDalContext }) => TSetup;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export declare function defineHttpSuite<TSetup = unknown>(
|
|
63
|
+
run: (context: HttpSuiteContext<TSetup>) => unknown
|
|
64
|
+
): TestkitSuite<TSetup>;
|
|
65
|
+
|
|
66
|
+
export declare function defineHttpSuite<TSetup = unknown>(
|
|
67
|
+
config: HttpSuiteConfig<TSetup>,
|
|
68
|
+
run: (context: HttpSuiteContext<TSetup>) => unknown
|
|
69
|
+
): TestkitSuite<TSetup>;
|
|
70
|
+
|
|
71
|
+
export declare function defineDalSuite<TSetup = unknown>(
|
|
72
|
+
run: (context: DalSuiteContext<TSetup>) => unknown
|
|
73
|
+
): TestkitSuite<TSetup>;
|
|
74
|
+
|
|
75
|
+
export declare function defineDalSuite<TSetup = unknown>(
|
|
76
|
+
config: DalSuiteConfig<TSetup>,
|
|
77
|
+
run: (context: DalSuiteContext<TSetup>) => unknown
|
|
78
|
+
): TestkitSuite<TSetup>;
|
|
79
|
+
|
|
80
|
+
export declare function createAuthAdapter<TSetup = unknown>(
|
|
81
|
+
adapter?: AuthAdapter<TSetup>
|
|
82
|
+
): AuthAdapter<TSetup>;
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
HttpClient,
|
|
86
|
+
HttpClientConfig,
|
|
87
|
+
RuntimeDb,
|
|
88
|
+
RuntimeDalContext,
|
|
89
|
+
RuntimeEnv,
|
|
90
|
+
RuntimeHeaders,
|
|
91
|
+
RuntimeOptions,
|
|
92
|
+
RuntimeResponse,
|
|
93
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
8
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
9
|
+
|
|
10
|
+
describe("package metadata", () => {
|
|
11
|
+
it("ships first-party type declarations for the public exports", () => {
|
|
12
|
+
expect(packageJson.types).toBe("./lib/index.d.ts");
|
|
13
|
+
expect(packageJson.exports["."]).toEqual({
|
|
14
|
+
types: "./lib/index.d.ts",
|
|
15
|
+
default: "./lib/index.mjs",
|
|
16
|
+
});
|
|
17
|
+
expect(packageJson.exports["./setup"]).toEqual({
|
|
18
|
+
types: "./lib/setup/index.d.ts",
|
|
19
|
+
default: "./lib/setup/index.mjs",
|
|
20
|
+
});
|
|
21
|
+
expect(packageJson.exports["./runtime"]).toEqual({
|
|
22
|
+
types: "./lib/runtime/index.d.ts",
|
|
23
|
+
default: "./lib/runtime/index.mjs",
|
|
24
|
+
});
|
|
25
|
+
expect(fs.existsSync(path.join(rootDir, "lib", "index.d.ts"))).toBe(true);
|
|
26
|
+
expect(fs.existsSync(path.join(rootDir, "lib", "setup", "index.d.ts"))).toBe(true);
|
|
27
|
+
expect(fs.existsSync(path.join(rootDir, "lib", "runtime", "index.d.ts"))).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
package/lib/runner/index.mjs
CHANGED
|
@@ -832,12 +832,15 @@ function printRunSummary(results, durationMs) {
|
|
|
832
832
|
);
|
|
833
833
|
const failedSuites = executedServices.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
834
834
|
const passedSuites = completedSuites - failedSuites;
|
|
835
|
+
const totalFiles = executedServices.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
836
|
+
const passedFiles = executedServices.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
835
837
|
|
|
836
838
|
console.log("\n══ Summary ══");
|
|
837
839
|
console.log(
|
|
838
840
|
[
|
|
839
841
|
`services ${passedServices.length}/${executedServices.length} passed`,
|
|
840
842
|
`suites ${passedSuites}/${totalSuites} passed`,
|
|
843
|
+
totalFiles > 0 ? `files ${passedFiles}/${totalFiles} passed` : null,
|
|
841
844
|
skippedServices.length > 0 ? `${skippedServices.length} skipped` : null,
|
|
842
845
|
`duration ${formatDuration(durationMs)}`,
|
|
843
846
|
]
|
package/lib/runner/results.mjs
CHANGED
|
@@ -156,6 +156,19 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
156
156
|
(suite) => suite.completedFileCount === suite.fileCount
|
|
157
157
|
).length;
|
|
158
158
|
const failedSuiteCount = suites.filter((suite) => suite.failedFiles.length > 0).length;
|
|
159
|
+
const totalFileCount = suites.reduce((sum, suite) => sum + suite.fileCount, 0);
|
|
160
|
+
const completedFileCount = suites.reduce(
|
|
161
|
+
(sum, suite) => sum + suite.completedFileCount,
|
|
162
|
+
0
|
|
163
|
+
);
|
|
164
|
+
const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFiles.length, 0);
|
|
165
|
+
const passedFileCount = suites.reduce(
|
|
166
|
+
(sum, suite) =>
|
|
167
|
+
sum +
|
|
168
|
+
suite.fileResults.filter((file) => file.status === "passed").length,
|
|
169
|
+
0
|
|
170
|
+
);
|
|
171
|
+
const notRunFileCount = totalFileCount - completedFileCount;
|
|
159
172
|
const accumulatedDurationMs = suites.reduce((sum, suite) => sum + suite.durationMs, 0);
|
|
160
173
|
const durationMs =
|
|
161
174
|
tracker.firstTaskAt && tracker.lastTaskAt
|
|
@@ -173,6 +186,11 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
173
186
|
suiteCount: tracker.suiteCount,
|
|
174
187
|
completedSuiteCount,
|
|
175
188
|
failedSuiteCount,
|
|
189
|
+
totalFileCount,
|
|
190
|
+
completedFileCount,
|
|
191
|
+
passedFileCount,
|
|
192
|
+
failedFileCount,
|
|
193
|
+
notRunFileCount,
|
|
176
194
|
durationMs,
|
|
177
195
|
suites: suites.map((suite) => ({
|
|
178
196
|
name: suite.name,
|
|
@@ -180,6 +198,10 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
180
198
|
framework: formatFrameworkForArtifact(suite.framework),
|
|
181
199
|
failed: suite.failedFiles.length > 0,
|
|
182
200
|
fileCount: suite.fileCount,
|
|
201
|
+
completedFileCount: suite.completedFileCount,
|
|
202
|
+
passedFileCount: suite.fileResults.filter((file) => file.status === "passed").length,
|
|
203
|
+
failedFileCount: suite.failedFiles.length,
|
|
204
|
+
notRunFileCount: suite.fileCount - suite.completedFileCount,
|
|
183
205
|
failedFiles: suite.failedFiles,
|
|
184
206
|
durationMs: suite.durationMs,
|
|
185
207
|
error: suite.error,
|
|
@@ -299,6 +321,10 @@ export function buildRunArtifact({
|
|
|
299
321
|
const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
|
|
300
322
|
const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
|
|
301
323
|
const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
324
|
+
const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
325
|
+
const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
326
|
+
const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
|
|
327
|
+
const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
|
|
302
328
|
const dbBackend = summarizeDbBackend(results);
|
|
303
329
|
|
|
304
330
|
return {
|
|
@@ -339,6 +365,12 @@ export function buildRunArtifact({
|
|
|
339
365
|
passed: completedSuites - failedSuites,
|
|
340
366
|
failed: failedSuites,
|
|
341
367
|
},
|
|
368
|
+
files: {
|
|
369
|
+
total: totalFiles,
|
|
370
|
+
passed: passedFiles,
|
|
371
|
+
failed: failedFiles,
|
|
372
|
+
notRun: notRunFiles,
|
|
373
|
+
},
|
|
342
374
|
},
|
|
343
375
|
services: results.map((result) => ({
|
|
344
376
|
name: result.name,
|
|
@@ -347,6 +379,11 @@ export function buildRunArtifact({
|
|
|
347
379
|
suiteCount: result.suiteCount,
|
|
348
380
|
completedSuiteCount: result.completedSuiteCount,
|
|
349
381
|
failedSuiteCount: result.failedSuiteCount,
|
|
382
|
+
totalFileCount: result.totalFileCount,
|
|
383
|
+
completedFileCount: result.completedFileCount,
|
|
384
|
+
passedFileCount: result.passedFileCount,
|
|
385
|
+
failedFileCount: result.failedFileCount,
|
|
386
|
+
notRunFileCount: result.notRunFileCount,
|
|
350
387
|
durationMs: result.durationMs,
|
|
351
388
|
dbBackend: result.dbBackend,
|
|
352
389
|
suites: result.suites,
|
|
@@ -372,10 +409,15 @@ export function formatDuration(durationMs) {
|
|
|
372
409
|
|
|
373
410
|
export function formatServiceSummary(result) {
|
|
374
411
|
const passedSuites = result.completedSuiteCount - result.failedSuiteCount;
|
|
375
|
-
const
|
|
412
|
+
const notRunSuites = result.suiteCount - result.completedSuiteCount;
|
|
376
413
|
let detail = `${passedSuites}/${result.suiteCount} suites passed`;
|
|
377
|
-
if (
|
|
378
|
-
detail += `, ${
|
|
414
|
+
if ((result.totalFileCount || 0) > 0) {
|
|
415
|
+
detail += `, ${result.passedFileCount}/${result.totalFileCount} files passed`;
|
|
416
|
+
}
|
|
417
|
+
if (notRunSuites > 0) {
|
|
418
|
+
detail += `, ${notRunSuites} ${pluralize(notRunSuites, "suite", "suites")} not run`;
|
|
419
|
+
} else if ((result.notRunFileCount || 0) > 0) {
|
|
420
|
+
detail += `, ${result.notRunFileCount} ${pluralize(result.notRunFileCount, "file", "files")} not run`;
|
|
379
421
|
}
|
|
380
422
|
return detail;
|
|
381
423
|
}
|
|
@@ -404,3 +446,7 @@ function sanitizeErrorMessage(message) {
|
|
|
404
446
|
.replace(/Command failed with exit code (\d+): k6 run\b/g, "Default runtime failed with exit code $1:")
|
|
405
447
|
.replace(/[\\/]vendor[\\/]k6\b/g, "default-runtime");
|
|
406
448
|
}
|
|
449
|
+
|
|
450
|
+
function pluralize(value, singular, plural) {
|
|
451
|
+
return value === 1 ? singular : plural;
|
|
452
|
+
}
|
|
@@ -64,8 +64,12 @@ describe("runner-results", () => {
|
|
|
64
64
|
const result = finalizeServiceResult(tracker, 1000, 1500);
|
|
65
65
|
expect(result.failed).toBe(true);
|
|
66
66
|
expect(result.failedSuiteCount).toBe(1);
|
|
67
|
+
expect(result.totalFileCount).toBe(1);
|
|
68
|
+
expect(result.failedFileCount).toBe(1);
|
|
69
|
+
expect(result.passedFileCount).toBe(0);
|
|
67
70
|
expect(result.errors).toEqual(["worker failed", "graph failed"]);
|
|
68
71
|
expect(result.suites[0].framework).toBe("default");
|
|
72
|
+
expect(result.suites[0].failedFileCount).toBe(1);
|
|
69
73
|
expect(result.suites[0].files).toEqual([
|
|
70
74
|
{
|
|
71
75
|
path: "tests/health.js",
|
|
@@ -86,6 +90,11 @@ describe("runner-results", () => {
|
|
|
86
90
|
suiteCount: 1,
|
|
87
91
|
completedSuiteCount: 1,
|
|
88
92
|
failedSuiteCount: 0,
|
|
93
|
+
totalFileCount: 3,
|
|
94
|
+
completedFileCount: 3,
|
|
95
|
+
passedFileCount: 3,
|
|
96
|
+
failedFileCount: 0,
|
|
97
|
+
notRunFileCount: 0,
|
|
89
98
|
durationMs: 1200,
|
|
90
99
|
dbBackend: "local",
|
|
91
100
|
suites: [],
|
|
@@ -98,6 +107,11 @@ describe("runner-results", () => {
|
|
|
98
107
|
suiteCount: 0,
|
|
99
108
|
completedSuiteCount: 0,
|
|
100
109
|
failedSuiteCount: 0,
|
|
110
|
+
totalFileCount: 0,
|
|
111
|
+
completedFileCount: 0,
|
|
112
|
+
passedFileCount: 0,
|
|
113
|
+
failedFileCount: 0,
|
|
114
|
+
notRunFileCount: 0,
|
|
101
115
|
durationMs: 0,
|
|
102
116
|
dbBackend: null,
|
|
103
117
|
suites: [],
|
|
@@ -134,6 +148,12 @@ describe("runner-results", () => {
|
|
|
134
148
|
|
|
135
149
|
expect(artifact.product.name).toBe("my-product");
|
|
136
150
|
expect(artifact.summary.services.total).toBe(1);
|
|
151
|
+
expect(artifact.summary.files).toEqual({
|
|
152
|
+
total: 3,
|
|
153
|
+
passed: 3,
|
|
154
|
+
failed: 0,
|
|
155
|
+
notRun: 0,
|
|
156
|
+
});
|
|
137
157
|
expect(summarizeDbBackend(results)).toBe("local");
|
|
138
158
|
expect(formatDuration(65_000)).toBe("1m 05s");
|
|
139
159
|
expect(
|
|
@@ -141,8 +161,11 @@ describe("runner-results", () => {
|
|
|
141
161
|
completedSuiteCount: 2,
|
|
142
162
|
failedSuiteCount: 1,
|
|
143
163
|
suiteCount: 3,
|
|
164
|
+
totalFileCount: 6,
|
|
165
|
+
passedFileCount: 5,
|
|
166
|
+
notRunFileCount: 1,
|
|
144
167
|
})
|
|
145
|
-
).toBe("1/3 suites passed, 1 not run");
|
|
168
|
+
).toBe("1/3 suites passed, 5/6 files passed, 1 suite not run");
|
|
146
169
|
expect(formatError(new Error("boom"))).toBe("boom");
|
|
147
170
|
});
|
|
148
171
|
|
package/lib/runner/template.mjs
CHANGED
|
@@ -72,7 +72,7 @@ export function buildPortMap(runtimeConfigs, workerId) {
|
|
|
72
72
|
if (existing) {
|
|
73
73
|
throw new Error(
|
|
74
74
|
`Worker port collision: services "${existing}" and "${config.name}" both resolve to ${actualPort}. ` +
|
|
75
|
-
`Assign distinct local.port/baseUrl ports in testkit.
|
|
75
|
+
`Assign distinct local.port/baseUrl ports in testkit.setup.ts.`
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
seen.set(actualPort, config.name);
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export type RuntimeMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
2
|
+
|
|
3
|
+
export interface RuntimeHeaders {
|
|
4
|
+
[key: string]: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RuntimeCookie {
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RuntimeResponse {
|
|
12
|
+
body: string;
|
|
13
|
+
cookies?: Record<string, RuntimeCookie[]>;
|
|
14
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
15
|
+
status: number;
|
|
16
|
+
timings?: {
|
|
17
|
+
duration: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RuntimeOptions {
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
thresholds?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RuntimeEnv {
|
|
27
|
+
BASE: string;
|
|
28
|
+
MACHINE_ID?: string;
|
|
29
|
+
routeParams: RuntimeHeaders;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RuntimeDb {
|
|
33
|
+
exec(sql: string): unknown;
|
|
34
|
+
query<T = Record<string, unknown>>(sql: string): T[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RuntimeDalContext {
|
|
38
|
+
db: RuntimeDb;
|
|
39
|
+
truncate(...tables: string[]): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface HttpRequestParams {
|
|
43
|
+
headers?: RuntimeHeaders;
|
|
44
|
+
redirects?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface RuntimeHttpClient {
|
|
48
|
+
del(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
|
|
49
|
+
file(data: unknown, filename?: string, contentType?: string): unknown;
|
|
50
|
+
get(url: string, params?: HttpRequestParams): RuntimeResponse;
|
|
51
|
+
patch(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
|
|
52
|
+
post(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
|
|
53
|
+
put(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Metric {
|
|
57
|
+
add(value: number): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare class Rate implements Metric {
|
|
61
|
+
constructor(name: string, isTime?: boolean);
|
|
62
|
+
add(value: number): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare class Trend implements Metric {
|
|
66
|
+
constructor(name: string, isTime?: boolean);
|
|
67
|
+
add(value: number): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface HttpClientConfig<TSetup = unknown> {
|
|
71
|
+
baseUrl: string;
|
|
72
|
+
defaultHeaders?: RuntimeHeaders;
|
|
73
|
+
getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
|
|
74
|
+
getRawHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void;
|
|
75
|
+
routeHeaders?: RuntimeHeaders;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface HttpClient<TSetup = unknown> {
|
|
79
|
+
delete(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
80
|
+
get(path: string, setupData?: TSetup | null, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
81
|
+
getWithHeaders(
|
|
82
|
+
path: string,
|
|
83
|
+
setupData?: TSetup | null,
|
|
84
|
+
extraHeaders?: RuntimeHeaders
|
|
85
|
+
): RuntimeResponse;
|
|
86
|
+
patch(
|
|
87
|
+
path: string,
|
|
88
|
+
setupData?: TSetup | null,
|
|
89
|
+
body?: unknown,
|
|
90
|
+
extraHeaders?: RuntimeHeaders
|
|
91
|
+
): RuntimeResponse;
|
|
92
|
+
post(
|
|
93
|
+
path: string,
|
|
94
|
+
setupData?: TSetup | null,
|
|
95
|
+
body?: unknown,
|
|
96
|
+
extraHeaders?: RuntimeHeaders
|
|
97
|
+
): RuntimeResponse;
|
|
98
|
+
put(
|
|
99
|
+
path: string,
|
|
100
|
+
setupData?: TSetup | null,
|
|
101
|
+
body?: unknown,
|
|
102
|
+
extraHeaders?: RuntimeHeaders
|
|
103
|
+
): RuntimeResponse;
|
|
104
|
+
raw(
|
|
105
|
+
method: RuntimeMethod,
|
|
106
|
+
path: string,
|
|
107
|
+
body?: unknown,
|
|
108
|
+
extraHeaders?: RuntimeHeaders
|
|
109
|
+
): RuntimeResponse;
|
|
110
|
+
rawDelete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
111
|
+
rawGet(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
112
|
+
rawPatch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
113
|
+
rawPost(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
114
|
+
rawPut(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
115
|
+
request(
|
|
116
|
+
method: RuntimeMethod,
|
|
117
|
+
path: string,
|
|
118
|
+
setupData?: TSetup | null,
|
|
119
|
+
body?: unknown,
|
|
120
|
+
extraHeaders?: RuntimeHeaders
|
|
121
|
+
): RuntimeResponse;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare const check: <T>(
|
|
125
|
+
value: T,
|
|
126
|
+
checks: Record<string, (value: T) => boolean>
|
|
127
|
+
) => boolean;
|
|
128
|
+
export declare const fail: (message: string) => never;
|
|
129
|
+
export declare const group: (name: string, fn: () => void) => void;
|
|
130
|
+
export declare const sleep: (seconds?: number) => void;
|
|
131
|
+
|
|
132
|
+
export declare const http: RuntimeHttpClient;
|
|
133
|
+
|
|
134
|
+
export declare function file(data: unknown, filename?: string, contentType?: string): unknown;
|
|
135
|
+
export declare function json<T = unknown>(response: Pick<RuntimeResponse, "body">): T;
|
|
136
|
+
export declare function contains<T extends Record<string, unknown>>(
|
|
137
|
+
rows: T[],
|
|
138
|
+
field: keyof T | string,
|
|
139
|
+
value: unknown
|
|
140
|
+
): boolean;
|
|
141
|
+
export declare function allMatch<T>(
|
|
142
|
+
rows: T[],
|
|
143
|
+
predicate: (row: T) => boolean
|
|
144
|
+
): boolean;
|
|
145
|
+
export declare function isSorted<T extends Record<string, unknown>>(
|
|
146
|
+
rows: T[],
|
|
147
|
+
field: keyof T | string,
|
|
148
|
+
direction?: "asc" | "desc"
|
|
149
|
+
): boolean;
|
|
150
|
+
|
|
151
|
+
export declare function singleIterationOptions(overrides?: RuntimeOptions): RuntimeOptions;
|
|
152
|
+
export declare const defaultOptions: RuntimeOptions;
|
|
153
|
+
export declare const httpDefaultOptions: RuntimeOptions;
|
|
154
|
+
|
|
155
|
+
export declare function createDalContext(db?: RuntimeDb): RuntimeDalContext;
|
|
156
|
+
export declare function openDb(): RuntimeDb;
|
|
157
|
+
export declare function truncate(db: RuntimeDb, ...tables: string[]): void;
|
|
158
|
+
|
|
159
|
+
export declare function getEnv(): RuntimeEnv;
|
|
160
|
+
export declare function createHttpClient<TSetup = unknown>(
|
|
161
|
+
config: HttpClientConfig<TSetup>
|
|
162
|
+
): HttpClient<TSetup>;
|
|
163
|
+
export declare function makeReq<TSetup = unknown>(
|
|
164
|
+
baseUrl: string,
|
|
165
|
+
routeHeaders?: RuntimeHeaders,
|
|
166
|
+
getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
|
|
167
|
+
): HttpClient<TSetup>["request"];
|
|
168
|
+
export declare function makeRawReq(
|
|
169
|
+
baseUrl: string,
|
|
170
|
+
routeHeaders?: RuntimeHeaders,
|
|
171
|
+
getRawHeaders?: (setupData?: never) => RuntimeHeaders | void
|
|
172
|
+
): HttpClient["raw"];
|
|
173
|
+
export declare function makeGetWithHeaders<TSetup = unknown>(
|
|
174
|
+
baseUrl: string,
|
|
175
|
+
routeHeaders?: RuntimeHeaders,
|
|
176
|
+
getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
|
|
177
|
+
): HttpClient<TSetup>["getWithHeaders"];
|
|
178
|
+
|
|
179
|
+
declare global {
|
|
180
|
+
const __ENV: Record<string, string | undefined>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {};
|
|
@@ -1,50 +1,49 @@
|
|
|
1
1
|
import { fail } from "k6";
|
|
2
2
|
import { defaultOptions, recordRuntimeFailure } from "./checks.js";
|
|
3
3
|
import { createHttpClient, getEnv } from "./http.js";
|
|
4
|
+
import {
|
|
5
|
+
clearRuntimeContext,
|
|
6
|
+
registerRuntimeContext,
|
|
7
|
+
resolveHttpProfile,
|
|
8
|
+
} from "../../setup/runtime.mjs";
|
|
4
9
|
|
|
5
10
|
export function defineHttpSuite(configOrRun, maybeRun) {
|
|
6
11
|
const { config, run } = normalizeSuiteArgs(configOrRun, maybeRun);
|
|
7
|
-
const env = config.env || getEnv();
|
|
8
|
-
const auth = config.auth || null;
|
|
9
|
-
|
|
10
|
-
const client = createHttpClient({
|
|
11
|
-
baseUrl: env.BASE,
|
|
12
|
-
routeHeaders: env.routeParams,
|
|
13
|
-
getHeaders(setupData) {
|
|
14
|
-
return {
|
|
15
|
-
...callHeaders(auth?.headers, setupData, env),
|
|
16
|
-
...callHeaders(config.headers, setupData, env),
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
|
-
getRawHeaders(setupData) {
|
|
20
|
-
return callHeaders(config.rawHeaders, setupData, env);
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
12
|
|
|
24
13
|
return {
|
|
25
|
-
|
|
14
|
+
get options() {
|
|
15
|
+
return mergeProfileConfig(config).options || defaultOptions;
|
|
16
|
+
},
|
|
26
17
|
setup() {
|
|
27
|
-
|
|
18
|
+
const resolved = resolveRuntimeConfig(config);
|
|
28
19
|
try {
|
|
29
|
-
|
|
20
|
+
registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
|
|
21
|
+
if (typeof resolved.auth?.setup !== "function") return null;
|
|
22
|
+
return resolved.auth.setup({ env: resolved.env });
|
|
30
23
|
} catch (error) {
|
|
31
24
|
recordRuntimeFailure();
|
|
32
25
|
fail(formatFatalSuiteError("setup", error));
|
|
26
|
+
} finally {
|
|
27
|
+
clearRuntimeContext();
|
|
33
28
|
}
|
|
34
29
|
},
|
|
35
30
|
exec(setupData) {
|
|
31
|
+
const resolved = resolveRuntimeConfig(config);
|
|
36
32
|
try {
|
|
33
|
+
registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
|
|
37
34
|
return run({
|
|
38
|
-
env,
|
|
39
|
-
req: client.request,
|
|
40
|
-
rawReq: client.raw,
|
|
41
|
-
getWithHeaders: client.getWithHeaders,
|
|
35
|
+
env: resolved.env,
|
|
36
|
+
req: resolved.client.request,
|
|
37
|
+
rawReq: resolved.client.raw,
|
|
38
|
+
getWithHeaders: resolved.client.getWithHeaders,
|
|
42
39
|
setupData,
|
|
43
40
|
session: setupData,
|
|
44
41
|
});
|
|
45
42
|
} catch (error) {
|
|
46
43
|
recordRuntimeFailure();
|
|
47
44
|
fail(formatFatalSuiteError("exec", error));
|
|
45
|
+
} finally {
|
|
46
|
+
clearRuntimeContext();
|
|
48
47
|
}
|
|
49
48
|
},
|
|
50
49
|
};
|
|
@@ -65,6 +64,50 @@ function callHeaders(builder, setupData, env) {
|
|
|
65
64
|
return builder(setupData, { env }) || {};
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
function mergeProfileConfig(config) {
|
|
68
|
+
if (!config?.profile) return config || {};
|
|
69
|
+
|
|
70
|
+
const profile = resolveHttpProfile(config.profile) || {};
|
|
71
|
+
return {
|
|
72
|
+
...profile,
|
|
73
|
+
...config,
|
|
74
|
+
auth: config.auth ?? profile.auth ?? null,
|
|
75
|
+
headers: config.headers ?? profile.headers,
|
|
76
|
+
rawHeaders: config.rawHeaders ?? profile.rawHeaders,
|
|
77
|
+
options: config.options ?? profile.options,
|
|
78
|
+
env: config.env ?? profile.env,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveRuntimeConfig(config) {
|
|
83
|
+
const resolvedConfig = mergeProfileConfig(config);
|
|
84
|
+
const env = {
|
|
85
|
+
...(resolvedConfig.env || getEnv()),
|
|
86
|
+
rawEnv: __ENV,
|
|
87
|
+
};
|
|
88
|
+
const auth = resolvedConfig.auth || null;
|
|
89
|
+
const client = createHttpClient({
|
|
90
|
+
baseUrl: env.BASE,
|
|
91
|
+
routeHeaders: env.routeParams,
|
|
92
|
+
getHeaders(setupData) {
|
|
93
|
+
return {
|
|
94
|
+
...callHeaders(auth?.headers, setupData, env),
|
|
95
|
+
...callHeaders(resolvedConfig.headers, setupData, env),
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
getRawHeaders(setupData) {
|
|
99
|
+
return callHeaders(resolvedConfig.rawHeaders, setupData, env);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
resolvedConfig,
|
|
105
|
+
env,
|
|
106
|
+
auth,
|
|
107
|
+
client,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
68
111
|
function formatFatalSuiteError(phase, error) {
|
|
69
112
|
if (error instanceof Error) {
|
|
70
113
|
return `Uncaught testkit suite error during ${phase}: ${error.message}`;
|