@ontrails/testing 1.0.0-beta.13 → 1.0.0-beta.15

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 (83) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +11 -11
  4. package/dist/all.d.ts +10 -5
  5. package/dist/all.d.ts.map +1 -1
  6. package/dist/all.js +78 -26
  7. package/dist/all.js.map +1 -1
  8. package/dist/assertions.d.ts +23 -0
  9. package/dist/assertions.d.ts.map +1 -1
  10. package/dist/assertions.js +154 -0
  11. package/dist/assertions.js.map +1 -1
  12. package/dist/context.d.ts +17 -16
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +31 -20
  15. package/dist/context.js.map +1 -1
  16. package/dist/contracts.d.ts.map +1 -1
  17. package/dist/contracts.js +9 -5
  18. package/dist/contracts.js.map +1 -1
  19. package/dist/crosses.d.ts +4 -4
  20. package/dist/crosses.d.ts.map +1 -1
  21. package/dist/crosses.js +49 -39
  22. package/dist/crosses.js.map +1 -1
  23. package/dist/detours.d.ts +5 -4
  24. package/dist/detours.d.ts.map +1 -1
  25. package/dist/detours.js +93 -14
  26. package/dist/detours.js.map +1 -1
  27. package/dist/effective-examples.d.ts +30 -0
  28. package/dist/effective-examples.d.ts.map +1 -0
  29. package/dist/effective-examples.js +227 -0
  30. package/dist/effective-examples.js.map +1 -0
  31. package/dist/examples.d.ts +1 -1
  32. package/dist/examples.d.ts.map +1 -1
  33. package/dist/examples.js +79 -41
  34. package/dist/examples.js.map +1 -1
  35. package/dist/harness-cli.d.ts +3 -3
  36. package/dist/harness-cli.d.ts.map +1 -1
  37. package/dist/harness-cli.js +25 -33
  38. package/dist/harness-cli.js.map +1 -1
  39. package/dist/harness-mcp.d.ts +3 -3
  40. package/dist/harness-mcp.d.ts.map +1 -1
  41. package/dist/harness-mcp.js +9 -8
  42. package/dist/harness-mcp.js.map +1 -1
  43. package/dist/index.d.ts +6 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -2
  46. package/dist/index.js.map +1 -1
  47. package/dist/scenario.d.ts +37 -0
  48. package/dist/scenario.d.ts.map +1 -0
  49. package/dist/scenario.js +235 -0
  50. package/dist/scenario.js.map +1 -0
  51. package/dist/types.d.ts +38 -5
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +9 -5
  54. package/src/__tests__/all.test.ts +217 -29
  55. package/src/__tests__/context.test.ts +32 -12
  56. package/src/__tests__/contracts.test.ts +72 -18
  57. package/src/__tests__/crosses.test.ts +78 -78
  58. package/src/__tests__/detours.test.ts +176 -19
  59. package/src/__tests__/effective-examples.test.ts +203 -0
  60. package/src/__tests__/examples.test.ts +152 -50
  61. package/src/__tests__/harness-cli.test.ts +90 -0
  62. package/src/__tests__/harness-mcp.test.ts +37 -0
  63. package/src/__tests__/partial-match.test.ts +126 -0
  64. package/src/__tests__/scenario.test.ts +381 -0
  65. package/src/all.ts +149 -12
  66. package/src/assertions.ts +253 -0
  67. package/src/context.ts +64 -38
  68. package/src/contracts.ts +14 -8
  69. package/src/crosses.ts +93 -51
  70. package/src/detours.ts +155 -18
  71. package/src/effective-examples.ts +350 -0
  72. package/src/examples.ts +127 -59
  73. package/src/harness-cli.ts +33 -49
  74. package/src/harness-mcp.ts +9 -8
  75. package/src/index.ts +13 -3
  76. package/src/scenario.ts +370 -0
  77. package/src/types.ts +63 -5
  78. package/tsconfig.tests.json +10 -0
  79. package/tsconfig.tsbuildinfo +1 -1
  80. package/dist/follows.d.ts +0 -38
  81. package/dist/follows.d.ts.map +0 -1
  82. package/dist/follows.js +0 -212
  83. package/dist/follows.js.map +0 -1
package/dist/types.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Shared types for @ontrails/testing.
3
3
  */
4
- import type { Logger, Topo } from '@ontrails/core';
4
+ import type { DeriveCliCommandsOptions } from '@ontrails/cli';
5
+ import type { McpExtra, DeriveMcpToolsOptions } from '@ontrails/mcp';
6
+ import type { AnyTrail, Logger, Topo, TraceFn, TrailContext } from '@ontrails/core';
5
7
  import type { LogLevel, LogRecord } from '@ontrails/logging';
6
8
  /** A custom test scenario for a single trail. */
7
9
  export interface TestScenario {
@@ -45,10 +47,12 @@ export interface TestTrailContextOptions {
45
47
  readonly logger?: Logger | undefined;
46
48
  readonly requestId?: string | undefined;
47
49
  readonly abortSignal?: AbortSignal | undefined;
50
+ readonly trace?: TraceFn | undefined;
48
51
  }
49
52
  /** Options for creating a CLI harness. */
50
- export interface CliHarnessOptions {
51
- readonly app: Topo;
53
+ export interface CliHarnessOptions extends Omit<DeriveCliCommandsOptions, 'onResult' | 'presets' | 'resolveInput'> {
54
+ readonly ctx?: Partial<TrailContext> | undefined;
55
+ readonly graph: Topo;
52
56
  }
53
57
  /** A test harness for CLI commands. */
54
58
  export interface CliHarness {
@@ -64,8 +68,9 @@ export interface CliHarnessResult {
64
68
  readonly stdout: string;
65
69
  }
66
70
  /** Options for creating an MCP harness. */
67
- export interface McpHarnessOptions {
68
- readonly app: Topo;
71
+ export interface McpHarnessOptions extends DeriveMcpToolsOptions {
72
+ readonly extra?: Partial<McpExtra> | undefined;
73
+ readonly graph: Topo;
69
74
  }
70
75
  /** A test harness for MCP tools. */
71
76
  export interface McpHarness {
@@ -77,4 +82,32 @@ export interface McpHarnessResult {
77
82
  readonly content: unknown;
78
83
  readonly isError: boolean;
79
84
  }
85
+ export interface TestAllEstablishedOptions {
86
+ readonly cli?: Omit<CliHarnessOptions, 'graph'> | undefined;
87
+ readonly createPermit?: ((trail: {
88
+ readonly permit?: {
89
+ readonly scopes: readonly string[];
90
+ } | 'public' | undefined;
91
+ }) => {
92
+ readonly id: string;
93
+ readonly scopes: readonly string[];
94
+ } | undefined) | undefined;
95
+ readonly ctx?: Partial<TrailContext> | undefined;
96
+ readonly mcp?: Omit<McpHarnessOptions, 'graph'> | undefined;
97
+ readonly resources?: Record<string, unknown> | undefined;
98
+ readonly strictPermits?: boolean | undefined;
99
+ }
100
+ /** Marker for cross-step references in scenario inputs. */
101
+ export interface RefToken {
102
+ readonly __ref: true;
103
+ readonly path: string;
104
+ }
105
+ /** A single step in a scenario. */
106
+ export interface ScenarioStep {
107
+ readonly cross: AnyTrail;
108
+ readonly input: Record<string, unknown>;
109
+ readonly as?: string | undefined;
110
+ readonly expected?: unknown | undefined;
111
+ readonly expectedMatch?: unknown | undefined;
112
+ }
80
113
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/testing",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.15",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -13,11 +13,15 @@
13
13
  "lint": "oxlint ./src",
14
14
  "clean": "rm -rf dist *.tsbuildinfo"
15
15
  },
16
+ "devDependencies": {
17
+ "@ontrails/drizzle": "^1.0.0-beta.14",
18
+ "@ontrails/store": "^1.0.0-beta.14"
19
+ },
16
20
  "peerDependencies": {
17
- "@ontrails/cli": "^1.0.0-beta.12",
18
- "@ontrails/core": "^1.0.0-beta.12",
19
- "@ontrails/logging": "^1.0.0-beta.12",
20
- "@ontrails/mcp": "^1.0.0-beta.12",
21
+ "@ontrails/cli": "^1.0.0-beta.14",
22
+ "@ontrails/core": "^1.0.0-beta.14",
23
+ "@ontrails/logging": "^1.0.0-beta.14",
24
+ "@ontrails/mcp": "^1.0.0-beta.14",
21
25
  "zod": "^4.3.5"
22
26
  }
23
27
  }
@@ -1,66 +1,254 @@
1
- import { describe } from 'bun:test';
1
+ import { afterAll, describe, expect, mock, test } from 'bun:test';
2
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
2
4
 
3
- import { Result, provision, trail, topo } from '@ontrails/core';
5
+ import { contour, Result, trail, topo } from '@ontrails/core';
6
+ import { connectDrizzle } from '@ontrails/drizzle';
4
7
  import { z } from 'zod';
5
8
 
6
9
  import { testAll } from '../all.js';
10
+ import { store as defineStore } from '@ontrails/store';
7
11
 
8
- const mockDbProvision = provision('db.mock.all', {
9
- create: () => Result.ok({ source: 'factory' }),
10
- mock: () => ({ source: 'mock' }),
12
+ const requireContourExample = (
13
+ contourDef: { examples?: readonly Record<string, unknown>[] },
14
+ index: number
15
+ ) => {
16
+ const example = contourDef.examples?.[index];
17
+ expect(example).toBeDefined();
18
+ if (!example) {
19
+ throw new Error(`Expected contour example at index ${index}`);
20
+ }
21
+ return example;
22
+ };
23
+
24
+ const dbDefinition = defineStore({
25
+ entities: {
26
+ fixtures: [
27
+ {
28
+ id: 'seed-1',
29
+ name: 'Alpha',
30
+ source: 'mock',
31
+ },
32
+ ],
33
+ generated: ['id'],
34
+ primaryKey: 'id',
35
+ schema: z.object({
36
+ id: z.string(),
37
+ name: z.string(),
38
+ source: z.string(),
39
+ }),
40
+ },
11
41
  });
12
42
 
13
- const mockedTrail = trail('provision.mocked.all', {
14
- blaze: (_input, ctx) =>
15
- Result.ok({ source: mockDbProvision.from(ctx).source }),
16
- description: 'Trail that uses a mocked provision through testAll',
43
+ const createDbResource = (
44
+ seed?: readonly {
45
+ readonly id: string;
46
+ readonly name: string;
47
+ readonly source: string;
48
+ }[]
49
+ ) =>
50
+ connectDrizzle(dbDefinition, {
51
+ id: 'db.mock.all',
52
+ ...(seed === undefined ? {} : { mockSeed: { entities: seed } }),
53
+ url: ':memory:',
54
+ });
55
+
56
+ const createOverrideStore = () => {
57
+ const { mock: mockFactory } = createDbResource([
58
+ {
59
+ id: 'seed-1',
60
+ name: 'Override',
61
+ source: 'override',
62
+ },
63
+ ]);
64
+
65
+ if (mockFactory === undefined) {
66
+ throw new Error('Expected drizzle test store to expose a mock factory');
67
+ }
68
+
69
+ const created = mockFactory();
70
+ if (created instanceof Promise) {
71
+ throw new TypeError(
72
+ 'Expected drizzle test store mock to resolve synchronously'
73
+ );
74
+ }
75
+
76
+ return created;
77
+ };
78
+
79
+ const mockDbResource = createDbResource();
80
+
81
+ const mockedTrail = trail('resource.mocked.all', {
82
+ blaze: async (_input, ctx) => {
83
+ const entity = await mockDbResource.from(ctx).entities.get('seed-1');
84
+ if (entity === null) {
85
+ return Result.err(new Error('expected seeded entity to exist'));
86
+ }
87
+
88
+ return Result.ok({ name: entity.name, source: entity.source });
89
+ },
90
+ description:
91
+ 'Trail that uses a mocked connector-bound resource through testAll',
17
92
  examples: [
18
93
  {
19
- expected: { source: 'mock' },
94
+ expected: { name: 'Alpha', source: 'mock' },
20
95
  input: {},
21
- name: 'Uses auto-resolved provision mock',
96
+ name: 'Uses auto-resolved resource mock',
22
97
  },
23
98
  ],
24
99
  input: z.object({}),
25
- output: z.object({ source: z.string() }),
26
- provisions: [mockDbProvision],
100
+ output: z.object({ name: z.string(), source: z.string() }),
101
+ resources: [mockDbResource],
27
102
  });
28
103
 
29
- const overrideTrail = trail('provision.override.all', {
30
- blaze: (_input, ctx) =>
31
- Result.ok({ source: mockDbProvision.from(ctx).source }),
104
+ const overrideTrail = trail('resource.override.all', {
105
+ blaze: async (_input, ctx) => {
106
+ const entity = await mockDbResource.from(ctx).entities.get('seed-1');
107
+ if (entity === null) {
108
+ return Result.err(new Error('expected overridden entity to exist'));
109
+ }
110
+
111
+ return Result.ok({ name: entity.name, source: entity.source });
112
+ },
32
113
  description: 'Trail that prefers explicit overrides over mock factories',
33
114
  examples: [
34
115
  {
35
- expected: { source: 'override' },
116
+ expected: { name: 'Override', source: 'override' },
36
117
  input: {},
37
- name: 'Explicit provision override wins',
118
+ name: 'Explicit resource override wins',
38
119
  },
39
120
  ],
40
121
  input: z.object({}),
41
- output: z.object({ source: z.string() }),
42
- provisions: [mockDbProvision],
122
+ output: z.object({ name: z.string(), source: z.string() }),
123
+ resources: [mockDbResource],
124
+ });
125
+
126
+ const contourFixture = contour(
127
+ 'allFixture',
128
+ {
129
+ id: z.string().uuid(),
130
+ name: z.string(),
131
+ },
132
+ {
133
+ examples: [
134
+ {
135
+ id: 'f46b837e-6c8d-42ec-8539-536f4e6daf0e',
136
+ name: 'Contour-derived governance fixture',
137
+ },
138
+ ],
139
+ identity: 'id',
140
+ }
141
+ );
142
+
143
+ const contourDerivedBlaze = mock(() =>
144
+ Result.ok(requireContourExample(contourFixture, 0))
145
+ );
146
+
147
+ const contourDerivedTrail = trail('contour.derived.all', {
148
+ blaze: () => contourDerivedBlaze(),
149
+ contours: [contourFixture],
150
+ description: 'Trail that relies on contour-derived fixtures inside testAll',
151
+ input: z.object({ id: contourFixture.shape.id }),
152
+ output: contourFixture,
43
153
  });
44
154
 
45
- describe('testAll provision mocks', () => {
155
+ const repoTempDir = (): string =>
156
+ join(
157
+ resolve(import.meta.dir, '../..'),
158
+ '.tmp-tests',
159
+ `test-all-established-${Date.now()}-${Math.random().toString(36).slice(2)}`
160
+ );
161
+
162
+ const runGeneratedGovernanceSuite = (
163
+ helperName: 'testAll' | 'testAllEstablished'
164
+ ): { readonly exitCode: number; readonly output: string } => {
165
+ const dir = repoTempDir();
166
+ const testFile = join(dir, `${helperName}.test.ts`);
167
+
168
+ mkdirSync(dir, { recursive: true });
169
+ writeFileSync(
170
+ testFile,
171
+ `import { Result, trail, topo } from '@ontrails/core';
172
+ import { ${helperName} } from '../../src/index.ts';
173
+ import { z } from 'zod';
174
+
175
+ const draftTrail = trail('_draft.entity.prepare', {
176
+ blaze: async () => Result.ok({ ok: true }),
177
+ input: z.object({}),
178
+ output: z.object({ ok: z.boolean() }),
179
+ });
180
+
181
+ ${helperName}(topo('draft-topo', { draftTrail }));
182
+ `
183
+ );
184
+
185
+ try {
186
+ const proc = Bun.spawnSync({
187
+ cmd: ['bun', 'test', testFile, '--bail'],
188
+ cwd: resolve(import.meta.dir, '..', '..', '..'),
189
+ stderr: 'pipe',
190
+ stdout: 'pipe',
191
+ });
192
+
193
+ return {
194
+ exitCode: proc.exitCode,
195
+ output: `${proc.stdout.toString()}\n${proc.stderr.toString()}`,
196
+ };
197
+ } finally {
198
+ rmSync(dir, { force: true, recursive: true });
199
+ }
200
+ };
201
+
202
+ describe('testAll resource mocks', () => {
46
203
  // eslint-disable-next-line jest/require-hook
47
204
  testAll(
48
- topo('test-all-provision-mock-app', {
49
- mockDbProvision,
205
+ topo('test-all-resource-mock-app', {
206
+ mockDbResource,
50
207
  mockedTrail,
51
208
  } as Record<string, unknown>)
52
209
  );
53
210
  });
54
211
 
55
- describe('testAll explicit provision overrides', () => {
212
+ describe('testAll explicit resource overrides', () => {
56
213
  // eslint-disable-next-line jest/require-hook
57
214
  testAll(
58
- topo('test-all-provision-override-app', {
59
- mockDbProvision,
215
+ topo('test-all-resource-override-app', {
216
+ mockDbResource,
60
217
  overrideTrail,
61
218
  } as Record<string, unknown>),
62
- {
63
- provisions: { 'db.mock.all': { source: 'override' } },
64
- }
219
+ () => ({
220
+ resources: {
221
+ 'db.mock.all': createOverrideStore(),
222
+ },
223
+ })
65
224
  );
66
225
  });
226
+
227
+ describe('testAll contour-derived fixtures', () => {
228
+ // eslint-disable-next-line jest/require-hook
229
+ testAll(
230
+ topo('test-all-contour-derived-app', {
231
+ contourDerivedTrail,
232
+ contourFixture,
233
+ } as Record<string, unknown>)
234
+ );
235
+
236
+ afterAll(() => {
237
+ expect(contourDerivedBlaze).toHaveBeenCalledTimes(2);
238
+ });
239
+ });
240
+
241
+ describe('testAllEstablished draft hygiene', () => {
242
+ test('keeps testAll draft-friendly for in-progress graphs', () => {
243
+ const result = runGeneratedGovernanceSuite('testAll');
244
+
245
+ expect(result.exitCode).toBe(0);
246
+ });
247
+
248
+ test('fails draft-contaminated graphs under testAllEstablished', () => {
249
+ const result = runGeneratedGovernanceSuite('testAllEstablished');
250
+
251
+ expect(result.exitCode).toBe(1);
252
+ expect(result.output).toContain('_draft.entity.prepare');
253
+ });
254
+ });
@@ -1,12 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
 
3
- import { Result, provision, topo } from '@ontrails/core';
3
+ import { Result, resource, topo } from '@ontrails/core';
4
4
 
5
5
  import {
6
6
  createCrossContext,
7
7
  createTestContext,
8
8
  mergeTestContext,
9
- resolveMockProvisions,
9
+ createMockResources,
10
10
  } from '../context.js';
11
11
  import type { TestLogger } from '../types.js';
12
12
 
@@ -22,6 +22,7 @@ describe('createTestContext', () => {
22
22
  expect(ctx.abortSignal).toBeDefined();
23
23
  expect(ctx.abortSignal.aborted).toBe(false);
24
24
  expect(ctx.logger).toBeDefined();
25
+ expect(ctx.trace).toBeDefined();
25
26
  expect(ctx.workspaceRoot).toBe(process.cwd());
26
27
  });
27
28
 
@@ -66,6 +67,25 @@ describe('createTestContext', () => {
66
67
  const env = ctx['env'] as Record<string, string>;
67
68
  expect(env).toEqual({ CUSTOM: '1', NODE_ENV: 'test' });
68
69
  });
70
+
71
+ test('default trace passthrough resolves the callback result', async () => {
72
+ const ctx = createTestContext();
73
+ const result = await ctx.trace('test-span', () => Promise.resolve('ok'));
74
+ expect(result).toBe('ok');
75
+ });
76
+
77
+ test('overrides trace', async () => {
78
+ const calls: string[] = [];
79
+ const ctx = createTestContext({
80
+ trace: async (label, fn) => {
81
+ calls.push(label);
82
+ return await fn();
83
+ },
84
+ });
85
+
86
+ await ctx.trace('custom-span', () => Promise.resolve('done'));
87
+ expect(calls).toEqual(['custom-span']);
88
+ });
69
89
  });
70
90
 
71
91
  describe('createCrossContext', () => {
@@ -102,7 +122,7 @@ describe('createCrossContext', () => {
102
122
  });
103
123
 
104
124
  describe('mergeTestContext', () => {
105
- test('merges provision overrides into extensions and the provision accessor', () => {
125
+ test('merges resource overrides into extensions and the resource accessor', () => {
106
126
  const ctx = mergeTestContext(
107
127
  {
108
128
  extensions: { existing: true },
@@ -114,14 +134,14 @@ describe('mergeTestContext', () => {
114
134
  'db.main': { source: 'mock' },
115
135
  existing: true,
116
136
  });
117
- expect(ctx.provision<{ source: string }>('db.main').source).toBe('mock');
137
+ expect(ctx.resource<{ source: string }>('db.main').source).toBe('mock');
118
138
  });
119
139
  });
120
140
 
121
- describe('resolveMockProvisions', () => {
122
- test('creates fresh mock provisions for each invocation', async () => {
141
+ describe('createMockResources', () => {
142
+ test('creates fresh mock resources for each invocation', async () => {
123
143
  let mockCalls = 0;
124
- const mockable = provision(`provision.mock.${Bun.randomUUIDv7()}`, {
144
+ const mockable = resource(`resource.mock.${Bun.randomUUIDv7()}`, {
125
145
  create: () => Result.ok({ source: 'factory' }),
126
146
  mock: () => {
127
147
  mockCalls += 1;
@@ -130,8 +150,8 @@ describe('resolveMockProvisions', () => {
130
150
  });
131
151
  const app = topo('mock-app', { mockable } as Record<string, unknown>);
132
152
 
133
- const first = await resolveMockProvisions(app);
134
- const second = await resolveMockProvisions(app);
153
+ const first = await createMockResources(app);
154
+ const second = await createMockResources(app);
135
155
 
136
156
  expect(first).toEqual({ [mockable.id]: { source: 'mock' } });
137
157
  expect(second).toEqual(first);
@@ -139,12 +159,12 @@ describe('resolveMockProvisions', () => {
139
159
  expect(mockCalls).toBe(2);
140
160
  });
141
161
 
142
- test('skips provisions without mock factories', async () => {
143
- const plain = provision(`provision.plain.${Bun.randomUUIDv7()}`, {
162
+ test('skips resources without mock factories', async () => {
163
+ const plain = resource(`resource.plain.${Bun.randomUUIDv7()}`, {
144
164
  create: () => Result.ok({ source: 'factory' }),
145
165
  });
146
166
  const app = topo('plain-app', { plain } as Record<string, unknown>);
147
167
 
148
- expect(await resolveMockProvisions(app)).toEqual({});
168
+ expect(await createMockResources(app)).toEqual({});
149
169
  });
150
170
  });
@@ -1,10 +1,22 @@
1
- import { describe, test } from 'bun:test';
1
+ import { afterAll, describe, expect, mock, test } from 'bun:test';
2
2
 
3
- import { Result, provision, trail, topo } from '@ontrails/core';
3
+ import { contour, Result, resource, trail, topo } from '@ontrails/core';
4
4
  import { z } from 'zod';
5
5
 
6
6
  import { testContracts } from '../contracts.js';
7
7
 
8
+ const requireContourExample = (
9
+ contourDef: { examples?: readonly Record<string, unknown>[] },
10
+ index: number
11
+ ) => {
12
+ const example = contourDef.examples?.[index];
13
+ expect(example).toBeDefined();
14
+ if (!example) {
15
+ throw new Error(`Expected contour example at index ${index}`);
16
+ }
17
+ return example;
18
+ };
19
+
8
20
  // ---------------------------------------------------------------------------
9
21
  // Test trails
10
22
  // ---------------------------------------------------------------------------
@@ -72,46 +84,74 @@ const transformedInputTrail = trail('contract.transformed', {
72
84
  output: z.object({ value: z.number() }),
73
85
  });
74
86
 
75
- const undeclaredContractDbProvision = provision('db.undeclared.contracts', {
87
+ const undeclaredContractDbResource = resource('db.undeclared.contracts', {
76
88
  create: () => Result.ok({ source: 'factory' }),
77
89
  mock: () => ({ source: 'mock' }),
78
90
  });
79
91
 
80
- const undeclaredContractTrail = trail('provision.undeclared.contracts', {
92
+ const undeclaredContractTrail = trail('resource.undeclared.contracts', {
81
93
  blaze: (_input, ctx) =>
82
94
  Result.ok({
83
- hasInjectedProvision:
84
- ctx.extensions?.[undeclaredContractDbProvision.id] !== undefined,
95
+ hasInjectedResource:
96
+ ctx.extensions?.[undeclaredContractDbResource.id] !== undefined,
85
97
  }),
86
98
  examples: [
87
99
  {
88
- expected: { hasInjectedProvision: false },
100
+ expected: { hasInjectedResource: false },
89
101
  input: {},
90
- name: 'Undeclared provisions are not preloaded into contract contexts',
102
+ name: 'Undeclared resources are not preloaded into contract contexts',
91
103
  },
92
104
  ],
93
105
  input: z.object({}),
94
- output: z.object({ hasInjectedProvision: z.literal(false) }),
106
+ output: z.object({ hasInjectedResource: z.literal(false) }),
95
107
  });
96
108
 
97
- const ctxOverrideContractProvision = provision('db.mock.contracts', {
109
+ const ctxOverrideContractResource = resource('db.mock.contracts', {
98
110
  create: () => Result.ok({ source: 'factory' }),
99
111
  mock: () => ({ source: 'mock' }),
100
112
  });
101
113
 
102
- const ctxOverrideContractTrail = trail('provision.ctx.contracts', {
114
+ const ctxOverrideContractTrail = trail('resource.ctx.contracts', {
103
115
  blaze: (_input, ctx) =>
104
- Result.ok({ source: ctxOverrideContractProvision.from(ctx).source }),
116
+ Result.ok({ source: ctxOverrideContractResource.from(ctx).source }),
105
117
  examples: [
106
118
  {
107
119
  expected: { source: 'ctx' },
108
120
  input: {},
109
- name: 'Context extensions beat auto-resolved contract provision mocks',
121
+ name: 'Context extensions beat auto-resolved contract resource mocks',
110
122
  },
111
123
  ],
112
124
  input: z.object({}),
113
125
  output: z.object({ source: z.literal('ctx') }),
114
- provisions: [ctxOverrideContractProvision],
126
+ resources: [ctxOverrideContractResource],
127
+ });
128
+
129
+ const derivedContractContour = contour(
130
+ 'contractFixture',
131
+ {
132
+ id: z.string().uuid(),
133
+ name: z.string(),
134
+ },
135
+ {
136
+ examples: [
137
+ {
138
+ id: '03a5873c-0ca6-43c4-9201-3cb3c07ca6bf',
139
+ name: 'Contour contract fixture',
140
+ },
141
+ ],
142
+ identity: 'id',
143
+ }
144
+ );
145
+
146
+ const derivedContractBlaze = mock(() =>
147
+ Result.ok(requireContourExample(derivedContractContour, 0))
148
+ );
149
+
150
+ const derivedContractTrail = trail('contract.derived', {
151
+ blaze: () => derivedContractBlaze(),
152
+ contours: [derivedContractContour],
153
+ input: z.object({ id: derivedContractContour.shape.id }),
154
+ output: derivedContractContour,
115
155
  });
116
156
 
117
157
  // ---------------------------------------------------------------------------
@@ -163,7 +203,7 @@ describe('testContracts: context extension overrides', () => {
163
203
  // eslint-disable-next-line jest/require-hook
164
204
  testContracts(
165
205
  topo('ctx-contract-app', {
166
- ctxOverrideContractProvision,
206
+ ctxOverrideContractResource,
167
207
  ctxOverrideContractTrail,
168
208
  } as Record<string, unknown>),
169
209
  {
@@ -174,12 +214,26 @@ describe('testContracts: context extension overrides', () => {
174
214
  );
175
215
  });
176
216
 
177
- describe('testContracts provision declarations', () => {
217
+ describe('testContracts resource declarations', () => {
178
218
  // eslint-disable-next-line jest/require-hook
179
219
  testContracts(
180
- topo('undeclared-contract-provision-app', {
181
- undeclaredContractDbProvision,
220
+ topo('undeclared-contract-resource-app', {
221
+ undeclaredContractDbResource,
182
222
  undeclaredContractTrail,
183
223
  } as Record<string, unknown>)
184
224
  );
185
225
  });
226
+
227
+ describe('testContracts derives contour examples when trail examples are absent', () => {
228
+ // eslint-disable-next-line jest/require-hook
229
+ testContracts(
230
+ topo('derived-contract-app', {
231
+ derivedContractContour,
232
+ derivedContractTrail,
233
+ } as Record<string, unknown>)
234
+ );
235
+
236
+ afterAll(() => {
237
+ expect(derivedContractBlaze).toHaveBeenCalledTimes(1);
238
+ });
239
+ });