@ontrails/testing 1.0.0-beta.14 → 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.
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +11 -11
- package/dist/all.d.ts +10 -5
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js +78 -26
- package/dist/all.js.map +1 -1
- package/dist/assertions.d.ts +23 -0
- package/dist/assertions.d.ts.map +1 -1
- package/dist/assertions.js +154 -0
- package/dist/assertions.js.map +1 -1
- package/dist/context.d.ts +17 -16
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +31 -20
- package/dist/context.js.map +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +9 -5
- package/dist/contracts.js.map +1 -1
- package/dist/crosses.d.ts +4 -4
- package/dist/crosses.d.ts.map +1 -1
- package/dist/crosses.js +49 -39
- package/dist/crosses.js.map +1 -1
- package/dist/detours.d.ts +5 -4
- package/dist/detours.d.ts.map +1 -1
- package/dist/detours.js +93 -14
- package/dist/detours.js.map +1 -1
- package/dist/effective-examples.d.ts +30 -0
- package/dist/effective-examples.d.ts.map +1 -0
- package/dist/effective-examples.js +227 -0
- package/dist/effective-examples.js.map +1 -0
- package/dist/examples.d.ts +1 -1
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +79 -41
- package/dist/examples.js.map +1 -1
- package/dist/harness-cli.d.ts +3 -3
- package/dist/harness-cli.d.ts.map +1 -1
- package/dist/harness-cli.js +16 -11
- package/dist/harness-cli.js.map +1 -1
- package/dist/harness-mcp.d.ts +3 -3
- package/dist/harness-mcp.d.ts.map +1 -1
- package/dist/harness-mcp.js +9 -8
- package/dist/harness-mcp.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/scenario.d.ts +37 -0
- package/dist/scenario.d.ts.map +1 -0
- package/dist/scenario.js +235 -0
- package/dist/scenario.js.map +1 -0
- package/dist/types.d.ts +38 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/all.test.ts +143 -24
- package/src/__tests__/context.test.ts +32 -12
- package/src/__tests__/contracts.test.ts +72 -18
- package/src/__tests__/crosses.test.ts +78 -78
- package/src/__tests__/detours.test.ts +176 -19
- package/src/__tests__/effective-examples.test.ts +203 -0
- package/src/__tests__/examples.test.ts +152 -50
- package/src/__tests__/harness-cli.test.ts +28 -4
- package/src/__tests__/harness-mcp.test.ts +37 -0
- package/src/__tests__/partial-match.test.ts +126 -0
- package/src/__tests__/scenario.test.ts +381 -0
- package/src/all.ts +149 -12
- package/src/assertions.ts +253 -0
- package/src/context.ts +64 -38
- package/src/contracts.ts +14 -8
- package/src/crosses.ts +93 -51
- package/src/detours.ts +155 -18
- package/src/effective-examples.ts +350 -0
- package/src/examples.ts +127 -59
- package/src/harness-cli.ts +19 -11
- package/src/harness-mcp.ts +9 -8
- package/src/index.ts +13 -3
- package/src/scenario.ts +370 -0
- package/src/types.ts +63 -5
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/follows.d.ts +0 -38
- package/dist/follows.d.ts.map +0 -1
- package/dist/follows.js +0 -212
- package/dist/follows.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@ontrails/
|
|
17
|
+
"@ontrails/drizzle": "^1.0.0-beta.14",
|
|
18
|
+
"@ontrails/store": "^1.0.0-beta.14"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|
|
20
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
21
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
22
|
-
"@ontrails/logging": "^1.0.0-beta.
|
|
23
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
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",
|
|
24
25
|
"zod": "^4.3.5"
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
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, 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';
|
|
7
10
|
import { store as defineStore } from '@ontrails/store';
|
|
8
|
-
|
|
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
|
+
};
|
|
9
23
|
|
|
10
24
|
const dbDefinition = defineStore({
|
|
11
25
|
entities: {
|
|
@@ -26,7 +40,7 @@ const dbDefinition = defineStore({
|
|
|
26
40
|
},
|
|
27
41
|
});
|
|
28
42
|
|
|
29
|
-
const
|
|
43
|
+
const createDbResource = (
|
|
30
44
|
seed?: readonly {
|
|
31
45
|
readonly id: string;
|
|
32
46
|
readonly name: string;
|
|
@@ -40,7 +54,7 @@ const createDbProvision = (
|
|
|
40
54
|
});
|
|
41
55
|
|
|
42
56
|
const createOverrideStore = () => {
|
|
43
|
-
const { mock } =
|
|
57
|
+
const { mock: mockFactory } = createDbResource([
|
|
44
58
|
{
|
|
45
59
|
id: 'seed-1',
|
|
46
60
|
name: 'Override',
|
|
@@ -48,11 +62,11 @@ const createOverrideStore = () => {
|
|
|
48
62
|
},
|
|
49
63
|
]);
|
|
50
64
|
|
|
51
|
-
if (
|
|
65
|
+
if (mockFactory === undefined) {
|
|
52
66
|
throw new Error('Expected drizzle test store to expose a mock factory');
|
|
53
67
|
}
|
|
54
68
|
|
|
55
|
-
const created =
|
|
69
|
+
const created = mockFactory();
|
|
56
70
|
if (created instanceof Promise) {
|
|
57
71
|
throw new TypeError(
|
|
58
72
|
'Expected drizzle test store mock to resolve synchronously'
|
|
@@ -62,11 +76,11 @@ const createOverrideStore = () => {
|
|
|
62
76
|
return created;
|
|
63
77
|
};
|
|
64
78
|
|
|
65
|
-
const
|
|
79
|
+
const mockDbResource = createDbResource();
|
|
66
80
|
|
|
67
|
-
const mockedTrail = trail('
|
|
81
|
+
const mockedTrail = trail('resource.mocked.all', {
|
|
68
82
|
blaze: async (_input, ctx) => {
|
|
69
|
-
const entity = await
|
|
83
|
+
const entity = await mockDbResource.from(ctx).entities.get('seed-1');
|
|
70
84
|
if (entity === null) {
|
|
71
85
|
return Result.err(new Error('expected seeded entity to exist'));
|
|
72
86
|
}
|
|
@@ -74,22 +88,22 @@ const mockedTrail = trail('provision.mocked.all', {
|
|
|
74
88
|
return Result.ok({ name: entity.name, source: entity.source });
|
|
75
89
|
},
|
|
76
90
|
description:
|
|
77
|
-
'Trail that uses a mocked connector-bound
|
|
91
|
+
'Trail that uses a mocked connector-bound resource through testAll',
|
|
78
92
|
examples: [
|
|
79
93
|
{
|
|
80
94
|
expected: { name: 'Alpha', source: 'mock' },
|
|
81
95
|
input: {},
|
|
82
|
-
name: 'Uses auto-resolved
|
|
96
|
+
name: 'Uses auto-resolved resource mock',
|
|
83
97
|
},
|
|
84
98
|
],
|
|
85
99
|
input: z.object({}),
|
|
86
100
|
output: z.object({ name: z.string(), source: z.string() }),
|
|
87
|
-
|
|
101
|
+
resources: [mockDbResource],
|
|
88
102
|
});
|
|
89
103
|
|
|
90
|
-
const overrideTrail = trail('
|
|
104
|
+
const overrideTrail = trail('resource.override.all', {
|
|
91
105
|
blaze: async (_input, ctx) => {
|
|
92
|
-
const entity = await
|
|
106
|
+
const entity = await mockDbResource.from(ctx).entities.get('seed-1');
|
|
93
107
|
if (entity === null) {
|
|
94
108
|
return Result.err(new Error('expected overridden entity to exist'));
|
|
95
109
|
}
|
|
@@ -101,35 +115,140 @@ const overrideTrail = trail('provision.override.all', {
|
|
|
101
115
|
{
|
|
102
116
|
expected: { name: 'Override', source: 'override' },
|
|
103
117
|
input: {},
|
|
104
|
-
name: 'Explicit
|
|
118
|
+
name: 'Explicit resource override wins',
|
|
105
119
|
},
|
|
106
120
|
],
|
|
107
121
|
input: z.object({}),
|
|
108
122
|
output: z.object({ name: z.string(), source: z.string() }),
|
|
109
|
-
|
|
123
|
+
resources: [mockDbResource],
|
|
110
124
|
});
|
|
111
125
|
|
|
112
|
-
|
|
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', () => {
|
|
113
203
|
// eslint-disable-next-line jest/require-hook
|
|
114
204
|
testAll(
|
|
115
|
-
topo('test-all-
|
|
116
|
-
|
|
205
|
+
topo('test-all-resource-mock-app', {
|
|
206
|
+
mockDbResource,
|
|
117
207
|
mockedTrail,
|
|
118
208
|
} as Record<string, unknown>)
|
|
119
209
|
);
|
|
120
210
|
});
|
|
121
211
|
|
|
122
|
-
describe('testAll explicit
|
|
212
|
+
describe('testAll explicit resource overrides', () => {
|
|
123
213
|
// eslint-disable-next-line jest/require-hook
|
|
124
214
|
testAll(
|
|
125
|
-
topo('test-all-
|
|
126
|
-
|
|
215
|
+
topo('test-all-resource-override-app', {
|
|
216
|
+
mockDbResource,
|
|
127
217
|
overrideTrail,
|
|
128
218
|
} as Record<string, unknown>),
|
|
129
219
|
() => ({
|
|
130
|
-
|
|
220
|
+
resources: {
|
|
131
221
|
'db.mock.all': createOverrideStore(),
|
|
132
222
|
},
|
|
133
223
|
})
|
|
134
224
|
);
|
|
135
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,
|
|
3
|
+
import { Result, resource, topo } from '@ontrails/core';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
createCrossContext,
|
|
7
7
|
createTestContext,
|
|
8
8
|
mergeTestContext,
|
|
9
|
-
|
|
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
|
|
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.
|
|
137
|
+
expect(ctx.resource<{ source: string }>('db.main').source).toBe('mock');
|
|
118
138
|
});
|
|
119
139
|
});
|
|
120
140
|
|
|
121
|
-
describe('
|
|
122
|
-
test('creates fresh mock
|
|
141
|
+
describe('createMockResources', () => {
|
|
142
|
+
test('creates fresh mock resources for each invocation', async () => {
|
|
123
143
|
let mockCalls = 0;
|
|
124
|
-
const mockable =
|
|
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
|
|
134
|
-
const second = await
|
|
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
|
|
143
|
-
const plain =
|
|
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
|
|
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,
|
|
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
|
|
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('
|
|
92
|
+
const undeclaredContractTrail = trail('resource.undeclared.contracts', {
|
|
81
93
|
blaze: (_input, ctx) =>
|
|
82
94
|
Result.ok({
|
|
83
|
-
|
|
84
|
-
ctx.extensions?.[
|
|
95
|
+
hasInjectedResource:
|
|
96
|
+
ctx.extensions?.[undeclaredContractDbResource.id] !== undefined,
|
|
85
97
|
}),
|
|
86
98
|
examples: [
|
|
87
99
|
{
|
|
88
|
-
expected: {
|
|
100
|
+
expected: { hasInjectedResource: false },
|
|
89
101
|
input: {},
|
|
90
|
-
name: 'Undeclared
|
|
102
|
+
name: 'Undeclared resources are not preloaded into contract contexts',
|
|
91
103
|
},
|
|
92
104
|
],
|
|
93
105
|
input: z.object({}),
|
|
94
|
-
output: z.object({
|
|
106
|
+
output: z.object({ hasInjectedResource: z.literal(false) }),
|
|
95
107
|
});
|
|
96
108
|
|
|
97
|
-
const
|
|
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('
|
|
114
|
+
const ctxOverrideContractTrail = trail('resource.ctx.contracts', {
|
|
103
115
|
blaze: (_input, ctx) =>
|
|
104
|
-
Result.ok({ 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
217
|
+
describe('testContracts resource declarations', () => {
|
|
178
218
|
// eslint-disable-next-line jest/require-hook
|
|
179
219
|
testContracts(
|
|
180
|
-
topo('undeclared-contract-
|
|
181
|
-
|
|
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
|
+
});
|