@ontrails/testing 1.0.0-beta.3 → 1.0.0-beta.32

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 (91) hide show
  1. package/CHANGELOG.md +507 -8
  2. package/README.md +86 -22
  3. package/package.json +34 -5
  4. package/src/all-established.ts +168 -0
  5. package/src/all.ts +51 -12
  6. package/src/assertions.ts +253 -0
  7. package/src/cli.ts +6 -0
  8. package/src/composes.ts +433 -0
  9. package/src/context.ts +200 -14
  10. package/src/contracts.ts +46 -35
  11. package/src/detours.ts +155 -18
  12. package/src/effective-examples.ts +414 -0
  13. package/src/errors.ts +47 -0
  14. package/src/examples.ts +302 -177
  15. package/src/harness-cli.ts +83 -58
  16. package/src/harness-http.ts +341 -0
  17. package/src/harness-mcp.ts +46 -16
  18. package/src/http.ts +10 -0
  19. package/src/index.ts +22 -14
  20. package/src/logger.ts +3 -1
  21. package/src/mcp.ts +6 -0
  22. package/src/scenario.ts +375 -0
  23. package/src/signals.ts +221 -0
  24. package/src/surface-parity.ts +389 -0
  25. package/src/trail.ts +1 -1
  26. package/src/types.ts +24 -52
  27. package/.turbo/turbo-build.log +0 -1
  28. package/.turbo/turbo-lint.log +0 -3
  29. package/.turbo/turbo-typecheck.log +0 -1
  30. package/dist/all.d.ts +0 -30
  31. package/dist/all.d.ts.map +0 -1
  32. package/dist/all.js +0 -47
  33. package/dist/all.js.map +0 -1
  34. package/dist/assertions.d.ts +0 -49
  35. package/dist/assertions.d.ts.map +0 -1
  36. package/dist/assertions.js +0 -84
  37. package/dist/assertions.js.map +0 -1
  38. package/dist/context.d.ts +0 -19
  39. package/dist/context.d.ts.map +0 -1
  40. package/dist/context.js +0 -33
  41. package/dist/context.js.map +0 -1
  42. package/dist/contracts.d.ts +0 -16
  43. package/dist/contracts.d.ts.map +0 -1
  44. package/dist/contracts.js +0 -66
  45. package/dist/contracts.js.map +0 -1
  46. package/dist/detours.d.ts +0 -12
  47. package/dist/detours.d.ts.map +0 -1
  48. package/dist/detours.js +0 -30
  49. package/dist/detours.js.map +0 -1
  50. package/dist/examples.d.ts +0 -22
  51. package/dist/examples.d.ts.map +0 -1
  52. package/dist/examples.js +0 -187
  53. package/dist/examples.js.map +0 -1
  54. package/dist/harness-cli.d.ts +0 -21
  55. package/dist/harness-cli.d.ts.map +0 -1
  56. package/dist/harness-cli.js +0 -213
  57. package/dist/harness-cli.js.map +0 -1
  58. package/dist/harness-mcp.d.ts +0 -21
  59. package/dist/harness-mcp.d.ts.map +0 -1
  60. package/dist/harness-mcp.js +0 -50
  61. package/dist/harness-mcp.js.map +0 -1
  62. package/dist/hike.d.ts +0 -32
  63. package/dist/hike.d.ts.map +0 -1
  64. package/dist/hike.js +0 -169
  65. package/dist/hike.js.map +0 -1
  66. package/dist/index.d.ts +0 -14
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/index.js +0 -16
  69. package/dist/index.js.map +0 -1
  70. package/dist/logger.d.ts +0 -15
  71. package/dist/logger.d.ts.map +0 -1
  72. package/dist/logger.js +0 -87
  73. package/dist/logger.js.map +0 -1
  74. package/dist/trail.d.ts +0 -20
  75. package/dist/trail.d.ts.map +0 -1
  76. package/dist/trail.js +0 -80
  77. package/dist/trail.js.map +0 -1
  78. package/dist/types.d.ts +0 -80
  79. package/dist/types.d.ts.map +0 -1
  80. package/dist/types.js +0 -5
  81. package/dist/types.js.map +0 -1
  82. package/src/__tests__/context.test.ts +0 -60
  83. package/src/__tests__/contracts.test.ts +0 -93
  84. package/src/__tests__/detours.test.ts +0 -55
  85. package/src/__tests__/examples.test.ts +0 -176
  86. package/src/__tests__/hike.test.ts +0 -164
  87. package/src/__tests__/logger.test.ts +0 -136
  88. package/src/__tests__/trail.test.ts +0 -99
  89. package/src/hike.ts +0 -283
  90. package/tsconfig.json +0 -9
  91. package/tsconfig.tsbuildinfo +0 -1
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @ontrails/testing
2
2
 
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.
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
 
7
7
  ```typescript
8
8
  import { testAll } from '@ontrails/testing';
9
- import { app } from '../app';
9
+ import { graph } from '../app';
10
10
 
11
- testAll(app);
11
+ testAll(graph);
12
12
  ```
13
13
 
14
14
  That single call covers example execution, contract validation, detour checks, and topo validation. For most apps, this is all you need.
@@ -18,25 +18,23 @@ If you want finer control:
18
18
  ```typescript
19
19
  import { testExamples, testContracts, testDetours } from '@ontrails/testing';
20
20
 
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
21
+ testExamples(graph); // Run every trail's examples as tests
22
+ testContracts(graph); // Validate outputs against declared schemas
23
+ testDetours(graph); // Validate detour constructor, recover, and ordering semantics
24
24
  ```
25
25
 
26
26
  ## API
27
27
 
28
28
  | Export | What it does |
29
29
  | --- | --- |
30
- | `testAll(topo, ctx?)` | Single-line governance suite: validation + examples + contracts + detours |
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 and error paths |
33
- | `testHike(hike, scenarios)` | Test composition graphs -- follow chains, failure injection |
34
- | `testContracts(topo, ctx?)` | Validate implementation output against declared schemas |
35
- | `testDetours(topo)` | Verify every detour target exists in the topo |
32
+ | `testTrail(trail, scenarios)` | Custom scenarios for edge cases, error paths, and composition chains |
33
+ | `testContracts(topo, ctx?)` | Validate output against declared schemas |
34
+ | `testDetours(topo)` | Validate detour constructor, recover, and shadowing semantics |
35
+ | `createComposeContext(options?)` | Mock `ComposeFn` for testing composite trails; returns preconfigured `Result` values keyed by trail ID |
36
36
  | `createTestContext(options?)` | `TrailContext` with sensible test defaults |
37
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 |
40
38
 
41
39
  See the [API Reference](../../docs/api-reference.md) for the full list.
42
40
 
@@ -55,37 +53,103 @@ testTrail(showTrail, [
55
53
  ]);
56
54
  ```
57
55
 
58
- ## testHike
56
+ ## Testing composition (trails with composes)
59
57
 
60
- Where `testTrail` exercises a single trail, `testHike` exercises the follow graph:
58
+ `testTrail` works the same for trails with `composes` -- it exercises the composition graph:
61
59
 
62
60
  ```typescript
63
- import { testHike } from '@ontrails/testing';
61
+ import { testTrail } from '@ontrails/testing';
64
62
 
65
- testHike(onboardHike, [
63
+ testTrail(onboardTrail, [
66
64
  { description: 'happy path', input: { name: 'Delta', type: 'tool' }, expectOk: true },
67
65
  { description: 'add fails', input: { name: 'Alpha' }, expectErr: AlreadyExistsError },
68
66
  ]);
69
67
  ```
70
68
 
71
- ## Surface harnesses
69
+ When you need to isolate a composite trail and stub out its dependencies, use `createComposeContext`:
70
+
71
+ ```typescript
72
+ import { createComposeContext, testTrail } from '@ontrails/testing';
73
+ import { Result } from '@ontrails/core';
74
+
75
+ const compose = createComposeContext({
76
+ responses: {
77
+ 'entity.add': Result.ok({ id: '1', name: 'Delta', type: 'tool' }),
78
+ 'search': Result.ok({ results: [] }),
79
+ },
80
+ });
81
+ testTrail(onboardTrail, [
82
+ {
83
+ description: 'uses mocked composes',
84
+ input: { name: 'Delta', type: 'tool' },
85
+ expectOk: true,
86
+ },
87
+ ], { compose });
88
+ ```
89
+
90
+ Calls to unregistered trail IDs return `Result.err` with a descriptive message, so missing stubs fail loudly.
91
+
92
+ ## Surface Harnesses
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
72
101
 
73
102
  ```typescript
74
- import { createCliHarness, createMcpHarness } from '@ontrails/testing';
103
+ import { createCliHarness } from '@ontrails/testing/cli';
104
+ import { createHttpHarness } from '@ontrails/testing/http';
105
+ import { createMcpHarness } from '@ontrails/testing/mcp';
75
106
 
76
107
  // CLI
77
- const cli = createCliHarness({ app });
108
+ const cli = createCliHarness({ graph });
78
109
  const result = await cli.run('entity show --name Alpha --output json');
79
110
  expect(result.exitCode).toBe(0);
80
111
 
81
112
  // MCP
82
- const mcp = createMcpHarness({ app });
113
+ const mcp = createMcpHarness({ graph });
83
114
  const tool = await mcp.callTool('myapp_entity_show', { name: 'Alpha' });
84
115
  expect(tool.isError).toBe(false);
116
+
117
+ // HTTP
118
+ const http = createHttpHarness({ graph });
119
+ const response = await http.get('/entity/show', { name: 'Alpha' });
120
+ expect(response.status).toBe(200);
121
+ ```
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
+
125
+ ## Surface Parity
126
+
127
+ `testSurfaceParity()` is a focused gate for established apps that want to prove their examples behave the same through every shipped surface.
128
+
129
+ ```typescript
130
+ import { testSurfaceParity } from '@ontrails/testing/surface-parity';
131
+ import { graph } from '../app';
132
+
133
+ const createDeterministicTestDb = () => ({});
134
+
135
+ testSurfaceParity(graph, {
136
+ createResources: () => ({
137
+ 'db.main': createDeterministicTestDb(),
138
+ }),
139
+ exclusions: [
140
+ {
141
+ example: 'Creates generated data',
142
+ reason: 'output includes generated IDs that intentionally differ per surface run',
143
+ trailId: 'entity.add',
144
+ },
145
+ ],
146
+ });
85
147
  ```
86
148
 
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.
150
+
87
151
  ## Installation
88
152
 
89
153
  ```bash
90
- bun add -d @ontrails/testing
154
+ bun add -d @ontrails/testing@beta
91
155
  ```
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@ontrails/testing",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.32",
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",
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",
7
20
  "./package.json": "./package.json"
8
21
  },
9
22
  "scripts": {
@@ -13,11 +26,27 @@
13
26
  "lint": "oxlint ./src",
14
27
  "clean": "rm -rf dist *.tsbuildinfo"
15
28
  },
29
+ "devDependencies": {
30
+ "@ontrails/drizzle": "^1.0.0-beta.32",
31
+ "@ontrails/store": "^1.0.0-beta.32"
32
+ },
16
33
  "peerDependencies": {
17
- "@ontrails/cli": "^1.0.0-beta.0",
18
- "@ontrails/core": "^1.0.0-beta.0",
19
- "@ontrails/logging": "^1.0.0-beta.0",
20
- "@ontrails/mcp": "^1.0.0-beta.0",
34
+ "@ontrails/cli": "^1.0.0-beta.32",
35
+ "@ontrails/core": "^1.0.0-beta.32",
36
+ "@ontrails/http": "^1.0.0-beta.32",
37
+ "@ontrails/mcp": "^1.0.0-beta.32",
38
+ "@ontrails/observe": "^1.0.0-beta.32",
21
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
+ }
22
51
  }
23
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
@@ -1,8 +1,8 @@
1
1
  /**
2
- * testAll — single-line governance suite for any Topo.
2
+ * testAll — single-line contract suite for any Topo.
3
3
  *
4
4
  * Wraps topo validation, example execution, contract checks, and detour
5
- * verification into one describe block.
5
+ * contract validation into one describe block.
6
6
  */
7
7
 
8
8
  import { describe, expect, test } from 'bun:test';
@@ -11,17 +11,18 @@ import type { Topo, TrailContext } from '@ontrails/core';
11
11
  import { validateTopo } from '@ontrails/core';
12
12
 
13
13
  import { testContracts } from './contracts.js';
14
+ import type { TestExecutionOptions } from './context.js';
14
15
  import { testDetours } from './detours.js';
15
16
  import { testExamples } from './examples.js';
16
17
 
17
18
  /**
18
- * Run the full governance test suite for a Topo.
19
+ * Run the full contract test suite for a Topo.
19
20
  *
20
- * Generates a `governance` describe block containing:
21
+ * Generates a `contract` describe block containing:
21
22
  * - Structural validation via `validateTopo`
22
23
  * - Example execution via `testExamples`
23
24
  * - Output contract checks via `testContracts`
24
- * - Detour target verification via `testDetours`
25
+ * - Detour contract validation via `testDetours`
25
26
  *
26
27
  * Accepts either a static context or a factory function that produces a
27
28
  * fresh context per test (useful when the context contains mutable state
@@ -30,19 +31,53 @@ import { testExamples } from './examples.js';
30
31
  * @example
31
32
  * ```ts
32
33
  * import { testAll } from '@ontrails/testing';
33
- * import { app } from '../src/app.js';
34
+ * import { graph } from '../src/app.js';
34
35
  *
35
- * testAll(app);
36
+ * testAll(graph);
36
37
  * ```
37
38
  */
38
- export const testAll = (
39
+ export type TestAllInput =
40
+ | Partial<TrailContext>
41
+ | TestExecutionOptions
42
+ | (() => Partial<TrailContext> | TestExecutionOptions);
43
+
44
+ const formatValidationFailure = (error: Error): string => {
45
+ const issues = (
46
+ error as {
47
+ context?: { issues?: readonly Record<string, unknown>[] };
48
+ }
49
+ ).context?.issues;
50
+
51
+ if (issues === undefined || issues.length === 0) {
52
+ return error.message;
53
+ }
54
+
55
+ const details = issues.map((issue) => {
56
+ const id = typeof issue['id'] === 'string' ? issue['id'] : undefined;
57
+ const message =
58
+ typeof issue['message'] === 'string' ? issue['message'] : undefined;
59
+ const rule = typeof issue['rule'] === 'string' ? issue['rule'] : undefined;
60
+
61
+ return [rule, id, message].filter(Boolean).join(': ');
62
+ });
63
+
64
+ return [error.message, ...details].join('\n');
65
+ };
66
+
67
+ const assertValidTopo = (result: ReturnType<typeof validateTopo>): void => {
68
+ if (result.isErr()) {
69
+ throw new Error(formatValidationFailure(result.error));
70
+ }
71
+ };
72
+
73
+ export const registerContractSuite = (
39
74
  topo: Topo,
40
- ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)
75
+ ctxOrFactory: TestAllInput | undefined,
76
+ validate: (topo: Topo) => ReturnType<typeof validateTopo>
41
77
  ): void => {
42
- describe('governance', () => {
78
+ describe('contract', () => {
43
79
  test('topo validates', () => {
44
- const result = validateTopo(topo);
45
- expect(result.isOk()).toBe(true);
80
+ expect(() => assertValidTopo(validate(topo))).not.toThrow();
46
81
  });
47
82
 
48
83
  // oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
@@ -53,3 +88,7 @@ export const testAll = (
53
88
  testDetours(topo);
54
89
  });
55
90
  };
91
+
92
+ export const testAll = (topo: Topo, ctxOrFactory?: TestAllInput): void => {
93
+ registerContractSuite(topo, ctxOrFactory, validateTopo);
94
+ };