@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.
- package/README.md +78 -56
- package/lib/cli/args.mjs +2 -14
- package/lib/cli/args.test.mjs +1 -17
- package/lib/cli/command-helpers.mjs +1 -20
- package/lib/cli/entrypoint.mjs +0 -4
- package/lib/cli/presentation/colors.mjs +1 -1
- package/lib/cli/presentation/failure-presentation.mjs +4 -4
- package/lib/cli/presentation/run-reporter.mjs +23 -9
- package/lib/cli/presentation/run-reporter.test.mjs +12 -6
- package/lib/cli/presentation/summary-box.test.mjs +4 -4
- package/lib/cli/viewer.mjs +18 -19
- package/lib/config/index.mjs +6 -6
- package/lib/config/runtime.mjs +8 -8
- package/lib/config-api/auth-fixtures.mjs +762 -0
- package/lib/config-api/index.d.ts +96 -112
- package/lib/config-api/index.mjs +22 -12
- package/lib/config-api/index.test.mjs +61 -222
- package/lib/index.d.ts +29 -9
- package/lib/package.test.mjs +4 -4
- package/lib/{known-failures → regressions}/github.mjs +36 -78
- package/lib/regressions/github.test.mjs +324 -0
- package/lib/regressions/index.d.ts +189 -0
- package/lib/{known-failures → regressions}/index.mjs +90 -93
- package/lib/{known-failures → regressions}/index.test.mjs +37 -48
- package/lib/runner/formatting.mjs +49 -34
- package/lib/runner/formatting.test.mjs +16 -15
- package/lib/runner/metadata.mjs +1 -1
- package/lib/runner/orchestrator.mjs +7 -9
- package/lib/runner/regressions.mjs +304 -0
- package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
- package/lib/runner/reporting.mjs +2 -2
- package/lib/runner/reporting.test.mjs +2 -2
- package/lib/runner/run-finalization.mjs +18 -30
- package/lib/runner/template-steps.mjs +2 -2
- package/lib/runtime/index.d.ts +50 -33
- package/lib/runtime/index.mjs +0 -1
- package/lib/runtime-src/k6/http-suite-runtime.js +147 -0
- package/lib/runtime-src/k6/http.js +80 -41
- package/lib/runtime-src/k6/scenario-suite.js +13 -110
- package/lib/runtime-src/k6/suite.js +13 -107
- 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 +8 -8
- package/lib/cli/commands/known-failures/render.mjs +0 -19
- package/lib/cli/commands/known-failures/validate.mjs +0 -20
- package/lib/cli/known-failures.mjs +0 -164
- package/lib/config-api/profiles.mjs +0 -640
- package/lib/known-failures/github.test.mjs +0 -512
- package/lib/known-failures/index.d.ts +0 -192
- package/lib/runner/triage.mjs +0 -221
- /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
- /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
package/lib/runtime/index.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface RuntimeResponse {
|
|
|
23
23
|
|
|
24
24
|
export interface RuntimeHttpTrace {
|
|
25
25
|
id: string;
|
|
26
|
+
actorName: string | null;
|
|
27
|
+
organizationKey?: string | null;
|
|
28
|
+
organizationName?: string | null;
|
|
26
29
|
requestId: string;
|
|
27
30
|
startedAt: string;
|
|
28
31
|
finishedAt?: string;
|
|
@@ -93,6 +96,27 @@ export interface RuntimeHttpClient {
|
|
|
93
96
|
put(url: string, body?: unknown, params?: HttpRequestParams): RuntimeResponse;
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
export interface RuntimeActorRecord<TSession = Record<string, unknown>> {
|
|
100
|
+
actorIndex: number;
|
|
101
|
+
actorName: string;
|
|
102
|
+
email?: string;
|
|
103
|
+
name?: string;
|
|
104
|
+
organizationKey?: string;
|
|
105
|
+
organizationName?: string;
|
|
106
|
+
session: TSession | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface AuthSessionBundle<TSession = Record<string, unknown>> {
|
|
110
|
+
actors: Record<string, RuntimeActorRecord<TSession> & TSession>;
|
|
111
|
+
primaryActor: string | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface HttpHeaderBuildContext<TSession = Record<string, unknown>> {
|
|
115
|
+
actor: (RuntimeActorRecord<TSession> & TSession) | null;
|
|
116
|
+
actorName: string | null;
|
|
117
|
+
sessionBundle: AuthSessionBundle<TSession> | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
96
120
|
export interface Metric {
|
|
97
121
|
add(value: number): void;
|
|
98
122
|
}
|
|
@@ -109,38 +133,35 @@ export declare class Trend implements Metric {
|
|
|
109
133
|
|
|
110
134
|
export interface HttpClientConfig<TSetup = unknown> {
|
|
111
135
|
baseUrl: string;
|
|
136
|
+
defaultActor?: string | null;
|
|
112
137
|
defaultHeaders?: RuntimeHeaders;
|
|
113
|
-
getHeaders?: (
|
|
114
|
-
getRawHeaders?: (
|
|
138
|
+
getHeaders?: (context: HttpHeaderBuildContext<TSetup>) => RuntimeHeaders | void;
|
|
139
|
+
getRawHeaders?: (context: HttpHeaderBuildContext<TSetup>) => RuntimeHeaders | void;
|
|
115
140
|
routeHeaders?: RuntimeHeaders;
|
|
141
|
+
sessionBundle?: AuthSessionBundle<TSetup> | null;
|
|
116
142
|
}
|
|
117
143
|
|
|
118
|
-
export interface
|
|
119
|
-
delete(path: string,
|
|
120
|
-
get(path: string,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
patch(
|
|
127
|
-
path: string,
|
|
128
|
-
setupData?: TSetup | null,
|
|
129
|
-
body?: unknown,
|
|
130
|
-
extraHeaders?: RuntimeHeaders
|
|
131
|
-
): RuntimeResponse;
|
|
132
|
-
post(
|
|
133
|
-
path: string,
|
|
134
|
-
setupData?: TSetup | null,
|
|
135
|
-
body?: unknown,
|
|
136
|
-
extraHeaders?: RuntimeHeaders
|
|
137
|
-
): RuntimeResponse;
|
|
138
|
-
put(
|
|
144
|
+
export interface ActorRequestClient {
|
|
145
|
+
delete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
146
|
+
get(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
147
|
+
patch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
148
|
+
post(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
149
|
+
put(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
150
|
+
request(
|
|
151
|
+
method: RuntimeMethod,
|
|
139
152
|
path: string,
|
|
140
|
-
setupData?: TSetup | null,
|
|
141
153
|
body?: unknown,
|
|
142
154
|
extraHeaders?: RuntimeHeaders
|
|
143
155
|
): RuntimeResponse;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface HttpClient<TSetup = unknown> {
|
|
159
|
+
as(actorName: string): ActorRequestClient;
|
|
160
|
+
delete(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
161
|
+
get(path: string, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
162
|
+
patch(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
163
|
+
post(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
164
|
+
put(path: string, body?: unknown, extraHeaders?: RuntimeHeaders): RuntimeResponse;
|
|
144
165
|
raw(
|
|
145
166
|
method: RuntimeMethod,
|
|
146
167
|
path: string,
|
|
@@ -155,7 +176,6 @@ export interface HttpClient<TSetup = unknown> {
|
|
|
155
176
|
request(
|
|
156
177
|
method: RuntimeMethod,
|
|
157
178
|
path: string,
|
|
158
|
-
setupData?: TSetup | null,
|
|
159
179
|
body?: unknown,
|
|
160
180
|
extraHeaders?: RuntimeHeaders
|
|
161
181
|
): RuntimeResponse;
|
|
@@ -226,19 +246,16 @@ export declare function createHttpClient<TSetup = unknown>(
|
|
|
226
246
|
): HttpClient<TSetup>;
|
|
227
247
|
export declare function makeReq<TSetup = unknown>(
|
|
228
248
|
baseUrl: string,
|
|
249
|
+
sessionBundle?: AuthSessionBundle<TSetup> | null,
|
|
229
250
|
routeHeaders?: RuntimeHeaders,
|
|
230
|
-
getHeaders?: (
|
|
231
|
-
|
|
251
|
+
getHeaders?: (context: HttpHeaderBuildContext<TSetup>) => RuntimeHeaders | void,
|
|
252
|
+
defaultActor?: string | null
|
|
253
|
+
): HttpClient<TSetup>;
|
|
232
254
|
export declare function makeRawReq(
|
|
233
255
|
baseUrl: string,
|
|
234
256
|
routeHeaders?: RuntimeHeaders,
|
|
235
|
-
getRawHeaders?: (
|
|
257
|
+
getRawHeaders?: (context: HttpHeaderBuildContext<never>) => RuntimeHeaders | void
|
|
236
258
|
): HttpClient["raw"];
|
|
237
|
-
export declare function makeGetWithHeaders<TSetup = unknown>(
|
|
238
|
-
baseUrl: string,
|
|
239
|
-
routeHeaders?: RuntimeHeaders,
|
|
240
|
-
getHeaders?: (setupData?: TSetup | null) => RuntimeHeaders | void
|
|
241
|
-
): HttpClient<TSetup>["getWithHeaders"];
|
|
242
259
|
|
|
243
260
|
export declare function expectStatus(
|
|
244
261
|
response: RuntimeResponse,
|
package/lib/runtime/index.mjs
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createHttpClient, getEnv } from "./http.js";
|
|
2
|
+
import { resolveHttpProfile } from "../../config-api/runtime.mjs";
|
|
3
|
+
|
|
4
|
+
export function normalizeSuiteArgs(configOrRun, maybeRun) {
|
|
5
|
+
if (typeof configOrRun === "function") {
|
|
6
|
+
return { config: {}, run: configOrRun };
|
|
7
|
+
}
|
|
8
|
+
if (typeof maybeRun !== "function") {
|
|
9
|
+
throw new Error("suite factory requires a run callback");
|
|
10
|
+
}
|
|
11
|
+
return { config: configOrRun || {}, run: maybeRun };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function mergeProfileConfig(config) {
|
|
15
|
+
if (!config?.profile) return config || {};
|
|
16
|
+
|
|
17
|
+
const profile = resolveHttpProfile(config.profile) || {};
|
|
18
|
+
return {
|
|
19
|
+
...profile,
|
|
20
|
+
...config,
|
|
21
|
+
auth: config.auth ?? profile.auth ?? null,
|
|
22
|
+
headers: config.headers ?? profile.headers,
|
|
23
|
+
rawHeaders: config.rawHeaders ?? profile.rawHeaders,
|
|
24
|
+
options: config.options ?? profile.options,
|
|
25
|
+
env: config.env ?? profile.env,
|
|
26
|
+
__testkitAuthProfile: config.__testkitAuthProfile ?? profile.__testkitAuthProfile ?? null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveRuntimeConfig(config, sessionBundle = null) {
|
|
31
|
+
const resolvedConfig = mergeProfileConfig(config);
|
|
32
|
+
const env = {
|
|
33
|
+
...(resolvedConfig.env || getEnv()),
|
|
34
|
+
rawEnv: __ENV,
|
|
35
|
+
};
|
|
36
|
+
const auth = resolvedConfig.auth || null;
|
|
37
|
+
const profileMeta = resolvedConfig.__testkitAuthProfile || null;
|
|
38
|
+
const client = createHttpClient({
|
|
39
|
+
baseUrl: env.BASE,
|
|
40
|
+
defaultActor: profileMeta?.primaryActor || sessionBundle?.primaryActor || null,
|
|
41
|
+
routeHeaders: env.routeParams,
|
|
42
|
+
sessionBundle,
|
|
43
|
+
getHeaders(context) {
|
|
44
|
+
return {
|
|
45
|
+
...callHeaders(auth?.headers, sessionBundle, env, context.actorName),
|
|
46
|
+
...callHeaders(resolvedConfig.headers, sessionBundle, env, context.actorName),
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
getRawHeaders(context) {
|
|
50
|
+
return callHeaders(resolvedConfig.rawHeaders, sessionBundle, env, context.actorName);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
auth,
|
|
56
|
+
client,
|
|
57
|
+
env,
|
|
58
|
+
profileMeta,
|
|
59
|
+
resolvedConfig,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createSuiteActors(sessionBundle, client, profileMeta) {
|
|
64
|
+
const actorNames = profileMeta?.actorNames || Object.keys(sessionBundle?.actors || {});
|
|
65
|
+
const entries = actorNames.map((actorName) => {
|
|
66
|
+
const actorRecord = sessionBundle?.actors?.[actorName] || null;
|
|
67
|
+
return {
|
|
68
|
+
email: actorRecord?.email || null,
|
|
69
|
+
index: Number(actorRecord?.actorIndex ?? 0),
|
|
70
|
+
key: actorName,
|
|
71
|
+
name: actorRecord?.name || null,
|
|
72
|
+
organizationKey: actorRecord?.organizationKey || null,
|
|
73
|
+
organizationName: actorRecord?.organizationName || null,
|
|
74
|
+
req: client.as(actorName),
|
|
75
|
+
session: actorRecord?.session || null,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const byName = new Map(entries.map((entry, index) => [actorNames[index], entry]));
|
|
79
|
+
const primaryName = profileMeta?.primaryActor || sessionBundle?.primaryActor || null;
|
|
80
|
+
const primary = primaryName ? byName.get(primaryName) || null : null;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
actor: primary,
|
|
84
|
+
actors: {
|
|
85
|
+
names: actorNames,
|
|
86
|
+
primary,
|
|
87
|
+
get(name) {
|
|
88
|
+
const actor = byName.get(name);
|
|
89
|
+
if (!actor) {
|
|
90
|
+
throw new Error(`Unknown testkit actor "${name}"`);
|
|
91
|
+
}
|
|
92
|
+
return actor;
|
|
93
|
+
},
|
|
94
|
+
has(name) {
|
|
95
|
+
return byName.has(name);
|
|
96
|
+
},
|
|
97
|
+
list() {
|
|
98
|
+
return entries.slice();
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function formatFatalSuiteError(phase, error) {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
return `Uncaught testkit suite error during ${phase}: ${error.message}`;
|
|
107
|
+
}
|
|
108
|
+
return `Uncaught testkit suite error during ${phase}: ${String(error)}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildRuntimeExceptionDetail(phase, error) {
|
|
112
|
+
const message =
|
|
113
|
+
error instanceof Error ? error.message : String(error);
|
|
114
|
+
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
115
|
+
const location = extractLocationFromStack(stack);
|
|
116
|
+
return {
|
|
117
|
+
kind: "runtime-exception",
|
|
118
|
+
key: location ? `${location.path}:${location.line}:${location.column}` : `runtime-exception:${phase}:${message}`,
|
|
119
|
+
title: "Uncaught runtime exception",
|
|
120
|
+
message: `Uncaught testkit suite error during ${phase}: ${message}`,
|
|
121
|
+
location,
|
|
122
|
+
stack,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function callHeaders(builder, sessionBundle, env, actorName) {
|
|
127
|
+
if (typeof builder !== "function") return {};
|
|
128
|
+
return builder(sessionBundle, { actor: actorName || null, env }) || {};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractLocationFromStack(stack) {
|
|
132
|
+
if (!stack) return null;
|
|
133
|
+
const matches = [...String(stack).matchAll(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/g)].map((match) => ({
|
|
134
|
+
path: normalizeStackPath(match[1]),
|
|
135
|
+
line: Number(match[2]),
|
|
136
|
+
column: Number(match[3]),
|
|
137
|
+
}));
|
|
138
|
+
return matches[0] || null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeStackPath(rawPath) {
|
|
142
|
+
if (typeof rawPath !== "string") return rawPath;
|
|
143
|
+
if (rawPath.startsWith("file://")) {
|
|
144
|
+
return rawPath.slice("file://".length);
|
|
145
|
+
}
|
|
146
|
+
return rawPath;
|
|
147
|
+
}
|
|
@@ -24,8 +24,10 @@ export function getEnv() {
|
|
|
24
24
|
export function createHttpClient(config) {
|
|
25
25
|
const {
|
|
26
26
|
baseUrl,
|
|
27
|
+
defaultActor = null,
|
|
27
28
|
routeHeaders = {},
|
|
28
29
|
defaultHeaders = { "Content-Type": "application/json" },
|
|
30
|
+
sessionBundle = null,
|
|
29
31
|
getHeaders = null,
|
|
30
32
|
getRawHeaders = null,
|
|
31
33
|
} = config;
|
|
@@ -34,55 +36,92 @@ export function createHttpClient(config) {
|
|
|
34
36
|
throw new Error("baseUrl is required");
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
function buildHeaders(builder,
|
|
39
|
+
function buildHeaders(builder, context, extraHeaders = {}) {
|
|
38
40
|
return {
|
|
39
41
|
...defaultHeaders,
|
|
40
|
-
...safeHeaders(builder,
|
|
42
|
+
...safeHeaders(builder, context),
|
|
41
43
|
...routeHeaders,
|
|
42
44
|
...extraHeaders,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
function
|
|
48
|
+
function buildHeaderContext(actorName = null) {
|
|
49
|
+
return {
|
|
50
|
+
actor: actorName && sessionBundle?.actors ? sessionBundle.actors[actorName] || null : null,
|
|
51
|
+
actorName,
|
|
52
|
+
sessionBundle,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureKnownActor(actorName) {
|
|
57
|
+
if (!actorName || !sessionBundle?.actors) return;
|
|
58
|
+
if (!sessionBundle.actors[actorName]) {
|
|
59
|
+
throw new Error(`Unknown testkit actor "${actorName}"`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function requestAs(actorName, method, path, body, extraHeaders = {}) {
|
|
64
|
+
ensureKnownActor(actorName);
|
|
47
65
|
const url = `${baseUrl}${path}`;
|
|
48
|
-
const
|
|
49
|
-
|
|
66
|
+
const headerContext = buildHeaderContext(actorName);
|
|
67
|
+
const headers = buildHeaders(getHeaders, headerContext, extraHeaders);
|
|
68
|
+
return runHttpRequest(method, path, url, body, headers, headerContext.actor);
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
function raw(method, path, body, extraHeaders = {}) {
|
|
53
72
|
const url = `${baseUrl}${path}`;
|
|
54
|
-
const
|
|
55
|
-
|
|
73
|
+
const headerContext = buildHeaderContext(null);
|
|
74
|
+
const headers = buildHeaders(getRawHeaders, headerContext, extraHeaders);
|
|
75
|
+
return runHttpRequest(method, path, url, body, headers, null);
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
function
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
function createActorClient(actorName) {
|
|
79
|
+
return {
|
|
80
|
+
delete(path, extraHeaders = {}) {
|
|
81
|
+
return requestAs(actorName, "DELETE", path, null, extraHeaders);
|
|
82
|
+
},
|
|
83
|
+
get(path, extraHeaders = {}) {
|
|
84
|
+
return requestAs(actorName, "GET", path, null, extraHeaders);
|
|
85
|
+
},
|
|
86
|
+
patch(path, body, extraHeaders = {}) {
|
|
87
|
+
return requestAs(actorName, "PATCH", path, body, extraHeaders);
|
|
88
|
+
},
|
|
89
|
+
post(path, body, extraHeaders = {}) {
|
|
90
|
+
return requestAs(actorName, "POST", path, body, extraHeaders);
|
|
91
|
+
},
|
|
92
|
+
put(path, body, extraHeaders = {}) {
|
|
93
|
+
return requestAs(actorName, "PUT", path, body, extraHeaders);
|
|
94
|
+
},
|
|
95
|
+
request(method, path, body, extraHeaders = {}) {
|
|
96
|
+
return requestAs(actorName, method, path, body, extraHeaders);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
66
99
|
}
|
|
67
100
|
|
|
101
|
+
const defaultClient = createActorClient(defaultActor);
|
|
102
|
+
|
|
68
103
|
return {
|
|
69
104
|
rawHttp: http,
|
|
70
|
-
|
|
105
|
+
as(actorName) {
|
|
106
|
+
ensureKnownActor(actorName);
|
|
107
|
+
return createActorClient(actorName);
|
|
108
|
+
},
|
|
109
|
+
request: defaultClient.request,
|
|
71
110
|
raw,
|
|
72
|
-
get(path,
|
|
73
|
-
return
|
|
111
|
+
get(path, extraHeaders = {}) {
|
|
112
|
+
return defaultClient.get(path, extraHeaders);
|
|
74
113
|
},
|
|
75
|
-
put(path,
|
|
76
|
-
return
|
|
114
|
+
put(path, body, extraHeaders = {}) {
|
|
115
|
+
return defaultClient.put(path, body, extraHeaders);
|
|
77
116
|
},
|
|
78
|
-
post(path,
|
|
79
|
-
return
|
|
117
|
+
post(path, body, extraHeaders = {}) {
|
|
118
|
+
return defaultClient.post(path, body, extraHeaders);
|
|
80
119
|
},
|
|
81
|
-
patch(path,
|
|
82
|
-
return
|
|
120
|
+
patch(path, body, extraHeaders = {}) {
|
|
121
|
+
return defaultClient.patch(path, body, extraHeaders);
|
|
83
122
|
},
|
|
84
|
-
delete(path,
|
|
85
|
-
return
|
|
123
|
+
delete(path, extraHeaders = {}) {
|
|
124
|
+
return defaultClient.delete(path, extraHeaders);
|
|
86
125
|
},
|
|
87
126
|
rawGet(path, extraHeaders = {}) {
|
|
88
127
|
return raw("GET", path, null, extraHeaders);
|
|
@@ -99,16 +138,17 @@ export function createHttpClient(config) {
|
|
|
99
138
|
rawDelete(path, extraHeaders = {}) {
|
|
100
139
|
return raw("DELETE", path, null, extraHeaders);
|
|
101
140
|
},
|
|
102
|
-
getWithHeaders,
|
|
103
141
|
};
|
|
104
142
|
}
|
|
105
143
|
|
|
106
|
-
export function makeReq(baseUrl, routeHeaders = {}, getHeaders = null) {
|
|
144
|
+
export function makeReq(baseUrl, sessionBundle = null, routeHeaders = {}, getHeaders = null, defaultActor = null) {
|
|
107
145
|
return createHttpClient({
|
|
108
146
|
baseUrl,
|
|
109
147
|
routeHeaders,
|
|
148
|
+
sessionBundle,
|
|
149
|
+
defaultActor,
|
|
110
150
|
getHeaders,
|
|
111
|
-
})
|
|
151
|
+
});
|
|
112
152
|
}
|
|
113
153
|
|
|
114
154
|
export function makeRawReq(baseUrl, routeHeaders = {}, getRawHeaders = null) {
|
|
@@ -119,15 +159,7 @@ export function makeRawReq(baseUrl, routeHeaders = {}, getRawHeaders = null) {
|
|
|
119
159
|
}).raw;
|
|
120
160
|
}
|
|
121
161
|
|
|
122
|
-
|
|
123
|
-
return createHttpClient({
|
|
124
|
-
baseUrl,
|
|
125
|
-
routeHeaders,
|
|
126
|
-
getHeaders,
|
|
127
|
-
}).getWithHeaders;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function runHttpRequest(method, path, url, body, headers) {
|
|
162
|
+
function runHttpRequest(method, path, url, body, headers, actorRecord = null) {
|
|
131
163
|
const ordinal = nextTraceOrdinal();
|
|
132
164
|
const requestId = buildRequestId(ordinal);
|
|
133
165
|
const finalHeaders = {
|
|
@@ -140,6 +172,7 @@ function runHttpRequest(method, path, url, body, headers) {
|
|
|
140
172
|
method,
|
|
141
173
|
path,
|
|
142
174
|
url,
|
|
175
|
+
actorRecord,
|
|
143
176
|
requestHeaders: finalHeaders,
|
|
144
177
|
});
|
|
145
178
|
const options = { headers: finalHeaders };
|
|
@@ -190,7 +223,10 @@ export function summarizeHttpTrace(response) {
|
|
|
190
223
|
const trace = getHttpTrace(response);
|
|
191
224
|
if (!trace) return null;
|
|
192
225
|
return {
|
|
226
|
+
actorName: trace.actorName,
|
|
193
227
|
id: trace.id,
|
|
228
|
+
organizationKey: trace.organizationKey,
|
|
229
|
+
organizationName: trace.organizationName,
|
|
194
230
|
requestId: trace.requestId,
|
|
195
231
|
method: trace.method,
|
|
196
232
|
path: trace.path,
|
|
@@ -235,9 +271,12 @@ export function toBodyPreview(response) {
|
|
|
235
271
|
return truncate(rawBody, TRACE_PREVIEW_LIMIT);
|
|
236
272
|
}
|
|
237
273
|
|
|
238
|
-
function createTrace({ ordinal, requestId, method, path, url, requestHeaders }) {
|
|
274
|
+
function createTrace({ ordinal, requestId, method, path, url, actorRecord, requestHeaders }) {
|
|
239
275
|
return {
|
|
276
|
+
actorName: actorRecord?.actorName || null,
|
|
240
277
|
id: `${traceState.phase}-${String(ordinal).padStart(3, "0")}`,
|
|
278
|
+
organizationKey: actorRecord?.organizationKey || null,
|
|
279
|
+
organizationName: actorRecord?.organizationName || null,
|
|
241
280
|
requestId,
|
|
242
281
|
startedAt: new Date().toISOString(),
|
|
243
282
|
method,
|
|
@@ -342,9 +381,9 @@ function normalizeLabel(value, fallback) {
|
|
|
342
381
|
return normalized.length > 0 ? normalized : fallback;
|
|
343
382
|
}
|
|
344
383
|
|
|
345
|
-
function safeHeaders(builder,
|
|
384
|
+
function safeHeaders(builder, context) {
|
|
346
385
|
if (typeof builder !== "function") return {};
|
|
347
|
-
return builder(
|
|
386
|
+
return builder(context) || {};
|
|
348
387
|
}
|
|
349
388
|
|
|
350
389
|
function createWrappedResponse(rawResponse, trace) {
|
|
@@ -7,17 +7,22 @@ import {
|
|
|
7
7
|
startFailureCollection,
|
|
8
8
|
} from "./checks.js";
|
|
9
9
|
import {
|
|
10
|
-
createHttpClient,
|
|
11
10
|
emitHttpTraceCollectionArtifact,
|
|
12
|
-
getEnv,
|
|
13
11
|
startHttpTraceCollection,
|
|
14
12
|
} from "./http.js";
|
|
15
13
|
import {
|
|
16
14
|
clearRuntimeContext,
|
|
17
15
|
registerRuntimeContext,
|
|
18
|
-
resolveHttpProfile,
|
|
19
16
|
} from "../../config-api/runtime.mjs";
|
|
20
17
|
import { createScenarioRuntime } from "./scenario-runtime.js";
|
|
18
|
+
import {
|
|
19
|
+
buildRuntimeExceptionDetail,
|
|
20
|
+
createSuiteActors,
|
|
21
|
+
formatFatalSuiteError,
|
|
22
|
+
mergeProfileConfig,
|
|
23
|
+
normalizeSuiteArgs,
|
|
24
|
+
resolveRuntimeConfig,
|
|
25
|
+
} from "./http-suite-runtime.js";
|
|
21
26
|
|
|
22
27
|
export function defineScenarioSuite(configOrRun, maybeRun) {
|
|
23
28
|
const { config, run } = normalizeSuiteArgs(configOrRun, maybeRun);
|
|
@@ -45,7 +50,8 @@ export function defineScenarioSuite(configOrRun, maybeRun) {
|
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
exec(setupData) {
|
|
48
|
-
const resolved = resolveRuntimeConfig(config);
|
|
53
|
+
const resolved = resolveRuntimeConfig(config, setupData);
|
|
54
|
+
const actorContext = createSuiteActors(setupData, resolved.client, resolved.profileMeta);
|
|
49
55
|
const scenario = createScenarioRuntime({
|
|
50
56
|
seed: resolved.env.rawEnv.TESTKIT_SCENARIO_SEED,
|
|
51
57
|
});
|
|
@@ -54,12 +60,11 @@ export function defineScenarioSuite(configOrRun, maybeRun) {
|
|
|
54
60
|
try {
|
|
55
61
|
registerRuntimeContext({ env: resolved.env, http: resolved.client.rawHttp || null });
|
|
56
62
|
return run({
|
|
63
|
+
actor: actorContext.actor,
|
|
64
|
+
actors: actorContext.actors,
|
|
57
65
|
env: resolved.env,
|
|
58
|
-
req: resolved.client
|
|
66
|
+
req: resolved.client,
|
|
59
67
|
rawReq: resolved.client.raw,
|
|
60
|
-
getWithHeaders: resolved.client.getWithHeaders,
|
|
61
|
-
setupData,
|
|
62
|
-
session: setupData,
|
|
63
68
|
scenario,
|
|
64
69
|
});
|
|
65
70
|
} catch (error) {
|
|
@@ -75,105 +80,3 @@ export function defineScenarioSuite(configOrRun, maybeRun) {
|
|
|
75
80
|
},
|
|
76
81
|
};
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
function normalizeSuiteArgs(configOrRun, maybeRun) {
|
|
80
|
-
if (typeof configOrRun === "function") {
|
|
81
|
-
return { config: {}, run: configOrRun };
|
|
82
|
-
}
|
|
83
|
-
if (typeof maybeRun !== "function") {
|
|
84
|
-
throw new Error("suite factory requires a run callback");
|
|
85
|
-
}
|
|
86
|
-
return { config: configOrRun || {}, run: maybeRun };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function callHeaders(builder, setupData, env) {
|
|
90
|
-
if (typeof builder !== "function") return {};
|
|
91
|
-
return builder(setupData, { env }) || {};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function mergeProfileConfig(config) {
|
|
95
|
-
if (!config?.profile) return config || {};
|
|
96
|
-
|
|
97
|
-
const profile = resolveHttpProfile(config.profile) || {};
|
|
98
|
-
return {
|
|
99
|
-
...profile,
|
|
100
|
-
...config,
|
|
101
|
-
auth: config.auth ?? profile.auth ?? null,
|
|
102
|
-
headers: config.headers ?? profile.headers,
|
|
103
|
-
rawHeaders: config.rawHeaders ?? profile.rawHeaders,
|
|
104
|
-
options: config.options ?? profile.options,
|
|
105
|
-
env: config.env ?? profile.env,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function resolveRuntimeConfig(config) {
|
|
110
|
-
const resolvedConfig = mergeProfileConfig(config);
|
|
111
|
-
const env = {
|
|
112
|
-
...(resolvedConfig.env || getEnv()),
|
|
113
|
-
rawEnv: __ENV,
|
|
114
|
-
};
|
|
115
|
-
const auth = resolvedConfig.auth || null;
|
|
116
|
-
const client = createHttpClient({
|
|
117
|
-
baseUrl: env.BASE,
|
|
118
|
-
routeHeaders: env.routeParams,
|
|
119
|
-
getHeaders(setupData) {
|
|
120
|
-
return {
|
|
121
|
-
...callHeaders(auth?.headers, setupData, env),
|
|
122
|
-
...callHeaders(resolvedConfig.headers, setupData, env),
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
getRawHeaders(setupData) {
|
|
126
|
-
return callHeaders(resolvedConfig.rawHeaders, setupData, env);
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
resolvedConfig,
|
|
132
|
-
env,
|
|
133
|
-
auth,
|
|
134
|
-
client,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function formatFatalSuiteError(phase, error) {
|
|
139
|
-
if (error instanceof Error) {
|
|
140
|
-
return `Uncaught testkit suite error during ${phase}: ${error.message}`;
|
|
141
|
-
}
|
|
142
|
-
return `Uncaught testkit suite error during ${phase}: ${String(error)}`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function buildRuntimeExceptionDetail(phase, error) {
|
|
146
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
-
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
148
|
-
const location = extractLocationFromStack(stack);
|
|
149
|
-
return {
|
|
150
|
-
kind: "runtime-exception",
|
|
151
|
-
key: location
|
|
152
|
-
? `${location.path}:${location.line}:${location.column}`
|
|
153
|
-
: `runtime-exception:${phase}:${message}`,
|
|
154
|
-
title: "Uncaught runtime exception",
|
|
155
|
-
message: `Uncaught testkit suite error during ${phase}: ${message}`,
|
|
156
|
-
location,
|
|
157
|
-
stack,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function extractLocationFromStack(stack) {
|
|
162
|
-
if (!stack) return null;
|
|
163
|
-
const matches = [...String(stack).matchAll(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/g)].map(
|
|
164
|
-
(match) => ({
|
|
165
|
-
path: normalizeStackPath(match[1]),
|
|
166
|
-
line: Number(match[2]),
|
|
167
|
-
column: Number(match[3]),
|
|
168
|
-
})
|
|
169
|
-
);
|
|
170
|
-
return matches[0] || null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function normalizeStackPath(rawPath) {
|
|
174
|
-
if (typeof rawPath !== "string") return rawPath;
|
|
175
|
-
if (rawPath.startsWith("file://")) {
|
|
176
|
-
return rawPath.slice("file://".length);
|
|
177
|
-
}
|
|
178
|
-
return rawPath;
|
|
179
|
-
}
|