@ontrails/testing 1.0.0-beta.15 → 1.0.0-beta.16
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/CHANGELOG.md +56 -1
- package/README.md +2 -2
- package/package.json +15 -7
- package/src/all.ts +7 -7
- package/src/context.ts +3 -0
- package/src/contracts.ts +1 -16
- package/src/examples.ts +19 -11
- package/src/index.ts +1 -1
- package/src/logger.ts +3 -1
- package/src/scenario.ts +3 -5
- package/src/signals.ts +221 -0
- package/src/types.ts +1 -1
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/all.d.ts +0 -36
- package/dist/all.d.ts.map +0 -1
- package/dist/all.js +0 -99
- package/dist/all.js.map +0 -1
- package/dist/assertions.d.ts +0 -72
- package/dist/assertions.d.ts.map +0 -1
- package/dist/assertions.js +0 -238
- package/dist/assertions.js.map +0 -1
- package/dist/context.d.ts +0 -76
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -118
- package/dist/context.js.map +0 -1
- package/dist/contracts.d.ts +0 -17
- package/dist/contracts.d.ts.map +0 -1
- package/dist/contracts.js +0 -75
- package/dist/contracts.js.map +0 -1
- package/dist/crosses.d.ts +0 -38
- package/dist/crosses.d.ts.map +0 -1
- package/dist/crosses.js +0 -223
- package/dist/crosses.js.map +0 -1
- package/dist/detours.d.ts +0 -13
- package/dist/detours.d.ts.map +0 -1
- package/dist/detours.js +0 -109
- package/dist/detours.js.map +0 -1
- package/dist/effective-examples.d.ts +0 -30
- package/dist/effective-examples.d.ts.map +0 -1
- package/dist/effective-examples.js +0 -227
- package/dist/effective-examples.js.map +0 -1
- package/dist/examples.d.ts +0 -23
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js +0 -240
- package/dist/examples.js.map +0 -1
- package/dist/harness-cli.d.ts +0 -21
- package/dist/harness-cli.d.ts.map +0 -1
- package/dist/harness-cli.js +0 -205
- package/dist/harness-cli.js.map +0 -1
- package/dist/harness-mcp.d.ts +0 -21
- package/dist/harness-mcp.d.ts.map +0 -1
- package/dist/harness-mcp.js +0 -54
- package/dist/harness-mcp.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -19
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -15
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -87
- package/dist/logger.js.map +0 -1
- package/dist/scenario.d.ts +0 -37
- package/dist/scenario.d.ts.map +0 -1
- package/dist/scenario.js +0 -235
- package/dist/scenario.js.map +0 -1
- package/dist/trail.d.ts +0 -20
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -80
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -113
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/src/__tests__/all.test.ts +0 -254
- package/src/__tests__/context.test.ts +0 -170
- package/src/__tests__/contracts.test.ts +0 -239
- package/src/__tests__/crosses.test.ts +0 -587
- package/src/__tests__/detours.test.ts +0 -212
- package/src/__tests__/effective-examples.test.ts +0 -203
- package/src/__tests__/examples.test.ts +0 -636
- package/src/__tests__/harness-cli.test.ts +0 -90
- package/src/__tests__/harness-mcp.test.ts +0 -37
- package/src/__tests__/logger.test.ts +0 -136
- package/src/__tests__/partial-match.test.ts +0 -126
- package/src/__tests__/scenario.test.ts +0 -381
- package/src/__tests__/trail.test.ts +0 -99
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
import { describe } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import type { AnyTrail, TrailContext } from '@ontrails/core';
|
|
4
|
-
import { InternalError, Result, resource, trail } from '@ontrails/core';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
import { testCrosses } from '../crosses.js';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Test trails (trails with crossings)
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
const addTrail = trail('entity.add', {
|
|
14
|
-
blaze: (input: { name: string }) => Result.ok({ id: '1', name: input.name }),
|
|
15
|
-
description: 'Add an entity',
|
|
16
|
-
examples: [
|
|
17
|
-
{ input: { name: 'Alpha' }, name: 'success' },
|
|
18
|
-
{
|
|
19
|
-
description: 'duplicate name',
|
|
20
|
-
error: 'AlreadyExistsError',
|
|
21
|
-
input: { name: '' },
|
|
22
|
-
name: 'duplicate',
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
input: z.object({ name: z.string() }),
|
|
26
|
-
output: z.object({ id: z.string(), name: z.string() }),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const relateTrail = trail('entity.relate', {
|
|
30
|
-
blaze: (input: { from: string; to: string }) =>
|
|
31
|
-
Result.ok({ from: input.from, to: input.to }),
|
|
32
|
-
description: 'Relate two entities',
|
|
33
|
-
input: z.object({ from: z.string(), to: z.string() }),
|
|
34
|
-
output: z.object({ from: z.string(), to: z.string() }),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Composition trail
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
const onboardTrail = trail('entity.onboard', {
|
|
42
|
-
blaze: async (
|
|
43
|
-
input: { name: string; relatedTo: string },
|
|
44
|
-
ctx: TrailContext
|
|
45
|
-
) => {
|
|
46
|
-
if (!ctx.cross) {
|
|
47
|
-
return Result.err(new Error('cross not available'));
|
|
48
|
-
}
|
|
49
|
-
const addResult = await ctx.cross<{ id: string; name: string }>(
|
|
50
|
-
'entity.add',
|
|
51
|
-
{ name: input.name }
|
|
52
|
-
);
|
|
53
|
-
if (addResult.isErr()) {
|
|
54
|
-
return Result.err(addResult.error);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const relateResult = await ctx.cross<{ from: string; to: string }>(
|
|
58
|
-
'entity.relate',
|
|
59
|
-
{ from: addResult.value.name, to: input.relatedTo }
|
|
60
|
-
);
|
|
61
|
-
if (relateResult.isErr()) {
|
|
62
|
-
return Result.err(relateResult.error);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return Result.ok({
|
|
66
|
-
name: addResult.value.name,
|
|
67
|
-
relatedTo: relateResult.value.to,
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
crosses: ['entity.add', 'entity.relate'],
|
|
71
|
-
input: z.object({ name: z.string(), relatedTo: z.string() }),
|
|
72
|
-
output: z.object({ name: z.string(), relatedTo: z.string() }),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const trailsMap = new Map<string, AnyTrail>([
|
|
76
|
-
['entity.add', addTrail],
|
|
77
|
-
['entity.relate', relateTrail],
|
|
78
|
-
]);
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// Tests
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
const opts = { trails: trailsMap };
|
|
85
|
-
|
|
86
|
-
describe('testCrosses: expectOk', () => {
|
|
87
|
-
// eslint-disable-next-line jest/require-hook
|
|
88
|
-
testCrosses(
|
|
89
|
-
onboardTrail,
|
|
90
|
-
[
|
|
91
|
-
{
|
|
92
|
-
description: 'basic onboard succeeds',
|
|
93
|
-
expectOk: true,
|
|
94
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
opts
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('testCrosses: expectCrossed', () => {
|
|
102
|
-
// eslint-disable-next-line jest/require-hook
|
|
103
|
-
testCrosses(
|
|
104
|
-
onboardTrail,
|
|
105
|
-
[
|
|
106
|
-
{
|
|
107
|
-
description: 'crosses add then relate in order',
|
|
108
|
-
expectCrossed: ['entity.add', 'entity.relate'],
|
|
109
|
-
expectOk: true,
|
|
110
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
opts
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('testCrosses: expectCrossedCount', () => {
|
|
118
|
-
// eslint-disable-next-line jest/require-hook
|
|
119
|
-
testCrosses(
|
|
120
|
-
onboardTrail,
|
|
121
|
-
[
|
|
122
|
-
{
|
|
123
|
-
description: 'each trail crossed exactly once',
|
|
124
|
-
expectCrossedCount: { 'entity.add': 1, 'entity.relate': 1 },
|
|
125
|
-
expectOk: true,
|
|
126
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
opts
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('testCrosses: injectFromExample', () => {
|
|
134
|
-
// eslint-disable-next-line jest/require-hook
|
|
135
|
-
testCrosses(
|
|
136
|
-
onboardTrail,
|
|
137
|
-
[
|
|
138
|
-
{
|
|
139
|
-
description: 'inject duplicate error from add trail example',
|
|
140
|
-
expectErr: Error,
|
|
141
|
-
expectErrMessage: 'AlreadyExistsError',
|
|
142
|
-
injectFromExample: { 'entity.add': 'duplicate' },
|
|
143
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
opts
|
|
147
|
-
);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe('testCrosses: expectValue', () => {
|
|
151
|
-
// eslint-disable-next-line jest/require-hook
|
|
152
|
-
testCrosses(
|
|
153
|
-
onboardTrail,
|
|
154
|
-
[
|
|
155
|
-
{
|
|
156
|
-
description: 'exact value match',
|
|
157
|
-
expectValue: { name: 'Alpha', relatedTo: 'Beta' },
|
|
158
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
opts
|
|
162
|
-
);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// Nested cross chain: A → B → C
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
|
|
169
|
-
const leafTrail = trail('step.leaf', {
|
|
170
|
-
blaze: (input: { value: string }) => Result.ok({ leaf: input.value }),
|
|
171
|
-
description: 'Leaf trail in a nested chain',
|
|
172
|
-
input: z.object({ value: z.string() }),
|
|
173
|
-
output: z.object({ leaf: z.string() }),
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const middleTrail = trail('step.middle', {
|
|
177
|
-
blaze: async (input: { value: string }, ctx: TrailContext) => {
|
|
178
|
-
if (!ctx.cross) {
|
|
179
|
-
return Result.err(new Error('cross not available'));
|
|
180
|
-
}
|
|
181
|
-
const leafResult = await ctx.cross<{ leaf: string }>('step.leaf', input);
|
|
182
|
-
if (leafResult.isErr()) {
|
|
183
|
-
return leafResult;
|
|
184
|
-
}
|
|
185
|
-
return Result.ok({ middle: leafResult.value.leaf });
|
|
186
|
-
},
|
|
187
|
-
crosses: ['step.leaf'],
|
|
188
|
-
description: 'Middle trail that crosses the leaf',
|
|
189
|
-
input: z.object({ value: z.string() }),
|
|
190
|
-
output: z.object({ middle: z.string() }),
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const nestedRootTrail = trail('step.root', {
|
|
194
|
-
blaze: async (input: { value: string }, ctx: TrailContext) => {
|
|
195
|
-
if (!ctx.cross) {
|
|
196
|
-
return Result.err(new Error('cross not available'));
|
|
197
|
-
}
|
|
198
|
-
const midResult = await ctx.cross<{ middle: string }>('step.middle', input);
|
|
199
|
-
if (midResult.isErr()) {
|
|
200
|
-
return midResult;
|
|
201
|
-
}
|
|
202
|
-
return Result.ok({ root: midResult.value.middle });
|
|
203
|
-
},
|
|
204
|
-
crosses: ['step.middle'],
|
|
205
|
-
description: 'Root trail that crosses the middle trail',
|
|
206
|
-
input: z.object({ value: z.string() }),
|
|
207
|
-
output: z.object({ root: z.string() }),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const nestedTrailsMap = new Map<string, AnyTrail>([
|
|
211
|
-
['step.leaf', leafTrail],
|
|
212
|
-
['step.middle', middleTrail],
|
|
213
|
-
]);
|
|
214
|
-
|
|
215
|
-
describe('testCrosses: nested cross chain (A → B → C)', () => {
|
|
216
|
-
// eslint-disable-next-line jest/require-hook
|
|
217
|
-
testCrosses(
|
|
218
|
-
nestedRootTrail,
|
|
219
|
-
[
|
|
220
|
-
{
|
|
221
|
-
description: 'nested ctx.cross works through A → B → C',
|
|
222
|
-
expectOk: true,
|
|
223
|
-
expectValue: { root: 'hello' },
|
|
224
|
-
input: { value: 'hello' },
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
{ trails: nestedTrailsMap }
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const mockDbResource = resource('db.mock.crosses', {
|
|
232
|
-
create: () => Result.ok({ source: 'factory' }),
|
|
233
|
-
mock: () => ({ source: 'mock' }),
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const provisionLeafTrail = trail('resource.leaf', {
|
|
237
|
-
blaze: (_input, ctx) =>
|
|
238
|
-
Result.ok({ childSource: mockDbResource.from(ctx).source }),
|
|
239
|
-
description: 'Leaf trail that reads from a resource',
|
|
240
|
-
input: z.object({}),
|
|
241
|
-
output: z.object({ childSource: z.string() }),
|
|
242
|
-
resources: [mockDbResource],
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const provisionRootTrail = trail('resource.root', {
|
|
246
|
-
blaze: async (_input, ctx: TrailContext) => {
|
|
247
|
-
if (!ctx.cross) {
|
|
248
|
-
return Result.err(new Error('cross not available'));
|
|
249
|
-
}
|
|
250
|
-
const childResult = await ctx.cross<{ childSource: string }>(
|
|
251
|
-
'resource.leaf',
|
|
252
|
-
{}
|
|
253
|
-
);
|
|
254
|
-
if (childResult.isErr()) {
|
|
255
|
-
return childResult;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return Result.ok({
|
|
259
|
-
childSource: childResult.value.childSource,
|
|
260
|
-
rootSource: mockDbResource.from(ctx).source,
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
crosses: ['resource.leaf'],
|
|
264
|
-
description:
|
|
265
|
-
'Root trail that reads from a resource and crosses a child trail',
|
|
266
|
-
input: z.object({}),
|
|
267
|
-
output: z.object({ childSource: z.string(), rootSource: z.string() }),
|
|
268
|
-
resources: [mockDbResource],
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const provisionTrailsMap = new Map<string, AnyTrail>([
|
|
272
|
-
['resource.leaf', provisionLeafTrail],
|
|
273
|
-
]);
|
|
274
|
-
|
|
275
|
-
const statefulMockDbResource = resource('db.mock.crosses.stateful', {
|
|
276
|
-
create: () => Result.ok({ count: 0 }),
|
|
277
|
-
mock: () => ({ count: 0 }),
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
const statefulResourceLeafTrail = trail('resource.stateful.leaf', {
|
|
281
|
-
blaze: (_input, ctx) =>
|
|
282
|
-
Result.ok({ childCount: statefulMockDbResource.from(ctx).count }),
|
|
283
|
-
description: 'Leaf trail that observes the current mock resource state',
|
|
284
|
-
input: z.object({}),
|
|
285
|
-
output: z.object({ childCount: z.number() }),
|
|
286
|
-
resources: [statefulMockDbResource],
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const statefulResourceRootTrail = trail('resource.stateful.root', {
|
|
290
|
-
blaze: async (_input, ctx: TrailContext) => {
|
|
291
|
-
if (!ctx.cross) {
|
|
292
|
-
return Result.err(new Error('cross not available'));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const statefulResource = statefulMockDbResource.from(ctx);
|
|
296
|
-
statefulResource.count += 1;
|
|
297
|
-
|
|
298
|
-
const childResult = await ctx.cross<{ childCount: number }>(
|
|
299
|
-
'resource.stateful.leaf',
|
|
300
|
-
{}
|
|
301
|
-
);
|
|
302
|
-
if (childResult.isErr()) {
|
|
303
|
-
return childResult;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return Result.ok({
|
|
307
|
-
childCount: childResult.value.childCount,
|
|
308
|
-
rootCount: statefulResource.count,
|
|
309
|
-
});
|
|
310
|
-
},
|
|
311
|
-
crosses: ['resource.stateful.leaf'],
|
|
312
|
-
description:
|
|
313
|
-
'Root trail that mutates a mocked resource and crosses a child trail',
|
|
314
|
-
input: z.object({}),
|
|
315
|
-
output: z.object({ childCount: z.number(), rootCount: z.number() }),
|
|
316
|
-
resources: [statefulMockDbResource],
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const statefulResourceTrailsMap = new Map<string, AnyTrail>([
|
|
320
|
-
['resource.stateful.leaf', statefulResourceLeafTrail],
|
|
321
|
-
]);
|
|
322
|
-
|
|
323
|
-
const scenarioStateResource = resource('db.mock.scenarios', {
|
|
324
|
-
create: () => Result.ok({ count: 0 }),
|
|
325
|
-
mock: () => ({ count: 0 }),
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
const scenarioLeafTrail = trail('resource.scenario.leaf', {
|
|
329
|
-
blaze: (_input, ctx) =>
|
|
330
|
-
Result.ok({ count: scenarioStateResource.from(ctx).count }),
|
|
331
|
-
description: 'Leaf trail that reads mutable scenario state',
|
|
332
|
-
input: z.object({}),
|
|
333
|
-
output: z.object({ count: z.number() }),
|
|
334
|
-
resources: [scenarioStateResource],
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const scenarioRootTrail = trail('resource.scenario.root', {
|
|
338
|
-
blaze: async (_input, ctx: TrailContext) => {
|
|
339
|
-
if (!ctx.cross) {
|
|
340
|
-
return Result.err(new Error('cross not available'));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const state = scenarioStateResource.from(ctx);
|
|
344
|
-
state.count += 1;
|
|
345
|
-
|
|
346
|
-
const leafResult = await ctx.cross<{ count: number }>(
|
|
347
|
-
'resource.scenario.leaf',
|
|
348
|
-
{}
|
|
349
|
-
);
|
|
350
|
-
if (leafResult.isErr()) {
|
|
351
|
-
return leafResult;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return Result.ok({ count: leafResult.value.count });
|
|
355
|
-
},
|
|
356
|
-
crosses: ['resource.scenario.leaf'],
|
|
357
|
-
description: 'Root trail that mutates scenario state and crosses a leaf',
|
|
358
|
-
input: z.object({}),
|
|
359
|
-
output: z.object({ count: z.number() }),
|
|
360
|
-
resources: [scenarioStateResource],
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const scenarioResourceTrailsMap = new Map<string, AnyTrail>([
|
|
364
|
-
['resource.scenario.leaf', scenarioLeafTrail],
|
|
365
|
-
]);
|
|
366
|
-
|
|
367
|
-
const transformedCrossLeafTrail = trail('cross.transformed.leaf', {
|
|
368
|
-
blaze: (input: { value: number }) => Result.ok({ value: input.value }),
|
|
369
|
-
description: 'Leaf trail in a transformed cross chain',
|
|
370
|
-
input: z.object({ value: z.number() }),
|
|
371
|
-
output: z.object({ value: z.number() }),
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
const transformedCrossRootTrail = trail('cross.transformed.root', {
|
|
375
|
-
blaze: async (input: { value: number }, ctx: TrailContext) => {
|
|
376
|
-
if (!ctx.cross) {
|
|
377
|
-
return Result.err(new Error('cross not available'));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const leafResult = await ctx.cross<{ value: number }>(
|
|
381
|
-
'cross.transformed.leaf',
|
|
382
|
-
{ value: input.value }
|
|
383
|
-
);
|
|
384
|
-
if (leafResult.isErr()) {
|
|
385
|
-
return leafResult;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return Result.ok({ root: leafResult.value.value });
|
|
389
|
-
},
|
|
390
|
-
crosses: ['cross.transformed.leaf'],
|
|
391
|
-
description: 'Root trail that transforms input once before crossing',
|
|
392
|
-
input: z
|
|
393
|
-
.object({ value: z.string() })
|
|
394
|
-
.transform(({ value }) => ({ value: Number(value) + 1 })),
|
|
395
|
-
output: z.object({ root: z.number() }),
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const transformedCrossTrailsMap = new Map<string, AnyTrail>([
|
|
399
|
-
['cross.transformed.leaf', transformedCrossLeafTrail],
|
|
400
|
-
]);
|
|
401
|
-
|
|
402
|
-
const undeclaredCrossResource = resource('db.undeclared.crosses', {
|
|
403
|
-
create: () => Result.ok({ source: 'factory' }),
|
|
404
|
-
mock: () => ({ source: 'mock' }),
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const undeclaredResourceLeafTrail = trail('resource.undeclared.leaf', {
|
|
408
|
-
blaze: (_input, ctx) =>
|
|
409
|
-
Result.ok({ childSource: undeclaredCrossResource.from(ctx).source }),
|
|
410
|
-
description: 'Leaf trail that declares the shared resource',
|
|
411
|
-
input: z.object({}),
|
|
412
|
-
output: z.object({ childSource: z.string() }),
|
|
413
|
-
resources: [undeclaredCrossResource],
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
const undeclaredResourceRootTrail = trail('resource.undeclared.root', {
|
|
417
|
-
blaze: async (_input, ctx: TrailContext) => {
|
|
418
|
-
if (!ctx.cross) {
|
|
419
|
-
return Result.err(new Error('cross not available'));
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const childResult = await ctx.cross<{ childSource: string }>(
|
|
423
|
-
'resource.undeclared.leaf',
|
|
424
|
-
{}
|
|
425
|
-
);
|
|
426
|
-
if (childResult.isErr()) {
|
|
427
|
-
return childResult;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return Result.ok({
|
|
431
|
-
childSource: childResult.value.childSource,
|
|
432
|
-
rootSource: undeclaredCrossResource.from(ctx).source,
|
|
433
|
-
});
|
|
434
|
-
},
|
|
435
|
-
crosses: ['resource.undeclared.leaf'],
|
|
436
|
-
description: 'Root trail that uses a resource without declaring it',
|
|
437
|
-
input: z.object({}),
|
|
438
|
-
output: z.object({ childSource: z.string(), rootSource: z.string() }),
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const undeclaredResourceTrailsMap = new Map<string, AnyTrail>([
|
|
442
|
-
['resource.undeclared.leaf', undeclaredResourceLeafTrail],
|
|
443
|
-
]);
|
|
444
|
-
|
|
445
|
-
const unrelatedCrossResource = resource('db.unrelated.crosses', {
|
|
446
|
-
create: () => Result.ok({ source: 'factory' }),
|
|
447
|
-
mock: () => {
|
|
448
|
-
throw new Error('unrelated mock should not be resolved');
|
|
449
|
-
},
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
const unrelatedResourceTrail = trail('resource.unrelated', {
|
|
453
|
-
blaze: (_input, ctx) =>
|
|
454
|
-
Result.ok({ source: unrelatedCrossResource.from(ctx).source }),
|
|
455
|
-
description: 'Trail that should not be traversed or mocked',
|
|
456
|
-
input: z.object({}),
|
|
457
|
-
output: z.object({ source: z.string() }),
|
|
458
|
-
resources: [unrelatedCrossResource],
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
const unrelatedResourceTrailsMap = new Map<string, AnyTrail>([
|
|
462
|
-
['resource.unrelated', unrelatedResourceTrail],
|
|
463
|
-
]);
|
|
464
|
-
|
|
465
|
-
describe('testCrosses resource mocks', () => {
|
|
466
|
-
// eslint-disable-next-line jest/require-hook
|
|
467
|
-
testCrosses(
|
|
468
|
-
provisionRootTrail,
|
|
469
|
-
[
|
|
470
|
-
{
|
|
471
|
-
description: 'propagates auto-resolved resource mocks through cross',
|
|
472
|
-
expectValue: { childSource: 'mock', rootSource: 'mock' },
|
|
473
|
-
input: {},
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
{ trails: provisionTrailsMap }
|
|
477
|
-
);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
describe('testCrosses resource mocks are fresh per scenario', () => {
|
|
481
|
-
// eslint-disable-next-line jest/require-hook
|
|
482
|
-
testCrosses(
|
|
483
|
-
scenarioRootTrail,
|
|
484
|
-
[
|
|
485
|
-
{
|
|
486
|
-
description: 'first scenario sees a fresh mutable resource',
|
|
487
|
-
expectValue: { count: 1 },
|
|
488
|
-
input: {},
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
description: 'second scenario also sees a fresh mutable resource',
|
|
492
|
-
expectValue: { count: 1 },
|
|
493
|
-
input: {},
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
{ trails: scenarioResourceTrailsMap }
|
|
497
|
-
);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
describe('testCrosses explicit resource overrides', () => {
|
|
501
|
-
// eslint-disable-next-line jest/require-hook
|
|
502
|
-
testCrosses(
|
|
503
|
-
provisionRootTrail,
|
|
504
|
-
[
|
|
505
|
-
{
|
|
506
|
-
description: 'propagates explicit resource overrides through cross',
|
|
507
|
-
expectValue: { childSource: 'override', rootSource: 'override' },
|
|
508
|
-
input: {},
|
|
509
|
-
},
|
|
510
|
-
],
|
|
511
|
-
{
|
|
512
|
-
resources: { 'db.mock.crosses': { source: 'override' } },
|
|
513
|
-
trails: provisionTrailsMap,
|
|
514
|
-
}
|
|
515
|
-
);
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
describe('testCrosses raw transformed input', () => {
|
|
519
|
-
// eslint-disable-next-line jest/require-hook
|
|
520
|
-
testCrosses(
|
|
521
|
-
transformedCrossRootTrail,
|
|
522
|
-
[
|
|
523
|
-
{
|
|
524
|
-
description: 'raw scenario input is only transformed once',
|
|
525
|
-
expectValue: { root: 2 },
|
|
526
|
-
input: { value: '1' },
|
|
527
|
-
},
|
|
528
|
-
],
|
|
529
|
-
{ trails: transformedCrossTrailsMap }
|
|
530
|
-
);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
describe('testCrosses resource declarations', () => {
|
|
534
|
-
// eslint-disable-next-line jest/require-hook
|
|
535
|
-
testCrosses(
|
|
536
|
-
undeclaredResourceRootTrail,
|
|
537
|
-
[
|
|
538
|
-
{
|
|
539
|
-
description: 'fails when the root trail omits a required resource',
|
|
540
|
-
expectErr: InternalError,
|
|
541
|
-
expectErrMessage: undeclaredCrossResource.id,
|
|
542
|
-
input: {},
|
|
543
|
-
},
|
|
544
|
-
],
|
|
545
|
-
{ trails: undeclaredResourceTrailsMap }
|
|
546
|
-
);
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
describe('testCrosses only resolves mocks for trails under test', () => {
|
|
550
|
-
// eslint-disable-next-line jest/require-hook
|
|
551
|
-
testCrosses(
|
|
552
|
-
provisionRootTrail,
|
|
553
|
-
[
|
|
554
|
-
{
|
|
555
|
-
description: 'unrelated resource mocks are not resolved',
|
|
556
|
-
expectValue: { childSource: 'mock', rootSource: 'mock' },
|
|
557
|
-
input: {},
|
|
558
|
-
},
|
|
559
|
-
],
|
|
560
|
-
{
|
|
561
|
-
trails: new Map<string, AnyTrail>([
|
|
562
|
-
...provisionTrailsMap.entries(),
|
|
563
|
-
...unrelatedResourceTrailsMap.entries(),
|
|
564
|
-
]),
|
|
565
|
-
}
|
|
566
|
-
);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
describe('testCrosses fresh resource mocks per scenario', () => {
|
|
570
|
-
// eslint-disable-next-line jest/require-hook
|
|
571
|
-
testCrosses(
|
|
572
|
-
statefulResourceRootTrail,
|
|
573
|
-
[
|
|
574
|
-
{
|
|
575
|
-
description: 'first scenario gets a fresh resource mock',
|
|
576
|
-
expectValue: { childCount: 1, rootCount: 1 },
|
|
577
|
-
input: {},
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
description: 'second scenario also gets a fresh resource mock',
|
|
581
|
-
expectValue: { childCount: 1, rootCount: 1 },
|
|
582
|
-
input: {},
|
|
583
|
-
},
|
|
584
|
-
],
|
|
585
|
-
{ trails: statefulResourceTrailsMap }
|
|
586
|
-
);
|
|
587
|
-
});
|