@ontrails/testing 1.0.0-beta.18 → 1.0.0-beta.19
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 +51 -0
- package/README.md +31 -29
- package/package.json +24 -8
- package/src/all-established.ts +168 -0
- package/src/all.ts +3 -130
- package/src/assertions.ts +2 -2
- package/src/cli.ts +6 -0
- package/src/{crosses.ts → composes.ts} +87 -79
- package/src/context.ts +22 -21
- package/src/contracts.ts +19 -17
- package/src/effective-examples.ts +67 -3
- package/src/errors.ts +47 -0
- package/src/examples.ts +131 -118
- package/src/harness-cli.ts +35 -7
- package/src/harness-http.ts +87 -9
- package/src/harness-mcp.ts +30 -6
- package/src/http.ts +10 -0
- package/src/index.ts +5 -34
- package/src/mcp.ts +6 -0
- package/src/scenario.ts +67 -60
- package/src/surface-parity.ts +43 -10
- package/src/types.ts +11 -232
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.19
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 1eb5bdc: Rename first-class trail composition from the `cross` API family to the `compose` family across core contracts, testing helpers, topo projections, Warden rules, CLI scaffolds, and docs. `composes`, `ctx.compose`, `composeInput`, and `Compose*` type names are now the public authoring vocabulary; topo persistence migrates legacy composition rows and graph keys forward.
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- 492f71c: Move CLI, MCP, HTTP, established-surface, and surface-parity helpers behind explicit subpaths so root contract testing imports no longer require optional surface peers. The Trails CLI scaffolder now emits `import { testAllEstablished } from '@ontrails/testing/established'` for generated verification.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- e41c382: Document beta-channel install guidance in package and adapter README install snippets so consumers use explicit `@beta` (or pinned `1.0.0-beta.N`) tags instead of accidental `latest` resolution during the prerelease line. Adds the policy doc at `docs/releases/beta-channel-policy.md`, prints both `latest` and `beta` dist-tags in `bun run publish:registry-check`, and aligns plugin/skill install snippets.
|
|
16
|
+
- f8d80b9: Refresh current-facing compose vocabulary in package documentation after the composition cutover.
|
|
17
|
+
- 337b467: Construct declared Trails error classes when `testComposes` injects composed trail error examples.
|
|
18
|
+
- 16cb740: Run examples and contract checks across live trail version entries, and project version-entry example coverage into topo and survey reports.
|
|
19
|
+
- 5d88104: Polish Trails blaze terminology across package docs and Warden guidance.
|
|
20
|
+
- f04a9ef: Tighten trail-versioning API polish by keeping executor cross-validation internals out of public options and improving absent marker diagnostics.
|
|
21
|
+
- Updated dependencies [e41c382]
|
|
22
|
+
- Updated dependencies [a2f1825]
|
|
23
|
+
- Updated dependencies [a2f1825]
|
|
24
|
+
- Updated dependencies [1eb5bdc]
|
|
25
|
+
- Updated dependencies [f8d80b9]
|
|
26
|
+
- Updated dependencies [94a8380]
|
|
27
|
+
- Updated dependencies [94a8380]
|
|
28
|
+
- Updated dependencies [846a597]
|
|
29
|
+
- Updated dependencies [8638dae]
|
|
30
|
+
- Updated dependencies [8638dae]
|
|
31
|
+
- Updated dependencies [8638dae]
|
|
32
|
+
- Updated dependencies [223aaad]
|
|
33
|
+
- Updated dependencies [3125f4d]
|
|
34
|
+
- Updated dependencies [2494dc6]
|
|
35
|
+
- Updated dependencies [2d53717]
|
|
36
|
+
- Updated dependencies [16cb740]
|
|
37
|
+
- Updated dependencies [8894ecb]
|
|
38
|
+
- Updated dependencies [fdf7ec9]
|
|
39
|
+
- Updated dependencies [92e709b]
|
|
40
|
+
- Updated dependencies [d76be13]
|
|
41
|
+
- Updated dependencies [84f56a5]
|
|
42
|
+
- Updated dependencies [431b04c]
|
|
43
|
+
- Updated dependencies [5d88104]
|
|
44
|
+
- Updated dependencies [f04a9ef]
|
|
45
|
+
- Updated dependencies [fc00aeb]
|
|
46
|
+
- Updated dependencies [ab1c77c]
|
|
47
|
+
- Updated dependencies [8ca5b85]
|
|
48
|
+
- @ontrails/core@1.0.0-beta.19
|
|
49
|
+
- @ontrails/cli@1.0.0-beta.19
|
|
50
|
+
- @ontrails/http@1.0.0-beta.19
|
|
51
|
+
- @ontrails/mcp@1.0.0-beta.19
|
|
52
|
+
- @ontrails/observe@1.0.0-beta.19
|
|
53
|
+
|
|
3
54
|
## 1.0.0-beta.18
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -29,16 +29,12 @@ testDetours(graph); // Validate detour constructor, recover, and ordering sem
|
|
|
29
29
|
| --- | --- |
|
|
30
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
|
-
| `testTrail(trail, scenarios)` | Custom scenarios for edge cases, error paths, and
|
|
32
|
+
| `testTrail(trail, scenarios)` | Custom scenarios for edge cases, error paths, and composition chains |
|
|
33
33
|
| `testContracts(topo, ctx?)` | Validate output against declared schemas |
|
|
34
34
|
| `testDetours(topo)` | Validate detour constructor, recover, and shadowing semantics |
|
|
35
|
-
| `
|
|
36
|
-
| `createCrossContext(options?)` | Mock `CrossFn` for testing composite trails; returns preconfigured `Result` values keyed by trail ID |
|
|
35
|
+
| `createComposeContext(options?)` | Mock `ComposeFn` for testing composite trails; returns preconfigured `Result` values keyed by trail ID |
|
|
37
36
|
| `createTestContext(options?)` | `TrailContext` with sensible test defaults |
|
|
38
37
|
| `createTestLogger()` | Logger that captures entries in memory for assertions |
|
|
39
|
-
| `createCliHarness(options)` | Execute CLI commands in-process, capture stdout/stderr |
|
|
40
|
-
| `createMcpHarness(options)` | Invoke MCP tools directly without transport |
|
|
41
|
-
| `createHttpHarness(options)` | Execute HTTP route projections in-process without a server |
|
|
42
38
|
|
|
43
39
|
See the [API Reference](../../docs/api-reference.md) for the full list.
|
|
44
40
|
|
|
@@ -57,9 +53,9 @@ testTrail(showTrail, [
|
|
|
57
53
|
]);
|
|
58
54
|
```
|
|
59
55
|
|
|
60
|
-
## Testing composition (trails with
|
|
56
|
+
## Testing composition (trails with composes)
|
|
61
57
|
|
|
62
|
-
`testTrail` works the same for trails with `
|
|
58
|
+
`testTrail` works the same for trails with `composes` -- it exercises the composition graph:
|
|
63
59
|
|
|
64
60
|
```typescript
|
|
65
61
|
import { testTrail } from '@ontrails/testing';
|
|
@@ -70,32 +66,43 @@ testTrail(onboardTrail, [
|
|
|
70
66
|
]);
|
|
71
67
|
```
|
|
72
68
|
|
|
73
|
-
When you need to isolate a composite trail and stub out its dependencies, use `
|
|
69
|
+
When you need to isolate a composite trail and stub out its dependencies, use `createComposeContext`:
|
|
74
70
|
|
|
75
71
|
```typescript
|
|
76
|
-
import {
|
|
72
|
+
import { createComposeContext, testTrail } from '@ontrails/testing';
|
|
77
73
|
import { Result } from '@ontrails/core';
|
|
78
74
|
|
|
79
|
-
const
|
|
75
|
+
const compose = createComposeContext({
|
|
80
76
|
responses: {
|
|
81
77
|
'entity.add': Result.ok({ id: '1', name: 'Delta', type: 'tool' }),
|
|
82
78
|
'search': Result.ok({ results: [] }),
|
|
83
79
|
},
|
|
84
80
|
});
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
testTrail(onboardTrail, [
|
|
82
|
+
{
|
|
83
|
+
description: 'uses mocked composes',
|
|
84
|
+
input: { name: 'Delta', type: 'tool' },
|
|
85
|
+
expectOk: true,
|
|
86
|
+
},
|
|
87
|
+
], { compose });
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
Calls to unregistered trail IDs return `Result.err` with a descriptive message, so missing stubs fail loudly.
|
|
90
91
|
|
|
91
92
|
## Surface Harnesses
|
|
92
93
|
|
|
94
|
+
Surface harnesses live on explicit subpaths so projects that only import contract helpers from `@ontrails/testing` do not need CLI, MCP, or HTTP peers. Install the peer package for the subpath you use:
|
|
95
|
+
|
|
96
|
+
- `@ontrails/testing/cli` requires `@ontrails/cli`
|
|
97
|
+
- `@ontrails/testing/mcp` requires `@ontrails/mcp`
|
|
98
|
+
- `@ontrails/testing/http` requires `@ontrails/http`
|
|
99
|
+
- `@ontrails/testing/established` and `@ontrails/testing/surface-parity`
|
|
100
|
+
require all three shipped surface peers
|
|
101
|
+
|
|
93
102
|
```typescript
|
|
94
|
-
import {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
createMcpHarness,
|
|
98
|
-
} from '@ontrails/testing';
|
|
103
|
+
import { createCliHarness } from '@ontrails/testing/cli';
|
|
104
|
+
import { createHttpHarness } from '@ontrails/testing/http';
|
|
105
|
+
import { createMcpHarness } from '@ontrails/testing/mcp';
|
|
99
106
|
|
|
100
107
|
// CLI
|
|
101
108
|
const cli = createCliHarness({ graph });
|
|
@@ -113,13 +120,14 @@ const response = await http.get('/entity/show', { name: 'Alpha' });
|
|
|
113
120
|
expect(response.status).toBe(200);
|
|
114
121
|
```
|
|
115
122
|
|
|
123
|
+
Use `testAllEstablished()` from `@ontrails/testing/established` when an established app should run the root contract suite and validate CLI, MCP, and HTTP projections in one call.
|
|
124
|
+
|
|
116
125
|
## Surface Parity
|
|
117
126
|
|
|
118
|
-
`testSurfaceParity()` is a focused gate for established apps that want to prove
|
|
119
|
-
their examples behave the same through every shipped surface.
|
|
127
|
+
`testSurfaceParity()` is a focused gate for established apps that want to prove their examples behave the same through every shipped surface.
|
|
120
128
|
|
|
121
129
|
```typescript
|
|
122
|
-
import { testSurfaceParity } from '@ontrails/testing';
|
|
130
|
+
import { testSurfaceParity } from '@ontrails/testing/surface-parity';
|
|
123
131
|
import { graph } from '../app';
|
|
124
132
|
|
|
125
133
|
const createDeterministicTestDb = () => ({});
|
|
@@ -138,16 +146,10 @@ testSurfaceParity(graph, {
|
|
|
138
146
|
});
|
|
139
147
|
```
|
|
140
148
|
|
|
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.
|
|
149
|
+
The helper compares normalized success payloads and normalized TrailsError category/code pairs. CLI command names, MCP tool names, HTTP method/path values, transport envelopes, activation consumers, internal trails, and planned WebSocket work stay outside the equality check. Use `createResources` when examples need deterministic fixtures for each surface invocation, and use exclusions for intentional semantic differences that should not be silently skipped.
|
|
148
150
|
|
|
149
151
|
## Installation
|
|
150
152
|
|
|
151
153
|
```bash
|
|
152
|
-
bun add -d @ontrails/testing
|
|
154
|
+
bun add -d @ontrails/testing@beta
|
|
153
155
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.19",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*.ts",
|
|
6
6
|
"!src/**/__tests__/**",
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
14
|
".": "./src/index.ts",
|
|
15
|
+
"./cli": "./src/cli.ts",
|
|
16
|
+
"./established": "./src/all-established.ts",
|
|
17
|
+
"./http": "./src/http.ts",
|
|
18
|
+
"./mcp": "./src/mcp.ts",
|
|
19
|
+
"./surface-parity": "./src/surface-parity.ts",
|
|
15
20
|
"./package.json": "./package.json"
|
|
16
21
|
},
|
|
17
22
|
"scripts": {
|
|
@@ -22,15 +27,26 @@
|
|
|
22
27
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
23
28
|
},
|
|
24
29
|
"devDependencies": {
|
|
25
|
-
"@ontrails/drizzle": "^1.0.0-beta.
|
|
26
|
-
"@ontrails/store": "^1.0.0-beta.
|
|
30
|
+
"@ontrails/drizzle": "^1.0.0-beta.19",
|
|
31
|
+
"@ontrails/store": "^1.0.0-beta.19"
|
|
27
32
|
},
|
|
28
33
|
"peerDependencies": {
|
|
29
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
30
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
31
|
-
"@ontrails/http": "^1.0.0-beta.
|
|
32
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
33
|
-
"@ontrails/observe": "^1.0.0-beta.
|
|
34
|
+
"@ontrails/cli": "^1.0.0-beta.19",
|
|
35
|
+
"@ontrails/core": "^1.0.0-beta.19",
|
|
36
|
+
"@ontrails/http": "^1.0.0-beta.19",
|
|
37
|
+
"@ontrails/mcp": "^1.0.0-beta.19",
|
|
38
|
+
"@ontrails/observe": "^1.0.0-beta.19",
|
|
34
39
|
"zod": "^4.3.5"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@ontrails/cli": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@ontrails/http": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@ontrails/mcp": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
35
51
|
}
|
|
36
52
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* testAllEstablished - contract suite plus shipped surface projection checks.
|
|
3
|
+
*
|
|
4
|
+
* This helper intentionally lives behind a surface subpath so root
|
|
5
|
+
* `@ontrails/testing` imports do not pull CLI, MCP, or HTTP peers into
|
|
6
|
+
* contract-only consumers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from 'bun:test';
|
|
10
|
+
|
|
11
|
+
import type { Topo, TrailContext } from '@ontrails/core';
|
|
12
|
+
import { validateEstablishedTopo } from '@ontrails/core';
|
|
13
|
+
|
|
14
|
+
import { registerContractSuite } from './all.js';
|
|
15
|
+
import type { TestExecutionOptions } from './context.js';
|
|
16
|
+
import { createCliHarness } from './harness-cli.js';
|
|
17
|
+
import type { CliHarnessOptions } from './harness-cli.js';
|
|
18
|
+
import { createHttpHarness } from './harness-http.js';
|
|
19
|
+
import type { HttpHarnessOptions } from './harness-http.js';
|
|
20
|
+
import { createMcpHarness } from './harness-mcp.js';
|
|
21
|
+
import type { McpHarnessOptions } from './harness-mcp.js';
|
|
22
|
+
|
|
23
|
+
export interface TestAllEstablishedOptions {
|
|
24
|
+
readonly cli?: Omit<CliHarnessOptions, 'graph'> | undefined;
|
|
25
|
+
readonly createPermit?:
|
|
26
|
+
| ((trail: {
|
|
27
|
+
readonly permit?:
|
|
28
|
+
| { readonly scopes: readonly string[] }
|
|
29
|
+
| 'public'
|
|
30
|
+
| undefined;
|
|
31
|
+
}) =>
|
|
32
|
+
| {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly scopes: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
| undefined)
|
|
37
|
+
| undefined;
|
|
38
|
+
readonly ctx?: Partial<TrailContext> | undefined;
|
|
39
|
+
readonly http?: Omit<HttpHarnessOptions, 'graph'> | undefined;
|
|
40
|
+
readonly mcp?: Omit<McpHarnessOptions, 'graph'> | undefined;
|
|
41
|
+
readonly resources?: Record<string, unknown> | undefined;
|
|
42
|
+
readonly strictPermits?: boolean | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type EstablishedInput =
|
|
46
|
+
| Partial<TrailContext>
|
|
47
|
+
| TestAllEstablishedOptions
|
|
48
|
+
| (() => Partial<TrailContext> | TestAllEstablishedOptions);
|
|
49
|
+
|
|
50
|
+
const isEstablishedOptions = (
|
|
51
|
+
input: Partial<TrailContext> | TestAllEstablishedOptions | undefined
|
|
52
|
+
): input is TestAllEstablishedOptions =>
|
|
53
|
+
input !== undefined &&
|
|
54
|
+
(Object.hasOwn(input, 'cli') ||
|
|
55
|
+
Object.hasOwn(input, 'createPermit') ||
|
|
56
|
+
Object.hasOwn(input, 'ctx') ||
|
|
57
|
+
Object.hasOwn(input, 'http') ||
|
|
58
|
+
Object.hasOwn(input, 'mcp') ||
|
|
59
|
+
Object.hasOwn(input, 'resources') ||
|
|
60
|
+
Object.hasOwn(input, 'strictPermits'));
|
|
61
|
+
|
|
62
|
+
const normalizeEstablishedOptions = (
|
|
63
|
+
input?: Partial<TrailContext> | TestAllEstablishedOptions
|
|
64
|
+
): TestAllEstablishedOptions =>
|
|
65
|
+
isEstablishedOptions(input) ? input : { ctx: input };
|
|
66
|
+
|
|
67
|
+
const toExecutionOptions = (
|
|
68
|
+
options: TestAllEstablishedOptions
|
|
69
|
+
): TestExecutionOptions => ({
|
|
70
|
+
...(options.createPermit === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { createPermit: options.createPermit }),
|
|
73
|
+
...(options.ctx === undefined ? {} : { ctx: options.ctx }),
|
|
74
|
+
...(options.resources === undefined ? {} : { resources: options.resources }),
|
|
75
|
+
...(options.strictPermits === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { strictPermits: options.strictPermits }),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const toCliHarnessOptions = (
|
|
81
|
+
topo: Topo,
|
|
82
|
+
options: TestAllEstablishedOptions
|
|
83
|
+
) => {
|
|
84
|
+
const cliOptions = {
|
|
85
|
+
graph: topo,
|
|
86
|
+
...options.cli,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (options.ctx !== undefined) {
|
|
90
|
+
cliOptions.ctx = options.ctx;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return cliOptions;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const toMcpHarnessOptions = (
|
|
97
|
+
topo: Topo,
|
|
98
|
+
options: TestAllEstablishedOptions
|
|
99
|
+
) => ({
|
|
100
|
+
graph: topo,
|
|
101
|
+
...options.mcp,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const toHttpHarnessOptions = (
|
|
105
|
+
topo: Topo,
|
|
106
|
+
options: TestAllEstablishedOptions
|
|
107
|
+
) => {
|
|
108
|
+
const httpOptions = {
|
|
109
|
+
graph: topo,
|
|
110
|
+
...options.http,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (options.ctx !== undefined) {
|
|
114
|
+
httpOptions.ctx = options.ctx;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return httpOptions;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const registerEstablishedSurfaceSuite = (
|
|
121
|
+
topo: Topo,
|
|
122
|
+
resolveInput: () =>
|
|
123
|
+
| Partial<TrailContext>
|
|
124
|
+
| TestAllEstablishedOptions
|
|
125
|
+
| undefined
|
|
126
|
+
): void => {
|
|
127
|
+
describe('surfaces', () => {
|
|
128
|
+
test('CLI projection validates established topo', () => {
|
|
129
|
+
const options = normalizeEstablishedOptions(resolveInput());
|
|
130
|
+
expect(() =>
|
|
131
|
+
createCliHarness(toCliHarnessOptions(topo, options))
|
|
132
|
+
).not.toThrow();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('MCP projection validates established topo', () => {
|
|
136
|
+
const options = normalizeEstablishedOptions(resolveInput());
|
|
137
|
+
expect(() =>
|
|
138
|
+
createMcpHarness(toMcpHarnessOptions(topo, options))
|
|
139
|
+
).not.toThrow();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('HTTP projection validates established topo', () => {
|
|
143
|
+
const options = normalizeEstablishedOptions(resolveInput());
|
|
144
|
+
expect(() =>
|
|
145
|
+
createHttpHarness(toHttpHarnessOptions(topo, options))
|
|
146
|
+
).not.toThrow();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const testAllEstablished = (
|
|
152
|
+
topo: Topo,
|
|
153
|
+
optionsOrFactory?: EstablishedInput
|
|
154
|
+
): void => {
|
|
155
|
+
const resolveInput =
|
|
156
|
+
typeof optionsOrFactory === 'function'
|
|
157
|
+
? optionsOrFactory
|
|
158
|
+
: () => optionsOrFactory;
|
|
159
|
+
|
|
160
|
+
registerContractSuite(
|
|
161
|
+
topo,
|
|
162
|
+
() => toExecutionOptions(normalizeEstablishedOptions(resolveInput())),
|
|
163
|
+
validateEstablishedTopo
|
|
164
|
+
);
|
|
165
|
+
registerEstablishedSurfaceSuite(topo, resolveInput);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export type { TestAllInput } from './all.js';
|
package/src/all.ts
CHANGED
|
@@ -8,16 +8,12 @@
|
|
|
8
8
|
import { describe, expect, test } from 'bun:test';
|
|
9
9
|
|
|
10
10
|
import type { Topo, TrailContext } from '@ontrails/core';
|
|
11
|
-
import {
|
|
11
|
+
import { validateTopo } from '@ontrails/core';
|
|
12
12
|
|
|
13
|
-
import { createCliHarness } from './harness-cli.js';
|
|
14
|
-
import { createHttpHarness } from './harness-http.js';
|
|
15
|
-
import { createMcpHarness } from './harness-mcp.js';
|
|
16
13
|
import { testContracts } from './contracts.js';
|
|
17
14
|
import type { TestExecutionOptions } from './context.js';
|
|
18
15
|
import { testDetours } from './detours.js';
|
|
19
16
|
import { testExamples } from './examples.js';
|
|
20
|
-
import type { TestAllEstablishedOptions } from './types.js';
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
19
|
* Run the full contract test suite for a Topo.
|
|
@@ -40,7 +36,7 @@ import type { TestAllEstablishedOptions } from './types.js';
|
|
|
40
36
|
* testAll(graph);
|
|
41
37
|
* ```
|
|
42
38
|
*/
|
|
43
|
-
type TestAllInput =
|
|
39
|
+
export type TestAllInput =
|
|
44
40
|
| Partial<TrailContext>
|
|
45
41
|
| TestExecutionOptions
|
|
46
42
|
| (() => Partial<TrailContext> | TestExecutionOptions);
|
|
@@ -74,7 +70,7 @@ const assertValidTopo = (result: ReturnType<typeof validateTopo>): void => {
|
|
|
74
70
|
}
|
|
75
71
|
};
|
|
76
72
|
|
|
77
|
-
const registerContractSuite = (
|
|
73
|
+
export const registerContractSuite = (
|
|
78
74
|
topo: Topo,
|
|
79
75
|
ctxOrFactory: TestAllInput | undefined,
|
|
80
76
|
validate: (topo: Topo) => ReturnType<typeof validateTopo>
|
|
@@ -96,126 +92,3 @@ const registerContractSuite = (
|
|
|
96
92
|
export const testAll = (topo: Topo, ctxOrFactory?: TestAllInput): void => {
|
|
97
93
|
registerContractSuite(topo, ctxOrFactory, validateTopo);
|
|
98
94
|
};
|
|
99
|
-
|
|
100
|
-
type EstablishedInput =
|
|
101
|
-
| Partial<TrailContext>
|
|
102
|
-
| TestAllEstablishedOptions
|
|
103
|
-
| (() => Partial<TrailContext> | TestAllEstablishedOptions);
|
|
104
|
-
|
|
105
|
-
const isEstablishedOptions = (
|
|
106
|
-
input: Partial<TrailContext> | TestAllEstablishedOptions | undefined
|
|
107
|
-
): input is TestAllEstablishedOptions =>
|
|
108
|
-
input !== undefined &&
|
|
109
|
-
(Object.hasOwn(input, 'cli') ||
|
|
110
|
-
Object.hasOwn(input, 'createPermit') ||
|
|
111
|
-
Object.hasOwn(input, 'ctx') ||
|
|
112
|
-
Object.hasOwn(input, 'http') ||
|
|
113
|
-
Object.hasOwn(input, 'mcp') ||
|
|
114
|
-
Object.hasOwn(input, 'resources') ||
|
|
115
|
-
Object.hasOwn(input, 'strictPermits'));
|
|
116
|
-
|
|
117
|
-
const normalizeEstablishedOptions = (
|
|
118
|
-
input?: Partial<TrailContext> | TestAllEstablishedOptions
|
|
119
|
-
): TestAllEstablishedOptions =>
|
|
120
|
-
isEstablishedOptions(input) ? input : { ctx: input };
|
|
121
|
-
|
|
122
|
-
const toExecutionOptions = (
|
|
123
|
-
options: TestAllEstablishedOptions
|
|
124
|
-
): TestExecutionOptions => ({
|
|
125
|
-
...(options.createPermit === undefined
|
|
126
|
-
? {}
|
|
127
|
-
: { createPermit: options.createPermit }),
|
|
128
|
-
...(options.ctx === undefined ? {} : { ctx: options.ctx }),
|
|
129
|
-
...(options.resources === undefined ? {} : { resources: options.resources }),
|
|
130
|
-
...(options.strictPermits === undefined
|
|
131
|
-
? {}
|
|
132
|
-
: { strictPermits: options.strictPermits }),
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const toCliHarnessOptions = (
|
|
136
|
-
topo: Topo,
|
|
137
|
-
options: TestAllEstablishedOptions
|
|
138
|
-
) => {
|
|
139
|
-
const cliOptions = {
|
|
140
|
-
graph: topo,
|
|
141
|
-
...options.cli,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
if (options.ctx !== undefined) {
|
|
145
|
-
cliOptions.ctx = options.ctx;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return cliOptions;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const toMcpHarnessOptions = (
|
|
152
|
-
topo: Topo,
|
|
153
|
-
options: TestAllEstablishedOptions
|
|
154
|
-
) => ({
|
|
155
|
-
graph: topo,
|
|
156
|
-
...options.mcp,
|
|
157
|
-
});
|
|
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
|
-
|
|
175
|
-
const registerEstablishedSurfaceSuite = (
|
|
176
|
-
topo: Topo,
|
|
177
|
-
resolveInput: () =>
|
|
178
|
-
| Partial<TrailContext>
|
|
179
|
-
| TestAllEstablishedOptions
|
|
180
|
-
| undefined
|
|
181
|
-
): void => {
|
|
182
|
-
describe('surfaces', () => {
|
|
183
|
-
test('CLI projection validates established topo', () => {
|
|
184
|
-
const options = normalizeEstablishedOptions(resolveInput());
|
|
185
|
-
expect(() =>
|
|
186
|
-
createCliHarness(toCliHarnessOptions(topo, options))
|
|
187
|
-
).not.toThrow();
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('MCP projection validates established topo', () => {
|
|
191
|
-
const options = normalizeEstablishedOptions(resolveInput());
|
|
192
|
-
expect(() =>
|
|
193
|
-
createMcpHarness(toMcpHarnessOptions(topo, options))
|
|
194
|
-
).not.toThrow();
|
|
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
|
-
});
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
export const testAllEstablished = (
|
|
207
|
-
topo: Topo,
|
|
208
|
-
optionsOrFactory?: EstablishedInput
|
|
209
|
-
): void => {
|
|
210
|
-
const resolveInput =
|
|
211
|
-
typeof optionsOrFactory === 'function'
|
|
212
|
-
? optionsOrFactory
|
|
213
|
-
: () => optionsOrFactory;
|
|
214
|
-
|
|
215
|
-
registerContractSuite(
|
|
216
|
-
topo,
|
|
217
|
-
() => toExecutionOptions(normalizeEstablishedOptions(resolveInput())),
|
|
218
|
-
validateEstablishedTopo
|
|
219
|
-
);
|
|
220
|
-
registerEstablishedSurfaceSuite(topo, resolveInput);
|
|
221
|
-
};
|
package/src/assertions.ts
CHANGED
|
@@ -67,7 +67,7 @@ type ResultMatchToken = OkResultMatch | ErrResultMatch;
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Create a partial-match token for `Result.ok(...)` values nested inside
|
|
70
|
-
* arrays or objects, such as the `Result[]` returned by batch `ctx.
|
|
70
|
+
* arrays or objects, such as the `Result[]` returned by batch `ctx.compose()`.
|
|
71
71
|
*/
|
|
72
72
|
export const okResultMatch = (value?: unknown): OkResultMatch => ({
|
|
73
73
|
__resultMatch: 'ok',
|
|
@@ -76,7 +76,7 @@ export const okResultMatch = (value?: unknown): OkResultMatch => ({
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Create a partial-match token for `Result.err(...)` values nested inside
|
|
79
|
-
* arrays or objects, such as mixed-success batch `ctx.
|
|
79
|
+
* arrays or objects, such as mixed-success batch `ctx.compose()`.
|
|
80
80
|
*/
|
|
81
81
|
export const errResultMatch = (error?: unknown): ErrResultMatch => ({
|
|
82
82
|
__resultMatch: 'err',
|