@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.
Files changed (148) hide show
  1. package/README.md +216 -81
  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/index.d.ts +13 -15
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +10 -14
  53. package/dist/index.js.map +1 -1
  54. package/dist/live/assertions.d.ts.map +1 -1
  55. package/dist/live/assertions.js +54 -21
  56. package/dist/live/assertions.js.map +1 -1
  57. package/dist/live/error-payloads.d.ts +5 -0
  58. package/dist/live/error-payloads.d.ts.map +1 -0
  59. package/dist/live/error-payloads.js +47 -0
  60. package/dist/live/error-payloads.js.map +1 -0
  61. package/dist/live/error-tester.d.ts +3 -0
  62. package/dist/live/error-tester.d.ts.map +1 -0
  63. package/dist/live/error-tester.js +121 -0
  64. package/dist/live/error-tester.js.map +1 -0
  65. package/dist/live/index.d.ts +3 -1
  66. package/dist/live/index.d.ts.map +1 -1
  67. package/dist/live/index.js +2 -0
  68. package/dist/live/index.js.map +1 -1
  69. package/dist/live/types.d.ts +9 -0
  70. package/dist/live/types.d.ts.map +1 -1
  71. package/dist/services/plugin-loader.d.ts +2 -1
  72. package/dist/services/plugin-loader.d.ts.map +1 -1
  73. package/dist/services/plugin-loader.js +2 -1
  74. package/dist/services/plugin-loader.js.map +1 -1
  75. package/dist/types.d.ts +0 -37
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +3 -6
  78. package/dist/cli.d.ts +0 -3
  79. package/dist/cli.d.ts.map +0 -1
  80. package/dist/cli.js +0 -83
  81. package/dist/cli.js.map +0 -1
  82. package/dist/contracts/audit-contract.d.ts +0 -3
  83. package/dist/contracts/audit-contract.d.ts.map +0 -1
  84. package/dist/contracts/audit-contract.js +0 -11
  85. package/dist/contracts/audit-contract.js.map +0 -1
  86. package/dist/contracts/datastore-contract.d.ts +0 -12
  87. package/dist/contracts/datastore-contract.d.ts.map +0 -1
  88. package/dist/contracts/datastore-contract.js +0 -20
  89. package/dist/contracts/datastore-contract.js.map +0 -1
  90. package/dist/contracts/pipeline-contract.d.ts +0 -3
  91. package/dist/contracts/pipeline-contract.d.ts.map +0 -1
  92. package/dist/contracts/pipeline-contract.js +0 -9
  93. package/dist/contracts/pipeline-contract.js.map +0 -1
  94. package/dist/contracts/roundtrip-contract.d.ts +0 -3
  95. package/dist/contracts/roundtrip-contract.d.ts.map +0 -1
  96. package/dist/contracts/roundtrip-contract.js +0 -9
  97. package/dist/contracts/roundtrip-contract.js.map +0 -1
  98. package/dist/contracts/wire-contract.d.ts +0 -3
  99. package/dist/contracts/wire-contract.d.ts.map +0 -1
  100. package/dist/contracts/wire-contract.js +0 -9
  101. package/dist/contracts/wire-contract.js.map +0 -1
  102. package/dist/dsl/assertions.d.ts +0 -6
  103. package/dist/dsl/assertions.d.ts.map +0 -1
  104. package/dist/dsl/assertions.js +0 -54
  105. package/dist/dsl/assertions.js.map +0 -1
  106. package/dist/dsl/suite-builder.d.ts +0 -25
  107. package/dist/dsl/suite-builder.d.ts.map +0 -1
  108. package/dist/dsl/suite-builder.js +0 -96
  109. package/dist/dsl/suite-builder.js.map +0 -1
  110. package/dist/factories/worker-request-factory.d.ts +0 -5
  111. package/dist/factories/worker-request-factory.d.ts.map +0 -1
  112. package/dist/factories/worker-request-factory.js +0 -19
  113. package/dist/factories/worker-request-factory.js.map +0 -1
  114. package/dist/fixtures/fixture-loader.d.ts +0 -6
  115. package/dist/fixtures/fixture-loader.d.ts.map +0 -1
  116. package/dist/fixtures/fixture-loader.js +0 -23
  117. package/dist/fixtures/fixture-loader.js.map +0 -1
  118. package/dist/runner/test-reporter.d.ts +0 -3
  119. package/dist/runner/test-reporter.d.ts.map +0 -1
  120. package/dist/runner/test-reporter.js +0 -43
  121. package/dist/runner/test-reporter.js.map +0 -1
  122. package/dist/runner/test-runner.d.ts +0 -12
  123. package/dist/runner/test-runner.d.ts.map +0 -1
  124. package/dist/runner/test-runner.js +0 -37
  125. package/dist/runner/test-runner.js.map +0 -1
  126. package/dist/services/http-fixture-server.d.ts +0 -14
  127. package/dist/services/http-fixture-server.d.ts.map +0 -1
  128. package/dist/services/http-fixture-server.js.map +0 -1
  129. package/dist/testers/audit-tester.d.ts +0 -4
  130. package/dist/testers/audit-tester.d.ts.map +0 -1
  131. package/dist/testers/audit-tester.js +0 -74
  132. package/dist/testers/audit-tester.js.map +0 -1
  133. package/dist/testers/datastore-tester.d.ts +0 -17
  134. package/dist/testers/datastore-tester.d.ts.map +0 -1
  135. package/dist/testers/datastore-tester.js +0 -306
  136. package/dist/testers/datastore-tester.js.map +0 -1
  137. package/dist/testers/pipeline-tester.d.ts +0 -4
  138. package/dist/testers/pipeline-tester.d.ts.map +0 -1
  139. package/dist/testers/pipeline-tester.js +0 -62
  140. package/dist/testers/pipeline-tester.js.map +0 -1
  141. package/dist/testers/roundtrip-tester.d.ts +0 -3
  142. package/dist/testers/roundtrip-tester.d.ts.map +0 -1
  143. package/dist/testers/roundtrip-tester.js +0 -77
  144. package/dist/testers/roundtrip-tester.js.map +0 -1
  145. package/dist/testers/wire-tester.d.ts +0 -4
  146. package/dist/testers/wire-tester.d.ts.map +0 -1
  147. package/dist/testers/wire-tester.js +0 -87
  148. 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. Provides fixture-driven testers, vitest contracts, a fluent
4
- 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.
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
- import {describe, it} from 'vitest';
18
- import {loadFixtures, runWireContract} from '@holokai/holo-test';
19
-
20
- const fixtures = loadFixtures('./tests/fixtures', {plugin: 'openai'});
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 | What it Tests | Function |
34
- |---------------|------------------------------------------------------------------------------------|----------------------------------------------|
35
- | **Wire** | Provider events serialize to correct HTTP wire chunks (SSE/JSON) | `testWire()` / `runWireContract()` |
36
- | **Audit** | Worker requests produce correct `ProviderRequest`/`ProviderResponse` audit records | `testAudit()` / `runAuditContract()` |
37
- | **Pipeline** | End-to-end: payload in, events out, text accumulated, done event emitted | `testPipeline()` / `runPipelineContract()` |
38
- | **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.
39
50
 
40
- ## 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.
41
54
 
42
- 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`:
43
59
 
44
60
  ```typescript
45
- import type {FixtureScenario} from '@holokai/holo-test';
61
+ import type {HoloScenario} from '@holokai/holo-test';
46
62
 
47
- const fixture: FixtureScenario = {
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
- 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
+ },
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
- 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: {
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
- 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
+ },
68
105
  };
69
106
 
70
- export default fixture;
107
+ export default scenario;
71
108
  ```
72
109
 
73
- Load fixtures:
110
+ ### Loader Invariants
74
111
 
75
- ```typescript
76
- 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.
77
117
 
78
- // All fixtures
79
- const all = loadFixtures('./tests/fixtures');
118
+ ### Canonical streaming fixtures
80
119
 
81
- // Filtered
82
- const streaming = loadFixtures('./tests/fixtures', {plugin: 'openai', tag: 'streaming'});
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
- ## Suite DSL
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
- Fluent builder for running multiple test modes:
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
- import {suite} from '@holokai/holo-test';
91
-
92
- await suite()
93
- .forPlugin('openai')
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
- ## Vitest Contracts
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
- Import contract functions directly into vitest test files:
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 {runWireContract, runAuditContract, runPipelineContract} from '@holokai/holo-test';
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
- describe('openai conformance', () => {
110
- it('wire: simple chat', async () => {
111
- await runWireContract('openai', fixture);
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
- it('audit: simple chat', async () => {
115
- await runAuditContract('openai', fixture);
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
- it('pipeline: simple chat', async () => {
119
- await runPipelineContract('openai', fixture);
120
- });
121
- });
189
+ export default adapter;
122
190
  ```
123
191
 
124
- ## 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.
125
194
 
126
- ```bash
127
- npx holo-test --fixture-dir ./tests/fixtures --plugin openai
128
- 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
+ }
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
- 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[]`.
134
262
 
135
263
  ```typescript
136
264
  import {runLiveTest, isProviderAvailable} from '@holokai/holo-test';
137
265
 
138
266
  if (isProviderAvailable('openai')) {
139
- const results = await runLiveTest({
140
- families: ['openai'],
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`, `GEMINI_API_KEY`, `OLLAMA_HOST`
150
- - `OPENAI_MODEL`, `CLAUDE_MODEL`, etc. (override default test models)
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,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":"AAAA,OAAO,EAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAC,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAWvC,MAAM,UAAU,sBAAsB,CAAC,MAAwB,EAAE,MAAuB;IACpF,IAAI,QAAQ,GAA8B,IAAI,CAAC;IAC/C,MAAM,WAAW,GAAoC,EAAE,CAAC;IAExD,QAAQ,CAAC,KAAK,IAAI,EAAE;QAChB,IAAI,QAAQ,EAAE,WAAW,EAAE,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;QAChC,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,CAAC,MAAO,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAO,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC3C,MAAM,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;QACvE,MAAM,CAAC,UAAU,EAAE,kCAAkC,CAAC,CAAC,UAAU,EAAE,CAAC;QACpE,KAAK,MAAM,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAU,EAAE,CAAC;YAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3B,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,OAAO,QAAQ,CAAC,CAAC,UAAU,EAAE,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,SAAS,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACvF,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACtC,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAChE,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,6BAA6B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,QAAS,CAAC,cAAc,EAAE,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;QAC3B,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG;YACX,UAAU,EAAE,UAAU,EAAE;YACxB,YAAY,EAAE,YAAY;YAC1B,SAAS,EAAE,KAAK;YAChB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;SAC/B,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,QAAS,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,2BAA2B,CAAC,CAAC,UAAU,EAAE,CAAC;QAC5D,WAAW,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,EAAG,EAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC5B,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG;YACX,UAAU,EAAE,UAAU,EAAE;YACxB,YAAY,EAAE,YAAY;YAC1B,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE;YACjB,YAAY,EAAE,EAAE;YAChB,mBAAmB,EAAE,GAAG;YACxB,qBAAqB,EAAE,GAAG;YAC1B,IAAI,EAAE,CAAC;YACP,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;SAC/B,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,QAAS,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,4BAA4B,CAAC,CAAC,UAAU,EAAE,CAAC;QAC7D,WAAW,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,EAAG,EAAC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;QACxB,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;QAC/E,MAAM,CAAC,UAAU,EAAE,6BAA6B,CAAC,CAAC,UAAU,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG;YACX,WAAW,EAAE,UAAU;YACvB,SAAS,EAAE,UAAU;YACrB,YAAY,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE;YACjB,iBAAiB,EAAE,CAAC;YACpB,kBAAkB,EAAE,CAAC;YACrB,UAAU,EAAE,MAAM;YAClB,WAAW,EAAE,MAAM;YACnB,eAAe,EAAE,CAAC;YAClB,gBAAgB,EAAE,CAAC;YACnB,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,KAAK;SAClB,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,QAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACZ,WAAW,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAC,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAS,CAAC,kBAAkB;YAAE,OAAO;QAC1C,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;QAC/E,MAAM,CAAC,UAAU,EAAE,6BAA6B,CAAC,CAAC,UAAU,EAAE,CAAC;QAC/D,MAAM,QAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,aAAa,EAAE,UAAW,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,iBAAiB,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QACpE,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YACxB,MAAM,MAAM,CAAC,QAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAC,EAAE,EAAE,MAAM,EAAC,CAAC,EAAE,kBAAkB,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACnG,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;QACxB,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAS,CAAC,WAAW,EAAE;YAAE,OAAO,CAAC,uCAAuC;QAC7E,MAAM,QAAS,CAAC,UAAU,EAAE,CAAC;QAC7B,MAAM,CAAC,QAAS,CAAC,WAAW,EAAE,EAAE,gCAAgC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -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"}