@ontrails/testing 1.0.0-beta.0 → 1.0.0-beta.10

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 (49) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +151 -0
  3. package/README.md +56 -168
  4. package/dist/context.d.ts +20 -1
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +25 -0
  7. package/dist/context.js.map +1 -1
  8. package/dist/contracts.d.ts +1 -1
  9. package/dist/contracts.d.ts.map +1 -1
  10. package/dist/contracts.js +15 -5
  11. package/dist/contracts.js.map +1 -1
  12. package/dist/examples.d.ts +2 -2
  13. package/dist/examples.d.ts.map +1 -1
  14. package/dist/examples.js +43 -55
  15. package/dist/examples.js.map +1 -1
  16. package/dist/follows.d.ts +32 -0
  17. package/dist/follows.d.ts.map +1 -0
  18. package/dist/{hike.js → follows.js} +15 -15
  19. package/dist/follows.js.map +1 -0
  20. package/dist/harness-mcp.d.ts.map +1 -1
  21. package/dist/harness-mcp.js +5 -2
  22. package/dist/harness-mcp.js.map +1 -1
  23. package/dist/index.d.ts +5 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/trail.js +1 -1
  28. package/dist/trail.js.map +1 -1
  29. package/dist/types.d.ts +2 -2
  30. package/dist/types.d.ts.map +1 -1
  31. package/package.json +6 -6
  32. package/src/__tests__/context.test.ts +36 -1
  33. package/src/__tests__/contracts.test.ts +30 -4
  34. package/src/__tests__/detours.test.ts +3 -3
  35. package/src/__tests__/examples.test.ts +21 -22
  36. package/src/__tests__/{hike.test.ts → follows.test.ts} +27 -28
  37. package/src/__tests__/trail.test.ts +4 -4
  38. package/src/context.ts +42 -1
  39. package/src/contracts.ts +19 -6
  40. package/src/examples.ts +55 -91
  41. package/src/{hike.ts → follows.ts} +30 -30
  42. package/src/harness-mcp.ts +5 -2
  43. package/src/index.ts +5 -4
  44. package/src/trail.ts +1 -1
  45. package/src/types.ts +3 -3
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/dist/hike.d.ts +0 -32
  48. package/dist/hike.d.ts.map +0 -1
  49. package/dist/hike.js.map +0 -1
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 109ms on 20 files with 93 rules using 24 threads.
3
+ Finished in 75ms on 20 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,156 @@
1
1
  # @ontrails/testing
2
2
 
3
+ ## 1.0.0-beta.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @ontrails/core@1.0.0-beta.10
9
+ - @ontrails/cli@1.0.0-beta.10
10
+ - @ontrails/mcp@1.0.0-beta.10
11
+ - @ontrails/logging@1.0.0-beta.10
12
+
13
+ ## 1.0.0-beta.9
14
+
15
+ ### Minor Changes
16
+
17
+ - Consolidated improvements across all surface packages.
18
+
19
+ **core**: Add `TrailResult<T>` utility type, `topo.ids()` and `topo.count` accessors, `dispatch()` for headless trail execution, and extract shared `executeTrail` pipeline used by CLI/MCP/HTTP.
20
+
21
+ **http**: Detect route path collisions and return `Result` from `buildHttpRoutes()`, wire request `AbortSignal` through to trail context, and make write → POST mapping explicit in intent-to-method lookup.
22
+
23
+ **mcp**: Return `Result` from `buildMcpTools()` on collision instead of throwing.
24
+
25
+ **cli**: Verify exception catching via centralized `executeTrail`.
26
+
27
+ **testing**: Follow context awareness improvements.
28
+
29
+ **warden**: Refactor rules as composable trails with examples.
30
+
31
+ **schema**: Error code and empty body fixes.
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies
36
+ - @ontrails/core@1.0.0-beta.9
37
+ - @ontrails/cli@1.0.0-beta.9
38
+ - @ontrails/mcp@1.0.0-beta.9
39
+ - @ontrails/logging@1.0.0-beta.9
40
+
41
+ ## 1.0.0-beta.8
42
+
43
+ ### Patch Changes
44
+
45
+ - @ontrails/cli@1.0.0-beta.8
46
+ - @ontrails/core@1.0.0-beta.8
47
+ - @ontrails/logging@1.0.0-beta.8
48
+ - @ontrails/mcp@1.0.0-beta.8
49
+
50
+ ## 1.0.0-beta.7
51
+
52
+ ### Patch Changes
53
+
54
+ - @ontrails/cli@1.0.0-beta.7
55
+ - @ontrails/core@1.0.0-beta.7
56
+ - @ontrails/logging@1.0.0-beta.7
57
+ - @ontrails/mcp@1.0.0-beta.7
58
+
59
+ ## 1.0.0-beta.6
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies
64
+ - @ontrails/core@1.0.0-beta.6
65
+ - @ontrails/cli@1.0.0-beta.6
66
+ - @ontrails/logging@1.0.0-beta.6
67
+ - @ontrails/mcp@1.0.0-beta.6
68
+
69
+ ## 1.0.0-beta.5
70
+
71
+ ### Patch Changes
72
+
73
+ - Updated dependencies
74
+ - @ontrails/core@1.0.0-beta.5
75
+ - @ontrails/logging@1.0.0-beta.5
76
+ - @ontrails/cli@1.0.0-beta.5
77
+ - @ontrails/mcp@1.0.0-beta.5
78
+
79
+ ## 1.0.0-beta.4
80
+
81
+ ### Major Changes
82
+
83
+ - API simplification: unified trail model, intent enum, run, metadata.
84
+
85
+ **BREAKING CHANGES:**
86
+
87
+ - `hike()` removed — use `trail()` with optional `follow: [...]` field
88
+ - `follows` renamed to `follow` (singular, matching `ctx.follow()`)
89
+ - `topo.hikes` removed — single `topo.trails` map
90
+ - `kind: 'hike'` removed — everything is `kind: 'trail'`
91
+ - `readOnly`/`destructive` booleans replaced by `intent: 'read' | 'write' | 'destroy'`
92
+ - `implementation` field renamed to `run`
93
+ - `markers` field renamed to `metadata`
94
+ - `testHike` renamed to `testFollows`, `HikeScenario` to `FollowScenario`
95
+ - `blaze()` now returns the surface handle (`Command` for CLI, `Server` for MCP)
96
+
97
+ ### Patch Changes
98
+
99
+ - Updated dependencies
100
+ - @ontrails/core@1.0.0-beta.4
101
+ - @ontrails/cli@1.0.0-beta.4
102
+ - @ontrails/mcp@1.0.0-beta.4
103
+ - @ontrails/logging@1.0.0-beta.4
104
+
105
+ ## 1.0.0-beta.3
106
+
107
+ ### Minor Changes
108
+
109
+ - Bug fixes across all surface packages found via parallel Codex review.
110
+
111
+ **core**: Fix Result.toJson false circular detection on DAGs, deserializeError subclass round-trip, topo cross-kind ID collisions, validateTopo multi-node cycle detection, error example input validation bypass, and deriveFields array type collapse.
112
+
113
+ **cli**: Switch blaze to parseAsync for proper async error handling, add boolean flag negation (--no-flag), and strict number parsing that rejects partial input.
114
+
115
+ **mcp**: Align BlobRef with core (including ReadableStream support) and detect tool-name collisions after normalization.
116
+
117
+ **testing**: Include hikes in testContracts validation, with follow-context awareness.
118
+
119
+ **warden**: Collect hike detour targets, validate detour refs in hike specs, and stop implementation-returns-result from walking into nested function bodies.
120
+
121
+ ### Patch Changes
122
+
123
+ - Updated dependencies
124
+ - @ontrails/core@1.0.0-beta.3
125
+ - @ontrails/cli@1.0.0-beta.3
126
+ - @ontrails/mcp@1.0.0-beta.3
127
+ - @ontrails/logging@1.0.0-beta.3
128
+
129
+ ## 1.0.0-beta.2
130
+
131
+ ### Patch Changes
132
+
133
+ - Fix workspace dependency resolution in published packages. Now using bun publish
134
+ which correctly replaces workspace:^ with actual version numbers.
135
+ - Updated dependencies
136
+ - @ontrails/core@1.0.0-beta.2
137
+ - @ontrails/cli@1.0.0-beta.2
138
+ - @ontrails/mcp@1.0.0-beta.2
139
+ - @ontrails/logging@1.0.0-beta.2
140
+
141
+ ## 1.0.0-beta.1
142
+
143
+ ### Patch Changes
144
+
145
+ - Fix two blocking bugs from real-world migration:
146
+ - Published packages now resolve correctly (workspace:^ instead of workspace:\*)
147
+ - Error forwarding works across different success types (Err no longer carries phantom T)
148
+ - Updated dependencies
149
+ - @ontrails/core@1.0.0-beta.1
150
+ - @ontrails/cli@1.0.0-beta.1
151
+ - @ontrails/mcp@1.0.0-beta.1
152
+ - @ontrails/logging@1.0.0-beta.1
153
+
3
154
  ## 1.0.0-beta.0
4
155
 
5
156
  ### Minor Changes
package/README.md CHANGED
@@ -1,40 +1,8 @@
1
1
  # @ontrails/testing
2
2
 
3
- Contract-driven testing utilities for Trails. Write examples for agent fluency -- get test cases for free.
3
+ Contract-driven testing for Trails. Add examples to your trails, then `testAll(app)` runs them as assertions, validates output schemas, checks composition graphs, and verifies structural integrity. One line of test code, full governance.
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- bun add -d @ontrails/testing
9
- ```
10
-
11
- Peer dependencies: `@ontrails/core`, `@ontrails/cli`, `@ontrails/mcp`, `@ontrails/logging`, `zod`.
12
-
13
- ## Quick Start
14
-
15
- ```typescript
16
- import { testExamples } from '@ontrails/testing';
17
- import { app } from '../app';
18
-
19
- testExamples(app);
20
- ```
21
-
22
- One line. The entire topo is tested. Every trail, every example: input validation, implementation execution, output verification.
23
-
24
- ```text
25
- PASS src/__tests__/app.test.ts
26
- greet
27
- example: Basic greeting
28
- example: Loud greeting
29
- entity.show
30
- example: Show entity by name
31
- ```
32
-
33
- ## API Overview
34
-
35
- ### `testAll(topo, ctx?)`
36
-
37
- Single-line governance suite. Wraps topo validation, example execution, contract checks, and detour verification into one `governance` describe block:
5
+ ## Usage
38
6
 
39
7
  ```typescript
40
8
  import { testAll } from '@ontrails/testing';
@@ -43,26 +11,38 @@ import { app } from '../app';
43
11
  testAll(app);
44
12
  ```
45
13
 
46
- For most apps, `testAll` is the only test call you need.
14
+ That single call covers example execution, contract validation, detour checks, and topo validation. For most apps, this is all you need.
47
15
 
48
- ### `testExamples(app, ctx?)`
16
+ If you want finer control:
49
17
 
50
- For each trail with `examples`, generates `describe`/`test` blocks using the Bun test runner.
18
+ ```typescript
19
+ import { testExamples, testContracts, testDetours } from '@ontrails/testing';
51
20
 
52
- Per example:
21
+ testExamples(app); // Run every trail's examples as tests
22
+ testContracts(app); // Validate outputs against declared schemas
23
+ testDetours(app); // Verify detour targets exist
24
+ ```
53
25
 
54
- 1. Validates `example.input` against the trail's Zod schema
55
- 2. Calls the implementation with validated input
56
- 3. Applies progressive assertion (see below)
57
- 4. Validates output against the trail's output schema (if present)
26
+ ## API
58
27
 
59
- Trails with no examples produce no tests.
28
+ | Export | What it does |
29
+ | --- | --- |
30
+ | `testAll(topo, ctx?)` | Single-line governance suite: validation + examples + contracts + detours |
31
+ | `testExamples(topo, ctx?)` | Run trail examples as `describe`/`test` blocks |
32
+ | `testTrail(trail, scenarios)` | Custom scenarios for edge cases, error paths, and follow chains |
33
+ | `testContracts(topo, ctx?)` | Validate output against declared schemas |
34
+ | `testDetours(topo)` | Verify every detour target exists in the topo |
35
+ | `createFollowContext(options?)` | Mock `FollowFn` for testing composite trails; returns preconfigured `Result` values keyed by trail ID |
36
+ | `createTestContext(options?)` | `TrailContext` with sensible test defaults |
37
+ | `createTestLogger()` | Logger that captures entries in memory for assertions |
38
+ | `createCliHarness(options)` | Execute CLI commands in-process, capture stdout/stderr |
39
+ | `createMcpHarness(options)` | Invoke MCP tools directly without transport |
60
40
 
61
- The runtime implementation is always awaited, so `testExamples()` behaves the same for sync-authored and async-authored trails.
41
+ See the [API Reference](../../docs/api-reference.md) for the full list.
62
42
 
63
- ### `testTrail(trail, scenarios, ctx?)`
43
+ ## testTrail
64
44
 
65
- Custom scenarios for edge cases, boundary values, and regressions that do not belong in agent-facing examples:
45
+ For edge cases that do not belong in agent-facing examples:
66
46
 
67
47
  ```typescript
68
48
  import { testTrail } from '@ontrails/testing';
@@ -71,151 +51,59 @@ import { ValidationError, NotFoundError } from '@ontrails/core';
71
51
  testTrail(showTrail, [
72
52
  { description: 'empty name', input: { name: '' }, expectOk: true },
73
53
  { description: 'missing name', input: {}, expectErr: ValidationError },
74
- {
75
- description: 'exact match',
76
- input: { name: 'Alpha' },
77
- expectValue: { name: 'Alpha', type: 'concept' },
78
- },
79
- {
80
- description: 'not found',
81
- input: { name: 'missing' },
82
- expectErr: NotFoundError,
83
- expectErrMessage: 'not found',
84
- },
54
+ { description: 'not found', input: { name: 'missing' }, expectErr: NotFoundError },
85
55
  ]);
86
56
  ```
87
57
 
88
- ### `testHike(hike, scenarios, ctx?)`
58
+ ## Testing composition (trails with follow)
89
59
 
90
- Tests a hike's composition graph -- follow chains, failure injection, and multi-trail interactions:
60
+ `testTrail` works the same for trails with `follow` -- it exercises the follow graph:
91
61
 
92
62
  ```typescript
93
- import { testHike } from '@ontrails/testing';
63
+ import { testTrail } from '@ontrails/testing';
94
64
 
95
- testHike(onboardHike, [
96
- { description: 'successful onboard', input: { name: 'Delta', type: 'tool' }, expectOk: true },
97
- { description: 'fails when add fails', input: { name: 'Alpha' }, expectErr: AlreadyExistsError },
65
+ testTrail(onboardTrail, [
66
+ { description: 'happy path', input: { name: 'Delta', type: 'tool' }, expectOk: true },
67
+ { description: 'add fails', input: { name: 'Alpha' }, expectErr: AlreadyExistsError },
98
68
  ]);
99
69
  ```
100
70
 
101
- Where `testTrail` exercises a single trail in isolation, `testHike` exercises the follow graph and verifies that upstream failures propagate correctly.
102
-
103
- ### `testContracts(app, ctx?)`
104
-
105
- Catches implementation-schema drift. Runs every example through the implementation, then validates the result against the trail's `output` schema. Reports detailed Zod errors on mismatch.
71
+ When you need to isolate a composite trail and stub out its dependencies, use `createFollowContext`:
106
72
 
107
73
  ```typescript
108
- import { testContracts } from '@ontrails/testing';
109
-
110
- testContracts(app);
111
- // Fails if any implementation returns data that doesn't match its declared output schema
112
- ```
113
-
114
- ### `testDetours(app)`
115
-
116
- Structural validation of detour declarations. Verifies every detour target trail exists in the topo. No implementation execution needed.
117
-
118
- ```typescript
119
- import { testDetours } from '@ontrails/testing';
120
-
121
- testDetours(app);
122
- // Fails: Trail "entity.show" has detour target "entity.search" which does not exist in the topo
123
- ```
124
-
125
- ### Progressive Assertion
74
+ import { createFollowContext, createTestContext } from '@ontrails/testing';
75
+ import { Result } from '@ontrails/core';
126
76
 
127
- What `testExamples` checks depends on what the example declares:
128
-
129
- **Full match** -- example has `expected`:
130
-
131
- ```typescript
132
- {
133
- name: 'Found',
134
- input: { name: 'Alpha' },
135
- expected: { name: 'Alpha', type: 'concept' },
136
- }
137
- ```
138
-
139
- Asserts `result.isOk()` and `result.value` deep-equals `expected`.
140
-
141
- **Schema-only match** -- example has no `expected` and no `error`:
142
-
143
- ```typescript
144
- { name: 'Returns something valid', input: { name: 'Alpha' } }
145
- ```
146
-
147
- Asserts `result.isOk()` and validates against the trail's output schema.
148
-
149
- **Error match** -- example has `error`:
150
-
151
- ```typescript
152
- { name: 'Not found', input: { name: 'missing' }, error: 'NotFoundError' }
153
- ```
154
-
155
- Asserts `result.isErr()` and `instanceof` check.
156
-
157
- ### Test Context and Mocks
158
-
159
- ```typescript
160
- import { createTestContext, createTestLogger } from '@ontrails/testing';
161
-
162
- // TrailContext with sensible test defaults
163
- const ctx = createTestContext({
164
- requestId: 'test-001',
165
- env: { TRAILS_ENV: 'test' },
77
+ const follow = createFollowContext({
78
+ responses: {
79
+ 'entity.add': Result.ok({ id: '1', name: 'Delta', type: 'tool' }),
80
+ 'search': Result.ok({ results: [] }),
81
+ },
166
82
  });
167
-
168
- // Logger that captures entries in memory
169
- const logger = createTestLogger();
170
- logger.info('hello');
171
- logger.entries; // All captured records
172
- logger.assertLogged('info', 'hello'); // Passes if any entry matches
173
- logger.clear(); // Reset
83
+ const ctx = { ...createTestContext(), follow };
84
+ const result = await onboardTrail.run({ name: 'Delta', type: 'tool' }, ctx);
174
85
  ```
175
86
 
176
- ### Surface Harnesses
87
+ Calls to unregistered trail IDs return `Result.err` with a descriptive message, so missing stubs fail loudly.
177
88
 
178
- **CLI harness** -- execute commands in-process and capture stdout/stderr:
89
+ ## Surface harnesses
179
90
 
180
91
  ```typescript
181
- import { createCliHarness } from '@ontrails/testing';
182
-
183
- const harness = createCliHarness({ app });
184
- const result = await harness.run('entity show --name Alpha --output json');
92
+ import { createCliHarness, createMcpHarness } from '@ontrails/testing';
185
93
 
94
+ // CLI
95
+ const cli = createCliHarness({ app });
96
+ const result = await cli.run('entity show --name Alpha --output json');
186
97
  expect(result.exitCode).toBe(0);
187
- expect(result.json).toMatchObject({ name: 'Alpha' });
188
- ```
189
-
190
- **MCP harness** -- invoke tools directly without transport:
191
-
192
- ```typescript
193
- import { createMcpHarness } from '@ontrails/testing';
194
-
195
- const harness = createMcpHarness({ app });
196
- const result = await harness.callTool('myapp_entity_show', { name: 'Alpha' });
197
98
 
198
- expect(result.isError).toBe(false);
99
+ // MCP
100
+ const mcp = createMcpHarness({ app });
101
+ const tool = await mcp.callTool('myapp_entity_show', { name: 'Alpha' });
102
+ expect(tool.isError).toBe(false);
199
103
  ```
200
104
 
201
- ## Exports
105
+ ## Installation
202
106
 
203
- ```typescript
204
- import {
205
- testAll,
206
- testExamples,
207
- testTrail,
208
- testHike,
209
- testContracts,
210
- testDetours,
211
- createTestContext,
212
- createTestLogger,
213
- createCliHarness,
214
- createMcpHarness,
215
- } from '@ontrails/testing';
107
+ ```bash
108
+ bun add -d @ontrails/testing
216
109
  ```
217
-
218
- ## Further Reading
219
-
220
- - [Testing Guide](../../docs/testing.md)
221
- - [Getting Started](../../docs/getting-started.md)
package/dist/context.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Test context factory for creating TrailContext instances suitable for testing.
3
3
  */
4
- import type { TrailContext } from '@ontrails/core';
4
+ import type { FollowFn, TrailContext } from '@ontrails/core';
5
+ import { Result } from '@ontrails/core';
5
6
  import type { TestTrailContextOptions } from './types.js';
6
7
  /**
7
8
  * Create a TrailContext with deterministic, test-friendly defaults.
@@ -11,6 +12,24 @@ import type { TestTrailContextOptions } from './types.js';
11
12
  * - `signal`: a non-aborted AbortController signal
12
13
  */
13
14
  export declare const createTestContext: (overrides?: TestTrailContextOptions) => TrailContext;
15
+ export interface CreateFollowContextOptions {
16
+ readonly responses?: Record<string, Result<unknown, Error>> | undefined;
17
+ }
18
+ /**
19
+ * Create a mock `FollowFn` for testing composite trails.
20
+ *
21
+ * Returns preconfigured `Result` values keyed by trail ID. Calls to
22
+ * unregistered IDs return `Result.err` with a descriptive message.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const follow = createFollowContext({
27
+ * responses: { 'entity.add': Result.ok({ id: '1', name: 'Alpha' }) },
28
+ * });
29
+ * const ctx = { ...createTestContext(), follow };
30
+ * ```
31
+ */
32
+ export declare const createFollowContext: (options?: CreateFollowContextOptions) => FollowFn;
14
33
  /**
15
34
  * Merge a Partial<TrailContext> into a test context.
16
35
  * Used internally when the public API accepts Partial<TrailContext>.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAM1D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,uBAAuB,KAClC,YAMD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,OAAO,CAAC,YAAY,CAAC,KAAG,YAO9D,CAAC"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAM1D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,uBAAuB,KAClC,YAMD,CAAC;AAMH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;CACzE;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,0BAA0B,KACnC,QAcF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,OAAO,CAAC,YAAY,CAAC,KAAG,YAO9D,CAAC"}
package/dist/context.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Test context factory for creating TrailContext instances suitable for testing.
3
3
  */
4
+ import { Result } from '@ontrails/core';
4
5
  import { createTestLogger } from './logger.js';
5
6
  // ---------------------------------------------------------------------------
6
7
  // createTestContext
@@ -19,6 +20,30 @@ export const createTestContext = (overrides) => ({
19
20
  signal: overrides?.signal ?? new AbortController().signal,
20
21
  workspaceRoot: overrides?.cwd ?? process.cwd(),
21
22
  });
23
+ /**
24
+ * Create a mock `FollowFn` for testing composite trails.
25
+ *
26
+ * Returns preconfigured `Result` values keyed by trail ID. Calls to
27
+ * unregistered IDs return `Result.err` with a descriptive message.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const follow = createFollowContext({
32
+ * responses: { 'entity.add': Result.ok({ id: '1', name: 'Alpha' }) },
33
+ * });
34
+ * const ctx = { ...createTestContext(), follow };
35
+ * ```
36
+ */
37
+ export const createFollowContext = (options) => {
38
+ const responses = options?.responses ?? {};
39
+ return (id, _input) => {
40
+ const response = responses[id];
41
+ if (response === undefined) {
42
+ return Promise.resolve(Result.err(new Error(`No mock response for follow("${id}")`)));
43
+ }
44
+ return Promise.resolve(response);
45
+ };
46
+ };
22
47
  /**
23
48
  * Merge a Partial<TrailContext> into a test context.
24
49
  * Used internally when the public API accepts Partial<TrailContext>.
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,SAAmC,EACrB,EAAE,CAAC,CAAC;IAClB,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE;IAC7C,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,gBAAgB,EAAE;IAC/C,SAAS,EAAE,SAAS,EAAE,SAAS,IAAI,kBAAkB;IACrD,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC,MAAM;IACzD,aAAa,EAAE,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;CAC/C,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAA2B,EAAgB,EAAE;IAC5E,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,CAAC;AAC7B,CAAC,CAAC"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,SAAmC,EACrB,EAAE,CAAC,CAAC;IAClB,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE;IAC7C,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,gBAAgB,EAAE;IAC/C,SAAS,EAAE,SAAS,EAAE,SAAS,IAAI,kBAAkB;IACrD,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC,MAAM;IACzD,aAAa,EAAE,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;CAC/C,CAAC,CAAC;AAUH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,OAAoC,EAC1B,EAAE;IACZ,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,EAAE,CAAC;IAC3C,OAAO,CAAI,EAAU,EAAE,MAAe,EAA6B,EAAE;QACnE,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,OAAO,CACpB,MAAM,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,gCAAgC,EAAE,IAAI,CAAC,CAG3D,CACF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,QAA4B,CAAC,CAAC;IACvD,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAA2B,EAAgB,EAAE;IAC5E,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,CAAC;AAC7B,CAAC,CAAC"}
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { Topo, TrailContext } from '@ontrails/core';
9
9
  /**
10
- * Verify that every trail's implementation output matches its declared
10
+ * Verify that every trail implementation output matches its declared
11
11
  * output schema. Catches implementation-schema drift.
12
12
  *
13
13
  * Trails without output schemas or examples are skipped.
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAuB,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8B9E;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAmCF,CAAC"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAuB,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA0C9E;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAoCF,CAAC"}
package/dist/contracts.js CHANGED
@@ -12,6 +12,14 @@ import { mergeTestContext } from './context.js';
12
12
  // ---------------------------------------------------------------------------
13
13
  // Helpers
14
14
  // ---------------------------------------------------------------------------
15
+ /** Check if a trail requires follow() but the context doesn't provide it. */
16
+ const needsFollowContext = (t, resolveCtx) => {
17
+ const spec = t;
18
+ if (!spec.follow || spec.follow.length === 0) {
19
+ return false;
20
+ }
21
+ return !resolveCtx()?.follow;
22
+ };
15
23
  const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
16
24
  const parsed = outputSchema.safeParse(value);
17
25
  if (!parsed.success) {
@@ -23,30 +31,32 @@ const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
23
31
  // testContracts
24
32
  // ---------------------------------------------------------------------------
25
33
  /**
26
- * Verify that every trail's implementation output matches its declared
34
+ * Verify that every trail implementation output matches its declared
27
35
  * output schema. Catches implementation-schema drift.
28
36
  *
29
37
  * Trails without output schemas or examples are skipped.
30
38
  */
31
39
  export const testContracts = (app, ctxOrFactory) => {
32
40
  const resolveCtx = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
33
- const trailEntries = [...app.trails];
41
+ const allEntries = app.list();
34
42
  describe('contracts', () => {
35
- describe.each(trailEntries)('%s', (_id, trailDef) => {
36
- const t = trailDef;
43
+ describe.each(allEntries)('$id', (t) => {
37
44
  if (t.output === undefined) {
38
45
  return;
39
46
  }
40
47
  if (t.examples === undefined || t.examples.length === 0) {
41
48
  return;
42
49
  }
50
+ if (needsFollowContext(t, resolveCtx)) {
51
+ return;
52
+ }
43
53
  const { examples, output: outputSchema } = t;
44
54
  const successExamples = examples.filter((e) => e.error === undefined);
45
55
  test.each(successExamples)('contract: $name', async (example) => {
46
56
  const testCtx = mergeTestContext(resolveCtx());
47
57
  const validated = validateInput(t.input, example.input);
48
58
  const validatedInput = expectOk(validated);
49
- const result = await t.implementation(validatedInput, testCtx);
59
+ const result = await t.run(validatedInput, testCtx);
50
60
  const resultValue = expectOk(result);
51
61
  validateOutputSchema(outputSchema, resultValue, t.id, example.name);
52
62
  });
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGhE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,6EAA6E;AAC7E,MAAM,kBAAkB,GAAG,CACzB,CAAU,EACV,UAAmD,EAC1C,EAAE;IACX,MAAM,IAAI,GAAG,CAAmC,CAAC;IACjD,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC3B,YAAuB,EACvB,KAAc,EACd,OAAe,EACf,WAAmB,EACb,EAAE;IACR,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,sCAAsC,OAAO,eAAe,WAAW,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAC7K,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,GAAS,EACT,YAAoE,EAC9D,EAAE;IACR,MAAM,UAAU,GACd,OAAO,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC;IACzE,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAA+B,CAAC;IAE3D,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;YACrC,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;YACD,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,IAAI,kBAAkB,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YAED,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;YAC7C,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;YAEtE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CACxB,iBAAiB,EACjB,KAAK,EAAE,OAAuC,EAAE,EAAE;gBAChD,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC;gBAE/C,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACxD,MAAM,cAAc,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAE3C,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;gBACpD,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAErC,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACtE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * Iterates every trail in the app's topo. For each trail with examples,
5
5
  * generates describe/test blocks using bun:test. Progressive assertion
6
- * determines which check to run per example. For hikes with `follows`
6
+ * determines which check to run per example. For trails with `follow`
7
7
  * declarations, checks that every declared follow was called at least once.
8
8
  */
9
9
  import type { Topo, TrailContext } from '@ontrails/core';
10
10
  /**
11
11
  * Generate describe/test blocks for every trail example in the app.
12
12
  *
13
- * For hikes with `follows` declarations and examples, also verifies that
13
+ * For trails with `follow` declarations and examples, also verifies that
14
14
  * every declared follow ID was called at least once across all examples.
15
15
  *
16
16
  * One line in your test file:
@@ -1 +1 @@
1
- {"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAGV,IAAI,EAGJ,YAAY,EACb,MAAM,gBAAgB,CAAC;AAkQxB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAuBF,CAAC"}
1
+ {"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAEV,IAAI,EAGJ,YAAY,EACb,MAAM,gBAAgB,CAAC;AAiMxB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAqDF,CAAC"}