@holokai/holo-test 1.4.5 → 1.5.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/README.md +216 -81
- package/dist/conformance/audit-tests.d.ts +4 -0
- package/dist/conformance/audit-tests.d.ts.map +1 -0
- package/dist/conformance/audit-tests.js +55 -0
- package/dist/conformance/audit-tests.js.map +1 -0
- package/dist/conformance/datastore-tests.d.ts +13 -0
- package/dist/conformance/datastore-tests.d.ts.map +1 -0
- package/dist/conformance/datastore-tests.js +121 -0
- package/dist/conformance/datastore-tests.js.map +1 -0
- package/dist/conformance/holo-fixture-loader.d.ts +4 -0
- package/dist/conformance/holo-fixture-loader.d.ts.map +1 -0
- package/dist/conformance/holo-fixture-loader.js +79 -0
- package/dist/conformance/holo-fixture-loader.js.map +1 -0
- package/dist/conformance/holo-fixture-tests.d.ts +4 -0
- package/dist/conformance/holo-fixture-tests.d.ts.map +1 -0
- package/dist/conformance/holo-fixture-tests.js +273 -0
- package/dist/conformance/holo-fixture-tests.js.map +1 -0
- package/dist/conformance/holo-fixture-types.d.ts +93 -0
- package/dist/conformance/holo-fixture-types.d.ts.map +1 -0
- package/dist/conformance/holo-fixture-types.js +2 -0
- package/dist/conformance/holo-fixture-types.js.map +1 -0
- package/dist/conformance/loader.d.ts +7 -0
- package/dist/conformance/loader.d.ts.map +1 -0
- package/dist/conformance/loader.js +71 -0
- package/dist/conformance/loader.js.map +1 -0
- package/dist/conformance/pipeline-tests.d.ts +4 -0
- package/dist/conformance/pipeline-tests.d.ts.map +1 -0
- package/dist/conformance/pipeline-tests.js +73 -0
- package/dist/conformance/pipeline-tests.js.map +1 -0
- package/dist/conformance/runner.d.ts +2 -0
- package/dist/conformance/runner.d.ts.map +1 -0
- package/dist/conformance/runner.js +102 -0
- package/dist/conformance/runner.js.map +1 -0
- package/dist/conformance/sdk-tests.d.ts +12 -0
- package/dist/conformance/sdk-tests.d.ts.map +1 -0
- package/dist/{services/http-fixture-server.js → conformance/sdk-tests.js} +56 -17
- package/dist/conformance/sdk-tests.js.map +1 -0
- package/dist/conformance/translator-tests.d.ts +4 -0
- package/dist/conformance/translator-tests.d.ts.map +1 -0
- package/dist/conformance/translator-tests.js +49 -0
- package/dist/conformance/translator-tests.js.map +1 -0
- package/dist/conformance/types.d.ts +81 -0
- package/dist/conformance/types.d.ts.map +1 -0
- package/dist/conformance/types.js +2 -0
- package/dist/conformance/types.js.map +1 -0
- package/dist/conformance/wire-tests.d.ts +4 -0
- package/dist/conformance/wire-tests.d.ts.map +1 -0
- package/dist/conformance/wire-tests.js +52 -0
- package/dist/conformance/wire-tests.js.map +1 -0
- package/dist/index.d.ts +13 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -14
- package/dist/index.js.map +1 -1
- package/dist/live/assertions.d.ts.map +1 -1
- package/dist/live/assertions.js +54 -21
- package/dist/live/assertions.js.map +1 -1
- package/dist/live/error-payloads.d.ts +5 -0
- package/dist/live/error-payloads.d.ts.map +1 -0
- package/dist/live/error-payloads.js +47 -0
- package/dist/live/error-payloads.js.map +1 -0
- package/dist/live/error-tester.d.ts +3 -0
- package/dist/live/error-tester.d.ts.map +1 -0
- package/dist/live/error-tester.js +121 -0
- package/dist/live/error-tester.js.map +1 -0
- package/dist/live/index.d.ts +3 -1
- package/dist/live/index.d.ts.map +1 -1
- package/dist/live/index.js +2 -0
- package/dist/live/index.js.map +1 -1
- package/dist/live/types.d.ts +9 -0
- package/dist/live/types.d.ts.map +1 -1
- package/dist/services/plugin-loader.d.ts +2 -1
- package/dist/services/plugin-loader.d.ts.map +1 -1
- package/dist/services/plugin-loader.js +2 -1
- package/dist/services/plugin-loader.js.map +1 -1
- package/dist/types.d.ts +0 -37
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -6
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -83
- package/dist/cli.js.map +0 -1
- package/dist/contracts/audit-contract.d.ts +0 -3
- package/dist/contracts/audit-contract.d.ts.map +0 -1
- package/dist/contracts/audit-contract.js +0 -11
- package/dist/contracts/audit-contract.js.map +0 -1
- package/dist/contracts/datastore-contract.d.ts +0 -12
- package/dist/contracts/datastore-contract.d.ts.map +0 -1
- package/dist/contracts/datastore-contract.js +0 -20
- package/dist/contracts/datastore-contract.js.map +0 -1
- package/dist/contracts/pipeline-contract.d.ts +0 -3
- package/dist/contracts/pipeline-contract.d.ts.map +0 -1
- package/dist/contracts/pipeline-contract.js +0 -9
- package/dist/contracts/pipeline-contract.js.map +0 -1
- package/dist/contracts/roundtrip-contract.d.ts +0 -3
- package/dist/contracts/roundtrip-contract.d.ts.map +0 -1
- package/dist/contracts/roundtrip-contract.js +0 -9
- package/dist/contracts/roundtrip-contract.js.map +0 -1
- package/dist/contracts/wire-contract.d.ts +0 -3
- package/dist/contracts/wire-contract.d.ts.map +0 -1
- package/dist/contracts/wire-contract.js +0 -9
- package/dist/contracts/wire-contract.js.map +0 -1
- package/dist/dsl/assertions.d.ts +0 -6
- package/dist/dsl/assertions.d.ts.map +0 -1
- package/dist/dsl/assertions.js +0 -54
- package/dist/dsl/assertions.js.map +0 -1
- package/dist/dsl/suite-builder.d.ts +0 -25
- package/dist/dsl/suite-builder.d.ts.map +0 -1
- package/dist/dsl/suite-builder.js +0 -96
- package/dist/dsl/suite-builder.js.map +0 -1
- package/dist/factories/worker-request-factory.d.ts +0 -5
- package/dist/factories/worker-request-factory.d.ts.map +0 -1
- package/dist/factories/worker-request-factory.js +0 -19
- package/dist/factories/worker-request-factory.js.map +0 -1
- package/dist/fixtures/fixture-loader.d.ts +0 -6
- package/dist/fixtures/fixture-loader.d.ts.map +0 -1
- package/dist/fixtures/fixture-loader.js +0 -23
- package/dist/fixtures/fixture-loader.js.map +0 -1
- package/dist/runner/test-reporter.d.ts +0 -3
- package/dist/runner/test-reporter.d.ts.map +0 -1
- package/dist/runner/test-reporter.js +0 -43
- package/dist/runner/test-reporter.js.map +0 -1
- package/dist/runner/test-runner.d.ts +0 -12
- package/dist/runner/test-runner.d.ts.map +0 -1
- package/dist/runner/test-runner.js +0 -37
- package/dist/runner/test-runner.js.map +0 -1
- package/dist/services/http-fixture-server.d.ts +0 -14
- package/dist/services/http-fixture-server.d.ts.map +0 -1
- package/dist/services/http-fixture-server.js.map +0 -1
- package/dist/testers/audit-tester.d.ts +0 -4
- package/dist/testers/audit-tester.d.ts.map +0 -1
- package/dist/testers/audit-tester.js +0 -74
- package/dist/testers/audit-tester.js.map +0 -1
- package/dist/testers/datastore-tester.d.ts +0 -17
- package/dist/testers/datastore-tester.d.ts.map +0 -1
- package/dist/testers/datastore-tester.js +0 -306
- package/dist/testers/datastore-tester.js.map +0 -1
- package/dist/testers/pipeline-tester.d.ts +0 -4
- package/dist/testers/pipeline-tester.d.ts.map +0 -1
- package/dist/testers/pipeline-tester.js +0 -62
- package/dist/testers/pipeline-tester.js.map +0 -1
- package/dist/testers/roundtrip-tester.d.ts +0 -3
- package/dist/testers/roundtrip-tester.d.ts.map +0 -1
- package/dist/testers/roundtrip-tester.js +0 -77
- package/dist/testers/roundtrip-tester.js.map +0 -1
- package/dist/testers/wire-tester.d.ts +0 -4
- package/dist/testers/wire-tester.d.ts.map +0 -1
- package/dist/testers/wire-tester.js +0 -87
- package/dist/testers/wire-tester.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# @holokai/holo-test
|
|
2
2
|
|
|
3
|
-
Conformance testing framework for Holo provider plugins.
|
|
4
|
-
|
|
3
|
+
Conformance testing framework for Holo provider and datastore plugins.
|
|
4
|
+
|
|
5
|
+
The framework owns the test logic. Plugins ship only data: per-protocol scenario files plus an optional minimal SDK
|
|
6
|
+
adapter. A single `runConformance()` call discovers every installed `@holokai/holo-provider-*` and
|
|
7
|
+
`@holokai/holo-datastore-*` package, loads its scenarios, and registers translator + wire + audit + pipeline + sdk
|
|
8
|
+
vitest blocks.
|
|
5
9
|
|
|
6
10
|
## Installation
|
|
7
11
|
|
|
@@ -13,161 +17,292 @@ Peer dependencies: `vitest ^3.0.0`, `tsyringe ^4.10.0`
|
|
|
13
17
|
|
|
14
18
|
## Quick Start
|
|
15
19
|
|
|
20
|
+
A single test file at the repo root (or in any package that depends on the plugins) is enough:
|
|
21
|
+
|
|
16
22
|
```typescript
|
|
17
|
-
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
describe('openai wire conformance', () => {
|
|
23
|
-
for (const fixture of fixtures) {
|
|
24
|
-
it(fixture.name, async () => {
|
|
25
|
-
await runWireContract('openai', fixture);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
});
|
|
23
|
+
// tests/conformance.test.ts
|
|
24
|
+
import 'reflect-metadata';
|
|
25
|
+
import {runConformance} from '@holokai/holo-test';
|
|
26
|
+
|
|
27
|
+
await runConformance();
|
|
29
28
|
```
|
|
30
29
|
|
|
30
|
+
`runConformance()` calls `vitest.describe` internally; running this file with `vitest run` produces one suite per
|
|
31
|
+
discovered plugin, with one `describe` per scenario.
|
|
32
|
+
|
|
31
33
|
## Testing Modes
|
|
32
34
|
|
|
33
|
-
| Mode
|
|
34
|
-
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
| **
|
|
35
|
+
| Mode | What it tests | Triggered when |
|
|
36
|
+
|----------------|--------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
|
|
37
|
+
| **Translator** | Plugin translator round-trips between native and Holo canonical types | `scenario.request` and/or `scenario.response` set |
|
|
38
|
+
| **Wire** | Provider events serialize to the expected HTTP wire (status, headers, body) | `scenario.wire` set + `scenario.providerChunks` populated |
|
|
39
|
+
| **Audit** | Provider events produce the expected `ProviderResponse` audit shape | `scenario.audit` set + `scenario.providerChunks` populated |
|
|
40
|
+
| **Pipeline** | End-to-end provider events → wire chunks → text accumulation; chunk-level shape and `done` terminator are asserted | `scenario.pipeline === true` + `scenario.providerChunks` + `scenario.expectedText` set |
|
|
41
|
+
| **SDK** | Real provider SDK client → fixture HTTP server → expected SDK result | `scenario.sdk` set + `tests/sdk-adapter.ts` exists |
|
|
42
|
+
| **Datastore** | Datastore plugin connects, writes a request/response/cost row, can be queried | `tests/datastore-config.ts` exists in the datastore plugin |
|
|
43
|
+
| **Live** | Real provider API call; asserts `protocol.streamEventSequence` via `assertEventSequence` | `holo-test-live` vitest project; gated on `isProviderAvailable(family)` |
|
|
44
|
+
|
|
45
|
+
Modes activate independently — a scenario can exercise just `wire`+`audit` without setting `sdk` or `pipeline`.
|
|
46
|
+
|
|
47
|
+
### Pipeline shape assertions
|
|
48
|
+
|
|
49
|
+
When `scenario.pipeline === true`, the framework collects every chunk delivered to `publishChunk` during the run.
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
- If `scenario.expectedPipelineChunks` is provided, the collected sequence must match exactly.
|
|
52
|
+
- Otherwise, the soft path checks that the chunk count is `> 0` and the final chunk carries `done: true` — enough to
|
|
53
|
+
catch wire-adapter regressions that drop the terminator.
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
## Scenarios
|
|
56
|
+
|
|
57
|
+
Place scenarios under `tests/data/{protocol}/{name}.scenario.ts` in a plugin. Each file default-exports a
|
|
58
|
+
`HoloScenario`:
|
|
43
59
|
|
|
44
60
|
```typescript
|
|
45
|
-
import type {
|
|
61
|
+
import type {HoloScenario} from '@holokai/holo-test';
|
|
46
62
|
|
|
47
|
-
const
|
|
63
|
+
const scenario: HoloScenario = {
|
|
48
64
|
name: 'simple chat completion',
|
|
49
|
-
plugin: 'openai',
|
|
50
65
|
protocol: 'openai.chatCompletions',
|
|
51
66
|
streaming: false,
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
// Translator round-trip (optional)
|
|
69
|
+
request: {
|
|
70
|
+
native: {model: 'gpt-4o', messages: [{role: 'user', content: 'Hi'}]},
|
|
71
|
+
holo: {model: 'gpt-4o', messages: [{role: 'user', content: 'Hi'}]},
|
|
72
|
+
},
|
|
73
|
+
response: {
|
|
74
|
+
native: {/* native ChatCompletion */},
|
|
75
|
+
holo: {/* partial HoloResponse */},
|
|
76
|
+
},
|
|
54
77
|
|
|
78
|
+
// Provider event sequence consumed by wire/audit/pipeline/sdk
|
|
79
|
+
providerChunks: [/* native chunk(s) */],
|
|
55
80
|
expectedText: 'Hello, world!',
|
|
56
|
-
expectedWire: ['{"id":"chatcmpl-...}'],
|
|
57
|
-
expectedStatus: 200,
|
|
58
|
-
expectedHeaders: {'content-type': 'application/json'},
|
|
59
81
|
|
|
60
|
-
|
|
82
|
+
// Wire-conformance
|
|
83
|
+
wire: {
|
|
84
|
+
expectedStatus: 200,
|
|
85
|
+
expectedHeaders: {'content-type': 'application/json'},
|
|
86
|
+
expectedWire: ['{"id":"chatcmpl-..."}'],
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Audit-conformance
|
|
90
|
+
audit: {
|
|
61
91
|
access_model: 'gpt-4o',
|
|
62
92
|
input_tokens: 10,
|
|
63
93
|
output_tokens: 5,
|
|
64
94
|
status: 'success',
|
|
65
95
|
},
|
|
66
96
|
|
|
67
|
-
|
|
97
|
+
// Pipeline opt-in
|
|
98
|
+
pipeline: true,
|
|
99
|
+
|
|
100
|
+
// SDK round-trip (requires tests/sdk-adapter.ts)
|
|
101
|
+
sdk: {
|
|
102
|
+
request: {model: 'gpt-4o', messages: [{role: 'user', content: 'Hi'}]},
|
|
103
|
+
expectedResult: {choices: [{message: {content: 'Hello, world!'}}]},
|
|
104
|
+
},
|
|
68
105
|
};
|
|
69
106
|
|
|
70
|
-
export default
|
|
107
|
+
export default scenario;
|
|
71
108
|
```
|
|
72
109
|
|
|
73
|
-
|
|
110
|
+
### Loader Invariants
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
- Directory name **must** equal `scenario.protocol` — the loader throws otherwise. So a scenario with
|
|
113
|
+
`protocol: 'claude.messages'` must live under `tests/data/claude.messages/`.
|
|
114
|
+
- Files matching `*.scenario.ts` (or `.scenario.js`) are picked up automatically; no central registration needed.
|
|
115
|
+
- The default export (or named `scenario` export) is what the loader reads.
|
|
116
|
+
- Every scenario's `protocol` must appear in the plugin's declared `protocols` map; mismatches throw at discovery time.
|
|
77
117
|
|
|
78
|
-
|
|
79
|
-
const all = loadFixtures('./tests/fixtures');
|
|
118
|
+
### Canonical streaming fixtures
|
|
80
119
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
120
|
+
`packages/holo-test/holo/streaming/*.holo.ts` declares the canonical Holo-pivot streaming sequences used by
|
|
121
|
+
`registerStreamingPerPlugin` and `registerStreamingCross`. Each fixture opens with a `message_start` chunk and closes
|
|
122
|
+
with a `message_stop` chunk, mirroring real provider SSE lifecycle. Per-plugin streaming round-trip is strict:
|
|
123
|
+
translators must emit and consume those lifecycle chunks to round-trip cleanly.
|
|
124
|
+
|
|
125
|
+
A plugin that doesn't yet emit/consume native equivalents of `message_start` / `message_stop` (e.g. Claude
|
|
126
|
+
`message_start` envelopes, OpenAI `[DONE]` terminators, Bedrock `messageStart`/`messageStop`) should pin its scenario
|
|
127
|
+
with `expectedFailure: { toHolo: true, fromHolo: true, reason: '...' }` until the gap is closed.
|
|
128
|
+
|
|
129
|
+
### Strip helpers
|
|
130
|
+
|
|
131
|
+
`registerStreamingPerPlugin` and `registerStreamingCross` strip volatile fields before comparing chunk arrays:
|
|
132
|
+
|
|
133
|
+
| Helper | Strips | Where applied |
|
|
134
|
+
|-------------------------|--------------------------------------------------------------------|----------------------------------------|
|
|
135
|
+
| `stripProviderMetadata` | `delta.provider`, `delta.provider_delta` (translator pass-through) | Per-plugin round-trip + cross-provider |
|
|
136
|
+
| `stripCrossVolatile` | Top-level `id` / `created` / `model`, message-level `id` / `model` | Cross-provider only |
|
|
84
137
|
|
|
85
|
-
|
|
138
|
+
Per-plugin (same-provider in/out) round-trip stays strict on `id` / `created` / `model` — a translator that drops or
|
|
139
|
+
fabricates them now fails the round-trip immediately rather than being masked.
|
|
86
140
|
|
|
87
|
-
|
|
141
|
+
### Streaming event sequence assertion
|
|
142
|
+
|
|
143
|
+
`assertEventSequence(events, sequence)` walks the `ProviderEvent` stream and verifies it satisfies the protocol's
|
|
144
|
+
declared `streamEventSequence` (`{ ordered: string[], repeatable: string[] }`). It returns an `AssertionError` for
|
|
145
|
+
missing required events, out-of-order non-repeatable events, or skipped required events; it is exercised via
|
|
146
|
+
`runLiveTest` (real provider call), not in the offline conformance run. The native event-type extractor handles both
|
|
147
|
+
top-level `.type` shapes (Claude, OpenAI Responses, Gemini structured) and discriminator-key shapes (Bedrock-style
|
|
148
|
+
`{contentBlockDelta: {...}}`).
|
|
149
|
+
|
|
150
|
+
### Pinning Known Losses
|
|
151
|
+
|
|
152
|
+
When a translator/wire/audit asymmetry is known and accepted (e.g., a provider drops a Holo content type that has no
|
|
153
|
+
native equivalent), record it on the scenario rather than silencing the test:
|
|
88
154
|
|
|
89
155
|
```typescript
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.fixtureDir('./tests/fixtures')
|
|
95
|
-
.wire()
|
|
96
|
-
.audit()
|
|
97
|
-
.pipeline()
|
|
98
|
-
.verbose()
|
|
99
|
-
.run();
|
|
156
|
+
scenario.expectedFailure = {
|
|
157
|
+
response: {fromHolo: true, roundTrip: true}, // Holo→native loses something
|
|
158
|
+
reason: 'Claude response blocks have no equivalent for Holo image content',
|
|
159
|
+
};
|
|
100
160
|
```
|
|
101
161
|
|
|
102
|
-
|
|
162
|
+
Failing-as-expected modes flip to `it.fails`, so CI still fails if the scenario unexpectedly *starts* passing.
|
|
163
|
+
|
|
164
|
+
## SDK Adapter
|
|
103
165
|
|
|
104
|
-
|
|
166
|
+
If you want to exercise SDK round-trip (real provider SDK client → fixture HTTP server → assert), drop a
|
|
167
|
+
`tests/sdk-adapter.ts` in the plugin:
|
|
105
168
|
|
|
106
169
|
```typescript
|
|
107
|
-
import
|
|
170
|
+
import OpenAI from 'openai';
|
|
171
|
+
import type {HoloScenario, ScenarioSdkAdapter} from '@holokai/holo-test';
|
|
172
|
+
|
|
173
|
+
const adapter: ScenarioSdkAdapter = {
|
|
174
|
+
family: 'openai',
|
|
108
175
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
176
|
+
async call(scenario: HoloScenario, port: number) {
|
|
177
|
+
const client = new OpenAI({apiKey: 'test', baseURL: `http://localhost:${port}/v1`});
|
|
178
|
+
return client.chat.completions.create(scenario.sdk!.request as never);
|
|
179
|
+
},
|
|
113
180
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
181
|
+
routes(scenario: HoloScenario) {
|
|
182
|
+
if (scenario.protocol === 'openai.chatCompletions') {
|
|
183
|
+
return {method: 'POST', path: '/v1/chat/completions'};
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
},
|
|
187
|
+
};
|
|
117
188
|
|
|
118
|
-
|
|
119
|
-
await runPipelineContract('openai', fixture);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
189
|
+
export default adapter;
|
|
122
190
|
```
|
|
123
191
|
|
|
124
|
-
|
|
192
|
+
The runner only loads `sdk-adapter.ts` when at least one scenario sets `sdk`, so plugins that don't yet care about
|
|
193
|
+
SDK round-trip can omit the file. See `plugins/holo-provider-openai/tests/sdk-adapter.ts` for the canonical example.
|
|
125
194
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
195
|
+
## Datastore Configs
|
|
196
|
+
|
|
197
|
+
Datastore plugins (`@holokai/holo-datastore-*`) supply a `tests/datastore-config.ts` instead of scenarios:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import type {DatastoreConfig} from '@holokai/holo-test';
|
|
201
|
+
|
|
202
|
+
const config: DatastoreConfig = {
|
|
203
|
+
connectionConfig: {/* host, port, auth, schema, ... */},
|
|
204
|
+
requestTable: 'provider_requests',
|
|
205
|
+
responseTable: 'provider_responses',
|
|
206
|
+
costTable: 'provider_response_costs',
|
|
207
|
+
cleanup: async (insertedIds) => {/* delete inserted rows */},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export default config;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`runConformance()` exercises connect, write, and read on each datastore plugin that has a config file.
|
|
214
|
+
|
|
215
|
+
## Authoring a New Plugin Test
|
|
216
|
+
|
|
217
|
+
1. Add `tests/setup.ts` with a single line: `import 'reflect-metadata';`
|
|
218
|
+
2. Drop one or more `tests/data/{protocol}/*.scenario.ts` files (default-export a `HoloScenario`).
|
|
219
|
+
3. (Optional) add `tests/sdk-adapter.ts` exporting a default `ScenarioSdkAdapter` if you want SDK round-trip
|
|
220
|
+
coverage.
|
|
221
|
+
4. Make sure the plugin's package depends on `@holokai/holo-test` (devDependency) and that the repo-level
|
|
222
|
+
`runConformance()` test file picks up the new package via `discoverPlugins()`.
|
|
223
|
+
|
|
224
|
+
`plugins/holo-provider-claude/tests/data/claude.messages/simple.scenario.ts` is a good template for translator + wire +
|
|
225
|
+
audit coverage; `plugins/holo-provider-openai/tests/sdk-adapter.ts` is the canonical adapter example.
|
|
226
|
+
|
|
227
|
+
## Composing Manually
|
|
228
|
+
|
|
229
|
+
For unusual cases — e.g. a single scenario you want to register outside the auto-discovery flow — the per-mode
|
|
230
|
+
register functions are exported:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import {
|
|
234
|
+
loadPlugin,
|
|
235
|
+
loadScenarios,
|
|
236
|
+
registerTranslatorTests,
|
|
237
|
+
registerWireTests,
|
|
238
|
+
registerAuditTests,
|
|
239
|
+
registerPipelineTests,
|
|
240
|
+
registerSdkTests,
|
|
241
|
+
} from '@holokai/holo-test';
|
|
242
|
+
|
|
243
|
+
const plugin = await loadPlugin('openai');
|
|
244
|
+
const scenarios = await loadScenarios('openai');
|
|
245
|
+
|
|
246
|
+
for (const scenario of scenarios.filter(s => s.name === 'tool calling')) {
|
|
247
|
+
registerTranslatorTests(plugin.translator, scenario);
|
|
248
|
+
registerWireTests(plugin, scenario);
|
|
249
|
+
registerAuditTests(plugin, scenario);
|
|
250
|
+
registerPipelineTests('openai', plugin, scenario);
|
|
251
|
+
registerSdkTests(plugin, scenario, undefined);
|
|
252
|
+
}
|
|
129
253
|
```
|
|
130
254
|
|
|
255
|
+
Prefer `runConformance()` for normal use; the manual path is an escape hatch.
|
|
256
|
+
|
|
131
257
|
## Live Testing
|
|
132
258
|
|
|
133
|
-
|
|
259
|
+
Tests against real provider APIs (separate from conformance — no fixture server, real network). `runLiveTest` loads the
|
|
260
|
+
plugin, makes a real call, collects the resulting `ProviderEvent` stream, and runs `assertEventSequence` against the
|
|
261
|
+
protocol's declared `streamEventSequence`. Failures surface in the returned `errors[]`.
|
|
134
262
|
|
|
135
263
|
```typescript
|
|
136
264
|
import {runLiveTest, isProviderAvailable} from '@holokai/holo-test';
|
|
137
265
|
|
|
138
266
|
if (isProviderAvailable('openai')) {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
timeout: 30000,
|
|
142
|
-
});
|
|
143
|
-
console.log(results);
|
|
267
|
+
const result = await runLiveTest('openai', /* streaming */ true);
|
|
268
|
+
console.log(result.passed, result.errors);
|
|
144
269
|
}
|
|
145
270
|
```
|
|
146
271
|
|
|
272
|
+
This package's own `tests/live/live.test.ts` exercises `runLiveTest` against every plugin family in both modes; it runs
|
|
273
|
+
under the `holo-test-live` vitest project, which `npm run test:live` chains after `app-aio` and `sdk-live`. Tests
|
|
274
|
+
auto-skip when the family's API key (or `OLLAMA_HOST`) isn't set.
|
|
275
|
+
|
|
147
276
|
Environment variables for live tests:
|
|
148
277
|
|
|
149
|
-
- `OPENAI_API_KEY`, `CLAUDE_API_KEY
|
|
150
|
-
|
|
278
|
+
- `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` (alias for `CLAUDE_API_KEY`), `GOOGLE_API_KEY` (alias for `GEMINI_API_KEY`),
|
|
279
|
+
`BEDROCK_API_KEY`, `OLLAMA_HOST`
|
|
280
|
+
- `OPENAI_TEST_MODEL`, `CLAUDE_TEST_MODEL`, etc. (override default test models)
|
|
151
281
|
|
|
152
282
|
## Helpers
|
|
153
283
|
|
|
154
284
|
```typescript
|
|
155
285
|
import {
|
|
156
286
|
loadPlugin, // Load and initialize a plugin by family name
|
|
287
|
+
loadScenarios, // Load scenarios for a single plugin
|
|
288
|
+
loadSdkAdapter, // Load tests/sdk-adapter.ts for a plugin
|
|
289
|
+
loadDatastoreConfig, // Load tests/datastore-config.ts for a datastore plugin
|
|
157
290
|
parseSseBody, // Parse SSE response body into frames
|
|
158
291
|
parseNdjsonBody, // Parse NDJSON response body
|
|
159
292
|
createMockFetch, // Mock fetch for unit tests
|
|
160
293
|
createSseMockFetch, // Mock SSE streaming responses
|
|
161
294
|
collectStreamText, // Extract all text from a stream
|
|
162
295
|
collectStreamEvents, // Collect all events from a stream
|
|
163
|
-
HttpFixtureServer, // Mock HTTP server serving fixture responses
|
|
164
|
-
createTestWorkerRequest, // Build a HoloWorkerRequest from a fixture
|
|
165
|
-
createTestEnvelope, // Build a WorkerResponseEnvelope from a fixture
|
|
166
296
|
discoverProviders, // Discover available providers via gateway
|
|
167
297
|
assertTestResult, // Assert a TestResult passed (throws on failure)
|
|
298
|
+
createTestPlugin, // Build a temporary plugin tarball for upload/install tests
|
|
299
|
+
packPluginFixture, // Pack an existing plugin directory into a tarball
|
|
168
300
|
} from '@holokai/holo-test';
|
|
169
301
|
```
|
|
170
302
|
|
|
303
|
+
`createTestPlugin` / `packPluginFixture` are for *runtime* plugin tests (upload/install/lifecycle), not for
|
|
304
|
+
conformance scenarios.
|
|
305
|
+
|
|
171
306
|
## License
|
|
172
307
|
|
|
173
308
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-tests.d.ts","sourceRoot":"","sources":["../../src/conformance/audit-tests.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,4BAA4B,CAAC;AAGhE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AAE1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAqDxF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
export function registerAuditTests(plugin, scenario) {
|
|
3
|
+
if (!scenario.audit || !scenario.providerChunks?.length)
|
|
4
|
+
return;
|
|
5
|
+
const expectedFailure = scenario.expectedFailure?.audit === true;
|
|
6
|
+
const t = expectedFailure ? it.fails : it;
|
|
7
|
+
t('audit', async () => {
|
|
8
|
+
const auditor = await resolveAuditor(plugin);
|
|
9
|
+
const lastChunk = scenario.providerChunks[scenario.providerChunks.length - 1];
|
|
10
|
+
const audit = scenario.audit;
|
|
11
|
+
const doneEvent = {
|
|
12
|
+
type: 'done',
|
|
13
|
+
requestId: 'test-req',
|
|
14
|
+
seq: scenario.providerChunks.length - 1,
|
|
15
|
+
message: lastChunk,
|
|
16
|
+
text: scenario.expectedText ?? '',
|
|
17
|
+
ts: Date.now(),
|
|
18
|
+
metrics: {
|
|
19
|
+
inputTokens: audit.input_tokens ?? 0,
|
|
20
|
+
outputTokens: audit.output_tokens ?? 0,
|
|
21
|
+
totalTokens: (audit.input_tokens ?? 0) + (audit.output_tokens ?? 0),
|
|
22
|
+
timeToFirstToken: 0,
|
|
23
|
+
totalProcessingTime: 0,
|
|
24
|
+
startTime: Date.now(),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const envelope = {
|
|
28
|
+
source_id: 'test-source-id',
|
|
29
|
+
request_id: 'test-req',
|
|
30
|
+
organization_id: 'test-org',
|
|
31
|
+
provider: { id: 'test-provider-id', name: 'test' },
|
|
32
|
+
protocol: { id: 'test-protocol-id', name: scenario.protocol, capability: 'chat' },
|
|
33
|
+
access_model: audit.access_model,
|
|
34
|
+
};
|
|
35
|
+
const responseRecord = await auditor.auditResponse(envelope, doneEvent);
|
|
36
|
+
expect(responseRecord.access_model).toBe(audit.access_model);
|
|
37
|
+
if (audit.input_tokens !== undefined) {
|
|
38
|
+
expect(responseRecord.input_tokens).toBe(audit.input_tokens);
|
|
39
|
+
}
|
|
40
|
+
if (audit.output_tokens !== undefined) {
|
|
41
|
+
expect(responseRecord.output_tokens).toBe(audit.output_tokens);
|
|
42
|
+
}
|
|
43
|
+
expect(responseRecord.status).toBe(audit.status);
|
|
44
|
+
if (audit.metadata) {
|
|
45
|
+
for (const [key, value] of Object.entries(audit.metadata)) {
|
|
46
|
+
expect(responseRecord.metadata?.[key]).toEqual(value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function resolveAuditor(plugin) {
|
|
52
|
+
const provider = await plugin.createProvider('test-id', 'test', { apiKey: 'test-key' });
|
|
53
|
+
return provider.auditor;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=audit-tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-tests.js","sourceRoot":"","sources":["../../src/conformance/audit-tests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IDatastorePlugin } from '@holokai/holo-types/plugin';
|
|
2
|
+
export interface DatastoreConfig {
|
|
3
|
+
connectionConfig: Record<string, unknown>;
|
|
4
|
+
requestTable: string;
|
|
5
|
+
responseTable: string;
|
|
6
|
+
costTable: string;
|
|
7
|
+
cleanup?: (insertedIds: {
|
|
8
|
+
table: string;
|
|
9
|
+
id: string;
|
|
10
|
+
}[]) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function registerDatastoreTests(plugin: IDatastorePlugin, config: DatastoreConfig): void;
|
|
13
|
+
//# sourceMappingURL=datastore-tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datastore-tests.d.ts","sourceRoot":"","sources":["../../src/conformance/datastore-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AAErF,MAAM,WAAW,eAAe;IAC5B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7E;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA6H9F"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { afterAll, expect, it } from 'vitest';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
export function registerDatastoreTests(plugin, config) {
|
|
4
|
+
let instance = null;
|
|
5
|
+
const insertedIds = [];
|
|
6
|
+
afterAll(async () => {
|
|
7
|
+
if (instance?.isConnected()) {
|
|
8
|
+
await instance.disconnect();
|
|
9
|
+
}
|
|
10
|
+
if (config.cleanup && insertedIds.length > 0) {
|
|
11
|
+
await config.cleanup(insertedIds);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
it('schema is well-formed', () => {
|
|
15
|
+
const schema = plugin.getSchema?.();
|
|
16
|
+
expect(schema, 'plugin.getSchema()').toBeDefined();
|
|
17
|
+
expect(schema.connection, 'schema.connection').toBeDefined();
|
|
18
|
+
expect(Object.keys(schema.connection.properties ?? {}).length).toBeGreaterThan(0);
|
|
19
|
+
});
|
|
20
|
+
it('default mapping is well-formed', () => {
|
|
21
|
+
const mapping = plugin.getDefaultMapping();
|
|
22
|
+
expect(mapping, 'plugin.getDefaultMapping()').toBeDefined();
|
|
23
|
+
const hasSection = mapping.request || mapping.response || mapping.cost;
|
|
24
|
+
expect(hasSection, 'mapping has at least one section').toBeTruthy();
|
|
25
|
+
for (const section of ['request', 'response', 'cost']) {
|
|
26
|
+
const m = mapping[section];
|
|
27
|
+
if (!m)
|
|
28
|
+
continue;
|
|
29
|
+
expect(m.table, `${section}.table`).toBeTruthy();
|
|
30
|
+
expect(Object.keys(m.fields ?? {}).length, `${section}.fields`).toBeGreaterThan(0);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('createInstance + connect', async () => {
|
|
34
|
+
instance = await plugin.createInstance(config.connectionConfig);
|
|
35
|
+
await instance.connect();
|
|
36
|
+
expect(instance.isConnected(), 'isConnected after connect()').toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('testConnection succeeds', async () => {
|
|
39
|
+
expect(instance, 'instance').not.toBeNull();
|
|
40
|
+
const result = await instance.testConnection();
|
|
41
|
+
expect(result.ok, result.error ?? 'testConnection.ok').toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('write request', async () => {
|
|
44
|
+
expect(instance, 'instance').not.toBeNull();
|
|
45
|
+
const record = {
|
|
46
|
+
request_id: randomUUID(),
|
|
47
|
+
access_model: 'test-model',
|
|
48
|
+
has_tools: false,
|
|
49
|
+
created_at: new Date().toISOString(),
|
|
50
|
+
metadata: JSON.stringify({}),
|
|
51
|
+
};
|
|
52
|
+
const result = await instance.write(config.requestTable, record);
|
|
53
|
+
expect(result.id, 'write request returned id').toBeTruthy();
|
|
54
|
+
insertedIds.push({ table: config.requestTable, id: result.id });
|
|
55
|
+
});
|
|
56
|
+
it('write response', async () => {
|
|
57
|
+
expect(instance, 'instance').not.toBeNull();
|
|
58
|
+
const record = {
|
|
59
|
+
request_id: randomUUID(),
|
|
60
|
+
access_model: 'test-model',
|
|
61
|
+
status: 'success',
|
|
62
|
+
input_tokens: 10,
|
|
63
|
+
output_tokens: 20,
|
|
64
|
+
total_tokens: 30,
|
|
65
|
+
time_to_first_token: 100,
|
|
66
|
+
total_processing_time: 500,
|
|
67
|
+
cost: 0,
|
|
68
|
+
created_at: new Date().toISOString(),
|
|
69
|
+
metadata: JSON.stringify({}),
|
|
70
|
+
};
|
|
71
|
+
const result = await instance.write(config.responseTable, record);
|
|
72
|
+
expect(result.id, 'write response returned id').toBeTruthy();
|
|
73
|
+
insertedIds.push({ table: config.responseTable, id: result.id });
|
|
74
|
+
});
|
|
75
|
+
it('write cost', async () => {
|
|
76
|
+
expect(instance, 'instance').not.toBeNull();
|
|
77
|
+
const responseId = insertedIds.find(r => r.table === config.responseTable)?.id;
|
|
78
|
+
expect(responseId, 'response id from prior test').toBeTruthy();
|
|
79
|
+
const record = {
|
|
80
|
+
response_id: responseId,
|
|
81
|
+
cost_type: 'provider',
|
|
82
|
+
input_tokens: 10,
|
|
83
|
+
output_tokens: 20,
|
|
84
|
+
cache_read_tokens: 0,
|
|
85
|
+
cache_write_tokens: 0,
|
|
86
|
+
input_cost: 0.0001,
|
|
87
|
+
output_cost: 0.0002,
|
|
88
|
+
cache_read_cost: 0,
|
|
89
|
+
cache_write_cost: 0,
|
|
90
|
+
total_cost: 0.0003,
|
|
91
|
+
currency: 'USD',
|
|
92
|
+
};
|
|
93
|
+
const result = await instance.write(config.costTable, record);
|
|
94
|
+
if (result.id) {
|
|
95
|
+
insertedIds.push({ table: config.costTable, id: result.id });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
it.skipIf(false)('updateResponseCost (if supported)', async () => {
|
|
99
|
+
expect(instance, 'instance').not.toBeNull();
|
|
100
|
+
if (!instance.updateResponseCost)
|
|
101
|
+
return;
|
|
102
|
+
const responseId = insertedIds.find(r => r.table === config.responseTable)?.id;
|
|
103
|
+
expect(responseId, 'response id from prior test').toBeTruthy();
|
|
104
|
+
await instance.updateResponseCost(config.responseTable, responseId, 0.0003);
|
|
105
|
+
});
|
|
106
|
+
it('rejects unsafe identifiers', async () => {
|
|
107
|
+
expect(instance, 'instance').not.toBeNull();
|
|
108
|
+
const unsafe = ["'; DROP TABLE x", 'table name', 'col--umn', 'a;b'];
|
|
109
|
+
for (const name of unsafe) {
|
|
110
|
+
await expect(instance.write(name, { id: 'test' }), `should reject "${name}"`).rejects.toThrow();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
it('disconnect', async () => {
|
|
114
|
+
expect(instance, 'instance').not.toBeNull();
|
|
115
|
+
if (!instance.isConnected())
|
|
116
|
+
return; // already torn down by a prior failure
|
|
117
|
+
await instance.disconnect();
|
|
118
|
+
expect(instance.isConnected(), 'isConnected after disconnect()').toBe(false);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=datastore-tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datastore-tests.js","sourceRoot":"","sources":["../../src/conformance/datastore-tests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HoloFixture, PluginHoloScenario } from './holo-fixture-types';
|
|
2
|
+
export declare function loadHoloFixtures(): Promise<HoloFixture[]>;
|
|
3
|
+
export declare function loadPluginHoloScenarios(family: string): Promise<PluginHoloScenario[]>;
|
|
4
|
+
//# sourceMappingURL=holo-fixture-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holo-fixture-loader.d.ts","sourceRoot":"","sources":["../../src/conformance/holo-fixture-loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,WAAW,EAAmB,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAe3F,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAqC/D;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAyB3F"}
|