@rulvar/testing 1.28.0 → 1.29.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 +17 -4
- package/dist/index.js +43 -10
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -161,9 +161,14 @@ declare function defaultRedact(value: string): string;
|
|
|
161
161
|
*/
|
|
162
162
|
declare function requestHash(req: ChatRequest): string;
|
|
163
163
|
/**
|
|
164
|
-
* Wraps live adapters for recording: every
|
|
165
|
-
*
|
|
166
|
-
*
|
|
164
|
+
* Wraps live adapters for recording: every stream that completes with
|
|
165
|
+
* exactly one terminal event (finish or error) appends one redacted
|
|
166
|
+
* row to the cassette JSONL. A stream that ends without a terminal
|
|
167
|
+
* (a requested abort or a truncated read), throws, or violates the
|
|
168
|
+
* adapter contract (a second terminal, data after the terminal)
|
|
169
|
+
* appends nothing, so a cassette row is always the record of one
|
|
170
|
+
* completed exchange (v1.28.0 review P2). The wrapped adapters are
|
|
171
|
+
* drop-in: same ids, providers, caps, and event streams.
|
|
167
172
|
*/
|
|
168
173
|
declare function record(options: {
|
|
169
174
|
adapters: ProviderAdapter[];
|
|
@@ -179,7 +184,15 @@ interface VcrCassette {
|
|
|
179
184
|
header: VcrHeader;
|
|
180
185
|
rows: VcrRow[];
|
|
181
186
|
}
|
|
182
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* Parses a cassette file (one header line plus one JSON row per line).
|
|
189
|
+
* The header must declare cassette format `v: 1`: the format version
|
|
190
|
+
* gates parsing itself, while hashVersion (checked by replay) only
|
|
191
|
+
* gates request identity and never substitutes for it, so a future
|
|
192
|
+
* incompatible format refuses loudly instead of being read as v1.
|
|
193
|
+
* Parse and shape failures throw a typed ConfigError naming the
|
|
194
|
+
* cassette path and line (v1.28.0 review P3).
|
|
195
|
+
*/
|
|
183
196
|
declare function readCassette(path: string): VcrCassette;
|
|
184
197
|
/**
|
|
185
198
|
* Builds replay adapters from a cassette. `onMiss: 'throw'` is the
|
package/dist/index.js
CHANGED
|
@@ -326,9 +326,14 @@ function headerLine() {
|
|
|
326
326
|
});
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
|
-
* Wraps live adapters for recording: every
|
|
330
|
-
*
|
|
331
|
-
*
|
|
329
|
+
* Wraps live adapters for recording: every stream that completes with
|
|
330
|
+
* exactly one terminal event (finish or error) appends one redacted
|
|
331
|
+
* row to the cassette JSONL. A stream that ends without a terminal
|
|
332
|
+
* (a requested abort or a truncated read), throws, or violates the
|
|
333
|
+
* adapter contract (a second terminal, data after the terminal)
|
|
334
|
+
* appends nothing, so a cassette row is always the record of one
|
|
335
|
+
* completed exchange (v1.28.0 review P2). The wrapped adapters are
|
|
336
|
+
* drop-in: same ids, providers, caps, and event streams.
|
|
332
337
|
*/
|
|
333
338
|
function record(options) {
|
|
334
339
|
const redact = options.redact ? (value) => defaultRedact(options.redact ? options.redact(value) : value) : defaultRedact;
|
|
@@ -341,8 +346,12 @@ function record(options) {
|
|
|
341
346
|
async *stream(req, signal) {
|
|
342
347
|
const events = [];
|
|
343
348
|
let thrown = false;
|
|
349
|
+
let terminals = 0;
|
|
350
|
+
let postTerminal = false;
|
|
344
351
|
try {
|
|
345
352
|
for await (const event of adapter.stream(req, signal)) {
|
|
353
|
+
if (terminals > 0) postTerminal = true;
|
|
354
|
+
if (event.type === "finish" || event.type === "error") terminals += 1;
|
|
346
355
|
events.push(event);
|
|
347
356
|
yield event;
|
|
348
357
|
}
|
|
@@ -350,7 +359,7 @@ function record(options) {
|
|
|
350
359
|
thrown = true;
|
|
351
360
|
throw error;
|
|
352
361
|
} finally {
|
|
353
|
-
if (!thrown) {
|
|
362
|
+
if (!thrown && terminals === 1 && !postTerminal) {
|
|
354
363
|
const row = {
|
|
355
364
|
adapterId: adapter.id,
|
|
356
365
|
...adapter.provider === void 0 ? {} : { provider: adapter.provider },
|
|
@@ -375,14 +384,38 @@ var VcrMissError = class extends Error {
|
|
|
375
384
|
this.requestHash = hash;
|
|
376
385
|
}
|
|
377
386
|
};
|
|
378
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* Parses a cassette file (one header line plus one JSON row per line).
|
|
389
|
+
* The header must declare cassette format `v: 1`: the format version
|
|
390
|
+
* gates parsing itself, while hashVersion (checked by replay) only
|
|
391
|
+
* gates request identity and never substitutes for it, so a future
|
|
392
|
+
* incompatible format refuses loudly instead of being read as v1.
|
|
393
|
+
* Parse and shape failures throw a typed ConfigError naming the
|
|
394
|
+
* cassette path and line (v1.28.0 review P3).
|
|
395
|
+
*/
|
|
379
396
|
function readCassette(path) {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
397
|
+
const numbered = readFileSync(path, "utf8").split("\n").map((text, index) => ({
|
|
398
|
+
text,
|
|
399
|
+
lineNo: index + 1
|
|
400
|
+
})).filter(({ text }) => text.trim() !== "");
|
|
401
|
+
const parse = (line) => {
|
|
402
|
+
try {
|
|
403
|
+
return JSON.parse(line.text);
|
|
404
|
+
} catch {
|
|
405
|
+
throw new ConfigError(`${path}:${String(line.lineNo)} is not valid JSON; the cassette is corrupt or truncated`);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const first = numbered[0];
|
|
409
|
+
const headerRaw = first === void 0 ? {} : parse(first);
|
|
410
|
+
if (headerRaw.kind !== "rulvar-vcr") throw new ConfigError(`${path} is not a rulvar VCR cassette`);
|
|
411
|
+
if (headerRaw.v !== 1) throw new ConfigError(`${path} declares cassette format v ${String(headerRaw.v)}; this build reads format v 1 only, so record the cassette again on a matching engine`);
|
|
383
412
|
return {
|
|
384
|
-
header,
|
|
385
|
-
rows:
|
|
413
|
+
header: headerRaw,
|
|
414
|
+
rows: numbered.slice(1).map((line) => {
|
|
415
|
+
const row = parse(line);
|
|
416
|
+
if (typeof row !== "object" || row === null || typeof row.adapterId !== "string" || typeof row.requestHash !== "string" || !Array.isArray(row.events)) throw new ConfigError(`${path}:${String(line.lineNo)} is not a VCR row (adapterId, requestHash, and events are required)`);
|
|
417
|
+
return row;
|
|
418
|
+
})
|
|
386
419
|
};
|
|
387
420
|
}
|
|
388
421
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/testing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.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.29.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.20.0",
|