@ontrails/testing 1.0.0-beta.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 (80) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/.turbo/turbo-lint.log +3 -0
  3. package/.turbo/turbo-typecheck.log +1 -0
  4. package/CHANGELOG.md +23 -0
  5. package/README.md +221 -0
  6. package/dist/all.d.ts +30 -0
  7. package/dist/all.d.ts.map +1 -0
  8. package/dist/all.js +47 -0
  9. package/dist/all.js.map +1 -0
  10. package/dist/assertions.d.ts +49 -0
  11. package/dist/assertions.d.ts.map +1 -0
  12. package/dist/assertions.js +84 -0
  13. package/dist/assertions.js.map +1 -0
  14. package/dist/context.d.ts +19 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +33 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/contracts.d.ts +16 -0
  19. package/dist/contracts.d.ts.map +1 -0
  20. package/dist/contracts.js +56 -0
  21. package/dist/contracts.js.map +1 -0
  22. package/dist/detours.d.ts +12 -0
  23. package/dist/detours.d.ts.map +1 -0
  24. package/dist/detours.js +30 -0
  25. package/dist/detours.js.map +1 -0
  26. package/dist/examples.d.ts +22 -0
  27. package/dist/examples.d.ts.map +1 -0
  28. package/dist/examples.js +187 -0
  29. package/dist/examples.js.map +1 -0
  30. package/dist/harness-cli.d.ts +21 -0
  31. package/dist/harness-cli.d.ts.map +1 -0
  32. package/dist/harness-cli.js +213 -0
  33. package/dist/harness-cli.js.map +1 -0
  34. package/dist/harness-mcp.d.ts +21 -0
  35. package/dist/harness-mcp.d.ts.map +1 -0
  36. package/dist/harness-mcp.js +50 -0
  37. package/dist/harness-mcp.js.map +1 -0
  38. package/dist/hike.d.ts +32 -0
  39. package/dist/hike.d.ts.map +1 -0
  40. package/dist/hike.js +169 -0
  41. package/dist/hike.js.map +1 -0
  42. package/dist/index.d.ts +14 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +16 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/logger.d.ts +15 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +87 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/trail.d.ts +20 -0
  51. package/dist/trail.d.ts.map +1 -0
  52. package/dist/trail.js +80 -0
  53. package/dist/trail.js.map +1 -0
  54. package/dist/types.d.ts +80 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +5 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +23 -0
  59. package/src/__tests__/context.test.ts +60 -0
  60. package/src/__tests__/contracts.test.ts +68 -0
  61. package/src/__tests__/detours.test.ts +55 -0
  62. package/src/__tests__/examples.test.ts +176 -0
  63. package/src/__tests__/hike.test.ts +164 -0
  64. package/src/__tests__/logger.test.ts +136 -0
  65. package/src/__tests__/trail.test.ts +99 -0
  66. package/src/all.ts +55 -0
  67. package/src/assertions.ts +108 -0
  68. package/src/context.ts +42 -0
  69. package/src/contracts.ts +85 -0
  70. package/src/detours.ts +44 -0
  71. package/src/examples.ts +314 -0
  72. package/src/harness-cli.ts +310 -0
  73. package/src/harness-mcp.ts +65 -0
  74. package/src/hike.ts +283 -0
  75. package/src/index.ts +40 -0
  76. package/src/logger.ts +125 -0
  77. package/src/trail.ts +116 -0
  78. package/src/types.ts +117 -0
  79. package/tsconfig.json +9 -0
  80. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1 @@
1
+ $ tsc -b
@@ -0,0 +1,3 @@
1
+ $ oxlint ./src
2
+ Found 0 warnings and 0 errors.
3
+ Finished in 109ms on 20 files with 93 rules using 24 threads.
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # @ontrails/testing
2
+
3
+ ## 1.0.0-beta.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial v1 beta release of the Trails framework.
8
+
9
+ - **@ontrails/core** — Result type, error taxonomy, trail/hike/event/topo, validateTopo, validateInput/Output, deriveFields, patterns, redaction, branded types, resilience
10
+ - **@ontrails/cli** — CLI surface adapter, Commander integration, flag derivation, layers
11
+ - **@ontrails/mcp** — MCP surface adapter, tool generation, annotations, progress bridge
12
+ - **@ontrails/logging** — Structured logging, sinks, formatters, LogTape adapter
13
+ - **@ontrails/testing** — testAll, testExamples, testTrail, testHike, testContracts, testDetours, surface harnesses
14
+ - **@ontrails/warden** — AST-based code convention rules via oxc-parser, drift detection, CI formatters
15
+ - **@ontrails/schema** — Surface map generation, hashing, semantic diffing
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - @ontrails/core@1.0.0-beta.0
21
+ - @ontrails/cli@1.0.0-beta.0
22
+ - @ontrails/mcp@1.0.0-beta.0
23
+ - @ontrails/logging@1.0.0-beta.0
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # @ontrails/testing
2
+
3
+ Contract-driven testing utilities for Trails. Write examples for agent fluency -- get test cases for free.
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:
38
+
39
+ ```typescript
40
+ import { testAll } from '@ontrails/testing';
41
+ import { app } from '../app';
42
+
43
+ testAll(app);
44
+ ```
45
+
46
+ For most apps, `testAll` is the only test call you need.
47
+
48
+ ### `testExamples(app, ctx?)`
49
+
50
+ For each trail with `examples`, generates `describe`/`test` blocks using the Bun test runner.
51
+
52
+ Per example:
53
+
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)
58
+
59
+ Trails with no examples produce no tests.
60
+
61
+ The runtime implementation is always awaited, so `testExamples()` behaves the same for sync-authored and async-authored trails.
62
+
63
+ ### `testTrail(trail, scenarios, ctx?)`
64
+
65
+ Custom scenarios for edge cases, boundary values, and regressions that do not belong in agent-facing examples:
66
+
67
+ ```typescript
68
+ import { testTrail } from '@ontrails/testing';
69
+ import { ValidationError, NotFoundError } from '@ontrails/core';
70
+
71
+ testTrail(showTrail, [
72
+ { description: 'empty name', input: { name: '' }, expectOk: true },
73
+ { 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
+ },
85
+ ]);
86
+ ```
87
+
88
+ ### `testHike(hike, scenarios, ctx?)`
89
+
90
+ Tests a hike's composition graph -- follow chains, failure injection, and multi-trail interactions:
91
+
92
+ ```typescript
93
+ import { testHike } from '@ontrails/testing';
94
+
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 },
98
+ ]);
99
+ ```
100
+
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.
106
+
107
+ ```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
126
+
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' },
166
+ });
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
174
+ ```
175
+
176
+ ### Surface Harnesses
177
+
178
+ **CLI harness** -- execute commands in-process and capture stdout/stderr:
179
+
180
+ ```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');
185
+
186
+ 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
+
198
+ expect(result.isError).toBe(false);
199
+ ```
200
+
201
+ ## Exports
202
+
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';
216
+ ```
217
+
218
+ ## Further Reading
219
+
220
+ - [Testing Guide](../../docs/testing.md)
221
+ - [Getting Started](../../docs/getting-started.md)
package/dist/all.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * testAll — single-line governance suite for any Topo.
3
+ *
4
+ * Wraps topo validation, example execution, contract checks, and detour
5
+ * verification into one describe block.
6
+ */
7
+ import type { Topo, TrailContext } from '@ontrails/core';
8
+ /**
9
+ * Run the full governance test suite for a Topo.
10
+ *
11
+ * Generates a `governance` describe block containing:
12
+ * - Structural validation via `validateTopo`
13
+ * - Example execution via `testExamples`
14
+ * - Output contract checks via `testContracts`
15
+ * - Detour target verification via `testDetours`
16
+ *
17
+ * Accepts either a static context or a factory function that produces a
18
+ * fresh context per test (useful when the context contains mutable state
19
+ * like an in-memory store).
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { testAll } from '@ontrails/testing';
24
+ * import { app } from '../src/app.js';
25
+ *
26
+ * testAll(app);
27
+ * ```
28
+ */
29
+ export declare const testAll: (topo: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
30
+ //# sourceMappingURL=all.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"all.d.ts","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAOzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,IAAI,EACV,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAcF,CAAC"}
package/dist/all.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * testAll — single-line governance suite for any Topo.
3
+ *
4
+ * Wraps topo validation, example execution, contract checks, and detour
5
+ * verification into one describe block.
6
+ */
7
+ import { describe, expect, test } from 'bun:test';
8
+ import { validateTopo } from '@ontrails/core';
9
+ import { testContracts } from './contracts.js';
10
+ import { testDetours } from './detours.js';
11
+ import { testExamples } from './examples.js';
12
+ /**
13
+ * Run the full governance test suite for a Topo.
14
+ *
15
+ * Generates a `governance` describe block containing:
16
+ * - Structural validation via `validateTopo`
17
+ * - Example execution via `testExamples`
18
+ * - Output contract checks via `testContracts`
19
+ * - Detour target verification via `testDetours`
20
+ *
21
+ * Accepts either a static context or a factory function that produces a
22
+ * fresh context per test (useful when the context contains mutable state
23
+ * like an in-memory store).
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { testAll } from '@ontrails/testing';
28
+ * import { app } from '../src/app.js';
29
+ *
30
+ * testAll(app);
31
+ * ```
32
+ */
33
+ export const testAll = (topo, ctxOrFactory) => {
34
+ describe('governance', () => {
35
+ test('topo validates', () => {
36
+ const result = validateTopo(topo);
37
+ expect(result.isOk()).toBe(true);
38
+ });
39
+ // oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
40
+ testExamples(topo, ctxOrFactory);
41
+ // oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
42
+ testContracts(topo, ctxOrFactory);
43
+ // oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
44
+ testDetours(topo);
45
+ });
46
+ };
47
+ //# sourceMappingURL=all.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"all.js","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGlD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,IAAU,EACV,YAAoE,EAC9D,EAAE;IACR,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE;YAC1B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,oGAAoG;QACpG,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACjC,oGAAoG;QACpG,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAClC,oGAAoG;QACpG,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Progressive assertion logic for example-driven testing.
3
+ *
4
+ * Three tiers:
5
+ * 1. Full match — example has `expected` output
6
+ * 2. Schema-only — no expected output, no error
7
+ * 3. Error match — example declares an error class name
8
+ */
9
+ import type { Result } from '@ontrails/core';
10
+ import type { z } from 'zod';
11
+ /**
12
+ * Assert that a Result is Ok and return its value.
13
+ *
14
+ * Eliminates the `if (result.isOk())` / `as unknown as` dance in tests.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const value = expectOk(result);
19
+ * expect(value.name).toBe('Alice');
20
+ * ```
21
+ */
22
+ export declare const expectOk: <T, E>(result: Result<T, E>) => T;
23
+ /**
24
+ * Assert that a Result is Err and return its error.
25
+ *
26
+ * Eliminates the `if (result.isErr())` / `as unknown as` dance in tests.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const error = expectErr(result);
31
+ * expect(error).toBeInstanceOf(ValidationError);
32
+ * ```
33
+ */
34
+ export declare const expectErr: <T, E>(result: Result<T, E>) => E;
35
+ /**
36
+ * Assert that the result is ok and its value deep-equals the expected output.
37
+ */
38
+ export declare const assertFullMatch: (result: Result<unknown, Error>, expected: unknown) => void;
39
+ /**
40
+ * Assert that the result is ok and, if an output schema is provided,
41
+ * the value parses against it.
42
+ */
43
+ export declare const assertSchemaMatch: (result: Result<unknown, Error>, outputSchema: z.ZodType | undefined) => void;
44
+ /**
45
+ * Assert that the result is an error of the specified type, with optional
46
+ * message substring matching.
47
+ */
48
+ export declare const assertErrorMatch: (result: Result<unknown, Error>, expectedError: new (...args: never[]) => Error, expectedMessage?: string) => void;
49
+ //# sourceMappingURL=assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAM7B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAGrD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAGtD,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAC9B,UAAU,OAAO,KAChB,IAGF,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAC9B,cAAc,CAAC,CAAC,OAAO,GAAG,SAAS,KAClC,IAUF,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAC9B,eAAe,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,KAAK,EAC9C,kBAAkB,MAAM,KACvB,IAMF,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Progressive assertion logic for example-driven testing.
3
+ *
4
+ * Three tiers:
5
+ * 1. Full match — example has `expected` output
6
+ * 2. Schema-only — no expected output, no error
7
+ * 3. Error match — example declares an error class name
8
+ */
9
+ import { expect } from 'bun:test';
10
+ import { formatZodIssues } from '@ontrails/core';
11
+ // ---------------------------------------------------------------------------
12
+ // Result narrowing helpers
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Assert that a Result is Ok and return its value.
16
+ *
17
+ * Eliminates the `if (result.isOk())` / `as unknown as` dance in tests.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const value = expectOk(result);
22
+ * expect(value.name).toBe('Alice');
23
+ * ```
24
+ */
25
+ export const expectOk = (result) => {
26
+ expect(result.isOk()).toBe(true);
27
+ return result.value;
28
+ };
29
+ /**
30
+ * Assert that a Result is Err and return its error.
31
+ *
32
+ * Eliminates the `if (result.isErr())` / `as unknown as` dance in tests.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const error = expectErr(result);
37
+ * expect(error).toBeInstanceOf(ValidationError);
38
+ * ```
39
+ */
40
+ export const expectErr = (result) => {
41
+ expect(result.isErr()).toBe(true);
42
+ return result.error;
43
+ };
44
+ // ---------------------------------------------------------------------------
45
+ // Full Match
46
+ // ---------------------------------------------------------------------------
47
+ /**
48
+ * Assert that the result is ok and its value deep-equals the expected output.
49
+ */
50
+ export const assertFullMatch = (result, expected) => {
51
+ const value = expectOk(result);
52
+ expect(value).toEqual(expected);
53
+ };
54
+ // ---------------------------------------------------------------------------
55
+ // Schema-Only Match
56
+ // ---------------------------------------------------------------------------
57
+ /**
58
+ * Assert that the result is ok and, if an output schema is provided,
59
+ * the value parses against it.
60
+ */
61
+ export const assertSchemaMatch = (result, outputSchema) => {
62
+ const value = expectOk(result);
63
+ if (outputSchema !== undefined) {
64
+ const parsed = outputSchema.safeParse(value);
65
+ if (!parsed.success) {
66
+ throw new Error(`Output does not match schema: ${formatZodIssues(parsed.error.issues).join('; ')}`);
67
+ }
68
+ }
69
+ };
70
+ // ---------------------------------------------------------------------------
71
+ // Error Match
72
+ // ---------------------------------------------------------------------------
73
+ /**
74
+ * Assert that the result is an error of the specified type, with optional
75
+ * message substring matching.
76
+ */
77
+ export const assertErrorMatch = (result, expectedError, expectedMessage) => {
78
+ const error = expectErr(result);
79
+ expect(error).toBeInstanceOf(expectedError);
80
+ if (expectedMessage !== undefined) {
81
+ expect(error.message).toContain(expectedMessage);
82
+ }
83
+ };
84
+ //# sourceMappingURL=assertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAO,MAAoB,EAAK,EAAE;IACxD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,OAAQ,MAAkC,CAAC,KAAK,CAAC;AACnD,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAO,MAAoB,EAAK,EAAE;IACzD,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,OAAQ,MAAkC,CAAC,KAAK,CAAC;AACnD,CAAC,CAAC;AAEF,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAA8B,EAC9B,QAAiB,EACX,EAAE;IACR,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAA8B,EAC9B,YAAmC,EAC7B,EAAE;IACR,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,iCAAiC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACnF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,MAA8B,EAC9B,aAA8C,EAC9C,eAAwB,EAClB,EAAE;IACR,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAC5C,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Test context factory for creating TrailContext instances suitable for testing.
3
+ */
4
+ import type { TrailContext } from '@ontrails/core';
5
+ import type { TestTrailContextOptions } from './types.js';
6
+ /**
7
+ * Create a TrailContext with deterministic, test-friendly defaults.
8
+ *
9
+ * - `requestId`: `"test-request-001"` (deterministic)
10
+ * - `logger`: a `TestLogger` that captures entries
11
+ * - `signal`: a non-aborted AbortController signal
12
+ */
13
+ export declare const createTestContext: (overrides?: TestTrailContextOptions) => TrailContext;
14
+ /**
15
+ * Merge a Partial<TrailContext> into a test context.
16
+ * Used internally when the public API accepts Partial<TrailContext>.
17
+ */
18
+ export declare const mergeTestContext: (ctx?: Partial<TrailContext>) => TrailContext;
19
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Test context factory for creating TrailContext instances suitable for testing.
3
+ */
4
+ import { createTestLogger } from './logger.js';
5
+ // ---------------------------------------------------------------------------
6
+ // createTestContext
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Create a TrailContext with deterministic, test-friendly defaults.
10
+ *
11
+ * - `requestId`: `"test-request-001"` (deterministic)
12
+ * - `logger`: a `TestLogger` that captures entries
13
+ * - `signal`: a non-aborted AbortController signal
14
+ */
15
+ export const createTestContext = (overrides) => ({
16
+ env: overrides?.env ?? { TRAILS_ENV: 'test' },
17
+ logger: overrides?.logger ?? createTestLogger(),
18
+ requestId: overrides?.requestId ?? 'test-request-001',
19
+ signal: overrides?.signal ?? new AbortController().signal,
20
+ workspaceRoot: overrides?.cwd ?? process.cwd(),
21
+ });
22
+ /**
23
+ * Merge a Partial<TrailContext> into a test context.
24
+ * Used internally when the public API accepts Partial<TrailContext>.
25
+ */
26
+ export const mergeTestContext = (ctx) => {
27
+ if (ctx === undefined) {
28
+ return createTestContext();
29
+ }
30
+ const base = createTestContext();
31
+ return { ...base, ...ctx };
32
+ };
33
+ //# sourceMappingURL=context.js.map
@@ -0,0 +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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * testContracts — output schema verification.
3
+ *
4
+ * For every trail that has both examples and an output schema,
5
+ * run each example and validate the implementation output against
6
+ * the declared schema.
7
+ */
8
+ import type { Topo, TrailContext } from '@ontrails/core';
9
+ /**
10
+ * Verify that every trail's implementation output matches its declared
11
+ * output schema. Catches implementation-schema drift.
12
+ *
13
+ * Trails without output schemas or examples are skipped.
14
+ */
15
+ export declare const testContracts: (app: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
16
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * testContracts — output schema verification.
3
+ *
4
+ * For every trail that has both examples and an output schema,
5
+ * run each example and validate the implementation output against
6
+ * the declared schema.
7
+ */
8
+ import { describe, test } from 'bun:test';
9
+ import { formatZodIssues, validateInput } from '@ontrails/core';
10
+ import { expectOk } from './assertions.js';
11
+ import { mergeTestContext } from './context.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+ const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
16
+ const parsed = outputSchema.safeParse(value);
17
+ if (!parsed.success) {
18
+ const issues = formatZodIssues(parsed.error.issues);
19
+ throw new Error(`Output schema violation for trail "${trailId}", example "${exampleName}":\n${issues.map((i) => ` - ${i}`).join('\n')}\n\nActual output: ${JSON.stringify(value, null, 2)}`);
20
+ }
21
+ };
22
+ // ---------------------------------------------------------------------------
23
+ // testContracts
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Verify that every trail's implementation output matches its declared
27
+ * output schema. Catches implementation-schema drift.
28
+ *
29
+ * Trails without output schemas or examples are skipped.
30
+ */
31
+ export const testContracts = (app, ctxOrFactory) => {
32
+ const resolveCtx = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
33
+ const trailEntries = [...app.trails];
34
+ describe('contracts', () => {
35
+ describe.each(trailEntries)('%s', (_id, trailDef) => {
36
+ const t = trailDef;
37
+ if (t.output === undefined) {
38
+ return;
39
+ }
40
+ if (t.examples === undefined || t.examples.length === 0) {
41
+ return;
42
+ }
43
+ const { examples, output: outputSchema } = t;
44
+ const successExamples = examples.filter((e) => e.error === undefined);
45
+ test.each(successExamples)('contract: $name', async (example) => {
46
+ const testCtx = mergeTestContext(resolveCtx());
47
+ const validated = validateInput(t.input, example.input);
48
+ const validatedInput = expectOk(validated);
49
+ const result = await t.implementation(validatedInput, testCtx);
50
+ const resultValue = expectOk(result);
51
+ validateOutputSchema(outputSchema, resultValue, t.id, example.name);
52
+ });
53
+ });
54
+ });
55
+ };
56
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * testDetours — verify that all detour targets exist in the topo.
3
+ *
4
+ * Pure structural validation. No implementation execution needed.
5
+ */
6
+ import type { Topo } from '@ontrails/core';
7
+ /**
8
+ * Verify that every trail's detour targets reference trails that
9
+ * actually exist in the app's topo.
10
+ */
11
+ export declare const testDetours: (app: Topo) => void;
12
+ //# sourceMappingURL=detours.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detours.d.ts","sourceRoot":"","sources":["../src/detours.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,gBAAgB,CAAC;AAMlD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,IAAI,KAAG,IAyBvC,CAAC"}