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

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 (95) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +47 -3
  3. package/package.json +16 -7
  4. package/src/all.ts +32 -7
  5. package/src/context.ts +3 -0
  6. package/src/contracts.ts +1 -16
  7. package/src/examples.ts +19 -11
  8. package/src/harness-cli.ts +16 -3
  9. package/src/harness-http.ts +263 -0
  10. package/src/harness-mcp.ts +2 -0
  11. package/src/index.ts +18 -1
  12. package/src/logger.ts +3 -1
  13. package/src/scenario.ts +3 -5
  14. package/src/signals.ts +221 -0
  15. package/src/surface-parity.ts +356 -0
  16. package/src/types.ts +136 -1
  17. package/.turbo/turbo-build.log +0 -1
  18. package/.turbo/turbo-lint.log +0 -3
  19. package/.turbo/turbo-typecheck.log +0 -1
  20. package/dist/all.d.ts +0 -36
  21. package/dist/all.d.ts.map +0 -1
  22. package/dist/all.js +0 -99
  23. package/dist/all.js.map +0 -1
  24. package/dist/assertions.d.ts +0 -72
  25. package/dist/assertions.d.ts.map +0 -1
  26. package/dist/assertions.js +0 -238
  27. package/dist/assertions.js.map +0 -1
  28. package/dist/context.d.ts +0 -76
  29. package/dist/context.d.ts.map +0 -1
  30. package/dist/context.js +0 -118
  31. package/dist/context.js.map +0 -1
  32. package/dist/contracts.d.ts +0 -17
  33. package/dist/contracts.d.ts.map +0 -1
  34. package/dist/contracts.js +0 -75
  35. package/dist/contracts.js.map +0 -1
  36. package/dist/crosses.d.ts +0 -38
  37. package/dist/crosses.d.ts.map +0 -1
  38. package/dist/crosses.js +0 -223
  39. package/dist/crosses.js.map +0 -1
  40. package/dist/detours.d.ts +0 -13
  41. package/dist/detours.d.ts.map +0 -1
  42. package/dist/detours.js +0 -109
  43. package/dist/detours.js.map +0 -1
  44. package/dist/effective-examples.d.ts +0 -30
  45. package/dist/effective-examples.d.ts.map +0 -1
  46. package/dist/effective-examples.js +0 -227
  47. package/dist/effective-examples.js.map +0 -1
  48. package/dist/examples.d.ts +0 -23
  49. package/dist/examples.d.ts.map +0 -1
  50. package/dist/examples.js +0 -240
  51. package/dist/examples.js.map +0 -1
  52. package/dist/harness-cli.d.ts +0 -21
  53. package/dist/harness-cli.d.ts.map +0 -1
  54. package/dist/harness-cli.js +0 -205
  55. package/dist/harness-cli.js.map +0 -1
  56. package/dist/harness-mcp.d.ts +0 -21
  57. package/dist/harness-mcp.d.ts.map +0 -1
  58. package/dist/harness-mcp.js +0 -54
  59. package/dist/harness-mcp.js.map +0 -1
  60. package/dist/index.d.ts +0 -18
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/index.js +0 -19
  63. package/dist/index.js.map +0 -1
  64. package/dist/logger.d.ts +0 -15
  65. package/dist/logger.d.ts.map +0 -1
  66. package/dist/logger.js +0 -87
  67. package/dist/logger.js.map +0 -1
  68. package/dist/scenario.d.ts +0 -37
  69. package/dist/scenario.d.ts.map +0 -1
  70. package/dist/scenario.js +0 -235
  71. package/dist/scenario.js.map +0 -1
  72. package/dist/trail.d.ts +0 -20
  73. package/dist/trail.d.ts.map +0 -1
  74. package/dist/trail.js +0 -80
  75. package/dist/trail.js.map +0 -1
  76. package/dist/types.d.ts +0 -113
  77. package/dist/types.d.ts.map +0 -1
  78. package/dist/types.js +0 -5
  79. package/dist/types.js.map +0 -1
  80. package/src/__tests__/all.test.ts +0 -254
  81. package/src/__tests__/context.test.ts +0 -170
  82. package/src/__tests__/contracts.test.ts +0 -239
  83. package/src/__tests__/crosses.test.ts +0 -587
  84. package/src/__tests__/detours.test.ts +0 -212
  85. package/src/__tests__/effective-examples.test.ts +0 -203
  86. package/src/__tests__/examples.test.ts +0 -636
  87. package/src/__tests__/harness-cli.test.ts +0 -90
  88. package/src/__tests__/harness-mcp.test.ts +0 -37
  89. package/src/__tests__/logger.test.ts +0 -136
  90. package/src/__tests__/partial-match.test.ts +0 -126
  91. package/src/__tests__/scenario.test.ts +0 -381
  92. package/src/__tests__/trail.test.ts +0 -99
  93. package/tsconfig.json +0 -9
  94. package/tsconfig.tests.json +0 -10
  95. package/tsconfig.tsbuildinfo +0 -1
@@ -1,254 +0,0 @@
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';
4
-
5
- import { contour, Result, trail, topo } from '@ontrails/core';
6
- import { connectDrizzle } from '@ontrails/drizzle';
7
- import { z } from 'zod';
8
-
9
- import { testAll } from '../all.js';
10
- import { store as defineStore } from '@ontrails/store';
11
-
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
- },
41
- });
42
-
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',
92
- examples: [
93
- {
94
- expected: { name: 'Alpha', source: 'mock' },
95
- input: {},
96
- name: 'Uses auto-resolved resource mock',
97
- },
98
- ],
99
- input: z.object({}),
100
- output: z.object({ name: z.string(), source: z.string() }),
101
- resources: [mockDbResource],
102
- });
103
-
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
- },
113
- description: 'Trail that prefers explicit overrides over mock factories',
114
- examples: [
115
- {
116
- expected: { name: 'Override', source: 'override' },
117
- input: {},
118
- name: 'Explicit resource override wins',
119
- },
120
- ],
121
- input: z.object({}),
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,
153
- });
154
-
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', () => {
203
- // eslint-disable-next-line jest/require-hook
204
- testAll(
205
- topo('test-all-resource-mock-app', {
206
- mockDbResource,
207
- mockedTrail,
208
- } as Record<string, unknown>)
209
- );
210
- });
211
-
212
- describe('testAll explicit resource overrides', () => {
213
- // eslint-disable-next-line jest/require-hook
214
- testAll(
215
- topo('test-all-resource-override-app', {
216
- mockDbResource,
217
- overrideTrail,
218
- } as Record<string, unknown>),
219
- () => ({
220
- resources: {
221
- 'db.mock.all': createOverrideStore(),
222
- },
223
- })
224
- );
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,170 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { Result, resource, topo } from '@ontrails/core';
4
-
5
- import {
6
- createCrossContext,
7
- createTestContext,
8
- mergeTestContext,
9
- createMockResources,
10
- } from '../context.js';
11
- import type { TestLogger } from '../types.js';
12
-
13
- // ---------------------------------------------------------------------------
14
- // Tests
15
- // ---------------------------------------------------------------------------
16
-
17
- describe('createTestContext', () => {
18
- test('produces a valid TrailContext with no args', () => {
19
- const ctx = createTestContext();
20
- expect(ctx.cwd).toBe(process.cwd());
21
- expect(ctx.requestId).toBe('test-request-001');
22
- expect(ctx.abortSignal).toBeDefined();
23
- expect(ctx.abortSignal.aborted).toBe(false);
24
- expect(ctx.logger).toBeDefined();
25
- expect(ctx.trace).toBeDefined();
26
- expect(ctx.workspaceRoot).toBe(process.cwd());
27
- });
28
-
29
- test('default logger is a TestLogger', () => {
30
- const ctx = createTestContext();
31
- const logger = ctx.logger as TestLogger;
32
- expect(typeof logger.info).toBe('function');
33
- expect(typeof logger.clear).toBe('function');
34
- expect(typeof logger.find).toBe('function');
35
- expect(typeof logger.assertLogged).toBe('function');
36
- expect(logger.entries).toBeDefined();
37
- });
38
-
39
- test('default env is set to test', () => {
40
- const ctx = createTestContext();
41
- const env = ctx['env'] as Record<string, string>;
42
- expect(env).toEqual({ TRAILS_ENV: 'test' });
43
- });
44
-
45
- test('overrides requestId', () => {
46
- const ctx = createTestContext({ requestId: 'custom-id' });
47
- expect(ctx.requestId).toBe('custom-id');
48
- });
49
-
50
- test('overrides abortSignal', () => {
51
- const controller = new AbortController();
52
- controller.abort();
53
- const ctx = createTestContext({ abortSignal: controller.signal });
54
- expect(ctx.abortSignal.aborted).toBe(true);
55
- });
56
-
57
- test('overrides cwd', () => {
58
- const ctx = createTestContext({ cwd: '/tmp/test' });
59
- expect(ctx.cwd).toBe('/tmp/test');
60
- expect(ctx.workspaceRoot).toBe('/tmp/test');
61
- });
62
-
63
- test('overrides env', () => {
64
- const ctx = createTestContext({
65
- env: { CUSTOM: '1', NODE_ENV: 'test' },
66
- });
67
- const env = ctx['env'] as Record<string, string>;
68
- expect(env).toEqual({ CUSTOM: '1', NODE_ENV: 'test' });
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
- });
89
- });
90
-
91
- describe('createCrossContext', () => {
92
- test('returns configured ok response for registered id', async () => {
93
- const cross = createCrossContext({
94
- responses: { 'entity.add': Result.ok({ id: '1', name: 'Alpha' }) },
95
- });
96
- const result = await cross('entity.add', { name: 'Alpha' });
97
- expect(result.unwrap()).toEqual({ id: '1', name: 'Alpha' });
98
- });
99
-
100
- test('returns configured err response for registered id', async () => {
101
- const err = new Error('conflict');
102
- const cross = createCrossContext({
103
- responses: { 'entity.add': Result.err(err) },
104
- });
105
- const result = await cross('entity.add', {});
106
- expect(result.isErr()).toBe(true);
107
- expect(result.error?.message).toBe('conflict');
108
- });
109
-
110
- test('returns err with descriptive message for unregistered id', async () => {
111
- const cross = createCrossContext();
112
- const result = await cross('unknown.trail', {});
113
- expect(result.isErr()).toBe(true);
114
- expect(result.error?.message).toContain('unknown.trail');
115
- });
116
-
117
- test('no options defaults to empty responses', async () => {
118
- const cross = createCrossContext();
119
- const result = await cross('any.id', {});
120
- expect(result.isErr()).toBe(true);
121
- });
122
- });
123
-
124
- describe('mergeTestContext', () => {
125
- test('merges resource overrides into extensions and the resource accessor', () => {
126
- const ctx = mergeTestContext(
127
- {
128
- extensions: { existing: true },
129
- },
130
- { 'db.main': { source: 'mock' } }
131
- );
132
-
133
- expect(ctx.extensions).toEqual({
134
- 'db.main': { source: 'mock' },
135
- existing: true,
136
- });
137
- expect(ctx.resource<{ source: string }>('db.main').source).toBe('mock');
138
- });
139
- });
140
-
141
- describe('createMockResources', () => {
142
- test('creates fresh mock resources for each invocation', async () => {
143
- let mockCalls = 0;
144
- const mockable = resource(`resource.mock.${Bun.randomUUIDv7()}`, {
145
- create: () => Result.ok({ source: 'factory' }),
146
- mock: () => {
147
- mockCalls += 1;
148
- return Promise.resolve({ source: 'mock' });
149
- },
150
- });
151
- const app = topo('mock-app', { mockable } as Record<string, unknown>);
152
-
153
- const first = await createMockResources(app);
154
- const second = await createMockResources(app);
155
-
156
- expect(first).toEqual({ [mockable.id]: { source: 'mock' } });
157
- expect(second).toEqual(first);
158
- expect(second[mockable.id]).not.toBe(first[mockable.id]);
159
- expect(mockCalls).toBe(2);
160
- });
161
-
162
- test('skips resources without mock factories', async () => {
163
- const plain = resource(`resource.plain.${Bun.randomUUIDv7()}`, {
164
- create: () => Result.ok({ source: 'factory' }),
165
- });
166
- const app = topo('plain-app', { plain } as Record<string, unknown>);
167
-
168
- expect(await createMockResources(app)).toEqual({});
169
- });
170
- });
@@ -1,239 +0,0 @@
1
- import { afterAll, describe, expect, mock, test } from 'bun:test';
2
-
3
- import { contour, Result, resource, trail, topo } from '@ontrails/core';
4
- import { z } from 'zod';
5
-
6
- import { testContracts } from '../contracts.js';
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
-
20
- // ---------------------------------------------------------------------------
21
- // Test trails
22
- // ---------------------------------------------------------------------------
23
-
24
- /** Trail whose implementation matches the output schema. */
25
- const validTrail = trail('valid', {
26
- blaze: (input: { name: string }) => Result.ok({ id: 1, name: input.name }),
27
- examples: [
28
- {
29
- expected: { id: 1, name: 'Alpha' },
30
- input: { name: 'Alpha' },
31
- name: 'Valid output',
32
- },
33
- ],
34
- input: z.object({ name: z.string() }),
35
- output: z.object({ id: z.number(), name: z.string() }),
36
- });
37
-
38
- /** Trail without output schema -- should be skipped. */
39
- const noSchemaTrail = trail('noschema', {
40
- blaze: (input: { x: number }) => Result.ok(input.x * 2),
41
- examples: [{ expected: 10, input: { x: 5 }, name: 'No schema' }],
42
- input: z.object({ x: z.number() }),
43
- });
44
-
45
- /** Trail without examples -- should be skipped. */
46
- const noExamplesTrail = trail('noexamples', {
47
- blaze: (input: { x: number }) => Result.ok({ value: input.x }),
48
- input: z.object({ x: z.number() }),
49
- output: z.object({ value: z.number() }),
50
- });
51
-
52
- // ---------------------------------------------------------------------------
53
- // Composition trail
54
- // ---------------------------------------------------------------------------
55
-
56
- /** Composition trail whose implementation matches the output schema. */
57
- const compositionTrail = trail('composition.valid', {
58
- blaze: (input: { a: number; b: number }) =>
59
- Result.ok({ total: input.a + input.b }),
60
- crosses: ['valid'],
61
- examples: [
62
- {
63
- expected: { total: 3 },
64
- input: { a: 1, b: 2 },
65
- name: 'Valid composition output',
66
- },
67
- ],
68
- input: z.object({ a: z.number(), b: z.number() }),
69
- output: z.object({ total: z.number() }),
70
- });
71
-
72
- const transformedInputTrail = trail('contract.transformed', {
73
- blaze: (input: { value: number }) => Result.ok({ value: input.value }),
74
- examples: [
75
- {
76
- expected: { value: 2 },
77
- input: { value: '1' },
78
- name: 'Raw contract input is only transformed once',
79
- },
80
- ],
81
- input: z
82
- .object({ value: z.string() })
83
- .transform(({ value }) => ({ value: Number(value) + 1 })),
84
- output: z.object({ value: z.number() }),
85
- });
86
-
87
- const undeclaredContractDbResource = resource('db.undeclared.contracts', {
88
- create: () => Result.ok({ source: 'factory' }),
89
- mock: () => ({ source: 'mock' }),
90
- });
91
-
92
- const undeclaredContractTrail = trail('resource.undeclared.contracts', {
93
- blaze: (_input, ctx) =>
94
- Result.ok({
95
- hasInjectedResource:
96
- ctx.extensions?.[undeclaredContractDbResource.id] !== undefined,
97
- }),
98
- examples: [
99
- {
100
- expected: { hasInjectedResource: false },
101
- input: {},
102
- name: 'Undeclared resources are not preloaded into contract contexts',
103
- },
104
- ],
105
- input: z.object({}),
106
- output: z.object({ hasInjectedResource: z.literal(false) }),
107
- });
108
-
109
- const ctxOverrideContractResource = resource('db.mock.contracts', {
110
- create: () => Result.ok({ source: 'factory' }),
111
- mock: () => ({ source: 'mock' }),
112
- });
113
-
114
- const ctxOverrideContractTrail = trail('resource.ctx.contracts', {
115
- blaze: (_input, ctx) =>
116
- Result.ok({ source: ctxOverrideContractResource.from(ctx).source }),
117
- examples: [
118
- {
119
- expected: { source: 'ctx' },
120
- input: {},
121
- name: 'Context extensions beat auto-resolved contract resource mocks',
122
- },
123
- ],
124
- input: z.object({}),
125
- output: z.object({ source: z.literal('ctx') }),
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,
155
- });
156
-
157
- // ---------------------------------------------------------------------------
158
- // Tests
159
- // ---------------------------------------------------------------------------
160
-
161
- describe('testContracts: valid output matches schema', () => {
162
- // eslint-disable-next-line jest/require-hook
163
- testContracts(topo('test-app', { validTrail } as Record<string, unknown>));
164
- });
165
-
166
- describe('testContracts: skips trails without output schemas', () => {
167
- // eslint-disable-next-line jest/require-hook
168
- testContracts(topo('test-app', { noSchemaTrail } as Record<string, unknown>));
169
-
170
- test('no-op marker', () => {
171
- // Trail without output schema is skipped -- no contract tests generated
172
- });
173
- });
174
-
175
- describe('testContracts: skips trails without examples', () => {
176
- // eslint-disable-next-line jest/require-hook
177
- testContracts(
178
- topo('test-app', { noExamplesTrail } as Record<string, unknown>)
179
- );
180
-
181
- test('no-op marker', () => {
182
- // Trail without examples is skipped -- no contract tests generated
183
- });
184
- });
185
-
186
- describe('testContracts: validates output schemas for trails with crossings', () => {
187
- // eslint-disable-next-line jest/require-hook
188
- testContracts(
189
- topo('test-app', { compositionTrail } as Record<string, unknown>)
190
- );
191
- });
192
-
193
- describe('testContracts: raw transformed input', () => {
194
- // eslint-disable-next-line jest/require-hook
195
- testContracts(
196
- topo('transformed-contract-app', {
197
- transformedInputTrail,
198
- } as Record<string, unknown>)
199
- );
200
- });
201
-
202
- describe('testContracts: context extension overrides', () => {
203
- // eslint-disable-next-line jest/require-hook
204
- testContracts(
205
- topo('ctx-contract-app', {
206
- ctxOverrideContractResource,
207
- ctxOverrideContractTrail,
208
- } as Record<string, unknown>),
209
- {
210
- ctx: {
211
- extensions: { 'db.mock.contracts': { source: 'ctx' } },
212
- },
213
- }
214
- );
215
- });
216
-
217
- describe('testContracts resource declarations', () => {
218
- // eslint-disable-next-line jest/require-hook
219
- testContracts(
220
- topo('undeclared-contract-resource-app', {
221
- undeclaredContractDbResource,
222
- undeclaredContractTrail,
223
- } as Record<string, unknown>)
224
- );
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
- });