@ontrails/testing 1.0.0-beta.1 → 1.0.0-beta.3

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.
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 27ms on 20 files with 93 rules using 24 threads.
3
+ Finished in 26ms on 20 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @ontrails/testing
2
2
 
3
+ ## 1.0.0-beta.3
4
+
5
+ ### Minor Changes
6
+
7
+ - Bug fixes across all surface packages found via parallel Codex review.
8
+
9
+ **core**: Fix Result.toJson false circular detection on DAGs, deserializeError subclass round-trip, topo cross-kind ID collisions, validateTopo multi-node cycle detection, error example input validation bypass, and deriveFields array type collapse.
10
+
11
+ **cli**: Switch blaze to parseAsync for proper async error handling, add boolean flag negation (--no-flag), and strict number parsing that rejects partial input.
12
+
13
+ **mcp**: Align BlobRef with core (including ReadableStream support) and detect tool-name collisions after normalization.
14
+
15
+ **testing**: Include hikes in testContracts validation, with follow-context awareness.
16
+
17
+ **warden**: Collect hike detour targets, validate detour refs in hike specs, and stop implementation-returns-result from walking into nested function bodies.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - @ontrails/core@1.0.0-beta.3
23
+ - @ontrails/cli@1.0.0-beta.3
24
+ - @ontrails/mcp@1.0.0-beta.3
25
+ - @ontrails/logging@1.0.0-beta.3
26
+
27
+ ## 1.0.0-beta.2
28
+
29
+ ### Patch Changes
30
+
31
+ - Fix workspace dependency resolution in published packages. Now using bun publish
32
+ which correctly replaces workspace:^ with actual version numbers.
33
+ - Updated dependencies
34
+ - @ontrails/core@1.0.0-beta.2
35
+ - @ontrails/cli@1.0.0-beta.2
36
+ - @ontrails/mcp@1.0.0-beta.2
37
+ - @ontrails/logging@1.0.0-beta.2
38
+
3
39
  ## 1.0.0-beta.1
4
40
 
5
41
  ### Patch Changes
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * testContracts — output schema verification.
3
3
  *
4
- * For every trail that has both examples and an output schema,
4
+ * For every trail and hike that has both examples and an output schema,
5
5
  * run each example and validate the implementation output against
6
6
  * the declared schema.
7
7
  */
8
8
  import type { Topo, TrailContext } from '@ontrails/core';
9
9
  /**
10
- * Verify that every trail's implementation output matches its declared
10
+ * Verify that every trail and hike implementation output matches its declared
11
11
  * output schema. Catches implementation-schema drift.
12
12
  *
13
- * Trails without output schemas or examples are skipped.
13
+ * Trails and hikes without output schemas or examples are skipped.
14
14
  */
15
15
  export declare const testContracts: (app: Topo, ctxOrFactory?: Partial<TrailContext> | (() => Partial<TrailContext>)) => void;
16
16
  //# sourceMappingURL=contracts.d.ts.map
@@ -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;AA8B9E;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAmCF,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;AA0C9E;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,eAAe,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,KACnE,IAoCF,CAAC"}
package/dist/contracts.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * testContracts — output schema verification.
3
3
  *
4
- * For every trail that has both examples and an output schema,
4
+ * For every trail and hike that has both examples and an output schema,
5
5
  * run each example and validate the implementation output against
6
6
  * the declared schema.
7
7
  */
@@ -12,6 +12,14 @@ import { mergeTestContext } from './context.js';
12
12
  // ---------------------------------------------------------------------------
13
13
  // Helpers
14
14
  // ---------------------------------------------------------------------------
15
+ /** Check if a trail/hike requires follow() but the context doesn't provide it. */
16
+ const needsFollowContext = (t, resolveCtx) => {
17
+ const spec = t;
18
+ if (!spec.follows || spec.follows.length === 0) {
19
+ return false;
20
+ }
21
+ return !resolveCtx()?.follow;
22
+ };
15
23
  const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
16
24
  const parsed = outputSchema.safeParse(value);
17
25
  if (!parsed.success) {
@@ -23,23 +31,25 @@ const validateOutputSchema = (outputSchema, value, trailId, exampleName) => {
23
31
  // testContracts
24
32
  // ---------------------------------------------------------------------------
25
33
  /**
26
- * Verify that every trail's implementation output matches its declared
34
+ * Verify that every trail and hike implementation output matches its declared
27
35
  * output schema. Catches implementation-schema drift.
28
36
  *
29
- * Trails without output schemas or examples are skipped.
37
+ * Trails and hikes without output schemas or examples are skipped.
30
38
  */
31
39
  export const testContracts = (app, ctxOrFactory) => {
32
40
  const resolveCtx = typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
33
- const trailEntries = [...app.trails];
41
+ const allEntries = app.list();
34
42
  describe('contracts', () => {
35
- describe.each(trailEntries)('%s', (_id, trailDef) => {
36
- const t = trailDef;
43
+ describe.each(allEntries)('$id', (t) => {
37
44
  if (t.output === undefined) {
38
45
  return;
39
46
  }
40
47
  if (t.examples === undefined || t.examples.length === 0) {
41
48
  return;
42
49
  }
50
+ if (needsFollowContext(t, resolveCtx)) {
51
+ return;
52
+ }
43
53
  const { examples, output: outputSchema } = t;
44
54
  const successExamples = examples.filter((e) => e.error === undefined);
45
55
  test.each(successExamples)('contract: $name', async (example) => {
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"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"}
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;AAGhE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,kFAAkF;AAClF,MAAM,kBAAkB,GAAG,CACzB,CAAU,EACV,UAAmD,EAC1C,EAAE;IACX,MAAM,IAAI,GAAG,CAAoC,CAAC;IAClD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC3B,YAAuB,EACvB,KAAc,EACd,OAAe,EACf,WAAmB,EACb,EAAE;IACR,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,sCAAsC,OAAO,eAAe,WAAW,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAC7K,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,GAAS,EACT,YAAoE,EAC9D,EAAE;IACR,MAAM,UAAU,GACd,OAAO,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC;IACzE,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAA+B,CAAC;IAE3D,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;YACrC,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;YACD,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,IAAI,kBAAkB,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;gBACtC,OAAO;YACT,CAAC;YAED,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;YAC7C,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;YAEtE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CACxB,iBAAiB,EACjB,KAAK,EAAE,OAAuC,EAAE,EAAE;gBAChD,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC;gBAE/C,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACxD,MAAM,cAAc,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAE3C,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,cAAc,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC/D,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAErC,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACtE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/testing",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -14,10 +14,10 @@
14
14
  "clean": "rm -rf dist *.tsbuildinfo"
15
15
  },
16
16
  "peerDependencies": {
17
- "@ontrails/cli": "workspace:^",
18
- "@ontrails/core": "workspace:^",
19
- "@ontrails/logging": "workspace:^",
20
- "@ontrails/mcp": "workspace:^",
21
- "zod": "catalog:"
17
+ "@ontrails/cli": "^1.0.0-beta.0",
18
+ "@ontrails/core": "^1.0.0-beta.0",
19
+ "@ontrails/logging": "^1.0.0-beta.0",
20
+ "@ontrails/mcp": "^1.0.0-beta.0",
21
+ "zod": "^4.3.5"
22
22
  }
23
23
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, test } from 'bun:test';
2
2
 
3
- import { Result, trail, topo } from '@ontrails/core';
3
+ import { Result, hike, trail, topo } from '@ontrails/core';
4
4
  import { z } from 'zod';
5
5
 
6
6
  import { testContracts } from '../contracts.js';
@@ -38,6 +38,26 @@ const noExamplesTrail = trail('noexamples', {
38
38
  output: z.object({ value: z.number() }),
39
39
  });
40
40
 
41
+ // ---------------------------------------------------------------------------
42
+ // Test hikes
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /** Hike whose implementation matches the output schema. */
46
+ const validHike = hike('hike.valid', {
47
+ examples: [
48
+ {
49
+ expected: { total: 3 },
50
+ input: { a: 1, b: 2 },
51
+ name: 'Valid hike output',
52
+ },
53
+ ],
54
+ follows: ['valid'],
55
+ implementation: (input: { a: number; b: number }) =>
56
+ Result.ok({ total: input.a + input.b }),
57
+ input: z.object({ a: z.number(), b: z.number() }),
58
+ output: z.object({ total: z.number() }),
59
+ });
60
+
41
61
  // ---------------------------------------------------------------------------
42
62
  // Tests
43
63
  // ---------------------------------------------------------------------------
@@ -66,3 +86,8 @@ describe('testContracts: skips trails without examples', () => {
66
86
  // Trail without examples is skipped -- no contract tests generated
67
87
  });
68
88
  });
89
+
90
+ describe('testContracts: validates hike output schemas', () => {
91
+ // eslint-disable-next-line jest/require-hook
92
+ testContracts(topo('test-app', { validHike } as Record<string, unknown>));
93
+ });
package/src/contracts.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * testContracts — output schema verification.
3
3
  *
4
- * For every trail that has both examples and an output schema,
4
+ * For every trail and hike that has both examples and an output schema,
5
5
  * run each example and validate the implementation output against
6
6
  * the declared schema.
7
7
  */
@@ -19,6 +19,18 @@ import { mergeTestContext } from './context.js';
19
19
  // Helpers
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
+ /** Check if a trail/hike requires follow() but the context doesn't provide it. */
23
+ const needsFollowContext = (
24
+ t: unknown,
25
+ resolveCtx: () => Partial<TrailContext> | undefined
26
+ ): boolean => {
27
+ const spec = t as { follows?: readonly string[] };
28
+ if (!spec.follows || spec.follows.length === 0) {
29
+ return false;
30
+ }
31
+ return !resolveCtx()?.follow;
32
+ };
33
+
22
34
  const validateOutputSchema = (
23
35
  outputSchema: z.ZodType,
24
36
  value: unknown,
@@ -39,10 +51,10 @@ const validateOutputSchema = (
39
51
  // ---------------------------------------------------------------------------
40
52
 
41
53
  /**
42
- * Verify that every trail's implementation output matches its declared
54
+ * Verify that every trail and hike implementation output matches its declared
43
55
  * output schema. Catches implementation-schema drift.
44
56
  *
45
- * Trails without output schemas or examples are skipped.
57
+ * Trails and hikes without output schemas or examples are skipped.
46
58
  */
47
59
  export const testContracts = (
48
60
  app: Topo,
@@ -50,18 +62,19 @@ export const testContracts = (
50
62
  ): void => {
51
63
  const resolveCtx =
52
64
  typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
53
- const trailEntries = [...app.trails];
65
+ const allEntries = app.list() as Trail<unknown, unknown>[];
54
66
 
55
67
  describe('contracts', () => {
56
- describe.each(trailEntries)('%s', (_id, trailDef) => {
57
- const t = trailDef as Trail<unknown, unknown>;
58
-
68
+ describe.each(allEntries)('$id', (t) => {
59
69
  if (t.output === undefined) {
60
70
  return;
61
71
  }
62
72
  if (t.examples === undefined || t.examples.length === 0) {
63
73
  return;
64
74
  }
75
+ if (needsFollowContext(t, resolveCtx)) {
76
+ return;
77
+ }
65
78
 
66
79
  const { examples, output: outputSchema } = t;
67
80
  const successExamples = examples.filter((e) => e.error === undefined);