@ontrails/testing 1.0.0-beta.10 → 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 +28 -0
- 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 +9 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +47 -13
- package/dist/context.js.map +1 -1
- package/dist/contracts.d.ts +2 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +13 -8
- package/dist/contracts.js.map +1 -1
- package/dist/examples.d.ts +2 -1
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +28 -21
- package/dist/examples.js.map +1 -1
- package/dist/follows.d.ts +7 -1
- package/dist/follows.d.ts.map +1 -1
- package/dist/follows.js +60 -17
- package/dist/follows.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/all.test.ts +64 -0
- package/src/__tests__/context.test.ts +58 -3
- package/src/__tests__/contracts.test.ts +92 -1
- package/src/__tests__/examples.test.ts +265 -1
- package/src/__tests__/follows.test.ts +427 -1
- package/src/all.ts +5 -1
- package/src/context.ts +82 -15
- package/src/contracts.ts +28 -10
- package/src/examples.ts +63 -21
- package/src/follows.ts +112 -17
- package/src/index.ts +1 -0
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add services as a first-class primitive.
|
|
8
|
+
|
|
9
|
+
Services make infrastructure dependencies declarative, injectable, and governable. Define a service with `service()`, declare it on a trail with `services: [db]`, and access it with `db.from(ctx)` or `ctx.service()`.
|
|
10
|
+
|
|
11
|
+
**Core:** `service()` factory, `ServiceSpec<T>`, `ServiceContext`, singleton resolution in `executeTrail`, in-flight creation dedup, `isService` guard, `findDuplicateServiceId`, topo service discovery and validation, `services` field on trail specs.
|
|
12
|
+
|
|
13
|
+
**Testing:** Auto-resolution of `mock` factories in `testAll`, `testExamples`, `testContracts`, and `testFollows`. Explicit `services` overrides with correct precedence (`explicit > ctx.extensions > auto-mock`). Service mock propagation through follow graphs.
|
|
14
|
+
|
|
15
|
+
**Warden:** `service-declarations` rule validates `db.from(ctx)` and `ctx.service()` usage matches declared `services: [...]`. `service-exists` rule validates declared service IDs resolve in project context. Scope-aware AST walking skips nested function boundaries.
|
|
16
|
+
|
|
17
|
+
**Surfaces:** Service overrides thread through `dispatch` and `blaze` on CLI, MCP, and HTTP.
|
|
18
|
+
|
|
19
|
+
**Introspection:** Survey and surface map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
|
|
20
|
+
|
|
21
|
+
**Docs:** ADR-009 accepted. Unified services guide, updated vocabulary, getting-started, architecture, and package READMEs.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @ontrails/core@1.0.0-beta.11
|
|
27
|
+
- @ontrails/cli@1.0.0-beta.11
|
|
28
|
+
- @ontrails/mcp@1.0.0-beta.11
|
|
29
|
+
- @ontrails/logging@1.0.0-beta.11
|
|
30
|
+
|
|
3
31
|
## 1.0.0-beta.10
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/dist/all.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* verification into one describe block.
|
|
6
6
|
*/
|
|
7
7
|
import type { Topo, TrailContext } from '@ontrails/core';
|
|
8
|
+
import type { TestExecutionOptions } from './context.js';
|
|
8
9
|
/**
|
|
9
10
|
* Run the full governance test suite for a Topo.
|
|
10
11
|
*
|
|
@@ -26,5 +27,5 @@ import type { Topo, TrailContext } from '@ontrails/core';
|
|
|
26
27
|
* testAll(app);
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
|
-
export declare const testAll: (topo: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
|
|
30
|
+
export declare const testAll: (topo: Topo, ctxOrFactory?: Partial<TrailContext> | TestExecutionOptions | (() => Partial<TrailContext> | TestExecutionOptions)) => void;
|
|
30
31
|
//# sourceMappingURL=all.d.ts.map
|
package/dist/all.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"all.d.ts","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"all.d.ts","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAIzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,IAAI,EACV,eACI,OAAO,CAAC,YAAY,CAAC,GACrB,oBAAoB,GACpB,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,oBAAoB,CAAC,KACvD,IAcF,CAAC"}
|
package/dist/all.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"all.js","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGlD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"all.js","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGlD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,IAAU,EACV,YAGwD,EAClD,EAAE;IACR,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE;YAC1B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,oGAAoG;QACpG,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACjC,oGAAoG;QACpG,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAClC,oGAAoG;QACpG,WAAW,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
|
package/dist/context.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test context factory for creating TrailContext instances suitable for testing.
|
|
3
3
|
*/
|
|
4
|
-
import type { FollowFn, TrailContext } from '@ontrails/core';
|
|
4
|
+
import type { FollowFn, ServiceOverrideMap, Topo, TrailContext } from '@ontrails/core';
|
|
5
5
|
import { Result } from '@ontrails/core';
|
|
6
6
|
import type { TestTrailContextOptions } from './types.js';
|
|
7
7
|
/**
|
|
@@ -15,6 +15,10 @@ export declare const createTestContext: (overrides?: TestTrailContextOptions) =>
|
|
|
15
15
|
export interface CreateFollowContextOptions {
|
|
16
16
|
readonly responses?: Record<string, Result<unknown, Error>> | undefined;
|
|
17
17
|
}
|
|
18
|
+
export interface TestExecutionOptions {
|
|
19
|
+
readonly ctx?: Partial<TrailContext> | undefined;
|
|
20
|
+
readonly services?: ServiceOverrideMap | undefined;
|
|
21
|
+
}
|
|
18
22
|
/**
|
|
19
23
|
* Create a mock `FollowFn` for testing composite trails.
|
|
20
24
|
*
|
|
@@ -30,9 +34,12 @@ export interface CreateFollowContextOptions {
|
|
|
30
34
|
* ```
|
|
31
35
|
*/
|
|
32
36
|
export declare const createFollowContext: (options?: CreateFollowContextOptions) => FollowFn;
|
|
37
|
+
export declare const normalizeTestExecutionOptions: (input?: Partial<TrailContext> | TestExecutionOptions) => TestExecutionOptions;
|
|
38
|
+
export declare const mergeServiceOverrides: (autoResolved: ServiceOverrideMap, ctx: Partial<TrailContext> | undefined, explicit: ServiceOverrideMap | undefined) => ServiceOverrideMap;
|
|
39
|
+
export declare const resolveMockServices: (app: Topo) => Promise<ServiceOverrideMap>;
|
|
33
40
|
/**
|
|
34
41
|
* Merge a Partial<TrailContext> into a test context.
|
|
35
42
|
* Used internally when the public API accepts Partial<TrailContext>.
|
|
36
43
|
*/
|
|
37
|
-
export declare const mergeTestContext: (ctx?: Partial<TrailContext
|
|
44
|
+
export declare const mergeTestContext: (ctx?: Partial<TrailContext>, services?: ServiceOverrideMap) => TrailContext;
|
|
38
45
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,IAAI,EACJ,YAAY,EACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAuB,MAAM,gBAAgB,CAAC;AAG7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAU1D;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,YAAY,uBAAuB,KAClC,YAaF,CAAC;AAMF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;CACzE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACpD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,0BAA0B,KACnC,QAcF,CAAC;AAQF,eAAO,MAAM,6BAA6B,GACxC,QAAQ,OAAO,CAAC,YAAY,CAAC,GAAG,oBAAoB,KACnD,oBACqD,CAAC;AAEzD,eAAO,MAAM,qBAAqB,GAChC,cAAc,kBAAkB,EAChC,KAAK,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,EACtC,UAAU,kBAAkB,GAAG,SAAS,KACvC,kBAID,CAAC;AAaH,eAAO,MAAM,mBAAmB,GAC9B,KAAK,IAAI,KACR,OAAO,CAAC,kBAAkB,CAAiC,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,OAAO,CAAC,YAAY,CAAC,EAC3B,WAAW,kBAAkB,KAC5B,YAcF,CAAC"}
|
package/dist/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test context factory for creating TrailContext instances suitable for testing.
|
|
3
3
|
*/
|
|
4
|
-
import { Result } from '@ontrails/core';
|
|
4
|
+
import { Result, createServiceLookup } from '@ontrails/core';
|
|
5
5
|
import { createTestLogger } from './logger.js';
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
// createTestContext
|
|
@@ -13,13 +13,20 @@ import { createTestLogger } from './logger.js';
|
|
|
13
13
|
* - `logger`: a `TestLogger` that captures entries
|
|
14
14
|
* - `signal`: a non-aborted AbortController signal
|
|
15
15
|
*/
|
|
16
|
-
export const createTestContext = (overrides) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
export const createTestContext = (overrides) => {
|
|
17
|
+
const cwd = overrides?.cwd ?? process.cwd();
|
|
18
|
+
const ctx = {
|
|
19
|
+
cwd,
|
|
20
|
+
env: overrides?.env ?? { TRAILS_ENV: 'test' },
|
|
21
|
+
extensions: undefined,
|
|
22
|
+
logger: overrides?.logger ?? createTestLogger(),
|
|
23
|
+
requestId: overrides?.requestId ?? 'test-request-001',
|
|
24
|
+
signal: overrides?.signal ?? new AbortController().signal,
|
|
25
|
+
workspaceRoot: cwd,
|
|
26
|
+
};
|
|
27
|
+
ctx.service = createServiceLookup(() => ctx);
|
|
28
|
+
return ctx;
|
|
29
|
+
};
|
|
23
30
|
/**
|
|
24
31
|
* Create a mock `FollowFn` for testing composite trails.
|
|
25
32
|
*
|
|
@@ -44,15 +51,42 @@ export const createFollowContext = (options) => {
|
|
|
44
51
|
return Promise.resolve(response);
|
|
45
52
|
};
|
|
46
53
|
};
|
|
54
|
+
const isTestExecutionOptions = (input) => input !== undefined &&
|
|
55
|
+
(Object.hasOwn(input, 'ctx') || Object.hasOwn(input, 'services'));
|
|
56
|
+
export const normalizeTestExecutionOptions = (input) => isTestExecutionOptions(input) ? input : { ctx: input };
|
|
57
|
+
export const mergeServiceOverrides = (autoResolved, ctx, explicit) => ({
|
|
58
|
+
...autoResolved,
|
|
59
|
+
...ctx?.extensions,
|
|
60
|
+
...explicit,
|
|
61
|
+
});
|
|
62
|
+
const buildMockServices = async (app) => {
|
|
63
|
+
const services = {};
|
|
64
|
+
for (const declaredService of app.listServices()) {
|
|
65
|
+
if (!declaredService.mock) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
services[declaredService.id] = await declaredService.mock();
|
|
69
|
+
}
|
|
70
|
+
return services;
|
|
71
|
+
};
|
|
72
|
+
export const resolveMockServices = async (app) => await buildMockServices(app);
|
|
47
73
|
/**
|
|
48
74
|
* Merge a Partial<TrailContext> into a test context.
|
|
49
75
|
* Used internally when the public API accepts Partial<TrailContext>.
|
|
50
76
|
*/
|
|
51
|
-
export const mergeTestContext = (ctx) => {
|
|
52
|
-
if (ctx === undefined) {
|
|
53
|
-
return createTestContext();
|
|
54
|
-
}
|
|
77
|
+
export const mergeTestContext = (ctx, services) => {
|
|
55
78
|
const base = createTestContext();
|
|
56
|
-
|
|
79
|
+
const extensions = {
|
|
80
|
+
...base.extensions,
|
|
81
|
+
...ctx?.extensions,
|
|
82
|
+
...services,
|
|
83
|
+
};
|
|
84
|
+
const merged = {
|
|
85
|
+
...base,
|
|
86
|
+
...ctx,
|
|
87
|
+
extensions: Object.keys(extensions).length === 0 ? undefined : extensions,
|
|
88
|
+
};
|
|
89
|
+
merged.service = createServiceLookup(() => merged);
|
|
90
|
+
return merged;
|
|
57
91
|
};
|
|
58
92
|
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/contracts.d.ts
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* the declared schema.
|
|
7
7
|
*/
|
|
8
8
|
import type { Topo, TrailContext } from '@ontrails/core';
|
|
9
|
+
import type { TestExecutionOptions } from './context.js';
|
|
9
10
|
/**
|
|
10
11
|
* Verify that every trail implementation output matches its declared
|
|
11
12
|
* output schema. Catches implementation-schema drift.
|
|
12
13
|
*
|
|
13
14
|
* Trails without output schemas or examples are skipped.
|
|
14
15
|
*/
|
|
15
|
-
export declare const testContracts: (app: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
|
|
16
|
+
export declare const testContracts: (app: Topo, ctxOrFactory?: Partial<TrailContext> | TestExecutionOptions | (() => Partial<TrailContext> | TestExecutionOptions)) => void;
|
|
16
17
|
//# sourceMappingURL=contracts.d.ts.map
|
package/dist/contracts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAuB,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAuB,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAW9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAqCzD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,eACI,OAAO,CAAC,YAAY,CAAC,GACrB,oBAAoB,GACpB,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,oBAAoB,CAAC,KACvD,IA6CF,CAAC"}
|
package/dist/contracts.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* the declared schema.
|
|
7
7
|
*/
|
|
8
8
|
import { describe, test } from 'bun:test';
|
|
9
|
-
import { formatZodIssues, validateInput } from '@ontrails/core';
|
|
9
|
+
import { executeTrail, formatZodIssues, validateInput } from '@ontrails/core';
|
|
10
10
|
import { expectOk } from './assertions.js';
|
|
11
|
-
import { mergeTestContext } from './context.js';
|
|
11
|
+
import { mergeServiceOverrides, mergeTestContext, normalizeTestExecutionOptions, resolveMockServices, } from './context.js';
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Helpers
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -18,7 +18,7 @@ const needsFollowContext = (t, resolveCtx) => {
|
|
|
18
18
|
if (!spec.follow || spec.follow.length === 0) {
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
|
-
return !resolveCtx()?.follow;
|
|
21
|
+
return !normalizeTestExecutionOptions(resolveCtx()).ctx?.follow;
|
|
22
22
|
};
|
|
23
23
|
const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
|
|
24
24
|
const parsed = outputSchema.safeParse(value);
|
|
@@ -37,7 +37,7 @@ const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
|
|
|
37
37
|
* Trails without output schemas or examples are skipped.
|
|
38
38
|
*/
|
|
39
39
|
export const testContracts = (app, ctxOrFactory) => {
|
|
40
|
-
const
|
|
40
|
+
const resolveInput = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
|
|
41
41
|
const allEntries = app.list();
|
|
42
42
|
describe('contracts', () => {
|
|
43
43
|
describe.each(allEntries)('$id', (t) => {
|
|
@@ -47,16 +47,21 @@ export const testContracts = (app, ctxOrFactory) => {
|
|
|
47
47
|
if (t.examples === undefined || t.examples.length === 0) {
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
if (needsFollowContext(t,
|
|
50
|
+
if (needsFollowContext(t, resolveInput)) {
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
const { examples, output: outputSchema } = t;
|
|
54
54
|
const successExamples = examples.filter((e) => e.error === undefined);
|
|
55
55
|
test.each(successExamples)('contract: $name', async (example) => {
|
|
56
|
-
const
|
|
56
|
+
const resolved = normalizeTestExecutionOptions(resolveInput());
|
|
57
|
+
const services = mergeServiceOverrides(await resolveMockServices(app), resolved.ctx, resolved.services);
|
|
58
|
+
const testCtx = mergeTestContext(resolved.ctx);
|
|
57
59
|
const validated = validateInput(t.input, example.input);
|
|
58
|
-
|
|
59
|
-
const result = await t.
|
|
60
|
+
expectOk(validated);
|
|
61
|
+
const result = await executeTrail(t, example.input, {
|
|
62
|
+
ctx: testCtx,
|
|
63
|
+
services,
|
|
64
|
+
});
|
|
60
65
|
const resultValue = expectOk(result);
|
|
61
66
|
validateOutputSchema(outputSchema, resultValue, t.id, example.name);
|
|
62
67
|
});
|
package/dist/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
|
package/dist/examples.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* declarations, checks that every declared follow was called at least once.
|
|
8
8
|
*/
|
|
9
9
|
import type { Topo, TrailContext } from '@ontrails/core';
|
|
10
|
+
import type { TestExecutionOptions } from './context.js';
|
|
10
11
|
/**
|
|
11
12
|
* Generate describe/test blocks for every trail example in the app.
|
|
12
13
|
*
|
|
@@ -18,5 +19,5 @@ import type { Topo, TrailContext } from '@ontrails/core';
|
|
|
18
19
|
* testExamples(app);
|
|
19
20
|
* ```
|
|
20
21
|
*/
|
|
21
|
-
export declare const testExamples: (app: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
|
|
22
|
+
export declare const testExamples: (app: Topo, ctxOrFactory?: Partial<TrailContext> | TestExecutionOptions | (() => Partial<TrailContext> | TestExecutionOptions)) => void;
|
|
22
23
|
//# sourceMappingURL=examples.d.ts.map
|
package/dist/examples.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAGV,IAAI,EAGJ,YAAY,EACb,MAAM,gBAAgB,CAAC;AAkCxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAiLzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,IAAI,EACT,eACI,OAAO,CAAC,YAAY,CAAC,GACrB,oBAAoB,GACpB,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,oBAAoB,CAAC,KACvD,IAyEF,CAAC"}
|
package/dist/examples.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* declarations, checks that every declared follow was called at least once.
|
|
8
8
|
*/
|
|
9
9
|
import { describe, expect, test } from 'bun:test';
|
|
10
|
-
import { AlreadyExistsError, AmbiguousError, AssertionError, AuthError, CancelledError, ConflictError, InternalError, NetworkError, NotFoundError, PermissionError, RateLimitError, Result, TimeoutError, TrailsError, ValidationError, validateInput, } from '@ontrails/core';
|
|
11
|
-
import { assertErrorMatch, assertFullMatch, assertSchemaMatch,
|
|
12
|
-
import { mergeTestContext } from './context.js';
|
|
10
|
+
import { AlreadyExistsError, AmbiguousError, AssertionError, AuthError, CancelledError, ConflictError, InternalError, NetworkError, NotFoundError, PermissionError, RateLimitError, executeTrail, Result, TimeoutError, TrailsError, ValidationError, validateInput, } from '@ontrails/core';
|
|
11
|
+
import { assertErrorMatch, assertFullMatch, assertSchemaMatch, } from './assertions.js';
|
|
12
|
+
import { mergeServiceOverrides, mergeTestContext, normalizeTestExecutionOptions, resolveMockServices, } from './context.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Error class name -> constructor map
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -69,13 +69,15 @@ const handleValidationError = (validated, example) => {
|
|
|
69
69
|
* Run a single example against a trail.
|
|
70
70
|
* Handles validation, execution, and assertions.
|
|
71
71
|
*/
|
|
72
|
-
const runExample = async (t, example, output, testCtx) => {
|
|
72
|
+
const runExample = async (t, example, output, testCtx, services) => {
|
|
73
73
|
const validated = validateInput(t.input, example.input);
|
|
74
74
|
if (handleValidationError(validated, example)) {
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const result = await executeTrail(t, example.input, {
|
|
78
|
+
ctx: testCtx,
|
|
79
|
+
services,
|
|
80
|
+
});
|
|
79
81
|
assertProgressiveMatch(result, example, output);
|
|
80
82
|
};
|
|
81
83
|
// ---------------------------------------------------------------------------
|
|
@@ -88,7 +90,7 @@ const runExample = async (t, example, output, testCtx) => {
|
|
|
88
90
|
* in the topo and executes it with validated input. Falls back to
|
|
89
91
|
* `Result.ok()` when neither is available.
|
|
90
92
|
*/
|
|
91
|
-
const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
93
|
+
const createCoverageFollow = (called, baseFollow, topo, ctx, services) => {
|
|
92
94
|
const follow = (id, input) => {
|
|
93
95
|
called.add(id);
|
|
94
96
|
if (baseFollow !== undefined) {
|
|
@@ -96,11 +98,10 @@ const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
|
96
98
|
}
|
|
97
99
|
const trailDef = topo.get(id);
|
|
98
100
|
if (trailDef !== undefined) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return Promise.resolve(trailDef.run(validated.value, ctx));
|
|
101
|
+
return executeTrail(trailDef, input, {
|
|
102
|
+
ctx: { ...ctx, follow },
|
|
103
|
+
services,
|
|
104
|
+
});
|
|
104
105
|
}
|
|
105
106
|
return Promise.resolve(Result.ok());
|
|
106
107
|
};
|
|
@@ -109,15 +110,17 @@ const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
|
109
110
|
/**
|
|
110
111
|
* Run a single example against a composition trail, recording follow calls.
|
|
111
112
|
*/
|
|
112
|
-
const runCompositionExample = async (trailDef, example, output, baseCtx, called, topo) => {
|
|
113
|
+
const runCompositionExample = async (trailDef, example, output, baseCtx, called, topo, services) => {
|
|
113
114
|
const validated = validateInput(trailDef.input, example.input);
|
|
114
115
|
if (handleValidationError(validated, example)) {
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
|
-
const
|
|
118
|
-
const follow = createCoverageFollow(called, baseCtx.follow, topo, baseCtx);
|
|
118
|
+
const follow = createCoverageFollow(called, baseCtx.follow, topo, baseCtx, services);
|
|
119
119
|
const testCtx = { ...baseCtx, follow };
|
|
120
|
-
const result = await trailDef.
|
|
120
|
+
const result = await executeTrail(trailDef, example.input, {
|
|
121
|
+
ctx: testCtx,
|
|
122
|
+
services,
|
|
123
|
+
});
|
|
121
124
|
assertProgressiveMatch(result, example, output);
|
|
122
125
|
};
|
|
123
126
|
// ---------------------------------------------------------------------------
|
|
@@ -135,7 +138,7 @@ const runCompositionExample = async (trailDef, example, output, baseCtx, called,
|
|
|
135
138
|
* ```
|
|
136
139
|
*/
|
|
137
140
|
export const testExamples = (app, ctxOrFactory) => {
|
|
138
|
-
const
|
|
141
|
+
const resolveInput = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
|
|
139
142
|
const allTrails = app.list();
|
|
140
143
|
const withExamples = allTrails.filter((t) => t.examples !== undefined && t.examples.length > 0);
|
|
141
144
|
const simpleTrails = withExamples.filter((t) => t.follow.length === 0);
|
|
@@ -148,8 +151,10 @@ export const testExamples = (app, ctxOrFactory) => {
|
|
|
148
151
|
return;
|
|
149
152
|
}
|
|
150
153
|
test.each([...examples])('example: $name', async (example) => {
|
|
151
|
-
const
|
|
152
|
-
await
|
|
154
|
+
const resolved = normalizeTestExecutionOptions(resolveInput());
|
|
155
|
+
const services = mergeServiceOverrides(await resolveMockServices(app), resolved.ctx, resolved.services);
|
|
156
|
+
const testCtx = mergeTestContext(resolved.ctx);
|
|
157
|
+
await runExample(t, example, output, testCtx, services);
|
|
153
158
|
});
|
|
154
159
|
});
|
|
155
160
|
}
|
|
@@ -162,8 +167,10 @@ export const testExamples = (app, ctxOrFactory) => {
|
|
|
162
167
|
}
|
|
163
168
|
const called = new Set();
|
|
164
169
|
test.each([...examples])('example: $name', async (example) => {
|
|
165
|
-
const
|
|
166
|
-
|
|
170
|
+
const resolved = normalizeTestExecutionOptions(resolveInput());
|
|
171
|
+
const services = mergeServiceOverrides(await resolveMockServices(app), resolved.ctx, resolved.services);
|
|
172
|
+
const baseCtx = mergeTestContext(resolved.ctx);
|
|
173
|
+
await runCompositionExample(t, example, output, baseCtx, called, app, services);
|
|
167
174
|
});
|
|
168
175
|
test('follow coverage', () => {
|
|
169
176
|
const uncovered = t.follow.filter((id) => !called.has(id));
|
package/dist/examples.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"examples.js","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"examples.js","sourceRoot":"","sources":["../src/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAWlD,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,SAAS,EACT,cAAc,EACd,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,eAAe,EACf,cAAc,EACd,YAAY,EACZ,MAAM,EACN,YAAY,EACZ,WAAW,EACX,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAGtB,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,MAAM,SAAS,GAAoD;IACjE,kBAAkB,EAAE,kBAAqD;IACzE,cAAc,EAAE,cAAiD;IACjE,cAAc,EAAE,cAAiD;IACjE,SAAS,EAAE,SAA4C;IACvD,cAAc,EAAE,cAAiD;IACjE,aAAa,EAAE,aAAgD;IAC/D,aAAa,EAAE,aAAgD;IAC/D,YAAY,EAAE,YAA+C;IAC7D,aAAa,EAAE,aAAgD;IAC/D,eAAe,EAAE,eAAkD;IACnE,cAAc,EAAE,cAAiD;IACjE,YAAY,EAAE,YAA+C;IAC7D,WAAW,EAAE,WAAyD;IACtE,eAAe,EAAE,eAAkD;CACpE,CAAC;AAEF;;;GAGG;AACH,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAqC,EAAE,CAC5E,SAAS,CAAC,IAAI,CAAC,IAAK,KAAyC,CAAC;AAEhE,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,MAAM,sBAAsB,GAAG,CAC7B,MAA8B,EAC9B,OAAuC,EACvC,MAA6B,EACvB,EAAE;IACR,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpD,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACrC,OAAO;IACT,CAAC;IAED,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,CAC5B,SAAiC,EACjC,OAAuC,EAC9B,EAAE;IACX,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,CAAC,IAAI,wBAAwB,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAC1E,CAAC;AACJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,GAAG,KAAK,EACtB,CAA0B,EAC1B,OAAuC,EACvC,MAA6B,EAC7B,OAAqB,EACrB,QAA6B,EACd,EAAE;IACjB,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAExD,IAAI,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE;QAClD,GAAG,EAAE,OAAO;QACZ,QAAQ;KACT,CAAC,CAAC;IACH,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF,8EAA8E;AAC9E,yCAAyC;AACzC,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAG,CAC3B,MAAmB,EACnB,UAAgC,EAChC,IAAU,EACV,GAAiB,EACjB,QAA6B,EACnB,EAAE;IACZ,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,KAAc,EAAE,EAAE;QAC5C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEf,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE;gBACnC,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE;gBACvB,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,OAAO,MAAkB,CAAC;AAC5B,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,qBAAqB,GAAG,KAAK,EACjC,QAAiC,EACjC,OAAuC,EACvC,MAA6B,EAC7B,OAAqB,EACrB,MAAmB,EACnB,IAAU,EACV,QAA6B,EACd,EAAE;IACjB,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAE/D,IAAI,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CACjC,MAAM,EACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,OAAO,EACP,QAAQ,CACT,CAAC;IACF,MAAM,OAAO,GAAiB,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC;IAErD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE;QACzD,GAAG,EAAE,OAAO;QACZ,QAAQ;KACT,CAAC,CAAC;IACH,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,GAAS,EACT,YAGwD,EAClD,EAAE;IACR,MAAM,YAAY,GAChB,OAAO,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC;IACzE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAA+B,CAAC;IAE1D,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CACnC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CACzD,CAAC;IACF,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IACvE,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE1E,uCAAuC;IACvC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;YACvC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CACtB,gBAAgB,EAChB,KAAK,EAAE,OAAuC,EAAE,EAAE;gBAChD,MAAM,QAAQ,GAAG,6BAA6B,CAAC,YAAY,EAAE,CAAC,CAAC;gBAC/D,MAAM,QAAQ,GAAG,qBAAqB,CACpC,MAAM,mBAAmB,CAAC,GAAG,CAAC,EAC9B,QAAQ,CAAC,GAAG,EACZ,QAAQ,CAAC,QAAQ,CAClB,CAAC;gBACF,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC/C,MAAM,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC1D,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8DAA8D;IAC9D,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;YAC5C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;YAEjC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CACtB,gBAAgB,EAChB,KAAK,EAAE,OAAuC,EAAE,EAAE;gBAChD,MAAM,QAAQ,GAAG,6BAA6B,CAAC,YAAY,EAAE,CAAC,CAAC;gBAC/D,MAAM,QAAQ,GAAG,qBAAqB,CACpC,MAAM,mBAAmB,CAAC,GAAG,CAAC,EAC9B,QAAQ,CAAC,GAAG,EACZ,QAAQ,CAAC,QAAQ,CAClB,CAAC;gBACF,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC/C,MAAM,qBAAqB,CACzB,CAAC,EACD,OAAO,EACP,MAAM,EACN,OAAO,EACP,MAAM,EACN,GAAG,EACH,QAAQ,CACT,CAAC;YACJ,CAAC,CACF,CAAC;YAEF,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE;gBAC3B,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC3D,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC"}
|
package/dist/follows.d.ts
CHANGED
|
@@ -4,12 +4,18 @@
|
|
|
4
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
|
-
import type { AnyTrail, TrailContext } from '@ontrails/core';
|
|
7
|
+
import type { AnyTrail, ServiceOverrideMap, TrailContext } from '@ontrails/core';
|
|
8
8
|
import type { FollowScenario } from './types.js';
|
|
9
9
|
/** Options for testFollows that provide trail definitions for injection. */
|
|
10
10
|
export interface TestFollowOptions {
|
|
11
11
|
/** Partial context overrides. */
|
|
12
12
|
readonly ctx?: Partial<TrailContext> | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Explicit service overrides merged on top of auto-resolved mocks for every
|
|
15
|
+
* scenario. Values are passed by reference — provide immutable objects, or
|
|
16
|
+
* use `mock()` on the service definition to get a fresh instance per run.
|
|
17
|
+
*/
|
|
18
|
+
readonly services?: ServiceOverrideMap | undefined;
|
|
13
19
|
/** Map of trail ID to trail definition, used for injectFromExample. */
|
|
14
20
|
readonly trails?: ReadonlyMap<string, AnyTrail> | undefined;
|
|
15
21
|
}
|
package/dist/follows.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"follows.d.ts","sourceRoot":"","sources":["../src/follows.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"follows.d.ts","sourceRoot":"","sources":["../src/follows.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,QAAQ,EAER,kBAAkB,EAClB,YAAY,EACb,MAAM,gBAAgB,CAAC;AAexB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAuSjD,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACnD,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GACtB,UAAU,QAAQ,EAClB,WAAW,SAAS,cAAc,EAAE,EACpC,UAAU,iBAAiB,KAC1B,IAoBF,CAAC"}
|
package/dist/follows.js
CHANGED
|
@@ -5,9 +5,48 @@
|
|
|
5
5
|
* and supports failure injection from followed trail examples.
|
|
6
6
|
*/
|
|
7
7
|
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
import { InternalError, Result, ValidationError, validateInput, } from '@ontrails/core';
|
|
9
|
-
import { assertErrorMatch, assertFullMatch, assertSchemaMatch,
|
|
10
|
-
import { mergeTestContext } from './context.js';
|
|
8
|
+
import { executeTrail, InternalError, Result, ValidationError, validateInput, } from '@ontrails/core';
|
|
9
|
+
import { assertErrorMatch, assertFullMatch, assertSchemaMatch, } from './assertions.js';
|
|
10
|
+
import { mergeServiceOverrides, mergeTestContext } from './context.js';
|
|
11
|
+
const collectDeclaredServices = (trailDef, trailsMap) => {
|
|
12
|
+
const seenServiceIds = new Set();
|
|
13
|
+
const seenTrailIds = new Set();
|
|
14
|
+
const services = [];
|
|
15
|
+
const collect = (candidate) => {
|
|
16
|
+
for (const declaredService of candidate.services) {
|
|
17
|
+
if (seenServiceIds.has(declaredService.id)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
seenServiceIds.add(declaredService.id);
|
|
21
|
+
services.push(declaredService);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const visit = (candidate) => {
|
|
25
|
+
if (seenTrailIds.has(candidate.id)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
seenTrailIds.add(candidate.id);
|
|
29
|
+
collect(candidate);
|
|
30
|
+
for (const followedId of candidate.follow) {
|
|
31
|
+
const followedTrail = trailsMap?.get(followedId);
|
|
32
|
+
if (followedTrail) {
|
|
33
|
+
visit(followedTrail);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
visit(trailDef);
|
|
38
|
+
return services;
|
|
39
|
+
};
|
|
40
|
+
const resolveMockServices = async (trailDef, trailsMap) => {
|
|
41
|
+
const services = {};
|
|
42
|
+
for (const declaredService of collectDeclaredServices(trailDef, trailsMap)) {
|
|
43
|
+
if (!declaredService.mock) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
services[declaredService.id] = await declaredService.mock();
|
|
47
|
+
}
|
|
48
|
+
return services;
|
|
49
|
+
};
|
|
11
50
|
// ---------------------------------------------------------------------------
|
|
12
51
|
// Injection helpers
|
|
13
52
|
// ---------------------------------------------------------------------------
|
|
@@ -41,16 +80,16 @@ const tryInjectError = (id, scenario, trailsMap) => {
|
|
|
41
80
|
/**
|
|
42
81
|
* Execute a trail from the map, validating input first.
|
|
43
82
|
*/
|
|
44
|
-
const executeFromMap = (id, input, trailsMap, ctx) => {
|
|
83
|
+
const executeFromMap = (id, input, trailsMap, ctx, services, follow) => {
|
|
45
84
|
const trailDef = trailsMap?.get(id);
|
|
46
85
|
if (trailDef === undefined) {
|
|
47
86
|
return undefined;
|
|
48
87
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
const nestedCtx = follow ? { ...ctx, follow } : ctx;
|
|
89
|
+
return executeTrail(trailDef, input, {
|
|
90
|
+
ctx: nestedCtx,
|
|
91
|
+
services,
|
|
92
|
+
});
|
|
54
93
|
};
|
|
55
94
|
// ---------------------------------------------------------------------------
|
|
56
95
|
// Follow factory
|
|
@@ -58,7 +97,7 @@ const executeFromMap = (id, input, trailsMap, ctx) => {
|
|
|
58
97
|
/**
|
|
59
98
|
* Build a recording follow function that optionally injects errors.
|
|
60
99
|
*/
|
|
61
|
-
const createRecordingFollow = (trace, scenario, trailsMap, baseFollow, ctx) => {
|
|
100
|
+
const createRecordingFollow = (trace, scenario, trailsMap, baseFollow, ctx, services) => {
|
|
62
101
|
// The generic O on FollowFn is erased at runtime; the cast is safe
|
|
63
102
|
// because callers narrow via isOk/isErr before accessing the value.
|
|
64
103
|
const follow = (id, input) => {
|
|
@@ -70,7 +109,7 @@ const createRecordingFollow = (trace, scenario, trailsMap, baseFollow, ctx) => {
|
|
|
70
109
|
if (baseFollow !== undefined) {
|
|
71
110
|
return baseFollow(id, input);
|
|
72
111
|
}
|
|
73
|
-
const executed = executeFromMap(id, input, trailsMap, ctx);
|
|
112
|
+
const executed = executeFromMap(id, input, trailsMap, ctx, services, follow);
|
|
74
113
|
if (executed !== undefined) {
|
|
75
114
|
return Promise.resolve(executed);
|
|
76
115
|
}
|
|
@@ -128,19 +167,22 @@ const handleValidationError = (validated, scenario) => {
|
|
|
128
167
|
// ---------------------------------------------------------------------------
|
|
129
168
|
// Scenario runner
|
|
130
169
|
// ---------------------------------------------------------------------------
|
|
131
|
-
const buildTestContext = (scenario, ctx, trailsMap) => {
|
|
170
|
+
const buildTestContext = (scenario, ctx, trailsMap, services) => {
|
|
132
171
|
const trace = [];
|
|
133
172
|
const baseCtx = mergeTestContext(ctx);
|
|
134
|
-
const follow = createRecordingFollow(trace, scenario, trailsMap, baseCtx.follow, baseCtx);
|
|
173
|
+
const follow = createRecordingFollow(trace, scenario, trailsMap, baseCtx.follow, baseCtx, services);
|
|
135
174
|
return { testCtx: { ...baseCtx, follow }, trace };
|
|
136
175
|
};
|
|
137
|
-
const runScenario = async (trailDef, scenario, ctx, trailsMap) => {
|
|
176
|
+
const runScenario = async (trailDef, scenario, ctx, trailsMap, services) => {
|
|
138
177
|
const validated = validateInput(trailDef.input, scenario.input);
|
|
139
178
|
if (handleValidationError(validated, scenario)) {
|
|
140
179
|
return;
|
|
141
180
|
}
|
|
142
|
-
const { trace, testCtx } = buildTestContext(scenario, ctx, trailsMap);
|
|
143
|
-
const result = await trailDef.
|
|
181
|
+
const { trace, testCtx } = buildTestContext(scenario, ctx, trailsMap, services);
|
|
182
|
+
const result = await executeTrail(trailDef, scenario.input, {
|
|
183
|
+
ctx: testCtx,
|
|
184
|
+
services,
|
|
185
|
+
});
|
|
144
186
|
assertFollowTrace(trace, scenario);
|
|
145
187
|
assertScenarioResult(result, scenario, trailDef);
|
|
146
188
|
};
|
|
@@ -162,7 +204,8 @@ const runScenario = async (trailDef, scenario, ctx, trailsMap) => {
|
|
|
162
204
|
export const testFollows = (trailDef, scenarios, options) => {
|
|
163
205
|
describe(trailDef.id, () => {
|
|
164
206
|
test.each([...scenarios])('$description', async (scenario) => {
|
|
165
|
-
await
|
|
207
|
+
const services = mergeServiceOverrides(await resolveMockServices(trailDef, options?.trails), options?.ctx, options?.services);
|
|
208
|
+
await runScenario(trailDef, scenario, options?.ctx, options?.trails, services);
|
|
166
209
|
});
|
|
167
210
|
});
|
|
168
211
|
};
|