@ontrails/testing 1.0.0-beta.15 → 1.0.0-beta.17
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/CHANGELOG.md +70 -1
- package/README.md +47 -3
- package/package.json +16 -7
- package/src/all.ts +32 -7
- package/src/context.ts +3 -0
- package/src/contracts.ts +1 -16
- package/src/examples.ts +19 -11
- package/src/harness-cli.ts +16 -3
- package/src/harness-http.ts +263 -0
- package/src/harness-mcp.ts +2 -0
- package/src/index.ts +18 -1
- package/src/logger.ts +3 -1
- package/src/scenario.ts +3 -5
- package/src/signals.ts +221 -0
- package/src/surface-parity.ts +356 -0
- package/src/types.ts +136 -1
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/all.d.ts +0 -36
- package/dist/all.d.ts.map +0 -1
- package/dist/all.js +0 -99
- package/dist/all.js.map +0 -1
- package/dist/assertions.d.ts +0 -72
- package/dist/assertions.d.ts.map +0 -1
- package/dist/assertions.js +0 -238
- package/dist/assertions.js.map +0 -1
- package/dist/context.d.ts +0 -76
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -118
- package/dist/context.js.map +0 -1
- package/dist/contracts.d.ts +0 -17
- package/dist/contracts.d.ts.map +0 -1
- package/dist/contracts.js +0 -75
- package/dist/contracts.js.map +0 -1
- package/dist/crosses.d.ts +0 -38
- package/dist/crosses.d.ts.map +0 -1
- package/dist/crosses.js +0 -223
- package/dist/crosses.js.map +0 -1
- package/dist/detours.d.ts +0 -13
- package/dist/detours.d.ts.map +0 -1
- package/dist/detours.js +0 -109
- package/dist/detours.js.map +0 -1
- package/dist/effective-examples.d.ts +0 -30
- package/dist/effective-examples.d.ts.map +0 -1
- package/dist/effective-examples.js +0 -227
- package/dist/effective-examples.js.map +0 -1
- package/dist/examples.d.ts +0 -23
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js +0 -240
- package/dist/examples.js.map +0 -1
- package/dist/harness-cli.d.ts +0 -21
- package/dist/harness-cli.d.ts.map +0 -1
- package/dist/harness-cli.js +0 -205
- package/dist/harness-cli.js.map +0 -1
- package/dist/harness-mcp.d.ts +0 -21
- package/dist/harness-mcp.d.ts.map +0 -1
- package/dist/harness-mcp.js +0 -54
- package/dist/harness-mcp.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -19
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -15
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -87
- package/dist/logger.js.map +0 -1
- package/dist/scenario.d.ts +0 -37
- package/dist/scenario.d.ts.map +0 -1
- package/dist/scenario.js +0 -235
- package/dist/scenario.js.map +0 -1
- package/dist/trail.d.ts +0 -20
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -80
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -113
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/src/__tests__/all.test.ts +0 -254
- package/src/__tests__/context.test.ts +0 -170
- package/src/__tests__/contracts.test.ts +0 -239
- package/src/__tests__/crosses.test.ts +0 -587
- package/src/__tests__/detours.test.ts +0 -212
- package/src/__tests__/effective-examples.test.ts +0 -203
- package/src/__tests__/examples.test.ts +0 -636
- package/src/__tests__/harness-cli.test.ts +0 -90
- package/src/__tests__/harness-mcp.test.ts +0 -37
- package/src/__tests__/logger.test.ts +0 -136
- package/src/__tests__/partial-match.test.ts +0 -126
- package/src/__tests__/scenario.test.ts +0 -381
- package/src/__tests__/trail.test.ts +0 -99
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ce42573: Add an example-driven CLI/MCP/HTTP surface parity helper.
|
|
8
|
+
- e1eb4ee: Add an HTTP surface harness and include HTTP projection validation in `testAllEstablished`.
|
|
9
|
+
- Updated dependencies [3dc8254]
|
|
10
|
+
- Updated dependencies [61497c5]
|
|
11
|
+
- @ontrails/core@1.0.0-beta.17
|
|
12
|
+
- @ontrails/cli@1.0.0-beta.17
|
|
13
|
+
- @ontrails/http@1.0.0-beta.17
|
|
14
|
+
- @ontrails/mcp@1.0.0-beta.17
|
|
15
|
+
- @ontrails/observe@1.0.0-beta.17
|
|
16
|
+
|
|
17
|
+
## 1.0.0-beta.16
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- bcdc484: Add an explicit `unmockable: { reason }` resource marker and have testing auto-mock resolution skip intentionally unmockable resources.
|
|
22
|
+
- bb1cadf: Rename the generated `testAll` suite from `governance` to `contract`.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- d40430d: Remove the retired `@ontrails/logging` workspace from the prerelease package set. Use `@ontrails/observe` for log and trace sink contracts and `@ontrails/logtape` for LogTape forwarding.
|
|
27
|
+
- 193ae78: Migrate testing logger types from the legacy `@ontrails/logging` package to `@ontrails/observe`.
|
|
28
|
+
- Updated dependencies [73622ae]
|
|
29
|
+
- Updated dependencies [e991a5b]
|
|
30
|
+
- Updated dependencies [25f3c5c]
|
|
31
|
+
- Updated dependencies [6300f70]
|
|
32
|
+
- Updated dependencies [d172013]
|
|
33
|
+
- Updated dependencies [c3fc5c3]
|
|
34
|
+
- Updated dependencies [20d7a5c]
|
|
35
|
+
- Updated dependencies [be5fb46]
|
|
36
|
+
- Updated dependencies [e898cc4]
|
|
37
|
+
- Updated dependencies [a8997ed]
|
|
38
|
+
- Updated dependencies [fe03945]
|
|
39
|
+
- Updated dependencies [3395234]
|
|
40
|
+
- Updated dependencies [d40430d]
|
|
41
|
+
- Updated dependencies [bcdc484]
|
|
42
|
+
- Updated dependencies [ed171d5]
|
|
43
|
+
- Updated dependencies [49c2e7d]
|
|
44
|
+
- Updated dependencies [331e3a9]
|
|
45
|
+
- Updated dependencies [4399fdb]
|
|
46
|
+
- Updated dependencies [4b8d13b]
|
|
47
|
+
- Updated dependencies [fbd42fc]
|
|
48
|
+
- Updated dependencies [63d1aef]
|
|
49
|
+
- Updated dependencies [112b9f2]
|
|
50
|
+
- Updated dependencies [893025e]
|
|
51
|
+
- Updated dependencies [ed888e2]
|
|
52
|
+
- Updated dependencies [2e05e27]
|
|
53
|
+
- Updated dependencies [9cdb0f2]
|
|
54
|
+
- Updated dependencies [c8caa5e]
|
|
55
|
+
- Updated dependencies [f4b90c9]
|
|
56
|
+
- Updated dependencies [eec5e9d]
|
|
57
|
+
- Updated dependencies [4e75129]
|
|
58
|
+
- Updated dependencies [47505fe]
|
|
59
|
+
- Updated dependencies [ebd4434]
|
|
60
|
+
- Updated dependencies [863d473]
|
|
61
|
+
- Updated dependencies [344f2f7]
|
|
62
|
+
- Updated dependencies [26f9ffd]
|
|
63
|
+
- Updated dependencies [66056ac]
|
|
64
|
+
- Updated dependencies [0bad534]
|
|
65
|
+
- Updated dependencies [10eae9a]
|
|
66
|
+
- Updated dependencies [22c6c06]
|
|
67
|
+
- @ontrails/core@1.0.0-beta.16
|
|
68
|
+
- @ontrails/cli@1.0.0-beta.16
|
|
69
|
+
- @ontrails/mcp@1.0.0-beta.16
|
|
70
|
+
- @ontrails/observe@1.0.0-beta.16
|
|
71
|
+
|
|
3
72
|
## 1.0.0-beta.15
|
|
4
73
|
|
|
5
74
|
### Minor Changes
|
|
@@ -11,7 +80,7 @@
|
|
|
11
80
|
- Updated dependencies [4ad6b25]
|
|
12
81
|
- @ontrails/core@1.0.0-beta.15
|
|
13
82
|
- @ontrails/cli@1.0.0-beta.15
|
|
14
|
-
- @ontrails/
|
|
83
|
+
- @ontrails/observe@1.0.0-beta.15
|
|
15
84
|
- @ontrails/mcp@1.0.0-beta.15
|
|
16
85
|
|
|
17
86
|
## 1.0.0-beta.14
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
-
Contract-driven testing for Trails. Add examples to your trails, then `testAll(graph)` runs them as assertions, validates output schemas, checks composition graphs, and verifies structural integrity. One line of test code, full
|
|
3
|
+
Contract-driven testing for Trails. Add examples to your trails, then `testAll(graph)` runs them as assertions, validates output schemas, checks composition graphs, and verifies structural integrity. One line of test code, full contract coverage.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -27,16 +27,18 @@ testDetours(graph); // Validate detour constructor, recover, and ordering sem
|
|
|
27
27
|
|
|
28
28
|
| Export | What it does |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| `testAll(topo, ctx?)` | Single-line
|
|
30
|
+
| `testAll(topo, ctx?)` | Single-line contract suite: validation + examples + contracts + detours |
|
|
31
31
|
| `testExamples(topo, ctx?)` | Run trail examples as `describe`/`test` blocks |
|
|
32
32
|
| `testTrail(trail, scenarios)` | Custom scenarios for edge cases, error paths, and cross chains |
|
|
33
33
|
| `testContracts(topo, ctx?)` | Validate output against declared schemas |
|
|
34
34
|
| `testDetours(topo)` | Validate detour constructor, recover, and shadowing semantics |
|
|
35
|
+
| `testSurfaceParity(topo, options?)` | Run trail examples through CLI, MCP, and HTTP and compare normalized semantics |
|
|
35
36
|
| `createCrossContext(options?)` | Mock `CrossFn` for testing composite trails; returns preconfigured `Result` values keyed by trail ID |
|
|
36
37
|
| `createTestContext(options?)` | `TrailContext` with sensible test defaults |
|
|
37
38
|
| `createTestLogger()` | Logger that captures entries in memory for assertions |
|
|
38
39
|
| `createCliHarness(options)` | Execute CLI commands in-process, capture stdout/stderr |
|
|
39
40
|
| `createMcpHarness(options)` | Invoke MCP tools directly without transport |
|
|
41
|
+
| `createHttpHarness(options)` | Execute HTTP route projections in-process without a server |
|
|
40
42
|
|
|
41
43
|
See the [API Reference](../../docs/api-reference.md) for the full list.
|
|
42
44
|
|
|
@@ -89,7 +91,11 @@ Calls to unregistered trail IDs return `Result.err` with a descriptive message,
|
|
|
89
91
|
## Surface Harnesses
|
|
90
92
|
|
|
91
93
|
```typescript
|
|
92
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
createCliHarness,
|
|
96
|
+
createHttpHarness,
|
|
97
|
+
createMcpHarness,
|
|
98
|
+
} from '@ontrails/testing';
|
|
93
99
|
|
|
94
100
|
// CLI
|
|
95
101
|
const cli = createCliHarness({ graph });
|
|
@@ -100,8 +106,46 @@ expect(result.exitCode).toBe(0);
|
|
|
100
106
|
const mcp = createMcpHarness({ graph });
|
|
101
107
|
const tool = await mcp.callTool('myapp_entity_show', { name: 'Alpha' });
|
|
102
108
|
expect(tool.isError).toBe(false);
|
|
109
|
+
|
|
110
|
+
// HTTP
|
|
111
|
+
const http = createHttpHarness({ graph });
|
|
112
|
+
const response = await http.get('/entity/show', { name: 'Alpha' });
|
|
113
|
+
expect(response.status).toBe(200);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Surface Parity
|
|
117
|
+
|
|
118
|
+
`testSurfaceParity()` is a focused gate for established apps that want to prove
|
|
119
|
+
their examples behave the same through every shipped surface.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { testSurfaceParity } from '@ontrails/testing';
|
|
123
|
+
import { graph } from '../app';
|
|
124
|
+
|
|
125
|
+
const createDeterministicTestDb = () => ({});
|
|
126
|
+
|
|
127
|
+
testSurfaceParity(graph, {
|
|
128
|
+
createResources: () => ({
|
|
129
|
+
'db.main': createDeterministicTestDb(),
|
|
130
|
+
}),
|
|
131
|
+
exclusions: [
|
|
132
|
+
{
|
|
133
|
+
example: 'Creates generated data',
|
|
134
|
+
reason: 'output includes generated IDs that intentionally differ per surface run',
|
|
135
|
+
trailId: 'entity.add',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
103
139
|
```
|
|
104
140
|
|
|
141
|
+
The helper compares normalized success payloads and normalized TrailsError
|
|
142
|
+
category/code pairs. CLI command names, MCP tool names, HTTP method/path values,
|
|
143
|
+
transport envelopes, activation consumers, internal trails, and planned
|
|
144
|
+
WebSocket work stay outside the equality check. Use `createResources` when
|
|
145
|
+
examples need deterministic fixtures for each surface invocation, and use
|
|
146
|
+
exclusions for intentional semantic differences that should not be silently
|
|
147
|
+
skipped.
|
|
148
|
+
|
|
105
149
|
## Installation
|
|
106
150
|
|
|
107
151
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.17",
|
|
4
|
+
"files": [
|
|
5
|
+
"src/**/*.ts",
|
|
6
|
+
"!src/**/__tests__/**",
|
|
7
|
+
"!src/**/*.test.ts",
|
|
8
|
+
"!src/**/*.test-d.ts",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
4
12
|
"type": "module",
|
|
5
13
|
"exports": {
|
|
6
14
|
".": "./src/index.ts",
|
|
@@ -14,14 +22,15 @@
|
|
|
14
22
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
15
23
|
},
|
|
16
24
|
"devDependencies": {
|
|
17
|
-
"@ontrails/drizzle": "^1.0.0-beta.
|
|
18
|
-
"@ontrails/store": "^1.0.0-beta.
|
|
25
|
+
"@ontrails/drizzle": "^1.0.0-beta.16",
|
|
26
|
+
"@ontrails/store": "^1.0.0-beta.16"
|
|
19
27
|
},
|
|
20
28
|
"peerDependencies": {
|
|
21
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
22
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
23
|
-
"@ontrails/
|
|
24
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
29
|
+
"@ontrails/cli": "^1.0.0-beta.16",
|
|
30
|
+
"@ontrails/core": "^1.0.0-beta.16",
|
|
31
|
+
"@ontrails/http": "^1.0.0-beta.16",
|
|
32
|
+
"@ontrails/mcp": "^1.0.0-beta.16",
|
|
33
|
+
"@ontrails/observe": "^1.0.0-beta.16",
|
|
25
34
|
"zod": "^4.3.5"
|
|
26
35
|
}
|
|
27
36
|
}
|
package/src/all.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* testAll — single-line
|
|
2
|
+
* testAll — single-line contract suite for any Topo.
|
|
3
3
|
*
|
|
4
4
|
* Wraps topo validation, example execution, contract checks, and detour
|
|
5
5
|
* contract validation into one describe block.
|
|
@@ -11,6 +11,7 @@ import type { Topo, TrailContext } from '@ontrails/core';
|
|
|
11
11
|
import { validateEstablishedTopo, validateTopo } from '@ontrails/core';
|
|
12
12
|
|
|
13
13
|
import { createCliHarness } from './harness-cli.js';
|
|
14
|
+
import { createHttpHarness } from './harness-http.js';
|
|
14
15
|
import { createMcpHarness } from './harness-mcp.js';
|
|
15
16
|
import { testContracts } from './contracts.js';
|
|
16
17
|
import type { TestExecutionOptions } from './context.js';
|
|
@@ -19,9 +20,9 @@ import { testExamples } from './examples.js';
|
|
|
19
20
|
import type { TestAllEstablishedOptions } from './types.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* Run the full
|
|
23
|
+
* Run the full contract test suite for a Topo.
|
|
23
24
|
*
|
|
24
|
-
* Generates a `
|
|
25
|
+
* Generates a `contract` describe block containing:
|
|
25
26
|
* - Structural validation via `validateTopo`
|
|
26
27
|
* - Example execution via `testExamples`
|
|
27
28
|
* - Output contract checks via `testContracts`
|
|
@@ -73,12 +74,12 @@ const assertValidTopo = (result: ReturnType<typeof validateTopo>): void => {
|
|
|
73
74
|
}
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
const
|
|
77
|
+
const registerContractSuite = (
|
|
77
78
|
topo: Topo,
|
|
78
79
|
ctxOrFactory: TestAllInput | undefined,
|
|
79
80
|
validate: (topo: Topo) => ReturnType<typeof validateTopo>
|
|
80
81
|
): void => {
|
|
81
|
-
describe('
|
|
82
|
+
describe('contract', () => {
|
|
82
83
|
test('topo validates', () => {
|
|
83
84
|
expect(() => assertValidTopo(validate(topo))).not.toThrow();
|
|
84
85
|
});
|
|
@@ -93,7 +94,7 @@ const registerGovernanceSuite = (
|
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
export const testAll = (topo: Topo, ctxOrFactory?: TestAllInput): void => {
|
|
96
|
-
|
|
97
|
+
registerContractSuite(topo, ctxOrFactory, validateTopo);
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
type EstablishedInput =
|
|
@@ -108,6 +109,7 @@ const isEstablishedOptions = (
|
|
|
108
109
|
(Object.hasOwn(input, 'cli') ||
|
|
109
110
|
Object.hasOwn(input, 'createPermit') ||
|
|
110
111
|
Object.hasOwn(input, 'ctx') ||
|
|
112
|
+
Object.hasOwn(input, 'http') ||
|
|
111
113
|
Object.hasOwn(input, 'mcp') ||
|
|
112
114
|
Object.hasOwn(input, 'resources') ||
|
|
113
115
|
Object.hasOwn(input, 'strictPermits'));
|
|
@@ -154,6 +156,22 @@ const toMcpHarnessOptions = (
|
|
|
154
156
|
...options.mcp,
|
|
155
157
|
});
|
|
156
158
|
|
|
159
|
+
const toHttpHarnessOptions = (
|
|
160
|
+
topo: Topo,
|
|
161
|
+
options: TestAllEstablishedOptions
|
|
162
|
+
) => {
|
|
163
|
+
const httpOptions = {
|
|
164
|
+
graph: topo,
|
|
165
|
+
...options.http,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (options.ctx !== undefined) {
|
|
169
|
+
httpOptions.ctx = options.ctx;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return httpOptions;
|
|
173
|
+
};
|
|
174
|
+
|
|
157
175
|
const registerEstablishedSurfaceSuite = (
|
|
158
176
|
topo: Topo,
|
|
159
177
|
resolveInput: () =>
|
|
@@ -175,6 +193,13 @@ const registerEstablishedSurfaceSuite = (
|
|
|
175
193
|
createMcpHarness(toMcpHarnessOptions(topo, options))
|
|
176
194
|
).not.toThrow();
|
|
177
195
|
});
|
|
196
|
+
|
|
197
|
+
test('HTTP projection validates established topo', () => {
|
|
198
|
+
const options = normalizeEstablishedOptions(resolveInput());
|
|
199
|
+
expect(() =>
|
|
200
|
+
createHttpHarness(toHttpHarnessOptions(topo, options))
|
|
201
|
+
).not.toThrow();
|
|
202
|
+
});
|
|
178
203
|
});
|
|
179
204
|
};
|
|
180
205
|
|
|
@@ -187,7 +212,7 @@ export const testAllEstablished = (
|
|
|
187
212
|
? optionsOrFactory
|
|
188
213
|
: () => optionsOrFactory;
|
|
189
214
|
|
|
190
|
-
|
|
215
|
+
registerContractSuite(
|
|
191
216
|
topo,
|
|
192
217
|
() => toExecutionOptions(normalizeEstablishedOptions(resolveInput())),
|
|
193
218
|
validateEstablishedTopo
|
package/src/context.ts
CHANGED
|
@@ -184,6 +184,9 @@ export const mergeResourceOverrides = (
|
|
|
184
184
|
const buildMockResources = async (app: Topo): Promise<ResourceOverrideMap> => {
|
|
185
185
|
const resources: Record<string, unknown> = {};
|
|
186
186
|
for (const declaredResource of app.listResources()) {
|
|
187
|
+
if (declaredResource.unmockable !== undefined) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
187
190
|
if (!declaredResource.mock) {
|
|
188
191
|
continue;
|
|
189
192
|
}
|
package/src/contracts.ts
CHANGED
|
@@ -26,18 +26,6 @@ import { deriveTrailExamples } from './effective-examples.js';
|
|
|
26
26
|
// Helpers
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
|
-
/** Check if a trail requires cross() but the context doesn't provide it. */
|
|
30
|
-
const needsCrossContext = (
|
|
31
|
-
t: unknown,
|
|
32
|
-
resolveCtx: () => Partial<TrailContext> | TestExecutionOptions | undefined
|
|
33
|
-
): boolean => {
|
|
34
|
-
const spec = t as { crosses?: readonly string[] };
|
|
35
|
-
if (!spec.crosses || spec.crosses.length === 0) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
return !normalizeTestExecutionOptions(resolveCtx()).ctx?.cross;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
29
|
const validateOutputSchema = (
|
|
42
30
|
outputSchema: z.ZodType,
|
|
43
31
|
value: unknown,
|
|
@@ -87,10 +75,6 @@ export const testContracts = (
|
|
|
87
75
|
if (t.examples.length === 0) {
|
|
88
76
|
return;
|
|
89
77
|
}
|
|
90
|
-
if (needsCrossContext(t, resolveInput)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
78
|
const { examples, output: outputSchema } = t;
|
|
95
79
|
const successExamples = examples.filter((e) => e.error === undefined);
|
|
96
80
|
|
|
@@ -111,6 +95,7 @@ export const testContracts = (
|
|
|
111
95
|
const result = await executeTrail(t, example.input, {
|
|
112
96
|
ctx: testCtx,
|
|
113
97
|
resources,
|
|
98
|
+
topo: app,
|
|
114
99
|
});
|
|
115
100
|
const resultValue = expectOk(result);
|
|
116
101
|
|
package/src/examples.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
NetworkError,
|
|
32
32
|
NotFoundError,
|
|
33
33
|
PermissionError,
|
|
34
|
+
PermitError,
|
|
34
35
|
RateLimitError,
|
|
35
36
|
RetryExhaustedError,
|
|
36
37
|
executeTrail,
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
} from './context.js';
|
|
58
59
|
import type { PermittedTrail, TestExecutionOptions } from './context.js';
|
|
59
60
|
import { isDerivedExample, deriveTrailExamples } from './effective-examples.js';
|
|
61
|
+
import { withSignalAssertions } from './signals.js';
|
|
60
62
|
|
|
61
63
|
// ---------------------------------------------------------------------------
|
|
62
64
|
// Error class name -> constructor map
|
|
@@ -74,6 +76,7 @@ const ERROR_MAP: Record<string, new (...args: never[]) => Error> = {
|
|
|
74
76
|
NetworkError: NetworkError as new (...args: never[]) => Error,
|
|
75
77
|
NotFoundError: NotFoundError as new (...args: never[]) => Error,
|
|
76
78
|
PermissionError: PermissionError as new (...args: never[]) => Error,
|
|
79
|
+
PermitError: PermitError as new (...args: never[]) => Error,
|
|
77
80
|
RateLimitError: RateLimitError as new (...args: never[]) => Error,
|
|
78
81
|
RetryExhaustedError: RetryExhaustedError as unknown as new (
|
|
79
82
|
...args: never[]
|
|
@@ -87,7 +90,7 @@ const ERROR_MAP: Record<string, new (...args: never[]) => Error> = {
|
|
|
87
90
|
* Resolve an error class name string to the actual constructor.
|
|
88
91
|
* Falls back to generic Error if the name is not in the core taxonomy.
|
|
89
92
|
*/
|
|
90
|
-
const resolveErrorClass = (name: string):
|
|
93
|
+
const resolveErrorClass = (name: string): new (...args: never[]) => Error =>
|
|
91
94
|
ERROR_MAP[name] ?? (Error as new (...args: never[]) => Error);
|
|
92
95
|
|
|
93
96
|
// ---------------------------------------------------------------------------
|
|
@@ -162,7 +165,7 @@ const applyAutoPermit = (
|
|
|
162
165
|
* Run a single example against a trail.
|
|
163
166
|
* Handles validation, execution, and assertions.
|
|
164
167
|
*/
|
|
165
|
-
const runExample = async (
|
|
168
|
+
export const runExample = async (
|
|
166
169
|
t: Trail<unknown, unknown, unknown>,
|
|
167
170
|
example: TrailExample<unknown, unknown>,
|
|
168
171
|
output: z.ZodType | undefined,
|
|
@@ -170,19 +173,21 @@ const runExample = async (
|
|
|
170
173
|
resources?: ResourceOverrideMap,
|
|
171
174
|
opts?: TestExecutionOptions
|
|
172
175
|
): Promise<void> => {
|
|
176
|
+
const ctx = opts ? applyAutoPermit(testCtx, t, opts) : testCtx;
|
|
177
|
+
const signals = withSignalAssertions(ctx, example);
|
|
173
178
|
const validated = validateInput(t.input, example.input);
|
|
174
179
|
|
|
175
180
|
if (handleValidationError(validated, example)) {
|
|
181
|
+
signals.assert();
|
|
176
182
|
return;
|
|
177
183
|
}
|
|
178
184
|
|
|
179
|
-
const ctx = opts ? applyAutoPermit(testCtx, t, opts) : testCtx;
|
|
180
|
-
|
|
181
185
|
const result = await executeTrail(t, example.input, {
|
|
182
|
-
ctx,
|
|
186
|
+
ctx: signals.ctx,
|
|
183
187
|
resources: resources ?? opts?.resources,
|
|
184
188
|
});
|
|
185
189
|
assertProgressiveMatch(result, example, output);
|
|
190
|
+
signals.assert();
|
|
186
191
|
};
|
|
187
192
|
|
|
188
193
|
// ---------------------------------------------------------------------------
|
|
@@ -267,23 +272,25 @@ const runCompositionExample = async (
|
|
|
267
272
|
resources?: ResourceOverrideMap,
|
|
268
273
|
opts?: TestExecutionOptions
|
|
269
274
|
): Promise<void> => {
|
|
275
|
+
const permittedCtx = opts
|
|
276
|
+
? applyAutoPermit(baseCtx, trailDef, opts)
|
|
277
|
+
: baseCtx;
|
|
278
|
+
const signals = withSignalAssertions(permittedCtx, example);
|
|
270
279
|
const validated = validateInput(trailDef.input, example.input);
|
|
271
280
|
|
|
272
281
|
if (handleValidationError(validated, example)) {
|
|
282
|
+
signals.assert();
|
|
273
283
|
return;
|
|
274
284
|
}
|
|
275
285
|
|
|
276
|
-
const permittedCtx = opts
|
|
277
|
-
? applyAutoPermit(baseCtx, trailDef, opts)
|
|
278
|
-
: baseCtx;
|
|
279
286
|
const cross = createCoverageCross(
|
|
280
287
|
called,
|
|
281
|
-
|
|
288
|
+
signals.ctx.cross,
|
|
282
289
|
topo,
|
|
283
|
-
|
|
290
|
+
signals.ctx,
|
|
284
291
|
resources
|
|
285
292
|
);
|
|
286
|
-
const testCtx: TrailContext = { ...
|
|
293
|
+
const testCtx: TrailContext = { ...signals.ctx, cross };
|
|
287
294
|
|
|
288
295
|
// Top-level trail validates against trail.input (not merged crossInput).
|
|
289
296
|
// Merged validation only applies to cross targets in executeFromMap/createCoverageCross.
|
|
@@ -292,6 +299,7 @@ const runCompositionExample = async (
|
|
|
292
299
|
resources: resources ?? opts?.resources,
|
|
293
300
|
});
|
|
294
301
|
assertProgressiveMatch(result, example, output);
|
|
302
|
+
signals.assert();
|
|
295
303
|
};
|
|
296
304
|
|
|
297
305
|
// ---------------------------------------------------------------------------
|
package/src/harness-cli.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { deriveCliCommands } from '@ontrails/cli';
|
|
9
9
|
import type { CliCommand } from '@ontrails/cli';
|
|
10
10
|
import type { TrailContext } from '@ontrails/core';
|
|
11
|
+
import { projectPublicSurfaceError } from '@ontrails/core';
|
|
11
12
|
|
|
12
13
|
import { mergeTestContext } from './context.js';
|
|
13
14
|
import type {
|
|
@@ -207,10 +208,16 @@ const buildErrorResult = (
|
|
|
207
208
|
streams: CapturedStreams
|
|
208
209
|
): CliHarnessResult => {
|
|
209
210
|
streams.restore();
|
|
210
|
-
const
|
|
211
|
+
const actualError = error instanceof Error ? error : new Error(String(error));
|
|
212
|
+
const projection = projectPublicSurfaceError('cli', actualError);
|
|
211
213
|
return {
|
|
214
|
+
error: {
|
|
215
|
+
category: projection.category,
|
|
216
|
+
code: projection.name,
|
|
217
|
+
message: projection.message,
|
|
218
|
+
},
|
|
212
219
|
exitCode: 1,
|
|
213
|
-
stderr: streams.getStderr() || message,
|
|
220
|
+
stderr: streams.getStderr() || projection.message,
|
|
214
221
|
stdout: streams.getStdout(),
|
|
215
222
|
};
|
|
216
223
|
};
|
|
@@ -227,9 +234,15 @@ const executeCommand = async (
|
|
|
227
234
|
streams.restore();
|
|
228
235
|
|
|
229
236
|
if (result.isErr()) {
|
|
237
|
+
const projection = projectPublicSurfaceError('cli', result.error);
|
|
230
238
|
return {
|
|
239
|
+
error: {
|
|
240
|
+
category: projection.category,
|
|
241
|
+
code: projection.name,
|
|
242
|
+
message: projection.message,
|
|
243
|
+
},
|
|
231
244
|
exitCode: 1,
|
|
232
|
-
stderr: streams.getStderr() ||
|
|
245
|
+
stderr: streams.getStderr() || projection.message,
|
|
233
246
|
stdout: streams.getStdout(),
|
|
234
247
|
};
|
|
235
248
|
}
|