@apifuse/provider-sdk 2.2.0-beta.26 → 2.2.0-beta.28
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/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +2 -2
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-perf.ts +2 -4
- package/bin/apifuse-record.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth-turn/index.js +1 -1
- package/dist/ceremonies/index.js +52 -11
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +4 -2
- package/dist/contract-serialization.d.ts +1 -20
- package/dist/contract-serialization.js +8 -587
- package/dist/contract.d.ts +0 -2
- package/dist/contract.js +5 -9
- package/dist/index.d.ts +9 -8
- package/dist/index.js +6 -8
- package/dist/provider.d.ts +3 -2
- package/dist/provider.js +2 -2
- package/dist/runtime/auth-flow.js +1 -1
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +135 -12
- package/dist/runtime/instrumentation.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +2 -33
- package/dist/runtime/native-network.js +2 -68
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
- package/dist/runtime/resolver-vendors/twocaptcha.js +80 -43
- package/dist/runtime/resolver-vendors/types.d.ts +1 -1
- package/dist/runtime/resolver.d.ts +6 -10
- package/dist/runtime/resolver.js +19 -18
- package/dist/runtime/state.js +5 -115
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.js +7 -130
- package/dist/schema.d.ts +0 -63
- package/dist/schema.js +8 -808
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/self-test.d.ts +13 -0
- package/dist/server/self-test.js +124 -46
- package/dist/server/serve-implementation.d.ts +199 -0
- package/dist/server/serve-implementation.js +2054 -0
- package/dist/server/serve.d.ts +1 -187
- package/dist/server/serve.js +1 -1827
- package/dist/stateful/errors.d.ts +5 -0
- package/dist/stateful/errors.js +10 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
- package/dist/stateful/stateful-provider-session-routing.js +2 -10
- package/dist/stream.js +7 -1
- package/package.json +27 -2
- package/src/auth-turn/index.ts +1 -1
- package/src/ceremonies/index.ts +68 -18
- package/src/config/loader.ts +5 -2
- package/src/contract-serialization.ts +8 -859
- package/src/contract.ts +5 -16
- package/src/index.ts +18 -34
- package/src/provider.ts +12 -24
- package/src/runtime/auth-flow.ts +1 -1
- package/src/runtime/http.ts +155 -11
- package/src/runtime/instrumentation.ts +1 -1
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +16 -97
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +100 -49
- package/src/runtime/resolver-vendors/types.ts +1 -0
- package/src/runtime/resolver.ts +47 -22
- package/src/runtime/state.ts +5 -144
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +14 -157
- package/src/schema.ts +9 -1060
- package/src/serve.ts +6 -1
- package/src/server/index.ts +1 -0
- package/src/server/self-test.ts +184 -59
- package/src/server/serve-implementation.ts +3024 -0
- package/src/server/serve.ts +1 -2661
- package/src/stateful/errors.ts +12 -0
- package/src/stateful/stateful-provider-session-routing.ts +2 -11
- package/src/stream.ts +8 -1
package/src/serve.ts
CHANGED
package/src/server/index.ts
CHANGED
package/src/server/self-test.ts
CHANGED
|
@@ -77,11 +77,22 @@ export interface SelfTestResponse {
|
|
|
77
77
|
results: SelfTestCaseResult[];
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export interface SelfTestCancellationLogEvent {
|
|
81
|
+
level: "info";
|
|
82
|
+
event: "self_test_run_cancelled";
|
|
83
|
+
providerId: string;
|
|
84
|
+
requestId: string;
|
|
85
|
+
operationId: string;
|
|
86
|
+
caseName: string;
|
|
87
|
+
reason: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
export type SelfTestOperationInvoke = (args: {
|
|
81
91
|
operationId: string;
|
|
82
92
|
input: unknown;
|
|
83
93
|
connection?: OperationConnection;
|
|
84
94
|
requestId: string;
|
|
95
|
+
signal?: AbortSignal;
|
|
85
96
|
}) => Promise<{
|
|
86
97
|
status: number;
|
|
87
98
|
data: unknown;
|
|
@@ -106,6 +117,7 @@ export type SelfTestAuthFlowInvoke = (args: {
|
|
|
106
117
|
externalRef?: string;
|
|
107
118
|
input?: Record<string, unknown>;
|
|
108
119
|
context?: Record<string, unknown>;
|
|
120
|
+
signal?: AbortSignal;
|
|
109
121
|
}) => Promise<{
|
|
110
122
|
status: number;
|
|
111
123
|
body: unknown;
|
|
@@ -165,6 +177,8 @@ export interface SelfTestAppOptions {
|
|
|
165
177
|
requestBudgetMs?: number;
|
|
166
178
|
/** Env override for secret collection + budget resolution (tests). */
|
|
167
179
|
env?: Readonly<Record<string, string | undefined>>;
|
|
180
|
+
/** Structured server logger; defaults to the same JSON console shape as the provider server. */
|
|
181
|
+
logger?: (event: SelfTestCancellationLogEvent) => void;
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
function resolveSdkVersion(): string {
|
|
@@ -248,16 +262,25 @@ export function isSelfTestReadOnlyOperation(operation: OperationDefinition): boo
|
|
|
248
262
|
export function createSelfTestInvoke(app: {
|
|
249
263
|
request: (input: string, requestInit?: RequestInit) => Response | Promise<Response>;
|
|
250
264
|
}): SelfTestOperationInvoke {
|
|
251
|
-
return async ({ operationId, input, connection, requestId }) => {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
265
|
+
return async ({ operationId, input, connection, requestId, signal }) => {
|
|
266
|
+
const responsePromise = Promise.resolve(
|
|
267
|
+
app.request(`/v1/${encodeURIComponent(operationId)}`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "content-type": "application/json" },
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
requestId,
|
|
272
|
+
input: input ?? {},
|
|
273
|
+
...(connection ? { connection } : {}),
|
|
274
|
+
}),
|
|
275
|
+
...(signal ? { signal } : {}),
|
|
259
276
|
}),
|
|
260
|
-
|
|
277
|
+
);
|
|
278
|
+
const response = await responsePromise;
|
|
279
|
+
if (signal?.aborted) {
|
|
280
|
+
await cancelSelfTestResponse(response);
|
|
281
|
+
signal.throwIfAborted();
|
|
282
|
+
}
|
|
283
|
+
// An abort after body consumption starts may let response.text() finish.
|
|
261
284
|
const text = await response.text();
|
|
262
285
|
let body: unknown = text;
|
|
263
286
|
try {
|
|
@@ -277,11 +300,58 @@ export function createSelfTestInvoke(app: {
|
|
|
277
300
|
};
|
|
278
301
|
}
|
|
279
302
|
|
|
303
|
+
async function cancelSelfTestResponse(response: Response): Promise<void> {
|
|
304
|
+
await response.body?.cancel().catch(() => undefined);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function selfTestAbortReason(reason: unknown, sensitiveValues: readonly string[]): string {
|
|
308
|
+
const text =
|
|
309
|
+
reason instanceof Error
|
|
310
|
+
? reason.message || reason.name
|
|
311
|
+
: reason === undefined
|
|
312
|
+
? "aborted"
|
|
313
|
+
: String(reason);
|
|
314
|
+
return redactSelfTestText(text, sensitiveValues);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function logSelfTestCancellation(
|
|
318
|
+
provider: ProviderDefinition,
|
|
319
|
+
options: SelfTestAppOptions,
|
|
320
|
+
requestId: string,
|
|
321
|
+
selected: SelectedCase,
|
|
322
|
+
signal: AbortSignal,
|
|
323
|
+
sensitiveValues: readonly string[],
|
|
324
|
+
): void {
|
|
325
|
+
const event: SelfTestCancellationLogEvent = {
|
|
326
|
+
level: "info",
|
|
327
|
+
event: "self_test_run_cancelled",
|
|
328
|
+
providerId: provider.id,
|
|
329
|
+
requestId,
|
|
330
|
+
operationId: selected.operationId,
|
|
331
|
+
caseName: selected.healthCase.name,
|
|
332
|
+
reason: selfTestAbortReason(signal.reason, sensitiveValues),
|
|
333
|
+
};
|
|
334
|
+
if (options.logger) {
|
|
335
|
+
options.logger(event);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
console.log(JSON.stringify(event));
|
|
339
|
+
}
|
|
340
|
+
|
|
280
341
|
/** Binds the self-test auth-flow driver to a tenant app's /auth pipeline in-process. */
|
|
281
342
|
export function createSelfTestAuthFlowInvoke(app: {
|
|
282
343
|
request: (input: string, requestInit?: RequestInit) => Response | Promise<Response>;
|
|
283
344
|
}): SelfTestAuthFlowInvoke {
|
|
284
|
-
return async ({
|
|
345
|
+
return async ({
|
|
346
|
+
route,
|
|
347
|
+
requestId,
|
|
348
|
+
flowId,
|
|
349
|
+
connectionId,
|
|
350
|
+
externalRef,
|
|
351
|
+
input,
|
|
352
|
+
context,
|
|
353
|
+
signal,
|
|
354
|
+
}) => {
|
|
285
355
|
const response = await app.request(`/auth/${route}`, {
|
|
286
356
|
method: "POST",
|
|
287
357
|
headers: { "content-type": "application/json" },
|
|
@@ -293,6 +363,7 @@ export function createSelfTestAuthFlowInvoke(app: {
|
|
|
293
363
|
...(input ? { input } : {}),
|
|
294
364
|
...(context ? { context } : {}),
|
|
295
365
|
}),
|
|
366
|
+
...(signal ? { signal } : {}),
|
|
296
367
|
});
|
|
297
368
|
const text = await response.text();
|
|
298
369
|
let body: unknown = text;
|
|
@@ -312,17 +383,32 @@ class SelfTestCaseTimeoutError extends Error {
|
|
|
312
383
|
}
|
|
313
384
|
}
|
|
314
385
|
|
|
315
|
-
async function withCaseTimeout<T>(
|
|
386
|
+
async function withCaseTimeout<T>(
|
|
387
|
+
run: () => Promise<T>,
|
|
388
|
+
timeoutMs: number,
|
|
389
|
+
controller: AbortController,
|
|
390
|
+
): Promise<T> {
|
|
316
391
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
392
|
+
let onAbort: (() => void) | undefined;
|
|
317
393
|
try {
|
|
318
394
|
return await Promise.race([
|
|
319
395
|
run(),
|
|
320
396
|
new Promise<never>((_, reject) => {
|
|
321
|
-
timer = setTimeout(() =>
|
|
397
|
+
timer = setTimeout(() => {
|
|
398
|
+
const error = new SelfTestCaseTimeoutError(timeoutMs);
|
|
399
|
+
controller.abort(error);
|
|
400
|
+
reject(error);
|
|
401
|
+
}, timeoutMs);
|
|
402
|
+
}),
|
|
403
|
+
new Promise<never>((_, reject) => {
|
|
404
|
+
onAbort = () => reject(controller.signal.reason);
|
|
405
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
406
|
+
if (controller.signal.aborted) onAbort();
|
|
322
407
|
}),
|
|
323
408
|
]);
|
|
324
409
|
} finally {
|
|
325
410
|
if (timer !== undefined) clearTimeout(timer);
|
|
411
|
+
if (onAbort) controller.signal.removeEventListener("abort", onAbort);
|
|
326
412
|
}
|
|
327
413
|
}
|
|
328
414
|
|
|
@@ -595,6 +681,7 @@ async function materializeFlowCredential(
|
|
|
595
681
|
isAbandoned?: () => boolean;
|
|
596
682
|
connectionId?: string;
|
|
597
683
|
externalRef?: string;
|
|
684
|
+
signal?: AbortSignal;
|
|
598
685
|
} = {},
|
|
599
686
|
): Promise<
|
|
600
687
|
| { credential: Record<string, string> }
|
|
@@ -621,6 +708,7 @@ async function materializeFlowCredential(
|
|
|
621
708
|
flowId,
|
|
622
709
|
...(options.connectionId ? { connectionId: options.connectionId } : {}),
|
|
623
710
|
...(options.externalRef ? { externalRef: options.externalRef } : {}),
|
|
711
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
624
712
|
}),
|
|
625
713
|
);
|
|
626
714
|
if (!started.ok) {
|
|
@@ -696,6 +784,7 @@ async function materializeFlowCredential(
|
|
|
696
784
|
...(options.externalRef ? { externalRef: options.externalRef } : {}),
|
|
697
785
|
input: submitInputs,
|
|
698
786
|
...(Object.keys(flowContext).length > 0 ? { context: flowContext } : {}),
|
|
787
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
699
788
|
}),
|
|
700
789
|
);
|
|
701
790
|
if (!continued.ok) {
|
|
@@ -756,7 +845,7 @@ async function resolveSelfTestConnection(
|
|
|
756
845
|
execution: SelfTestExecutionContext,
|
|
757
846
|
operationId: string,
|
|
758
847
|
suite: AnyHealthCheckSuite,
|
|
759
|
-
options: { forceLogin?: boolean; isAbandoned?: () => boolean } = {},
|
|
848
|
+
options: { forceLogin?: boolean; isAbandoned?: () => boolean; signal?: AbortSignal } = {},
|
|
760
849
|
): Promise<SelfTestConnectionResolution> {
|
|
761
850
|
if (!suite.requiresConnection) return { kind: "connection" };
|
|
762
851
|
const inputs = execution.credentials ?? {};
|
|
@@ -861,6 +950,7 @@ async function resolveSelfTestConnection(
|
|
|
861
950
|
...(options.isAbandoned !== undefined ? { isAbandoned: options.isAbandoned } : {}),
|
|
862
951
|
connectionId,
|
|
863
952
|
externalRef: `${execution.provider.id}-${operationId}-self-test`,
|
|
953
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
864
954
|
});
|
|
865
955
|
if (!("credential" in materialized)) {
|
|
866
956
|
// Only the multi-turn SKIP is negative-cached. Flow ERRORS
|
|
@@ -879,10 +969,9 @@ async function resolveSelfTestConnection(
|
|
|
879
969
|
}
|
|
880
970
|
return materialized;
|
|
881
971
|
}
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
// reuse a login whose latency just failed the case, hiding the failure.
|
|
972
|
+
// Abort is cooperative: a flow may still complete after the case deadline
|
|
973
|
+
// if provider code ignores the signal. Never cache that late credential or
|
|
974
|
+
// let the next probe hide the timed-out login.
|
|
886
975
|
if (options.isAbandoned?.() === true) {
|
|
887
976
|
return {
|
|
888
977
|
kind: "flow_error",
|
|
@@ -913,6 +1002,7 @@ async function executeSelfTestCase(
|
|
|
913
1002
|
operationId: string,
|
|
914
1003
|
suite: AnyHealthCheckSuite,
|
|
915
1004
|
healthCase: AnyHealthCheckCase,
|
|
1005
|
+
caseController: AbortController,
|
|
916
1006
|
): Promise<SelfTestCaseResult> {
|
|
917
1007
|
const { provider, invoke } = execution;
|
|
918
1008
|
// execution.sensitiveValues may grow while the case runs (flow-issued
|
|
@@ -926,6 +1016,8 @@ async function executeSelfTestCase(
|
|
|
926
1016
|
const caseDeadlineAtMs = performance.now() + timeoutMs;
|
|
927
1017
|
const remainingCaseTimeoutMs = () =>
|
|
928
1018
|
Math.max(1, Math.ceil(caseDeadlineAtMs - performance.now()));
|
|
1019
|
+
const runWithCaseTimeout = <T>(run: () => Promise<T>, remainingMs: number) =>
|
|
1020
|
+
withCaseTimeout(run, remainingMs, caseController);
|
|
929
1021
|
|
|
930
1022
|
const beginCase = () => {
|
|
931
1023
|
const startedAt = new Date().toISOString();
|
|
@@ -959,16 +1051,17 @@ async function executeSelfTestCase(
|
|
|
959
1051
|
}
|
|
960
1052
|
|
|
961
1053
|
const resolveConnection = async (forceLogin: boolean): Promise<SelfTestConnectionResolution> => {
|
|
962
|
-
// The
|
|
963
|
-
//
|
|
964
|
-
//
|
|
1054
|
+
// The deadline aborts the in-process flow request. Keep the abandonment
|
|
1055
|
+
// guard as a backstop for provider code that ignores cancellation so a
|
|
1056
|
+
// late completion still cannot write the session cache.
|
|
965
1057
|
let abandoned = false;
|
|
966
1058
|
try {
|
|
967
|
-
return await
|
|
1059
|
+
return await runWithCaseTimeout(
|
|
968
1060
|
() =>
|
|
969
1061
|
resolveSelfTestConnection(execution, operationId, suite, {
|
|
970
1062
|
forceLogin,
|
|
971
1063
|
isAbandoned: () => abandoned,
|
|
1064
|
+
signal: caseController.signal,
|
|
972
1065
|
}),
|
|
973
1066
|
remainingCaseTimeoutMs(),
|
|
974
1067
|
);
|
|
@@ -1012,7 +1105,7 @@ async function executeSelfTestCase(
|
|
|
1012
1105
|
// operation attempt, or a slow login reads as a fast healthy case.
|
|
1013
1106
|
const { startedAtMs, finish } = caseScope;
|
|
1014
1107
|
try {
|
|
1015
|
-
return await
|
|
1108
|
+
return await runWithCaseTimeout(async () => {
|
|
1016
1109
|
const resolvedInput = resolveHealthCheckInputDateTokens(healthCase.input);
|
|
1017
1110
|
const preparedInput = healthCase.prepareInput
|
|
1018
1111
|
? await healthCase.prepareInput({
|
|
@@ -1033,6 +1126,7 @@ async function executeSelfTestCase(
|
|
|
1033
1126
|
input: gatewayInput,
|
|
1034
1127
|
connection,
|
|
1035
1128
|
requestId: `${execution.requestId}-prepare-${randomUUID()}`,
|
|
1129
|
+
signal: caseController.signal,
|
|
1036
1130
|
});
|
|
1037
1131
|
if (executed.status === 401 || executed.status === 403) {
|
|
1038
1132
|
prepareAuthStatus = executed.status;
|
|
@@ -1053,6 +1147,7 @@ async function executeSelfTestCase(
|
|
|
1053
1147
|
input: preparedInput,
|
|
1054
1148
|
connection,
|
|
1055
1149
|
requestId: `${execution.requestId}-${randomUUID()}`,
|
|
1150
|
+
signal: caseController.signal,
|
|
1056
1151
|
});
|
|
1057
1152
|
const durationMs = performance.now() - startedAtMs;
|
|
1058
1153
|
|
|
@@ -1383,47 +1478,77 @@ export function createSelfTestApp(provider: ProviderDefinition, options: SelfTes
|
|
|
1383
1478
|
};
|
|
1384
1479
|
const deadline = performance.now() + requestBudgetMs;
|
|
1385
1480
|
const results: SelfTestCaseResult[] = [];
|
|
1481
|
+
const runSignal = c.req.raw.signal;
|
|
1482
|
+
let interruptedCase: SelectedCase | undefined;
|
|
1386
1483
|
// Sequential execution (parallelism 1): self-tests run on serving pods
|
|
1387
1484
|
// and must never compete with themselves for upstream quota.
|
|
1388
1485
|
for (const selected of selection.cases) {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1486
|
+
const caseController = new AbortController();
|
|
1487
|
+
const abortFromRun = () => caseController.abort(runSignal.reason);
|
|
1488
|
+
runSignal.addEventListener("abort", abortFromRun, { once: true });
|
|
1489
|
+
if (runSignal.aborted) abortFromRun();
|
|
1490
|
+
try {
|
|
1491
|
+
if (runSignal.aborted) {
|
|
1492
|
+
interruptedCase = selected;
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
if (performance.now() >= deadline) {
|
|
1496
|
+
const now = new Date().toISOString();
|
|
1497
|
+
results.push({
|
|
1498
|
+
operationId: selected.operationId,
|
|
1499
|
+
caseName: selected.healthCase.name,
|
|
1500
|
+
status: "skipped",
|
|
1501
|
+
label: selected.healthCase.name,
|
|
1502
|
+
responseTimeMs: 0,
|
|
1503
|
+
skipReason: "budget_exhausted",
|
|
1504
|
+
startedAt: now,
|
|
1505
|
+
finishedAt: now,
|
|
1506
|
+
});
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (!isSelfTestReadOnlyOperation(selected.operation)) {
|
|
1510
|
+
const now = new Date().toISOString();
|
|
1511
|
+
results.push({
|
|
1512
|
+
operationId: selected.operationId,
|
|
1513
|
+
caseName: selected.healthCase.name,
|
|
1514
|
+
status: "error",
|
|
1515
|
+
label: selected.healthCase.name,
|
|
1516
|
+
responseTimeMs: 0,
|
|
1517
|
+
error: {
|
|
1518
|
+
code: "operation_not_read_only",
|
|
1519
|
+
message: `Operation "${selected.operationId}" is not classified read-only; self-test refuses to execute it.`,
|
|
1520
|
+
},
|
|
1521
|
+
startedAt: now,
|
|
1522
|
+
finishedAt: now,
|
|
1523
|
+
});
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
results.push(
|
|
1527
|
+
await executeSelfTestCase(
|
|
1528
|
+
execution,
|
|
1529
|
+
selected.operationId,
|
|
1530
|
+
selected.suite,
|
|
1531
|
+
selected.healthCase,
|
|
1532
|
+
caseController,
|
|
1533
|
+
),
|
|
1534
|
+
);
|
|
1535
|
+
if (runSignal.aborted) {
|
|
1536
|
+
interruptedCase = selected;
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
} finally {
|
|
1540
|
+
runSignal.removeEventListener("abort", abortFromRun);
|
|
1541
|
+
caseController.abort();
|
|
1419
1542
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1543
|
+
}
|
|
1544
|
+
if (interruptedCase) {
|
|
1545
|
+
logSelfTestCancellation(
|
|
1546
|
+
provider,
|
|
1547
|
+
options,
|
|
1548
|
+
request.requestId,
|
|
1549
|
+
interruptedCase,
|
|
1550
|
+
runSignal,
|
|
1551
|
+
execution.sensitiveValues,
|
|
1427
1552
|
);
|
|
1428
1553
|
}
|
|
1429
1554
|
const singleCase = request.operationId !== undefined && request.caseName !== undefined;
|