@ontrails/testing 1.0.0-beta.1 → 1.0.0-beta.11
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 +166 -0
- package/README.md +25 -7
- package/dist/all.d.ts +2 -1
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js.map +1 -1
- package/dist/context.d.ts +28 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +70 -11
- package/dist/context.js.map +1 -1
- package/dist/contracts.d.ts +3 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +25 -10
- package/dist/contracts.js.map +1 -1
- package/dist/examples.d.ts +4 -3
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +63 -68
- package/dist/examples.js.map +1 -1
- package/dist/follows.d.ts +38 -0
- package/dist/follows.d.ts.map +1 -0
- package/dist/{hike.js → follows.js} +71 -28
- package/dist/follows.js.map +1 -0
- package/dist/harness-mcp.d.ts.map +1 -1
- package/dist/harness-mcp.js +5 -2
- 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 +2 -2
- package/dist/index.js.map +1 -1
- package/dist/trail.js +1 -1
- package/dist/trail.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/all.test.ts +64 -0
- package/src/__tests__/context.test.ts +92 -2
- package/src/__tests__/contracts.test.ts +122 -5
- package/src/__tests__/detours.test.ts +3 -3
- package/src/__tests__/examples.test.ts +285 -22
- package/src/__tests__/follows.test.ts +589 -0
- package/src/__tests__/trail.test.ts +4 -4
- package/src/all.ts +5 -1
- package/src/context.ts +121 -13
- package/src/contracts.ts +43 -12
- package/src/examples.ts +111 -105
- package/src/{hike.ts → follows.ts} +138 -43
- package/src/harness-mcp.ts +5 -2
- package/src/index.ts +6 -4
- package/src/trail.ts +1 -1
- package/src/types.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/hike.d.ts +0 -32
- package/dist/hike.d.ts.map +0 -1
- package/dist/hike.js.map +0 -1
- package/src/__tests__/hike.test.ts +0 -164
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* testFollows — composition-aware scenario testing for trails with follow.
|
|
3
3
|
*
|
|
4
|
-
* Tests the
|
|
4
|
+
* Tests the follow graph: which trails were followed, in what order,
|
|
5
5
|
* and supports failure injection from followed trail examples.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, expect, test } from 'bun:test';
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
AnyTrail,
|
|
12
|
+
FollowFn,
|
|
13
|
+
ServiceOverrideMap,
|
|
14
|
+
TrailContext,
|
|
15
|
+
} from '@ontrails/core';
|
|
11
16
|
import {
|
|
17
|
+
executeTrail,
|
|
12
18
|
InternalError,
|
|
13
19
|
Result,
|
|
14
20
|
ValidationError,
|
|
@@ -19,10 +25,9 @@ import {
|
|
|
19
25
|
assertErrorMatch,
|
|
20
26
|
assertFullMatch,
|
|
21
27
|
assertSchemaMatch,
|
|
22
|
-
expectOk,
|
|
23
28
|
} from './assertions.js';
|
|
24
|
-
import { mergeTestContext } from './context.js';
|
|
25
|
-
import type {
|
|
29
|
+
import { mergeServiceOverrides, mergeTestContext } from './context.js';
|
|
30
|
+
import type { FollowScenario } from './types.js';
|
|
26
31
|
|
|
27
32
|
// ---------------------------------------------------------------------------
|
|
28
33
|
// Follow trace
|
|
@@ -33,6 +38,58 @@ interface FollowRecord {
|
|
|
33
38
|
readonly input: unknown;
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
const collectDeclaredServices = (
|
|
42
|
+
trailDef: AnyTrail,
|
|
43
|
+
trailsMap: ReadonlyMap<string, AnyTrail> | undefined
|
|
44
|
+
): AnyTrail['services'] => {
|
|
45
|
+
const seenServiceIds = new Set<string>();
|
|
46
|
+
const seenTrailIds = new Set<string>();
|
|
47
|
+
const services: AnyTrail['services'][number][] = [];
|
|
48
|
+
|
|
49
|
+
const collect = (candidate: AnyTrail): void => {
|
|
50
|
+
for (const declaredService of candidate.services) {
|
|
51
|
+
if (seenServiceIds.has(declaredService.id)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
seenServiceIds.add(declaredService.id);
|
|
55
|
+
services.push(declaredService);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const visit = (candidate: AnyTrail): void => {
|
|
60
|
+
if (seenTrailIds.has(candidate.id)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
seenTrailIds.add(candidate.id);
|
|
64
|
+
collect(candidate);
|
|
65
|
+
for (const followedId of candidate.follow) {
|
|
66
|
+
const followedTrail = trailsMap?.get(followedId);
|
|
67
|
+
if (followedTrail) {
|
|
68
|
+
visit(followedTrail);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
visit(trailDef);
|
|
74
|
+
return services;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const resolveMockServices = async (
|
|
78
|
+
trailDef: AnyTrail,
|
|
79
|
+
trailsMap: ReadonlyMap<string, AnyTrail> | undefined
|
|
80
|
+
): Promise<ServiceOverrideMap> => {
|
|
81
|
+
const services: Record<string, unknown> = {};
|
|
82
|
+
|
|
83
|
+
for (const declaredService of collectDeclaredServices(trailDef, trailsMap)) {
|
|
84
|
+
if (!declaredService.mock) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
services[declaredService.id] = await declaredService.mock();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return services;
|
|
91
|
+
};
|
|
92
|
+
|
|
36
93
|
// ---------------------------------------------------------------------------
|
|
37
94
|
// Injection helpers
|
|
38
95
|
// ---------------------------------------------------------------------------
|
|
@@ -58,7 +115,7 @@ const findErrorExample = (
|
|
|
58
115
|
*/
|
|
59
116
|
const tryInjectError = (
|
|
60
117
|
id: string,
|
|
61
|
-
scenario:
|
|
118
|
+
scenario: FollowScenario,
|
|
62
119
|
trailsMap: ReadonlyMap<string, AnyTrail> | undefined
|
|
63
120
|
): Result<unknown, Error> | undefined => {
|
|
64
121
|
const injection = scenario.injectFromExample?.[id];
|
|
@@ -90,18 +147,20 @@ const executeFromMap = (
|
|
|
90
147
|
id: string,
|
|
91
148
|
input: unknown,
|
|
92
149
|
trailsMap: ReadonlyMap<string, AnyTrail> | undefined,
|
|
93
|
-
ctx: TrailContext
|
|
150
|
+
ctx: TrailContext,
|
|
151
|
+
services: ServiceOverrideMap | undefined,
|
|
152
|
+
follow?: FollowFn
|
|
94
153
|
): Result<unknown, Error> | Promise<Result<unknown, Error>> | undefined => {
|
|
95
154
|
const trailDef = trailsMap?.get(id);
|
|
96
155
|
if (trailDef === undefined) {
|
|
97
156
|
return undefined;
|
|
98
157
|
}
|
|
99
158
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
const nestedCtx = follow ? { ...ctx, follow } : ctx;
|
|
160
|
+
return executeTrail(trailDef, input, {
|
|
161
|
+
ctx: nestedCtx,
|
|
162
|
+
services,
|
|
163
|
+
});
|
|
105
164
|
};
|
|
106
165
|
|
|
107
166
|
// ---------------------------------------------------------------------------
|
|
@@ -113,10 +172,11 @@ const executeFromMap = (
|
|
|
113
172
|
*/
|
|
114
173
|
const createRecordingFollow = (
|
|
115
174
|
trace: FollowRecord[],
|
|
116
|
-
scenario:
|
|
175
|
+
scenario: FollowScenario,
|
|
117
176
|
trailsMap: ReadonlyMap<string, AnyTrail> | undefined,
|
|
118
177
|
baseFollow: FollowFn | undefined,
|
|
119
|
-
ctx: TrailContext
|
|
178
|
+
ctx: TrailContext,
|
|
179
|
+
services: ServiceOverrideMap | undefined
|
|
120
180
|
): FollowFn => {
|
|
121
181
|
// The generic O on FollowFn is erased at runtime; the cast is safe
|
|
122
182
|
// because callers narrow via isOk/isErr before accessing the value.
|
|
@@ -132,7 +192,14 @@ const createRecordingFollow = (
|
|
|
132
192
|
return baseFollow(id, input);
|
|
133
193
|
}
|
|
134
194
|
|
|
135
|
-
const executed = executeFromMap(
|
|
195
|
+
const executed = executeFromMap(
|
|
196
|
+
id,
|
|
197
|
+
input,
|
|
198
|
+
trailsMap,
|
|
199
|
+
ctx,
|
|
200
|
+
services,
|
|
201
|
+
follow as FollowFn
|
|
202
|
+
);
|
|
136
203
|
if (executed !== undefined) {
|
|
137
204
|
return Promise.resolve(executed);
|
|
138
205
|
}
|
|
@@ -148,8 +215,8 @@ const createRecordingFollow = (
|
|
|
148
215
|
|
|
149
216
|
const assertScenarioResult = (
|
|
150
217
|
result: Result<unknown, Error>,
|
|
151
|
-
scenario:
|
|
152
|
-
|
|
218
|
+
scenario: FollowScenario,
|
|
219
|
+
trailDef: AnyTrail
|
|
153
220
|
): void => {
|
|
154
221
|
if (scenario.expectValue !== undefined) {
|
|
155
222
|
assertFullMatch(result, scenario.expectValue);
|
|
@@ -162,13 +229,13 @@ const assertScenarioResult = (
|
|
|
162
229
|
}
|
|
163
230
|
} else if (scenario.expectOk === true) {
|
|
164
231
|
expect(result.isOk()).toBe(true);
|
|
165
|
-
assertSchemaMatch(result,
|
|
232
|
+
assertSchemaMatch(result, trailDef.output);
|
|
166
233
|
}
|
|
167
234
|
};
|
|
168
235
|
|
|
169
236
|
const assertFollowTrace = (
|
|
170
237
|
trace: readonly FollowRecord[],
|
|
171
|
-
scenario:
|
|
238
|
+
scenario: FollowScenario
|
|
172
239
|
): void => {
|
|
173
240
|
if (scenario.expectFollowed !== undefined) {
|
|
174
241
|
const followedIds = trace.map((r) => r.id);
|
|
@@ -185,7 +252,7 @@ const assertFollowTrace = (
|
|
|
185
252
|
|
|
186
253
|
const handleValidationError = (
|
|
187
254
|
validated: Result<unknown, Error>,
|
|
188
|
-
scenario:
|
|
255
|
+
scenario: FollowScenario
|
|
189
256
|
): boolean => {
|
|
190
257
|
if (!validated.isErr()) {
|
|
191
258
|
return false;
|
|
@@ -207,9 +274,10 @@ const handleValidationError = (
|
|
|
207
274
|
// ---------------------------------------------------------------------------
|
|
208
275
|
|
|
209
276
|
const buildTestContext = (
|
|
210
|
-
scenario:
|
|
277
|
+
scenario: FollowScenario,
|
|
211
278
|
ctx: Partial<TrailContext> | undefined,
|
|
212
|
-
trailsMap: ReadonlyMap<string, AnyTrail> | undefined
|
|
279
|
+
trailsMap: ReadonlyMap<string, AnyTrail> | undefined,
|
|
280
|
+
services: ServiceOverrideMap | undefined
|
|
213
281
|
): { trace: FollowRecord[]; testCtx: TrailContext } => {
|
|
214
282
|
const trace: FollowRecord[] = [];
|
|
215
283
|
const baseCtx = mergeTestContext(ctx);
|
|
@@ -218,46 +286,62 @@ const buildTestContext = (
|
|
|
218
286
|
scenario,
|
|
219
287
|
trailsMap,
|
|
220
288
|
baseCtx.follow,
|
|
221
|
-
baseCtx
|
|
289
|
+
baseCtx,
|
|
290
|
+
services
|
|
222
291
|
);
|
|
223
292
|
return { testCtx: { ...baseCtx, follow }, trace };
|
|
224
293
|
};
|
|
225
294
|
|
|
226
295
|
const runScenario = async (
|
|
227
|
-
|
|
228
|
-
scenario:
|
|
296
|
+
trailDef: AnyTrail,
|
|
297
|
+
scenario: FollowScenario,
|
|
229
298
|
ctx: Partial<TrailContext> | undefined,
|
|
230
|
-
trailsMap: ReadonlyMap<string, AnyTrail> | undefined
|
|
299
|
+
trailsMap: ReadonlyMap<string, AnyTrail> | undefined,
|
|
300
|
+
services: ServiceOverrideMap | undefined
|
|
231
301
|
): Promise<void> => {
|
|
232
|
-
const validated = validateInput(
|
|
302
|
+
const validated = validateInput(trailDef.input, scenario.input);
|
|
233
303
|
if (handleValidationError(validated, scenario)) {
|
|
234
304
|
return;
|
|
235
305
|
}
|
|
236
306
|
|
|
237
|
-
const { trace, testCtx } = buildTestContext(
|
|
238
|
-
|
|
307
|
+
const { trace, testCtx } = buildTestContext(
|
|
308
|
+
scenario,
|
|
309
|
+
ctx,
|
|
310
|
+
trailsMap,
|
|
311
|
+
services
|
|
312
|
+
);
|
|
313
|
+
const result = await executeTrail(trailDef, scenario.input, {
|
|
314
|
+
ctx: testCtx,
|
|
315
|
+
services,
|
|
316
|
+
});
|
|
239
317
|
assertFollowTrace(trace, scenario);
|
|
240
|
-
assertScenarioResult(result, scenario,
|
|
318
|
+
assertScenarioResult(result, scenario, trailDef);
|
|
241
319
|
};
|
|
242
320
|
|
|
243
321
|
// ---------------------------------------------------------------------------
|
|
244
|
-
//
|
|
322
|
+
// testFollows
|
|
245
323
|
// ---------------------------------------------------------------------------
|
|
246
324
|
|
|
247
|
-
/** Options for
|
|
248
|
-
export interface
|
|
325
|
+
/** Options for testFollows that provide trail definitions for injection. */
|
|
326
|
+
export interface TestFollowOptions {
|
|
249
327
|
/** Partial context overrides. */
|
|
250
328
|
readonly ctx?: Partial<TrailContext> | undefined;
|
|
329
|
+
/**
|
|
330
|
+
* Explicit service overrides merged on top of auto-resolved mocks for every
|
|
331
|
+
* scenario. Values are passed by reference — provide immutable objects, or
|
|
332
|
+
* use `mock()` on the service definition to get a fresh instance per run.
|
|
333
|
+
*/
|
|
334
|
+
readonly services?: ServiceOverrideMap | undefined;
|
|
251
335
|
/** Map of trail ID to trail definition, used for injectFromExample. */
|
|
252
336
|
readonly trails?: ReadonlyMap<string, AnyTrail> | undefined;
|
|
253
337
|
}
|
|
254
338
|
|
|
255
339
|
/**
|
|
256
|
-
* Generate a describe block for a
|
|
340
|
+
* Generate a describe block for a composition trail with one test per scenario.
|
|
257
341
|
*
|
|
258
342
|
* @example
|
|
259
343
|
* ```ts
|
|
260
|
-
*
|
|
344
|
+
* testFollows(onboardTrail, [
|
|
261
345
|
* {
|
|
262
346
|
* description: "follows add then relate",
|
|
263
347
|
* input: { name: "Alpha" },
|
|
@@ -267,16 +351,27 @@ export interface TestHikeOptions {
|
|
|
267
351
|
* ]);
|
|
268
352
|
* ```
|
|
269
353
|
*/
|
|
270
|
-
export const
|
|
271
|
-
|
|
272
|
-
scenarios: readonly
|
|
273
|
-
options?:
|
|
354
|
+
export const testFollows = (
|
|
355
|
+
trailDef: AnyTrail,
|
|
356
|
+
scenarios: readonly FollowScenario[],
|
|
357
|
+
options?: TestFollowOptions
|
|
274
358
|
): void => {
|
|
275
|
-
describe(
|
|
359
|
+
describe(trailDef.id, () => {
|
|
276
360
|
test.each([...scenarios])(
|
|
277
361
|
'$description',
|
|
278
|
-
async (scenario:
|
|
279
|
-
|
|
362
|
+
async (scenario: FollowScenario) => {
|
|
363
|
+
const services = mergeServiceOverrides(
|
|
364
|
+
await resolveMockServices(trailDef, options?.trails),
|
|
365
|
+
options?.ctx,
|
|
366
|
+
options?.services
|
|
367
|
+
);
|
|
368
|
+
await runScenario(
|
|
369
|
+
trailDef,
|
|
370
|
+
scenario,
|
|
371
|
+
options?.ctx,
|
|
372
|
+
options?.trails,
|
|
373
|
+
services
|
|
374
|
+
);
|
|
280
375
|
}
|
|
281
376
|
);
|
|
282
377
|
});
|
package/src/harness-mcp.ts
CHANGED
|
@@ -31,9 +31,12 @@ import type {
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
export const createMcpHarness = (options: McpHarnessOptions): McpHarness => {
|
|
34
|
-
const
|
|
34
|
+
const toolsResult = buildMcpTools(options.app);
|
|
35
|
+
if (toolsResult.isErr()) {
|
|
36
|
+
throw toolsResult.error;
|
|
37
|
+
}
|
|
35
38
|
const toolMap = new Map<string, McpToolDefinition>();
|
|
36
|
-
for (const tool of
|
|
39
|
+
for (const tool of toolsResult.value) {
|
|
37
40
|
toolMap.set(tool.name, tool);
|
|
38
41
|
}
|
|
39
42
|
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Contract-driven testing
|
|
2
2
|
export { testAll } from './all.js';
|
|
3
3
|
export { testExamples } from './examples.js';
|
|
4
|
-
export {
|
|
4
|
+
export { testFollows } from './follows.js';
|
|
5
5
|
export { testTrail } from './trail.js';
|
|
6
6
|
export { testContracts } from './contracts.js';
|
|
7
7
|
export { testDetours } from './detours.js';
|
|
@@ -16,7 +16,7 @@ export {
|
|
|
16
16
|
} from './assertions.js';
|
|
17
17
|
|
|
18
18
|
// Mock factories
|
|
19
|
-
export { createTestContext } from './context.js';
|
|
19
|
+
export { createFollowContext, createTestContext } from './context.js';
|
|
20
20
|
export { createTestLogger } from './logger.js';
|
|
21
21
|
|
|
22
22
|
// Surface harnesses
|
|
@@ -24,10 +24,12 @@ export { createCliHarness } from './harness-cli.js';
|
|
|
24
24
|
export { createMcpHarness } from './harness-mcp.js';
|
|
25
25
|
|
|
26
26
|
// Types
|
|
27
|
-
export type {
|
|
27
|
+
export type { CreateFollowContextOptions } from './context.js';
|
|
28
|
+
export type { TestExecutionOptions } from './context.js';
|
|
29
|
+
export type { TestFollowOptions } from './follows.js';
|
|
28
30
|
|
|
29
31
|
export type {
|
|
30
|
-
|
|
32
|
+
FollowScenario,
|
|
31
33
|
TestScenario,
|
|
32
34
|
TestLogger,
|
|
33
35
|
TestTrailContextOptions,
|
package/src/trail.ts
CHANGED
|
@@ -82,7 +82,7 @@ const runScenario = async (
|
|
|
82
82
|
}
|
|
83
83
|
const validatedInput = expectOk(validated);
|
|
84
84
|
|
|
85
|
-
const result = await trailDef.
|
|
85
|
+
const result = await trailDef.run(validatedInput, testCtx);
|
|
86
86
|
assertScenarioResult(result, scenario, trailDef);
|
|
87
87
|
};
|
|
88
88
|
|
package/src/types.ts
CHANGED
|
@@ -26,11 +26,11 @@ export interface TestScenario {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
|
-
//
|
|
29
|
+
// Follow Scenario (for testFollows)
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
|
-
/** A test scenario for a
|
|
33
|
-
export interface
|
|
32
|
+
/** A test scenario for a trail's composition graph. */
|
|
33
|
+
export interface FollowScenario extends TestScenario {
|
|
34
34
|
/** Assert these trail IDs were followed, in order. */
|
|
35
35
|
readonly expectFollowed?: readonly string[] | undefined;
|
|
36
36
|
/** Assert follow counts per trail ID. */
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/all.ts","./src/assertions.ts","./src/context.ts","./src/contracts.ts","./src/detours.ts","./src/examples.ts","./src/
|
|
1
|
+
{"root":["./src/all.ts","./src/assertions.ts","./src/context.ts","./src/contracts.ts","./src/detours.ts","./src/examples.ts","./src/follows.ts","./src/harness-cli.ts","./src/harness-mcp.ts","./src/index.ts","./src/logger.ts","./src/trail.ts","./src/types.ts"],"version":"5.9.3"}
|
package/dist/hike.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* testHike — composition-aware scenario testing for hikes.
|
|
3
|
-
*
|
|
4
|
-
* Tests the composition graph: which trails were followed, in what order,
|
|
5
|
-
* and supports failure injection from followed trail examples.
|
|
6
|
-
*/
|
|
7
|
-
import type { AnyHike, AnyTrail, TrailContext } from '@ontrails/core';
|
|
8
|
-
import type { HikeScenario } from './types.js';
|
|
9
|
-
/** Options for testHike that provide trail definitions for injection. */
|
|
10
|
-
export interface TestHikeOptions {
|
|
11
|
-
/** Partial context overrides. */
|
|
12
|
-
readonly ctx?: Partial<TrailContext> | undefined;
|
|
13
|
-
/** Map of trail ID to trail definition, used for injectFromExample. */
|
|
14
|
-
readonly trails?: ReadonlyMap<string, AnyTrail> | undefined;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Generate a describe block for a hike with one test per scenario.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* testHike(onboardHike, [
|
|
22
|
-
* {
|
|
23
|
-
* description: "follows add then relate",
|
|
24
|
-
* input: { name: "Alpha" },
|
|
25
|
-
* expectOk: true,
|
|
26
|
-
* expectFollowed: ["entity.add", "entity.relate"],
|
|
27
|
-
* },
|
|
28
|
-
* ]);
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare const testHike: (hikeDef: AnyHike, scenarios: readonly HikeScenario[], options?: TestHikeOptions) => void;
|
|
32
|
-
//# sourceMappingURL=hike.d.ts.map
|
package/dist/hike.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hike.d.ts","sourceRoot":"","sources":["../src/hike.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAY,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAehF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA8N/C,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,GACnB,SAAS,OAAO,EAChB,WAAW,SAAS,YAAY,EAAE,EAClC,UAAU,eAAe,KACxB,IASF,CAAC"}
|
package/dist/hike.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hike.js","sourceRoot":"","sources":["../src/hike.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { describe } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import type { AnyTrail, TrailContext } from '@ontrails/core';
|
|
4
|
-
import { Result, hike, trail } from '@ontrails/core';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
import { testHike } from '../hike.js';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Test trails (followed by hikes)
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
const addTrail = trail('entity.add', {
|
|
14
|
-
description: 'Add an entity',
|
|
15
|
-
examples: [
|
|
16
|
-
{ input: { name: 'Alpha' }, name: 'success' },
|
|
17
|
-
{
|
|
18
|
-
description: 'duplicate name',
|
|
19
|
-
error: 'AlreadyExistsError',
|
|
20
|
-
input: { name: '' },
|
|
21
|
-
name: 'duplicate',
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
implementation: (input: { name: string }) =>
|
|
25
|
-
Result.ok({ id: '1', name: input.name }),
|
|
26
|
-
input: z.object({ name: z.string() }),
|
|
27
|
-
output: z.object({ id: z.string(), name: z.string() }),
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const relateTrail = trail('entity.relate', {
|
|
31
|
-
description: 'Relate two entities',
|
|
32
|
-
implementation: (input: { from: string; to: string }) =>
|
|
33
|
-
Result.ok({ from: input.from, to: input.to }),
|
|
34
|
-
input: z.object({ from: z.string(), to: z.string() }),
|
|
35
|
-
output: z.object({ from: z.string(), to: z.string() }),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Test hike
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
const onboardHike = hike('entity.onboard', {
|
|
43
|
-
follows: ['entity.add', 'entity.relate'],
|
|
44
|
-
implementation: async (
|
|
45
|
-
input: { name: string; relatedTo: string },
|
|
46
|
-
ctx: TrailContext
|
|
47
|
-
) => {
|
|
48
|
-
if (!ctx.follow) {
|
|
49
|
-
return Result.err(new Error('follow not available'));
|
|
50
|
-
}
|
|
51
|
-
const addResult = await ctx.follow<{ id: string; name: string }>(
|
|
52
|
-
'entity.add',
|
|
53
|
-
{ name: input.name }
|
|
54
|
-
);
|
|
55
|
-
if (addResult.isErr()) {
|
|
56
|
-
return Result.err(addResult.error);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const relateResult = await ctx.follow<{ from: string; to: string }>(
|
|
60
|
-
'entity.relate',
|
|
61
|
-
{ from: addResult.value.name, to: input.relatedTo }
|
|
62
|
-
);
|
|
63
|
-
if (relateResult.isErr()) {
|
|
64
|
-
return Result.err(relateResult.error);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return Result.ok({
|
|
68
|
-
name: addResult.value.name,
|
|
69
|
-
relatedTo: relateResult.value.to,
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
|
-
input: z.object({ name: z.string(), relatedTo: z.string() }),
|
|
73
|
-
output: z.object({ name: z.string(), relatedTo: z.string() }),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const trailsMap = new Map<string, AnyTrail>([
|
|
77
|
-
['entity.add', addTrail],
|
|
78
|
-
['entity.relate', relateTrail],
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Tests
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
const opts = { trails: trailsMap };
|
|
86
|
-
|
|
87
|
-
describe('testHike: expectOk', () => {
|
|
88
|
-
// eslint-disable-next-line jest/require-hook
|
|
89
|
-
testHike(
|
|
90
|
-
onboardHike,
|
|
91
|
-
[
|
|
92
|
-
{
|
|
93
|
-
description: 'basic onboard succeeds',
|
|
94
|
-
expectOk: true,
|
|
95
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
opts
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('testHike: expectFollowed', () => {
|
|
103
|
-
// eslint-disable-next-line jest/require-hook
|
|
104
|
-
testHike(
|
|
105
|
-
onboardHike,
|
|
106
|
-
[
|
|
107
|
-
{
|
|
108
|
-
description: 'follows add then relate in order',
|
|
109
|
-
expectFollowed: ['entity.add', 'entity.relate'],
|
|
110
|
-
expectOk: true,
|
|
111
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
opts
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('testHike: expectFollowedCount', () => {
|
|
119
|
-
// eslint-disable-next-line jest/require-hook
|
|
120
|
-
testHike(
|
|
121
|
-
onboardHike,
|
|
122
|
-
[
|
|
123
|
-
{
|
|
124
|
-
description: 'each trail followed exactly once',
|
|
125
|
-
expectFollowedCount: { 'entity.add': 1, 'entity.relate': 1 },
|
|
126
|
-
expectOk: true,
|
|
127
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
opts
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('testHike: injectFromExample', () => {
|
|
135
|
-
// eslint-disable-next-line jest/require-hook
|
|
136
|
-
testHike(
|
|
137
|
-
onboardHike,
|
|
138
|
-
[
|
|
139
|
-
{
|
|
140
|
-
description: 'inject duplicate error from add trail example',
|
|
141
|
-
expectErr: Error,
|
|
142
|
-
expectErrMessage: 'AlreadyExistsError',
|
|
143
|
-
injectFromExample: { 'entity.add': 'duplicate' },
|
|
144
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
145
|
-
},
|
|
146
|
-
],
|
|
147
|
-
opts
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('testHike: expectValue', () => {
|
|
152
|
-
// eslint-disable-next-line jest/require-hook
|
|
153
|
-
testHike(
|
|
154
|
-
onboardHike,
|
|
155
|
-
[
|
|
156
|
-
{
|
|
157
|
-
description: 'exact value match',
|
|
158
|
-
expectValue: { name: 'Alpha', relatedTo: 'Beta' },
|
|
159
|
-
input: { name: 'Alpha', relatedTo: 'Beta' },
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
opts
|
|
163
|
-
);
|
|
164
|
-
});
|