@ontrails/testing 1.0.0-beta.10 → 1.0.0-beta.12
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 +47 -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 +39 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +60 -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 +49 -22
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- 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 +359 -1
- package/src/__tests__/follows.test.ts +427 -1
- package/src/all.ts +5 -1
- package/src/context.ts +126 -15
- package/src/contracts.ts +28 -10
- package/src/examples.ts +94 -22
- package/src/follows.ts +112 -17
- package/src/index.ts +10 -1
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Complete trifecta for config, permits, and crumbs (formerly tracks)
|
|
8
|
+
|
|
9
|
+
- **config**: Add `configService`, `config.layer`, `config.trail`, and `config.workspace` trails with full `defineConfig`, `resolve`, `describe`, `explain`, `doctor`, and code generation support
|
|
10
|
+
- **permits**: Add `authService` and `auth.verify` trail for runtime authorization checks
|
|
11
|
+
- **crumbs**: Rename tracks to crumbs; add `crumbsService` and `crumbs.status` trail for structured event tracking
|
|
12
|
+
- **cli**: Fix build flag handling and improve bootstrap scaffolding
|
|
13
|
+
- **testing**: Expand test context helpers and example-based testing utilities
|
|
14
|
+
- **core/mcp/http**: Internal alignment for service and composition updates
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @ontrails/core@1.0.0-beta.12
|
|
18
|
+
- @ontrails/cli@1.0.0-beta.12
|
|
19
|
+
- @ontrails/mcp@1.0.0-beta.12
|
|
20
|
+
- @ontrails/logging@1.0.0-beta.12
|
|
21
|
+
|
|
22
|
+
## 1.0.0-beta.11
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- Add services as a first-class primitive.
|
|
27
|
+
|
|
28
|
+
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()`.
|
|
29
|
+
|
|
30
|
+
**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.
|
|
31
|
+
|
|
32
|
+
**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.
|
|
33
|
+
|
|
34
|
+
**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.
|
|
35
|
+
|
|
36
|
+
**Surfaces:** Service overrides thread through `dispatch` and `blaze` on CLI, MCP, and HTTP.
|
|
37
|
+
|
|
38
|
+
**Introspection:** Survey and surface map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
|
|
39
|
+
|
|
40
|
+
**Docs:** ADR-009 accepted. Unified services guide, updated vocabulary, getting-started, architecture, and package READMEs.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- Updated dependencies
|
|
45
|
+
- @ontrails/core@1.0.0-beta.11
|
|
46
|
+
- @ontrails/cli@1.0.0-beta.11
|
|
47
|
+
- @ontrails/mcp@1.0.0-beta.11
|
|
48
|
+
- @ontrails/logging@1.0.0-beta.11
|
|
49
|
+
|
|
3
50
|
## 1.0.0-beta.10
|
|
4
51
|
|
|
5
52
|
### 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,35 @@ export declare const createTestContext: (overrides?: TestTrailContextOptions) =>
|
|
|
15
15
|
export interface CreateFollowContextOptions {
|
|
16
16
|
readonly responses?: Record<string, Result<unknown, Error>> | undefined;
|
|
17
17
|
}
|
|
18
|
+
/** Minimal permit shape returned by the mint function. */
|
|
19
|
+
export interface MintedPermit {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly scopes: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/** Trail shape consumed by the mint function — avoids importing permits. */
|
|
24
|
+
export interface MintableTrail {
|
|
25
|
+
readonly permit?: {
|
|
26
|
+
readonly scopes: readonly string[];
|
|
27
|
+
} | 'public' | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface TestExecutionOptions {
|
|
30
|
+
readonly ctx?: Partial<TrailContext> | undefined;
|
|
31
|
+
readonly services?: ServiceOverrideMap | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* When true, disables automatic permit minting. Tests must provide
|
|
34
|
+
* explicit permits.
|
|
35
|
+
*/
|
|
36
|
+
readonly strictPermits?: boolean | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Optional function to mint a test permit for a trail. When provided,
|
|
39
|
+
* called for each trail with a non-public `permit` requirement.
|
|
40
|
+
* Returning `undefined` skips minting for that trail.
|
|
41
|
+
*
|
|
42
|
+
* A default inline implementation is used when this is not provided,
|
|
43
|
+
* keeping the testing package free of a hard dependency on `@ontrails/permits`.
|
|
44
|
+
*/
|
|
45
|
+
readonly mintPermit?: (trail: MintableTrail) => MintedPermit | undefined;
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
* Create a mock `FollowFn` for testing composite trails.
|
|
20
49
|
*
|
|
@@ -30,9 +59,17 @@ export interface CreateFollowContextOptions {
|
|
|
30
59
|
* ```
|
|
31
60
|
*/
|
|
32
61
|
export declare const createFollowContext: (options?: CreateFollowContextOptions) => FollowFn;
|
|
62
|
+
/**
|
|
63
|
+
* Default permit minter — reads `trail.permit.scopes` and produces a
|
|
64
|
+
* minimal permit object. No dependency on `@ontrails/permits`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const defaultMintPermit: (trail: MintableTrail) => MintedPermit | undefined;
|
|
67
|
+
export declare const normalizeTestExecutionOptions: (input?: Partial<TrailContext> | TestExecutionOptions) => TestExecutionOptions;
|
|
68
|
+
export declare const mergeServiceOverrides: (autoResolved: ServiceOverrideMap, ctx: Partial<TrailContext> | undefined, explicit: ServiceOverrideMap | undefined) => ServiceOverrideMap;
|
|
69
|
+
export declare const resolveMockServices: (app: Topo) => Promise<ServiceOverrideMap>;
|
|
33
70
|
/**
|
|
34
71
|
* Merge a Partial<TrailContext> into a test context.
|
|
35
72
|
* Used internally when the public API accepts Partial<TrailContext>.
|
|
36
73
|
*/
|
|
37
|
-
export declare const mergeTestContext: (ctx?: Partial<TrailContext
|
|
74
|
+
export declare const mergeTestContext: (ctx?: Partial<TrailContext>, services?: ServiceOverrideMap) => TrailContext;
|
|
38
75
|
//# 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,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,CAAC,EACZ;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GACtC,QAAQ,GACR,SAAS,CAAC;CACf;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;IACnD;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7C;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,YAAY,GAAG,SAAS,CAAC;CAC1E;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,0BAA0B,KACnC,QAcF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,aAAa,KACnB,YAAY,GAAG,SAKjB,CAAC;AAWF,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,55 @@ export const createFollowContext = (options) => {
|
|
|
44
51
|
return Promise.resolve(response);
|
|
45
52
|
};
|
|
46
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Default permit minter — reads `trail.permit.scopes` and produces a
|
|
56
|
+
* minimal permit object. No dependency on `@ontrails/permits`.
|
|
57
|
+
*/
|
|
58
|
+
export const defaultMintPermit = (trail) => {
|
|
59
|
+
if (!trail.permit || trail.permit === 'public') {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return { id: 'test-permit', scopes: trail.permit.scopes };
|
|
63
|
+
};
|
|
64
|
+
const isTestExecutionOptions = (input) => input !== undefined &&
|
|
65
|
+
(Object.hasOwn(input, 'ctx') ||
|
|
66
|
+
Object.hasOwn(input, 'services') ||
|
|
67
|
+
Object.hasOwn(input, 'strictPermits') ||
|
|
68
|
+
Object.hasOwn(input, 'mintPermit'));
|
|
69
|
+
export const normalizeTestExecutionOptions = (input) => isTestExecutionOptions(input) ? input : { ctx: input };
|
|
70
|
+
export const mergeServiceOverrides = (autoResolved, ctx, explicit) => ({
|
|
71
|
+
...autoResolved,
|
|
72
|
+
...ctx?.extensions,
|
|
73
|
+
...explicit,
|
|
74
|
+
});
|
|
75
|
+
const buildMockServices = async (app) => {
|
|
76
|
+
const services = {};
|
|
77
|
+
for (const declaredService of app.listServices()) {
|
|
78
|
+
if (!declaredService.mock) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
services[declaredService.id] = await declaredService.mock();
|
|
82
|
+
}
|
|
83
|
+
return services;
|
|
84
|
+
};
|
|
85
|
+
export const resolveMockServices = async (app) => await buildMockServices(app);
|
|
47
86
|
/**
|
|
48
87
|
* Merge a Partial<TrailContext> into a test context.
|
|
49
88
|
* Used internally when the public API accepts Partial<TrailContext>.
|
|
50
89
|
*/
|
|
51
|
-
export const mergeTestContext = (ctx) => {
|
|
52
|
-
if (ctx === undefined) {
|
|
53
|
-
return createTestContext();
|
|
54
|
-
}
|
|
90
|
+
export const mergeTestContext = (ctx, services) => {
|
|
55
91
|
const base = createTestContext();
|
|
56
|
-
|
|
92
|
+
const extensions = {
|
|
93
|
+
...base.extensions,
|
|
94
|
+
...ctx?.extensions,
|
|
95
|
+
...services,
|
|
96
|
+
};
|
|
97
|
+
const merged = {
|
|
98
|
+
...base,
|
|
99
|
+
...ctx,
|
|
100
|
+
extensions: Object.keys(extensions).length === 0 ? undefined : extensions,
|
|
101
|
+
};
|
|
102
|
+
merged.service = createServiceLookup(() => merged);
|
|
103
|
+
return merged;
|
|
57
104
|
};
|
|
58
105
|
//# 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;AAmCxB,OAAO,KAAK,EAAiB,oBAAoB,EAAE,MAAM,cAAc,CAAC;AA6MxE;;;;;;;;;;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,IA0EF,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 { defaultMintPermit, mergeServiceOverrides, mergeTestContext, normalizeTestExecutionOptions, resolveMockServices, } from './context.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Error class name -> constructor map
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -65,17 +65,38 @@ const handleValidationError = (validated, example) => {
|
|
|
65
65
|
}
|
|
66
66
|
throw new Error(`Example "${example.name}" has invalid input: ${validated.error.message}`);
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Apply auto-minting: if the trail declares scoped permits and the context
|
|
70
|
+
* doesn't already have a permit, mint one and merge it into the context.
|
|
71
|
+
*/
|
|
72
|
+
const applyAutoMint = (ctx, trailDef, opts) => {
|
|
73
|
+
if (opts.strictPermits) {
|
|
74
|
+
return ctx;
|
|
75
|
+
}
|
|
76
|
+
if (ctx.permit !== undefined) {
|
|
77
|
+
return ctx;
|
|
78
|
+
}
|
|
79
|
+
const mint = opts.mintPermit ?? defaultMintPermit;
|
|
80
|
+
const permit = mint(trailDef);
|
|
81
|
+
if (!permit) {
|
|
82
|
+
return ctx;
|
|
83
|
+
}
|
|
84
|
+
return { ...ctx, permit };
|
|
85
|
+
};
|
|
68
86
|
/**
|
|
69
87
|
* Run a single example against a trail.
|
|
70
88
|
* Handles validation, execution, and assertions.
|
|
71
89
|
*/
|
|
72
|
-
const runExample = async (t, example, output, testCtx) => {
|
|
90
|
+
const runExample = async (t, example, output, testCtx, services, opts) => {
|
|
73
91
|
const validated = validateInput(t.input, example.input);
|
|
74
92
|
if (handleValidationError(validated, example)) {
|
|
75
93
|
return;
|
|
76
94
|
}
|
|
77
|
-
const
|
|
78
|
-
const result = await t.
|
|
95
|
+
const ctx = opts ? applyAutoMint(testCtx, t, opts) : testCtx;
|
|
96
|
+
const result = await executeTrail(t, example.input, {
|
|
97
|
+
ctx,
|
|
98
|
+
services,
|
|
99
|
+
});
|
|
79
100
|
assertProgressiveMatch(result, example, output);
|
|
80
101
|
};
|
|
81
102
|
// ---------------------------------------------------------------------------
|
|
@@ -88,7 +109,7 @@ const runExample = async (t, example, output, testCtx) => {
|
|
|
88
109
|
* in the topo and executes it with validated input. Falls back to
|
|
89
110
|
* `Result.ok()` when neither is available.
|
|
90
111
|
*/
|
|
91
|
-
const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
112
|
+
const createCoverageFollow = (called, baseFollow, topo, ctx, services) => {
|
|
92
113
|
const follow = (id, input) => {
|
|
93
114
|
called.add(id);
|
|
94
115
|
if (baseFollow !== undefined) {
|
|
@@ -96,11 +117,10 @@ const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
|
96
117
|
}
|
|
97
118
|
const trailDef = topo.get(id);
|
|
98
119
|
if (trailDef !== undefined) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return Promise.resolve(trailDef.run(validated.value, ctx));
|
|
120
|
+
return executeTrail(trailDef, input, {
|
|
121
|
+
ctx: { ...ctx, follow },
|
|
122
|
+
services,
|
|
123
|
+
});
|
|
104
124
|
}
|
|
105
125
|
return Promise.resolve(Result.ok());
|
|
106
126
|
};
|
|
@@ -109,15 +129,18 @@ const createCoverageFollow = (called, baseFollow, topo, ctx) => {
|
|
|
109
129
|
/**
|
|
110
130
|
* Run a single example against a composition trail, recording follow calls.
|
|
111
131
|
*/
|
|
112
|
-
const runCompositionExample = async (trailDef, example, output, baseCtx, called, topo) => {
|
|
132
|
+
const runCompositionExample = async (trailDef, example, output, baseCtx, called, topo, services, opts) => {
|
|
113
133
|
const validated = validateInput(trailDef.input, example.input);
|
|
114
134
|
if (handleValidationError(validated, example)) {
|
|
115
135
|
return;
|
|
116
136
|
}
|
|
117
|
-
const
|
|
118
|
-
const follow = createCoverageFollow(called,
|
|
119
|
-
const testCtx = { ...
|
|
120
|
-
const result = await trailDef.
|
|
137
|
+
const mintedCtx = opts ? applyAutoMint(baseCtx, trailDef, opts) : baseCtx;
|
|
138
|
+
const follow = createCoverageFollow(called, mintedCtx.follow, topo, mintedCtx, services);
|
|
139
|
+
const testCtx = { ...mintedCtx, follow };
|
|
140
|
+
const result = await executeTrail(trailDef, example.input, {
|
|
141
|
+
ctx: testCtx,
|
|
142
|
+
services,
|
|
143
|
+
});
|
|
121
144
|
assertProgressiveMatch(result, example, output);
|
|
122
145
|
};
|
|
123
146
|
// ---------------------------------------------------------------------------
|
|
@@ -135,7 +158,7 @@ const runCompositionExample = async (trailDef, example, output, baseCtx, called,
|
|
|
135
158
|
* ```
|
|
136
159
|
*/
|
|
137
160
|
export const testExamples = (app, ctxOrFactory) => {
|
|
138
|
-
const
|
|
161
|
+
const resolveInput = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
|
|
139
162
|
const allTrails = app.list();
|
|
140
163
|
const withExamples = allTrails.filter((t) => t.examples !== undefined && t.examples.length > 0);
|
|
141
164
|
const simpleTrails = withExamples.filter((t) => t.follow.length === 0);
|
|
@@ -148,8 +171,10 @@ export const testExamples = (app, ctxOrFactory) => {
|
|
|
148
171
|
return;
|
|
149
172
|
}
|
|
150
173
|
test.each([...examples])('example: $name', async (example) => {
|
|
151
|
-
const
|
|
152
|
-
await
|
|
174
|
+
const resolved = normalizeTestExecutionOptions(resolveInput());
|
|
175
|
+
const services = mergeServiceOverrides(await resolveMockServices(app), resolved.ctx, resolved.services);
|
|
176
|
+
const testCtx = mergeTestContext(resolved.ctx);
|
|
177
|
+
await runExample(t, example, output, testCtx, services, resolved);
|
|
153
178
|
});
|
|
154
179
|
});
|
|
155
180
|
}
|
|
@@ -162,8 +187,10 @@ export const testExamples = (app, ctxOrFactory) => {
|
|
|
162
187
|
}
|
|
163
188
|
const called = new Set();
|
|
164
189
|
test.each([...examples])('example: $name', async (example) => {
|
|
165
|
-
const
|
|
166
|
-
|
|
190
|
+
const resolved = normalizeTestExecutionOptions(resolveInput());
|
|
191
|
+
const services = mergeServiceOverrides(await resolveMockServices(app), resolved.ctx, resolved.services);
|
|
192
|
+
const baseCtx = mergeTestContext(resolved.ctx);
|
|
193
|
+
await runCompositionExample(t, example, output, baseCtx, called, app, services, resolved);
|
|
167
194
|
});
|
|
168
195
|
test('follow coverage', () => {
|
|
169
196
|
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":"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"}
|
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"}
|