@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.
Files changed (169) hide show
  1. package/README.md +217 -80
  2. package/dist/conformance/audit-tests.d.ts +4 -0
  3. package/dist/conformance/audit-tests.d.ts.map +1 -0
  4. package/dist/conformance/audit-tests.js +55 -0
  5. package/dist/conformance/audit-tests.js.map +1 -0
  6. package/dist/conformance/datastore-tests.d.ts +13 -0
  7. package/dist/conformance/datastore-tests.d.ts.map +1 -0
  8. package/dist/conformance/datastore-tests.js +121 -0
  9. package/dist/conformance/datastore-tests.js.map +1 -0
  10. package/dist/conformance/holo-fixture-loader.d.ts +4 -0
  11. package/dist/conformance/holo-fixture-loader.d.ts.map +1 -0
  12. package/dist/conformance/holo-fixture-loader.js +79 -0
  13. package/dist/conformance/holo-fixture-loader.js.map +1 -0
  14. package/dist/conformance/holo-fixture-tests.d.ts +4 -0
  15. package/dist/conformance/holo-fixture-tests.d.ts.map +1 -0
  16. package/dist/conformance/holo-fixture-tests.js +273 -0
  17. package/dist/conformance/holo-fixture-tests.js.map +1 -0
  18. package/dist/conformance/holo-fixture-types.d.ts +93 -0
  19. package/dist/conformance/holo-fixture-types.d.ts.map +1 -0
  20. package/dist/conformance/holo-fixture-types.js +2 -0
  21. package/dist/conformance/holo-fixture-types.js.map +1 -0
  22. package/dist/conformance/loader.d.ts +7 -0
  23. package/dist/conformance/loader.d.ts.map +1 -0
  24. package/dist/conformance/loader.js +71 -0
  25. package/dist/conformance/loader.js.map +1 -0
  26. package/dist/conformance/pipeline-tests.d.ts +4 -0
  27. package/dist/conformance/pipeline-tests.d.ts.map +1 -0
  28. package/dist/conformance/pipeline-tests.js +73 -0
  29. package/dist/conformance/pipeline-tests.js.map +1 -0
  30. package/dist/conformance/runner.d.ts +2 -0
  31. package/dist/conformance/runner.d.ts.map +1 -0
  32. package/dist/conformance/runner.js +102 -0
  33. package/dist/conformance/runner.js.map +1 -0
  34. package/dist/conformance/sdk-tests.d.ts +12 -0
  35. package/dist/conformance/sdk-tests.d.ts.map +1 -0
  36. package/dist/{services/http-fixture-server.js → conformance/sdk-tests.js} +56 -17
  37. package/dist/conformance/sdk-tests.js.map +1 -0
  38. package/dist/conformance/translator-tests.d.ts +4 -0
  39. package/dist/conformance/translator-tests.d.ts.map +1 -0
  40. package/dist/conformance/translator-tests.js +49 -0
  41. package/dist/conformance/translator-tests.js.map +1 -0
  42. package/dist/conformance/types.d.ts +81 -0
  43. package/dist/conformance/types.d.ts.map +1 -0
  44. package/dist/conformance/types.js +2 -0
  45. package/dist/conformance/types.js.map +1 -0
  46. package/dist/conformance/wire-tests.d.ts +4 -0
  47. package/dist/conformance/wire-tests.d.ts.map +1 -0
  48. package/dist/conformance/wire-tests.js +52 -0
  49. package/dist/conformance/wire-tests.js.map +1 -0
  50. package/dist/fixtures/plugin.d.ts +27 -0
  51. package/dist/fixtures/plugin.d.ts.map +1 -0
  52. package/dist/fixtures/plugin.js +110 -0
  53. package/dist/fixtures/plugin.js.map +1 -0
  54. package/dist/helpers/env.d.ts +2 -0
  55. package/dist/helpers/env.d.ts.map +1 -1
  56. package/dist/helpers/env.js +5 -0
  57. package/dist/helpers/env.js.map +1 -1
  58. package/dist/helpers/ids.d.ts +2 -0
  59. package/dist/helpers/ids.d.ts.map +1 -0
  60. package/dist/helpers/ids.js +5 -0
  61. package/dist/helpers/ids.js.map +1 -0
  62. package/dist/helpers/matrix.d.ts +9 -1
  63. package/dist/helpers/matrix.d.ts.map +1 -1
  64. package/dist/helpers/matrix.js +8 -3
  65. package/dist/helpers/matrix.js.map +1 -1
  66. package/dist/helpers/mocks.d.ts +4 -0
  67. package/dist/helpers/mocks.d.ts.map +1 -0
  68. package/dist/helpers/mocks.js +37 -0
  69. package/dist/helpers/mocks.js.map +1 -0
  70. package/dist/helpers/polling.d.ts +3 -0
  71. package/dist/helpers/polling.d.ts.map +1 -0
  72. package/dist/helpers/polling.js +24 -0
  73. package/dist/helpers/polling.js.map +1 -0
  74. package/dist/helpers/vitest-helpers.d.ts +1 -1
  75. package/dist/helpers/vitest-helpers.d.ts.map +1 -1
  76. package/dist/index.d.ts +26 -23
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +22 -21
  79. package/dist/index.js.map +1 -1
  80. package/dist/live/assertions.d.ts.map +1 -1
  81. package/dist/live/assertions.js +54 -21
  82. package/dist/live/assertions.js.map +1 -1
  83. package/dist/live/error-payloads.d.ts +5 -0
  84. package/dist/live/error-payloads.d.ts.map +1 -0
  85. package/dist/live/error-payloads.js +47 -0
  86. package/dist/live/error-payloads.js.map +1 -0
  87. package/dist/live/error-tester.d.ts +3 -0
  88. package/dist/live/error-tester.d.ts.map +1 -0
  89. package/dist/live/error-tester.js +121 -0
  90. package/dist/live/error-tester.js.map +1 -0
  91. package/dist/live/index.d.ts +3 -1
  92. package/dist/live/index.d.ts.map +1 -1
  93. package/dist/live/index.js +2 -0
  94. package/dist/live/index.js.map +1 -1
  95. package/dist/live/live-tester.d.ts.map +1 -1
  96. package/dist/live/live-tester.js +17 -3
  97. package/dist/live/live-tester.js.map +1 -1
  98. package/dist/live/types.d.ts +9 -0
  99. package/dist/live/types.d.ts.map +1 -1
  100. package/dist/services/plugin-loader.d.ts +4 -2
  101. package/dist/services/plugin-loader.d.ts.map +1 -1
  102. package/dist/services/plugin-loader.js +14 -2
  103. package/dist/services/plugin-loader.js.map +1 -1
  104. package/dist/types.d.ts +0 -37
  105. package/dist/types.d.ts.map +1 -1
  106. package/package.json +3 -6
  107. package/dist/cli.d.ts +0 -3
  108. package/dist/cli.d.ts.map +0 -1
  109. package/dist/cli.js +0 -83
  110. package/dist/cli.js.map +0 -1
  111. package/dist/contracts/audit-contract.d.ts +0 -3
  112. package/dist/contracts/audit-contract.d.ts.map +0 -1
  113. package/dist/contracts/audit-contract.js +0 -11
  114. package/dist/contracts/audit-contract.js.map +0 -1
  115. package/dist/contracts/pipeline-contract.d.ts +0 -3
  116. package/dist/contracts/pipeline-contract.d.ts.map +0 -1
  117. package/dist/contracts/pipeline-contract.js +0 -9
  118. package/dist/contracts/pipeline-contract.js.map +0 -1
  119. package/dist/contracts/roundtrip-contract.d.ts +0 -3
  120. package/dist/contracts/roundtrip-contract.d.ts.map +0 -1
  121. package/dist/contracts/roundtrip-contract.js +0 -9
  122. package/dist/contracts/roundtrip-contract.js.map +0 -1
  123. package/dist/contracts/wire-contract.d.ts +0 -3
  124. package/dist/contracts/wire-contract.d.ts.map +0 -1
  125. package/dist/contracts/wire-contract.js +0 -9
  126. package/dist/contracts/wire-contract.js.map +0 -1
  127. package/dist/dsl/assertions.d.ts +0 -6
  128. package/dist/dsl/assertions.d.ts.map +0 -1
  129. package/dist/dsl/assertions.js +0 -54
  130. package/dist/dsl/assertions.js.map +0 -1
  131. package/dist/dsl/suite-builder.d.ts +0 -25
  132. package/dist/dsl/suite-builder.d.ts.map +0 -1
  133. package/dist/dsl/suite-builder.js +0 -96
  134. package/dist/dsl/suite-builder.js.map +0 -1
  135. package/dist/factories/worker-request-factory.d.ts +0 -5
  136. package/dist/factories/worker-request-factory.d.ts.map +0 -1
  137. package/dist/factories/worker-request-factory.js +0 -19
  138. package/dist/factories/worker-request-factory.js.map +0 -1
  139. package/dist/fixtures/fixture-loader.d.ts +0 -6
  140. package/dist/fixtures/fixture-loader.d.ts.map +0 -1
  141. package/dist/fixtures/fixture-loader.js +0 -23
  142. package/dist/fixtures/fixture-loader.js.map +0 -1
  143. package/dist/runner/test-reporter.d.ts +0 -3
  144. package/dist/runner/test-reporter.d.ts.map +0 -1
  145. package/dist/runner/test-reporter.js +0 -43
  146. package/dist/runner/test-reporter.js.map +0 -1
  147. package/dist/runner/test-runner.d.ts +0 -12
  148. package/dist/runner/test-runner.d.ts.map +0 -1
  149. package/dist/runner/test-runner.js +0 -37
  150. package/dist/runner/test-runner.js.map +0 -1
  151. package/dist/services/http-fixture-server.d.ts +0 -14
  152. package/dist/services/http-fixture-server.d.ts.map +0 -1
  153. package/dist/services/http-fixture-server.js.map +0 -1
  154. package/dist/testers/audit-tester.d.ts +0 -4
  155. package/dist/testers/audit-tester.d.ts.map +0 -1
  156. package/dist/testers/audit-tester.js +0 -74
  157. package/dist/testers/audit-tester.js.map +0 -1
  158. package/dist/testers/pipeline-tester.d.ts +0 -4
  159. package/dist/testers/pipeline-tester.d.ts.map +0 -1
  160. package/dist/testers/pipeline-tester.js +0 -62
  161. package/dist/testers/pipeline-tester.js.map +0 -1
  162. package/dist/testers/roundtrip-tester.d.ts +0 -3
  163. package/dist/testers/roundtrip-tester.d.ts.map +0 -1
  164. package/dist/testers/roundtrip-tester.js +0 -77
  165. package/dist/testers/roundtrip-tester.js.map +0 -1
  166. package/dist/testers/wire-tester.d.ts +0 -4
  167. package/dist/testers/wire-tester.d.ts.map +0 -1
  168. package/dist/testers/wire-tester.js +0 -87
  169. 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 plugins. Provides fixture-driven testers, vitest contracts, a fluent DSL, and a CLI.
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
- import {describe, it} from 'vitest';
17
- import {loadFixtures, runWireContract} from '@holokai/holo-test';
18
-
19
- const fixtures = loadFixtures('./tests/fixtures', {plugin: 'openai'});
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 | What it Tests | Function |
33
- |------|--------------|----------|
34
- | **Wire** | Provider events serialize to correct HTTP wire chunks (SSE/JSON) | `testWire()` / `runWireContract()` |
35
- | **Audit** | Worker requests produce correct `ProviderRequest`/`ProviderResponse` audit records | `testAudit()` / `runAuditContract()` |
36
- | **Pipeline** | End-to-end: payload in, events out, text accumulated, done event emitted | `testPipeline()` / `runPipelineContract()` |
37
- | **RoundTrip** | SDK requestHTTP fixture server SDK response matches expected output | `testRoundTrip()` / `runRoundTripContract()` |
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 chunkstext 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
- ## Fixtures
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
- Create `.fixture.ts` files that export a `FixtureScenario`:
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 {FixtureScenario} from '@holokai/holo-test';
61
+ import type {HoloScenario} from '@holokai/holo-test';
45
62
 
46
- const fixture: FixtureScenario = {
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
- providerChunks: [/* mock provider response objects */],
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
- expectedAudit: {
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
- tags: ['chat', 'non-streaming'],
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 fixture;
107
+ export default scenario;
70
108
  ```
71
109
 
72
- Load fixtures:
110
+ ### Loader Invariants
73
111
 
74
- ```typescript
75
- import {loadFixtures} from '@holokai/holo-test';
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
- // All fixtures
78
- const all = loadFixtures('./tests/fixtures');
118
+ ### Canonical streaming fixtures
79
119
 
80
- // Filtered
81
- const streaming = loadFixtures('./tests/fixtures', {plugin: 'openai', tag: 'streaming'});
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
- ## Suite DSL
141
+ ### Streaming event sequence assertion
85
142
 
86
- Fluent builder for running multiple test modes:
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
- import {suite} from '@holokai/holo-test';
90
-
91
- await suite()
92
- .forPlugin('openai')
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
- ## Vitest Contracts
162
+ Failing-as-expected modes flip to `it.fails`, so CI still fails if the scenario unexpectedly *starts* passing.
102
163
 
103
- Import contract functions directly into vitest test files:
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 {runWireContract, runAuditContract, runPipelineContract} from '@holokai/holo-test';
170
+ import OpenAI from 'openai';
171
+ import type {HoloScenario, ScenarioSdkAdapter} from '@holokai/holo-test';
107
172
 
108
- describe('openai conformance', () => {
109
- it('wire: simple chat', async () => {
110
- await runWireContract('openai', fixture);
111
- });
173
+ const adapter: ScenarioSdkAdapter = {
174
+ family: 'openai',
112
175
 
113
- it('audit: simple chat', async () => {
114
- await runAuditContract('openai', fixture);
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
- it('pipeline: simple chat', async () => {
118
- await runPipelineContract('openai', fixture);
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
- ## CLI
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
- ```bash
126
- npx holo-test --fixture-dir ./tests/fixtures --plugin openai
127
- npx holo-test --fixture-dir ./tests/fixtures --plugin claude --tag streaming --verbose
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
- Test against real provider APIs:
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 results = await runLiveTest({
139
- families: ['openai'],
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
- - `OPENAI_API_KEY`, `CLAUDE_API_KEY`, `GEMINI_API_KEY`, `OLLAMA_HOST`
148
- - `OPENAI_MODEL`, `CLAUDE_MODEL`, etc. (override default test models)
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,4 @@
1
+ import type { IProviderPlugin } from '@holokai/holo-types/plugin';
2
+ import type { HoloScenario } from './types';
3
+ export declare function registerAuditTests(plugin: IProviderPlugin, scenario: HoloScenario): void;
4
+ //# sourceMappingURL=audit-tests.d.ts.map
@@ -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"}