@rulvar/testing 1.13.0 → 1.14.0
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/dist/index.d.ts +62 -1
- package/dist/index.js +73 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,67 @@ declare function replayRun<A, R>(wf: Workflow<A, R>, args: A, options: ReplayRun
|
|
|
27
27
|
preview: ResumePreview;
|
|
28
28
|
}>;
|
|
29
29
|
//#endregion
|
|
30
|
+
//#region src/live.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* True only when `RULVAR_LIVE_TESTS` is exactly `'1'` AND every named
|
|
33
|
+
* environment key is set to a non-empty value. Gate live tests as
|
|
34
|
+
* `it.skipIf(!liveTestEnabled('ANTHROPIC_API_KEY'))(...)` so an
|
|
35
|
+
* unrelated key in the shell never triggers a paid provider call from
|
|
36
|
+
* an ordinary test run.
|
|
37
|
+
*/
|
|
38
|
+
declare function liveTestEnabled(...requiredEnvKeys: string[]): boolean;
|
|
39
|
+
interface RunLiveSmokeOptions {
|
|
40
|
+
/** Total attempts including the first (default 3, minimum 1). */
|
|
41
|
+
attempts?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Backoff before retry n (1-based) is `baseDelayMs * n` (default
|
|
44
|
+
* 2000). Pass 0 to retry without sleeping (unit tests).
|
|
45
|
+
*/
|
|
46
|
+
baseDelayMs?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The classified result of a bounded live smoke. `attempts` is how many
|
|
50
|
+
* streams were actually opened; only `'exhausted'` reaches the
|
|
51
|
+
* configured bound.
|
|
52
|
+
*/
|
|
53
|
+
type LiveSmokeOutcome = {
|
|
54
|
+
status: "ok";
|
|
55
|
+
attempts: number;
|
|
56
|
+
events: ChatEvent[];
|
|
57
|
+
} | {
|
|
58
|
+
status: "failed";
|
|
59
|
+
attempts: number;
|
|
60
|
+
error: WireError;
|
|
61
|
+
events: ChatEvent[];
|
|
62
|
+
} | {
|
|
63
|
+
status: "exhausted";
|
|
64
|
+
attempts: number;
|
|
65
|
+
errors: WireError[];
|
|
66
|
+
} | {
|
|
67
|
+
status: "no-terminal";
|
|
68
|
+
attempts: number;
|
|
69
|
+
events: ChatEvent[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Drains `adapter.stream(req)` with a bounded retry policy and classifies
|
|
73
|
+
* the outcome instead of throwing:
|
|
74
|
+
*
|
|
75
|
+
* - `'ok'`: a `finish` event arrived (the events of the successful
|
|
76
|
+
* attempt are included for further assertions).
|
|
77
|
+
* - `'failed'`: a terminal error with `retryable: false`; never retried,
|
|
78
|
+
* diagnostics preserved.
|
|
79
|
+
* - `'exhausted'`: every attempt ended in a `retryable: true` error; the
|
|
80
|
+
* per-attempt errors are preserved in order.
|
|
81
|
+
* - `'no-terminal'`: the stream ended with neither `finish` nor `error`,
|
|
82
|
+
* which violates the provider SPI; never retried (spending again on a
|
|
83
|
+
* misbehaving adapter is wrong).
|
|
84
|
+
*
|
|
85
|
+
* Retries only ever follow typed retryable errors, so a live smoke never
|
|
86
|
+
* converts a real adapter failure into a pass and never spends more than
|
|
87
|
+
* `attempts` calls.
|
|
88
|
+
*/
|
|
89
|
+
declare function runLiveSmoke(adapter: Pick<ProviderAdapter, "stream">, req: ChatRequest, options?: RunLiveSmokeOptions): Promise<LiveSmokeOutcome>;
|
|
90
|
+
//#endregion
|
|
30
91
|
//#region src/cassettes/build-fixtures.d.ts
|
|
31
92
|
/** One cassette fixture file: id, provenance note, and the journal. */
|
|
32
93
|
/** @internal */
|
|
@@ -152,4 +213,4 @@ declare function replay(options: {
|
|
|
152
213
|
adapters?: ProviderAdapter[];
|
|
153
214
|
}): ProviderAdapter[];
|
|
154
215
|
//#endregion
|
|
155
|
-
export { type CassetteFixture, type CreateTestEngineOptions, FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, type FakeAdapterOptions, type FakeCall, type FakeResponder, type FakeToolCallsValue, type FakeWireErrorValue, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, RedactFn, type ReplayRunOptions, type TestEngine, type TestRunHandle, VcrCassette, VcrMissError, VcrRow, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash };
|
|
216
|
+
export { type CassetteFixture, type CreateTestEngineOptions, FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, type FakeAdapterOptions, type FakeCall, type FakeResponder, type FakeToolCallsValue, type FakeWireErrorValue, type LiveSmokeOutcome, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, RedactFn, type ReplayRunOptions, type RunLiveSmokeOptions, type TestEngine, type TestRunHandle, VcrCassette, VcrMissError, VcrRow, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, liveTestEnabled, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash, runLiveSmoke };
|
package/dist/index.js
CHANGED
|
@@ -353,6 +353,78 @@ async function replayRun(wf, args, options) {
|
|
|
353
353
|
};
|
|
354
354
|
}
|
|
355
355
|
//#endregion
|
|
356
|
+
//#region src/live.ts
|
|
357
|
+
/**
|
|
358
|
+
* True only when `RULVAR_LIVE_TESTS` is exactly `'1'` AND every named
|
|
359
|
+
* environment key is set to a non-empty value. Gate live tests as
|
|
360
|
+
* `it.skipIf(!liveTestEnabled('ANTHROPIC_API_KEY'))(...)` so an
|
|
361
|
+
* unrelated key in the shell never triggers a paid provider call from
|
|
362
|
+
* an ordinary test run.
|
|
363
|
+
*/
|
|
364
|
+
function liveTestEnabled(...requiredEnvKeys) {
|
|
365
|
+
if (process.env.RULVAR_LIVE_TESTS !== "1") return false;
|
|
366
|
+
return requiredEnvKeys.every((key) => {
|
|
367
|
+
const value = process.env[key];
|
|
368
|
+
return value !== void 0 && value !== "";
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Drains `adapter.stream(req)` with a bounded retry policy and classifies
|
|
373
|
+
* the outcome instead of throwing:
|
|
374
|
+
*
|
|
375
|
+
* - `'ok'`: a `finish` event arrived (the events of the successful
|
|
376
|
+
* attempt are included for further assertions).
|
|
377
|
+
* - `'failed'`: a terminal error with `retryable: false`; never retried,
|
|
378
|
+
* diagnostics preserved.
|
|
379
|
+
* - `'exhausted'`: every attempt ended in a `retryable: true` error; the
|
|
380
|
+
* per-attempt errors are preserved in order.
|
|
381
|
+
* - `'no-terminal'`: the stream ended with neither `finish` nor `error`,
|
|
382
|
+
* which violates the provider SPI; never retried (spending again on a
|
|
383
|
+
* misbehaving adapter is wrong).
|
|
384
|
+
*
|
|
385
|
+
* Retries only ever follow typed retryable errors, so a live smoke never
|
|
386
|
+
* converts a real adapter failure into a pass and never spends more than
|
|
387
|
+
* `attempts` calls.
|
|
388
|
+
*/
|
|
389
|
+
async function runLiveSmoke(adapter, req, options) {
|
|
390
|
+
const attempts = Math.max(1, options?.attempts ?? 3);
|
|
391
|
+
const baseDelayMs = options?.baseDelayMs ?? 2e3;
|
|
392
|
+
const retryableErrors = [];
|
|
393
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
394
|
+
const events = [];
|
|
395
|
+
for await (const event of adapter.stream(req)) events.push(event);
|
|
396
|
+
if (events.some((event) => event.type === "finish")) return {
|
|
397
|
+
status: "ok",
|
|
398
|
+
attempts: attempt,
|
|
399
|
+
events
|
|
400
|
+
};
|
|
401
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
402
|
+
if (errorEvent === void 0) return {
|
|
403
|
+
status: "no-terminal",
|
|
404
|
+
attempts: attempt,
|
|
405
|
+
events
|
|
406
|
+
};
|
|
407
|
+
if (!errorEvent.error.retryable) return {
|
|
408
|
+
status: "failed",
|
|
409
|
+
attempts: attempt,
|
|
410
|
+
error: errorEvent.error,
|
|
411
|
+
events
|
|
412
|
+
};
|
|
413
|
+
retryableErrors.push(errorEvent.error);
|
|
414
|
+
if (attempt < attempts && baseDelayMs > 0) await delay(baseDelayMs * attempt);
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
status: "exhausted",
|
|
418
|
+
attempts,
|
|
419
|
+
errors: retryableErrors
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function delay(ms) {
|
|
423
|
+
return new Promise((resolve) => {
|
|
424
|
+
setTimeout(resolve, ms);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
356
428
|
//#region src/cassettes/build-fixtures.ts
|
|
357
429
|
/**
|
|
358
430
|
* M2 cassette and frozen-fixture builders (M2-T12). Fixtures are
|
|
@@ -2017,4 +2089,4 @@ function replay(options) {
|
|
|
2017
2089
|
});
|
|
2018
2090
|
}
|
|
2019
2091
|
//#endregion
|
|
2020
|
-
export { FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, VcrMissError, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash };
|
|
2092
|
+
export { FAKE_MODEL, FAKE_MODEL_REF, FakeAdapter, M6_ORCH_GOAL, M6_ORCH_PROFILES, M6_ORCH_RUN_ID, VcrMissError, buildFrozenV1JournalRaw, buildM2CassetteFixtures, buildV2GoldenIdentity, createTestEngine, defaultRedact, fakeToolCalls, fakeWireError, handlesInRequest, liveTestEnabled, normalizeM6Entries, readCassette, record, recordLiveCassettes, recordOrchestratorCrash, replay, replayRun, requestHash, runLiveSmoke };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/testing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Rulvar test harness: createTestEngine, FakeAdapter, VCR cassettes, replay-strict runs, matchers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@rulvar/core": "1.
|
|
29
|
+
"@rulvar/core": "1.14.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.20.0",
|