@rulvar/testing 1.0.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/LICENSE +202 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +1964 -0
- package/dist/matchers.d.ts +32 -0
- package/dist/matchers.js +32 -0
- package/dist/test-engine-kYp9C72n.d.ts +81 -0
- package/package.json +53 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { r as TestRunHandle } from "./test-engine-kYp9C72n.js";
|
|
2
|
+
|
|
3
|
+
//#region src/matchers.d.ts
|
|
4
|
+
interface MatcherResult {
|
|
5
|
+
pass: boolean;
|
|
6
|
+
message: () => string;
|
|
7
|
+
}
|
|
8
|
+
/** expect(run).toHaveCalledAgent('reviewer', { times: 3 }) */
|
|
9
|
+
declare function toHaveCalledAgent(received: TestRunHandle<unknown>, agentType: string, options?: {
|
|
10
|
+
times?: number;
|
|
11
|
+
}): Promise<MatcherResult>;
|
|
12
|
+
/** expect(run).toStayUnderBudget({ usd: 5 }) */
|
|
13
|
+
declare function toStayUnderBudget(received: TestRunHandle<unknown>, options: {
|
|
14
|
+
usd: number;
|
|
15
|
+
}): Promise<MatcherResult>;
|
|
16
|
+
/** The bundle for expect.extend (Vitest 4 and Jest compatible shapes). */
|
|
17
|
+
declare const rulvarMatchers: {
|
|
18
|
+
toHaveCalledAgent: typeof toHaveCalledAgent;
|
|
19
|
+
toStayUnderBudget: typeof toStayUnderBudget;
|
|
20
|
+
};
|
|
21
|
+
declare module "vitest" {
|
|
22
|
+
interface Matchers<T = any> {
|
|
23
|
+
toHaveCalledAgent(agentType: string, options?: {
|
|
24
|
+
times?: number;
|
|
25
|
+
}): Promise<T>;
|
|
26
|
+
toStayUnderBudget(options: {
|
|
27
|
+
usd: number;
|
|
28
|
+
}): Promise<T>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { MatcherResult, rulvarMatchers, toHaveCalledAgent, toStayUnderBudget };
|
package/dist/matchers.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/matchers.ts
|
|
2
|
+
async function settled(handle) {
|
|
3
|
+
await handle.result;
|
|
4
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5
|
+
return handle;
|
|
6
|
+
}
|
|
7
|
+
/** expect(run).toHaveCalledAgent('reviewer', { times: 3 }) */
|
|
8
|
+
async function toHaveCalledAgent(received, agentType, options) {
|
|
9
|
+
const calls = (await settled(received)).eventsSeen.filter((event) => event.type === "agent:end" && event.agentType === agentType).length;
|
|
10
|
+
const expected = options?.times;
|
|
11
|
+
const pass = expected === void 0 ? calls > 0 : calls === expected;
|
|
12
|
+
return {
|
|
13
|
+
pass,
|
|
14
|
+
message: () => expected === void 0 ? `expected run ${pass ? "not " : ""}to have called agent '${agentType}' (called ${calls} times)` : `expected run to have called agent '${agentType}' ${expected} times, saw ${calls}`
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** expect(run).toStayUnderBudget({ usd: 5 }) */
|
|
18
|
+
async function toStayUnderBudget(received, options) {
|
|
19
|
+
const outcome = await received.result;
|
|
20
|
+
const spent = outcome.cost.totalUsd;
|
|
21
|
+
return {
|
|
22
|
+
pass: spent < options.usd && outcome.status !== "exhausted",
|
|
23
|
+
message: () => `expected run to stay under ${options.usd.toFixed(4)} USD; spent ${spent.toFixed(4)} USD with outcome '${outcome.status}'`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** The bundle for expect.extend (Vitest 4 and Jest compatible shapes). */
|
|
27
|
+
const rulvarMatchers = {
|
|
28
|
+
toHaveCalledAgent,
|
|
29
|
+
toStayUnderBudget
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { rulvarMatchers, toHaveCalledAgent, toStayUnderBudget };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AgentProfile, ChatEvent, ChatRequest, CreateEngineOptions, Engine, InMemoryStore, ModelCaps, ProviderAdapter, RunHandle, RunOptions, WireError, Workflow, WorkflowEvent } from "@rulvar/core";
|
|
2
|
+
|
|
3
|
+
//#region src/fake-adapter.d.ts
|
|
4
|
+
/** What a responder sees about the call. */
|
|
5
|
+
interface FakeCall {
|
|
6
|
+
prompt: string;
|
|
7
|
+
agentType?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
req: ChatRequest;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A static string (plain text output), a static value (structured output),
|
|
13
|
+
* or a function of the call. Thrown errors become terminal error events.
|
|
14
|
+
* fakeToolCalls() and fakeWireError() values script tool-calling turns and
|
|
15
|
+
* typed wire failures (M3).
|
|
16
|
+
*/
|
|
17
|
+
type FakeResponder = string | ((call: FakeCall) => unknown) | object;
|
|
18
|
+
/** Marker value: the model answers this turn with tool calls (M3). */
|
|
19
|
+
interface FakeToolCallsValue {
|
|
20
|
+
__fake: "tool-calls";
|
|
21
|
+
calls: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
args: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/** Scripts a tool-calling turn from a responder. */
|
|
27
|
+
declare function fakeToolCalls(...calls: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
args: unknown;
|
|
30
|
+
}>): FakeToolCallsValue;
|
|
31
|
+
/** Marker value: the stream terminates with this typed wire error (M3). */
|
|
32
|
+
interface FakeWireErrorValue {
|
|
33
|
+
__fake: "wire-error";
|
|
34
|
+
error: WireError;
|
|
35
|
+
}
|
|
36
|
+
/** Scripts a typed wire failure (e.g. a retryable rate limit). */
|
|
37
|
+
declare function fakeWireError(error: WireError): FakeWireErrorValue;
|
|
38
|
+
interface FakeAdapterOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Patterns match on agentType, label, or a regex over the prompt; '*'
|
|
41
|
+
* is the fallback (docs/09, section "Tier 1").
|
|
42
|
+
*/
|
|
43
|
+
agents: Record<string, FakeResponder>;
|
|
44
|
+
}
|
|
45
|
+
declare const FAKE_MODEL = "fake-model";
|
|
46
|
+
declare const FAKE_MODEL_REF = "fake:fake-model";
|
|
47
|
+
declare class FakeAdapter implements ProviderAdapter {
|
|
48
|
+
readonly id = "fake";
|
|
49
|
+
private readonly agents;
|
|
50
|
+
private readonly mintId;
|
|
51
|
+
/** Every request this adapter served, in order. */
|
|
52
|
+
readonly calls: FakeCall[];
|
|
53
|
+
constructor(options: FakeAdapterOptions);
|
|
54
|
+
caps(this: void): ModelCaps;
|
|
55
|
+
private match;
|
|
56
|
+
stream(req: ChatRequest): AsyncIterable<ChatEvent>;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/test-engine.d.ts
|
|
60
|
+
/** A RunHandle that records its own event stream for the matchers. */
|
|
61
|
+
interface TestRunHandle<R> extends RunHandle<R> {
|
|
62
|
+
/** Every event emitted by the run, in seq order. */
|
|
63
|
+
eventsSeen: WorkflowEvent[];
|
|
64
|
+
}
|
|
65
|
+
interface TestEngine extends Engine {
|
|
66
|
+
run<A, R>(wf: Workflow<A, R>, args: A, opts?: RunOptions): TestRunHandle<R>;
|
|
67
|
+
/** The adapter instance, for call-level assertions. */
|
|
68
|
+
fake: FakeAdapter;
|
|
69
|
+
/** The backing journal store (journal capture for replay-strict tests). */
|
|
70
|
+
store: InMemoryStore;
|
|
71
|
+
}
|
|
72
|
+
interface CreateTestEngineOptions {
|
|
73
|
+
agents: Record<string, FakeResponder>;
|
|
74
|
+
/** Additional profiles; every agents key is auto-registered as an empty profile. */
|
|
75
|
+
profiles?: Record<string, AgentProfile>;
|
|
76
|
+
budgetDefaults?: CreateEngineOptions["budgetDefaults"];
|
|
77
|
+
concurrency?: CreateEngineOptions["concurrency"];
|
|
78
|
+
}
|
|
79
|
+
declare function createTestEngine(options: CreateTestEngineOptions): TestEngine;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { FAKE_MODEL as a, FakeAdapterOptions as c, FakeToolCallsValue as d, FakeWireErrorValue as f, createTestEngine as i, FakeCall as l, fakeWireError as m, TestEngine as n, FAKE_MODEL_REF as o, fakeToolCalls as p, TestRunHandle as r, FakeAdapter as s, CreateTestEngineOptions as t, FakeResponder as u };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rulvar/testing",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "rulvar test harness: createTestEngine, FakeAdapter, VCR cassettes, replay-strict runs, matchers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.12.0"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./matchers": {
|
|
16
|
+
"types": "./dist/matchers.d.ts",
|
|
17
|
+
"default": "./dist/matchers.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@rulvar/core": "1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.20.0",
|
|
33
|
+
"tsdown": "^0.22.3",
|
|
34
|
+
"typescript": "~6.0.3",
|
|
35
|
+
"zod": "^4.4.3",
|
|
36
|
+
"@rulvar/compat": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/o-stepper/rulvar.git",
|
|
41
|
+
"directory": "packages/testing"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://rulvar.com",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/o-stepper/rulvar/issues"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "eslint .",
|
|
51
|
+
"pack-check": "publint --pack pnpm && attw --pack . --profile esm-only"
|
|
52
|
+
}
|
|
53
|
+
}
|