@holokai/holo-test 1.4.2 → 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 +217 -80
- 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/fixtures/plugin.d.ts +27 -0
- package/dist/fixtures/plugin.d.ts.map +1 -0
- package/dist/fixtures/plugin.js +110 -0
- package/dist/fixtures/plugin.js.map +1 -0
- package/dist/helpers/env.d.ts +2 -0
- package/dist/helpers/env.d.ts.map +1 -1
- package/dist/helpers/env.js +5 -0
- package/dist/helpers/env.js.map +1 -1
- package/dist/helpers/ids.d.ts +2 -0
- package/dist/helpers/ids.d.ts.map +1 -0
- package/dist/helpers/ids.js +5 -0
- package/dist/helpers/ids.js.map +1 -0
- package/dist/helpers/matrix.d.ts +9 -1
- package/dist/helpers/matrix.d.ts.map +1 -1
- package/dist/helpers/matrix.js +8 -3
- package/dist/helpers/matrix.js.map +1 -1
- package/dist/helpers/mocks.d.ts +4 -0
- package/dist/helpers/mocks.d.ts.map +1 -0
- package/dist/helpers/mocks.js +37 -0
- package/dist/helpers/mocks.js.map +1 -0
- package/dist/helpers/polling.d.ts +3 -0
- package/dist/helpers/polling.d.ts.map +1 -0
- package/dist/helpers/polling.js +24 -0
- package/dist/helpers/polling.js.map +1 -0
- package/dist/helpers/vitest-helpers.d.ts +1 -1
- package/dist/helpers/vitest-helpers.d.ts.map +1 -1
- package/dist/index.d.ts +26 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -21
- 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/live-tester.d.ts.map +1 -1
- package/dist/live/live-tester.js +17 -3
- package/dist/live/live-tester.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 +4 -2
- package/dist/services/plugin-loader.d.ts.map +1 -1
- package/dist/services/plugin-loader.js +14 -2
- 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/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/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,6 +1,11 @@
|
|
|
1
1
|
# @holokai/holo-test
|
|
2
2
|
|
|
3
|
-
Conformance testing framework for Holo provider
|
|
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.
|
|
4
9
|
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
@@ -12,160 +17,292 @@ Peer dependencies: `vitest ^3.0.0`, `tsyringe ^4.10.0`
|
|
|
12
17
|
|
|
13
18
|
## Quick Start
|
|
14
19
|
|
|
20
|
+
A single test file at the repo root (or in any package that depends on the plugins) is enough:
|
|
21
|
+
|
|
15
22
|
```typescript
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
describe('openai wire conformance', () => {
|
|
22
|
-
for (const fixture of fixtures) {
|
|
23
|
-
it(fixture.name, async () => {
|
|
24
|
-
await runWireContract('openai', fixture);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
});
|
|
23
|
+
// tests/conformance.test.ts
|
|
24
|
+
import 'reflect-metadata';
|
|
25
|
+
import {runConformance} from '@holokai/holo-test';
|
|
26
|
+
|
|
27
|
+
await runConformance();
|
|
28
28
|
```
|
|
29
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
|
+
|
|
30
33
|
## Testing Modes
|
|
31
34
|
|
|
32
|
-
| Mode
|
|
33
|
-
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
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.
|
|
38
50
|
|
|
39
|
-
|
|
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.
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
## Scenarios
|
|
56
|
+
|
|
57
|
+
Place scenarios under `tests/data/{protocol}/{name}.scenario.ts` in a plugin. Each file default-exports a
|
|
58
|
+
`HoloScenario`:
|
|
42
59
|
|
|
43
60
|
```typescript
|
|
44
|
-
import type {
|
|
61
|
+
import type {HoloScenario} from '@holokai/holo-test';
|
|
45
62
|
|
|
46
|
-
const
|
|
63
|
+
const scenario: HoloScenario = {
|
|
47
64
|
name: 'simple chat completion',
|
|
48
|
-
plugin: 'openai',
|
|
49
65
|
protocol: 'openai.chatCompletions',
|
|
50
66
|
streaming: false,
|
|
51
67
|
|
|
52
|
-
|
|
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
|
+
},
|
|
53
77
|
|
|
78
|
+
// Provider event sequence consumed by wire/audit/pipeline/sdk
|
|
79
|
+
providerChunks: [/* native chunk(s) */],
|
|
54
80
|
expectedText: 'Hello, world!',
|
|
55
|
-
expectedWire: ['{"id":"chatcmpl-...}'],
|
|
56
|
-
expectedStatus: 200,
|
|
57
|
-
expectedHeaders: {'content-type': 'application/json'},
|
|
58
81
|
|
|
59
|
-
|
|
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: {
|
|
60
91
|
access_model: 'gpt-4o',
|
|
61
92
|
input_tokens: 10,
|
|
62
93
|
output_tokens: 5,
|
|
63
94
|
status: 'success',
|
|
64
95
|
},
|
|
65
96
|
|
|
66
|
-
|
|
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
|
+
},
|
|
67
105
|
};
|
|
68
106
|
|
|
69
|
-
export default
|
|
107
|
+
export default scenario;
|
|
70
108
|
```
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
### Loader Invariants
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
76
117
|
|
|
77
|
-
|
|
78
|
-
const all = loadFixtures('./tests/fixtures');
|
|
118
|
+
### Canonical streaming fixtures
|
|
79
119
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 |
|
|
137
|
+
|
|
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.
|
|
83
140
|
|
|
84
|
-
|
|
141
|
+
### Streaming event sequence assertion
|
|
85
142
|
|
|
86
|
-
|
|
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:
|
|
87
154
|
|
|
88
155
|
```typescript
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.fixtureDir('./tests/fixtures')
|
|
94
|
-
.wire()
|
|
95
|
-
.audit()
|
|
96
|
-
.pipeline()
|
|
97
|
-
.verbose()
|
|
98
|
-
.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
|
+
};
|
|
99
160
|
```
|
|
100
161
|
|
|
101
|
-
|
|
162
|
+
Failing-as-expected modes flip to `it.fails`, so CI still fails if the scenario unexpectedly *starts* passing.
|
|
102
163
|
|
|
103
|
-
|
|
164
|
+
## SDK Adapter
|
|
165
|
+
|
|
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:
|
|
104
168
|
|
|
105
169
|
```typescript
|
|
106
|
-
import
|
|
170
|
+
import OpenAI from 'openai';
|
|
171
|
+
import type {HoloScenario, ScenarioSdkAdapter} from '@holokai/holo-test';
|
|
107
172
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
await runWireContract('openai', fixture);
|
|
111
|
-
});
|
|
173
|
+
const adapter: ScenarioSdkAdapter = {
|
|
174
|
+
family: 'openai',
|
|
112
175
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
},
|
|
116
180
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
181
|
+
routes(scenario: HoloScenario) {
|
|
182
|
+
if (scenario.protocol === 'openai.chatCompletions') {
|
|
183
|
+
return {method: 'POST', path: '/v1/chat/completions'};
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export default adapter;
|
|
121
190
|
```
|
|
122
191
|
|
|
123
|
-
|
|
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.
|
|
124
194
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
253
|
```
|
|
129
254
|
|
|
255
|
+
Prefer `runConformance()` for normal use; the manual path is an escape hatch.
|
|
256
|
+
|
|
130
257
|
## Live Testing
|
|
131
258
|
|
|
132
|
-
|
|
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[]`.
|
|
133
262
|
|
|
134
263
|
```typescript
|
|
135
264
|
import {runLiveTest, isProviderAvailable} from '@holokai/holo-test';
|
|
136
265
|
|
|
137
266
|
if (isProviderAvailable('openai')) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
timeout: 30000,
|
|
141
|
-
});
|
|
142
|
-
console.log(results);
|
|
267
|
+
const result = await runLiveTest('openai', /* streaming */ true);
|
|
268
|
+
console.log(result.passed, result.errors);
|
|
143
269
|
}
|
|
144
270
|
```
|
|
145
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
|
+
|
|
146
276
|
Environment variables for live tests:
|
|
147
|
-
|
|
148
|
-
- `
|
|
277
|
+
|
|
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)
|
|
149
281
|
|
|
150
282
|
## Helpers
|
|
151
283
|
|
|
152
284
|
```typescript
|
|
153
285
|
import {
|
|
154
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
|
|
155
290
|
parseSseBody, // Parse SSE response body into frames
|
|
156
291
|
parseNdjsonBody, // Parse NDJSON response body
|
|
157
292
|
createMockFetch, // Mock fetch for unit tests
|
|
158
293
|
createSseMockFetch, // Mock SSE streaming responses
|
|
159
294
|
collectStreamText, // Extract all text from a stream
|
|
160
295
|
collectStreamEvents, // Collect all events from a stream
|
|
161
|
-
HttpFixtureServer, // Mock HTTP server serving fixture responses
|
|
162
|
-
createTestWorkerRequest, // Build a HoloWorkerRequest from a fixture
|
|
163
|
-
createTestEnvelope, // Build a WorkerResponseEnvelope from a fixture
|
|
164
296
|
discoverProviders, // Discover available providers via gateway
|
|
165
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
|
|
166
300
|
} from '@holokai/holo-test';
|
|
167
301
|
```
|
|
168
302
|
|
|
303
|
+
`createTestPlugin` / `packPluginFixture` are for *runtime* plugin tests (upload/install/lifecycle), not for
|
|
304
|
+
conformance scenarios.
|
|
305
|
+
|
|
169
306
|
## License
|
|
170
307
|
|
|
171
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":"AAAA,OAAO,EAAC,MAAM,EAAE,EAAE,EAAC,MAAM,QAAQ,CAAC;AAMlC,MAAM,UAAU,kBAAkB,CAAC,MAAuB,EAAE,QAAsB;IAC9E,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM;QAAE,OAAO;IAEhE,MAAM,eAAe,GAAG,QAAQ,CAAC,eAAe,EAAE,KAAK,KAAK,IAAI,CAAC;IACjE,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAE1C,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;QAE7C,MAAM,SAAS,GAAG,QAAQ,CAAC,cAAe,CAAC,QAAQ,CAAC,cAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAChF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAM,CAAC;QAC9B,MAAM,SAAS,GAAkB;YAC7B,IAAI,EAAE,MAAM;YACZ,SAAS,EAAE,UAAU;YACrB,GAAG,EAAE,QAAQ,CAAC,cAAe,CAAC,MAAM,GAAG,CAAC;YACxC,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,QAAQ,CAAC,YAAY,IAAI,EAAE;YACjC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,OAAO,EAAE;gBACL,WAAW,EAAE,KAAK,CAAC,YAAY,IAAI,CAAC;gBACpC,YAAY,EAAE,KAAK,CAAC,aAAa,IAAI,CAAC;gBACtC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;gBACnE,gBAAgB,EAAE,CAAC;gBACnB,mBAAmB,EAAE,CAAC;gBACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACxB;SACa,CAAC;QAEnB,MAAM,QAAQ,GAA2B;YACrC,SAAS,EAAE,gBAAgB;YAC3B,UAAU,EAAE,UAAU;YACtB,eAAe,EAAE,UAAU;YAC3B,QAAQ,EAAE,EAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAQ;YACvD,QAAQ,EAAE,EAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAQ;YACtF,YAAY,EAAE,KAAK,CAAC,YAAmB;SAC1C,CAAC;QAEF,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAExE,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC7D,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACpC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxD,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,MAAuB;IACjD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAC,MAAM,EAAE,UAAU,EAAC,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC,OAAO,CAAC;AAC5B,CAAC"}
|
|
@@ -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"}
|