@ontrails/testing 1.0.0-beta.13 → 1.0.0-beta.15

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.
Files changed (83) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +11 -11
  4. package/dist/all.d.ts +10 -5
  5. package/dist/all.d.ts.map +1 -1
  6. package/dist/all.js +78 -26
  7. package/dist/all.js.map +1 -1
  8. package/dist/assertions.d.ts +23 -0
  9. package/dist/assertions.d.ts.map +1 -1
  10. package/dist/assertions.js +154 -0
  11. package/dist/assertions.js.map +1 -1
  12. package/dist/context.d.ts +17 -16
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +31 -20
  15. package/dist/context.js.map +1 -1
  16. package/dist/contracts.d.ts.map +1 -1
  17. package/dist/contracts.js +9 -5
  18. package/dist/contracts.js.map +1 -1
  19. package/dist/crosses.d.ts +4 -4
  20. package/dist/crosses.d.ts.map +1 -1
  21. package/dist/crosses.js +49 -39
  22. package/dist/crosses.js.map +1 -1
  23. package/dist/detours.d.ts +5 -4
  24. package/dist/detours.d.ts.map +1 -1
  25. package/dist/detours.js +93 -14
  26. package/dist/detours.js.map +1 -1
  27. package/dist/effective-examples.d.ts +30 -0
  28. package/dist/effective-examples.d.ts.map +1 -0
  29. package/dist/effective-examples.js +227 -0
  30. package/dist/effective-examples.js.map +1 -0
  31. package/dist/examples.d.ts +1 -1
  32. package/dist/examples.d.ts.map +1 -1
  33. package/dist/examples.js +79 -41
  34. package/dist/examples.js.map +1 -1
  35. package/dist/harness-cli.d.ts +3 -3
  36. package/dist/harness-cli.d.ts.map +1 -1
  37. package/dist/harness-cli.js +25 -33
  38. package/dist/harness-cli.js.map +1 -1
  39. package/dist/harness-mcp.d.ts +3 -3
  40. package/dist/harness-mcp.d.ts.map +1 -1
  41. package/dist/harness-mcp.js +9 -8
  42. package/dist/harness-mcp.js.map +1 -1
  43. package/dist/index.d.ts +6 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -2
  46. package/dist/index.js.map +1 -1
  47. package/dist/scenario.d.ts +37 -0
  48. package/dist/scenario.d.ts.map +1 -0
  49. package/dist/scenario.js +235 -0
  50. package/dist/scenario.js.map +1 -0
  51. package/dist/types.d.ts +38 -5
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +9 -5
  54. package/src/__tests__/all.test.ts +217 -29
  55. package/src/__tests__/context.test.ts +32 -12
  56. package/src/__tests__/contracts.test.ts +72 -18
  57. package/src/__tests__/crosses.test.ts +78 -78
  58. package/src/__tests__/detours.test.ts +176 -19
  59. package/src/__tests__/effective-examples.test.ts +203 -0
  60. package/src/__tests__/examples.test.ts +152 -50
  61. package/src/__tests__/harness-cli.test.ts +90 -0
  62. package/src/__tests__/harness-mcp.test.ts +37 -0
  63. package/src/__tests__/partial-match.test.ts +126 -0
  64. package/src/__tests__/scenario.test.ts +381 -0
  65. package/src/all.ts +149 -12
  66. package/src/assertions.ts +253 -0
  67. package/src/context.ts +64 -38
  68. package/src/contracts.ts +14 -8
  69. package/src/crosses.ts +93 -51
  70. package/src/detours.ts +155 -18
  71. package/src/effective-examples.ts +350 -0
  72. package/src/examples.ts +127 -59
  73. package/src/harness-cli.ts +33 -49
  74. package/src/harness-mcp.ts +9 -8
  75. package/src/index.ts +13 -3
  76. package/src/scenario.ts +370 -0
  77. package/src/types.ts +63 -5
  78. package/tsconfig.tests.json +10 -0
  79. package/tsconfig.tsbuildinfo +1 -1
  80. package/dist/follows.d.ts +0 -38
  81. package/dist/follows.d.ts.map +0 -1
  82. package/dist/follows.js +0 -212
  83. package/dist/follows.js.map +0 -1
package/src/assertions.ts CHANGED
@@ -49,6 +49,40 @@ export const expectErr = <T, E>(result: Result<T, E>): E => {
49
49
  return (result as unknown as { error: E }).error;
50
50
  };
51
51
 
52
+ // ---------------------------------------------------------------------------
53
+ // Result Match Tokens
54
+ // ---------------------------------------------------------------------------
55
+
56
+ export interface OkResultMatch {
57
+ readonly __resultMatch: 'ok';
58
+ readonly value?: unknown | undefined;
59
+ }
60
+
61
+ export interface ErrResultMatch {
62
+ readonly __resultMatch: 'err';
63
+ readonly error?: unknown | undefined;
64
+ }
65
+
66
+ type ResultMatchToken = OkResultMatch | ErrResultMatch;
67
+
68
+ /**
69
+ * Create a partial-match token for `Result.ok(...)` values nested inside
70
+ * arrays or objects, such as the `Result[]` returned by batch `ctx.cross()`.
71
+ */
72
+ export const okResultMatch = (value?: unknown): OkResultMatch => ({
73
+ __resultMatch: 'ok',
74
+ value,
75
+ });
76
+
77
+ /**
78
+ * Create a partial-match token for `Result.err(...)` values nested inside
79
+ * arrays or objects, such as mixed-success batch `ctx.cross()`.
80
+ */
81
+ export const errResultMatch = (error?: unknown): ErrResultMatch => ({
82
+ __resultMatch: 'err',
83
+ error,
84
+ });
85
+
52
86
  // ---------------------------------------------------------------------------
53
87
  // Full Match
54
88
  // ---------------------------------------------------------------------------
@@ -87,6 +121,225 @@ export const assertSchemaMatch = (
87
121
  }
88
122
  };
89
123
 
124
+ // ---------------------------------------------------------------------------
125
+ // Partial Match
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /** Format a path for error messages. */
129
+ const formatLoc = (path: readonly string[]): string =>
130
+ path.length > 0 ? path.join('.') : 'root';
131
+
132
+ interface ResultLike {
133
+ readonly error?: unknown;
134
+ isErr(): boolean;
135
+ isOk(): boolean;
136
+ readonly value?: unknown;
137
+ }
138
+
139
+ const isResultMatchToken = (value: unknown): value is ResultMatchToken =>
140
+ typeof value === 'object' &&
141
+ value !== null &&
142
+ '__resultMatch' in value &&
143
+ ((value as Record<string, unknown>)['__resultMatch'] === 'ok' ||
144
+ (value as Record<string, unknown>)['__resultMatch'] === 'err');
145
+
146
+ const isResultLike = (value: unknown): value is ResultLike =>
147
+ typeof value === 'object' &&
148
+ value !== null &&
149
+ 'isOk' in value &&
150
+ typeof (value as Record<string, unknown>)['isOk'] === 'function' &&
151
+ 'isErr' in value &&
152
+ typeof (value as Record<string, unknown>)['isErr'] === 'function';
153
+
154
+ /** Find an unconsumed actual element that deep-matches the expected object. */
155
+ const findObjectMatch = (
156
+ actual: unknown[],
157
+ elem: object,
158
+ consumed: ReadonlySet<number>,
159
+ path: readonly string[],
160
+ index: number
161
+ ): number =>
162
+ actual.findIndex((a, idx) => {
163
+ if (consumed.has(idx)) {
164
+ return false;
165
+ }
166
+ try {
167
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion with assertSubset
168
+ assertSubset(a, elem, [...path, `[${String(index)}]`]);
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ });
174
+
175
+ /**
176
+ * Assert that every element in `expected` exists in `actual` (order-independent).
177
+ *
178
+ * Tracks consumed indices so that duplicate expected elements each require a
179
+ * distinct actual element — `['a', 'a']` does not match `['a']`.
180
+ */
181
+ const assertArraySubset = (
182
+ actual: unknown[],
183
+ expected: unknown[],
184
+ path: readonly string[],
185
+ loc: string
186
+ ): void => {
187
+ const consumed = new Set<number>();
188
+ for (let i = 0; i < expected.length; i += 1) {
189
+ const elem = expected[i];
190
+ const matchIndex =
191
+ typeof elem === 'object' && elem !== null
192
+ ? findObjectMatch(actual, elem, consumed, path, i)
193
+ : actual.findIndex((a, idx) => !consumed.has(idx) && a === elem);
194
+
195
+ if (matchIndex === -1) {
196
+ throw new Error(
197
+ `at ${loc}[${String(i)}]: expected array to contain ${JSON.stringify(elem)}`
198
+ );
199
+ }
200
+ consumed.add(matchIndex);
201
+ }
202
+ };
203
+
204
+ /** Assert that every key in `expected` exists in `actual` with a matching value. */
205
+ const assertObjectSubset = (
206
+ actual: Record<string, unknown>,
207
+ expected: Record<string, unknown>,
208
+ path: readonly string[]
209
+ ): void => {
210
+ for (const key of Object.keys(expected)) {
211
+ if (!(key in actual)) {
212
+ throw new Error(
213
+ `at ${[...path, key].join('.')}: key not found in actual`
214
+ );
215
+ }
216
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion with assertSubset
217
+ assertSubset(actual[key], expected[key], [...path, key]);
218
+ }
219
+ };
220
+
221
+ /**
222
+ * Recursively assert that `actual` is a superset of `expected`.
223
+ *
224
+ * - **Scalars:** strict equality.
225
+ * - **Objects:** every key in `expected` must exist in `actual` with a matching
226
+ * value. Extra keys in `actual` are ignored.
227
+ * - **Arrays:** every element in `expected` must exist in `actual`
228
+ * (order-independent subset check).
229
+ * - **Nested objects:** recursive subset matching.
230
+ */
231
+ // oxlint-disable-next-line max-statements -- recursive dispatch across four type branches
232
+ const assertSubset = (
233
+ actual: unknown,
234
+ expected: unknown,
235
+ path: readonly string[]
236
+ ): void => {
237
+ const loc = formatLoc(path);
238
+
239
+ if (expected === null || expected === undefined) {
240
+ if (actual !== expected) {
241
+ throw new Error(
242
+ `at ${loc}: expected ${String(expected)}, got ${String(actual)}`
243
+ );
244
+ }
245
+ return;
246
+ }
247
+
248
+ if (isResultMatchToken(expected)) {
249
+ // oxlint-disable-next-line no-use-before-define -- result token matching delegates back into assertSubset
250
+ assertResultTokenMatch(actual, expected, path);
251
+ return;
252
+ }
253
+
254
+ if (Array.isArray(expected)) {
255
+ if (!Array.isArray(actual)) {
256
+ throw new TypeError(`at ${loc}: expected an array, got ${typeof actual}`);
257
+ }
258
+ assertArraySubset(actual, expected, path, loc);
259
+ return;
260
+ }
261
+
262
+ if (typeof expected === 'object') {
263
+ if (
264
+ typeof actual !== 'object' ||
265
+ actual === null ||
266
+ Array.isArray(actual)
267
+ ) {
268
+ throw new Error(`at ${loc}: expected an object, got ${typeof actual}`);
269
+ }
270
+ assertObjectSubset(
271
+ actual as Record<string, unknown>,
272
+ expected as Record<string, unknown>,
273
+ path
274
+ );
275
+ return;
276
+ }
277
+
278
+ if (actual !== expected) {
279
+ throw new Error(
280
+ `at ${loc}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`
281
+ );
282
+ }
283
+ };
284
+
285
+ const assertOkResultTokenMatch = (
286
+ actual: ResultLike,
287
+ expected: OkResultMatch,
288
+ loc: string,
289
+ path: readonly string[]
290
+ ): void => {
291
+ if (!actual.isOk()) {
292
+ throw new Error(`at ${loc}: expected Result.ok(...), got Result.err(...)`);
293
+ }
294
+ if (expected.value !== undefined) {
295
+ assertSubset(actual.value, expected.value, [...path, 'value']);
296
+ }
297
+ };
298
+
299
+ const assertErrResultTokenMatch = (
300
+ actual: ResultLike,
301
+ expected: ErrResultMatch,
302
+ loc: string,
303
+ path: readonly string[]
304
+ ): void => {
305
+ if (!actual.isErr()) {
306
+ throw new Error(`at ${loc}: expected Result.err(...), got Result.ok(...)`);
307
+ }
308
+ if (expected.error !== undefined) {
309
+ assertSubset(actual.error, expected.error, [...path, 'error']);
310
+ }
311
+ };
312
+
313
+ const assertResultTokenMatch = (
314
+ actual: unknown,
315
+ expected: ResultMatchToken,
316
+ path: readonly string[]
317
+ ): void => {
318
+ const loc = formatLoc(path);
319
+ if (!isResultLike(actual)) {
320
+ throw new TypeError(`at ${loc}: expected a Result-like value`);
321
+ }
322
+
323
+ if (expected.__resultMatch === 'ok') {
324
+ assertOkResultTokenMatch(actual, expected, loc, path);
325
+ return;
326
+ }
327
+
328
+ assertErrResultTokenMatch(actual, expected, loc, path);
329
+ };
330
+
331
+ /**
332
+ * Assert that the result is ok and its value is a superset of the expected
333
+ * partial output. Declared fields must match; extra fields are ignored.
334
+ */
335
+ export const assertPartialMatch = (
336
+ result: Result<unknown, Error>,
337
+ expectedMatch: unknown
338
+ ): void => {
339
+ const value = expectOk(result);
340
+ assertSubset(value, expectedMatch, []);
341
+ };
342
+
90
343
  // ---------------------------------------------------------------------------
91
344
  // Error Match
92
345
  // ---------------------------------------------------------------------------
package/src/context.ts CHANGED
@@ -4,11 +4,16 @@
4
4
 
5
5
  import type {
6
6
  CrossFn,
7
- ProvisionOverrideMap,
7
+ ResourceOverrideMap,
8
8
  Topo,
9
9
  TrailContext,
10
10
  } from '@ontrails/core';
11
- import { Result, createProvisionLookup } from '@ontrails/core';
11
+ import {
12
+ Result,
13
+ buildCrossValidationSchema,
14
+ createResourceLookup,
15
+ passthroughTrace,
16
+ } from '@ontrails/core';
12
17
 
13
18
  import { createTestLogger } from './logger.js';
14
19
  import type { TestTrailContextOptions } from './types.js';
@@ -39,10 +44,11 @@ export const createTestContext = (
39
44
  extensions: undefined,
40
45
  logger: overrides?.logger ?? createTestLogger(),
41
46
  requestId: overrides?.requestId ?? 'test-request-001',
47
+ trace: overrides?.trace ?? passthroughTrace,
42
48
  workspaceRoot: cwd,
43
49
  } as MutableTrailContext;
44
- const lookup = createProvisionLookup(() => ctx);
45
- ctx.provision = lookup;
50
+ const lookup = createResourceLookup(() => ctx);
51
+ ctx.resource = lookup;
46
52
  return ctx;
47
53
  };
48
54
 
@@ -54,14 +60,14 @@ export interface CreateCrossContextOptions {
54
60
  readonly responses?: Record<string, Result<unknown, Error>> | undefined;
55
61
  }
56
62
 
57
- /** Minimal permit shape returned by the mint function. */
58
- export interface MintedPermit {
63
+ /** Minimal permit shape returned by the create function. */
64
+ export interface MinimalPermit {
59
65
  readonly id: string;
60
66
  readonly scopes: readonly string[];
61
67
  }
62
68
 
63
- /** Trail shape consumed by the mint function — avoids importing permits. */
64
- export interface MintableTrail {
69
+ /** Trail shape consumed by the create function — avoids importing permits. */
70
+ export interface PermittedTrail {
65
71
  readonly permit?:
66
72
  | { readonly scopes: readonly string[] }
67
73
  | 'public'
@@ -70,21 +76,21 @@ export interface MintableTrail {
70
76
 
71
77
  export interface TestExecutionOptions {
72
78
  readonly ctx?: Partial<TrailContext> | undefined;
73
- readonly provisions?: ProvisionOverrideMap | undefined;
79
+ readonly resources?: ResourceOverrideMap | undefined;
74
80
  /**
75
- * When true, disables automatic permit minting. Tests must provide
81
+ * When true, disables automatic permit creation. Tests must provide
76
82
  * explicit permits.
77
83
  */
78
84
  readonly strictPermits?: boolean | undefined;
79
85
  /**
80
- * Optional function to mint a test permit for a trail. When provided,
86
+ * Optional function to create a test permit for a trail. When provided,
81
87
  * called for each trail with a non-public `permit` requirement.
82
- * Returning `undefined` skips minting for that trail.
88
+ * Returning `undefined` skips creation for that trail.
83
89
  *
84
90
  * A default inline implementation is used when this is not provided,
85
91
  * keeping the testing package free of a hard dependency on `@ontrails/permits`.
86
92
  */
87
- readonly mintPermit?: (trail: MintableTrail) => MintedPermit | undefined;
93
+ readonly createPermit?: (trail: PermittedTrail) => MinimalPermit | undefined;
88
94
  }
89
95
 
90
96
  /**
@@ -105,7 +111,10 @@ export const createCrossContext = (
105
111
  options?: CreateCrossContextOptions
106
112
  ): CrossFn => {
107
113
  const responses = options?.responses ?? {};
108
- return <O>(id: string, _input: unknown): Promise<Result<O, Error>> => {
114
+ const respondToCross = <O>(
115
+ idOrTrail: string | { readonly id: string }
116
+ ): Promise<Result<O, Error>> => {
117
+ const id = typeof idOrTrail === 'string' ? idOrTrail : idOrTrail.id;
109
118
  const response = responses[id];
110
119
  if (response === undefined) {
111
120
  return Promise.resolve(
@@ -117,15 +126,31 @@ export const createCrossContext = (
117
126
  }
118
127
  return Promise.resolve(response as Result<O, Error>);
119
128
  };
129
+ const cross = (async (
130
+ idOrTrail:
131
+ | string
132
+ | { readonly id: string }
133
+ | readonly (readonly [string | { readonly id: string }, unknown])[],
134
+ _input?: unknown
135
+ ) => {
136
+ if (Array.isArray(idOrTrail)) {
137
+ return await Promise.all(
138
+ idOrTrail.map(([target]) => respondToCross(target))
139
+ );
140
+ }
141
+
142
+ return await respondToCross(idOrTrail as string | { readonly id: string });
143
+ }) as CrossFn;
144
+ return cross;
120
145
  };
121
146
 
122
147
  /**
123
- * Default permit minter — reads `trail.permit.scopes` and produces a
148
+ * Default permit creator — reads `trail.permit.scopes` and produces a
124
149
  * minimal permit object. No dependency on `@ontrails/permits`.
125
150
  */
126
- export const defaultMintPermit = (
127
- trail: MintableTrail
128
- ): MintedPermit | undefined => {
151
+ export const defaultCreatePermit = (
152
+ trail: PermittedTrail
153
+ ): MinimalPermit | undefined => {
129
154
  if (!trail.permit || trail.permit === 'public') {
130
155
  return undefined;
131
156
  }
@@ -137,41 +162,42 @@ const isTestExecutionOptions = (
137
162
  ): input is TestExecutionOptions =>
138
163
  input !== undefined &&
139
164
  (Object.hasOwn(input, 'ctx') ||
140
- Object.hasOwn(input, 'provisions') ||
165
+ Object.hasOwn(input, 'resources') ||
141
166
  Object.hasOwn(input, 'strictPermits') ||
142
- Object.hasOwn(input, 'mintPermit'));
167
+ Object.hasOwn(input, 'createPermit'));
143
168
 
144
169
  export const normalizeTestExecutionOptions = (
145
170
  input?: Partial<TrailContext> | TestExecutionOptions
146
171
  ): TestExecutionOptions =>
147
172
  isTestExecutionOptions(input) ? input : { ctx: input };
148
173
 
149
- export const mergeProvisionOverrides = (
150
- autoResolved: ProvisionOverrideMap,
174
+ export const mergeResourceOverrides = (
175
+ autoResolved: ResourceOverrideMap,
151
176
  ctx: Partial<TrailContext> | undefined,
152
- explicit: ProvisionOverrideMap | undefined
153
- ): ProvisionOverrideMap => ({
177
+ explicit: ResourceOverrideMap | undefined
178
+ ): ResourceOverrideMap => ({
154
179
  ...autoResolved,
155
180
  ...ctx?.extensions,
156
181
  ...explicit,
157
182
  });
158
183
 
159
- const buildMockProvisions = async (
160
- app: Topo
161
- ): Promise<ProvisionOverrideMap> => {
162
- const provisions: Record<string, unknown> = {};
163
- for (const declaredProvision of app.listProvisions()) {
164
- if (!declaredProvision.mock) {
184
+ const buildMockResources = async (app: Topo): Promise<ResourceOverrideMap> => {
185
+ const resources: Record<string, unknown> = {};
186
+ for (const declaredResource of app.listResources()) {
187
+ if (!declaredResource.mock) {
165
188
  continue;
166
189
  }
167
- provisions[declaredProvision.id] = await declaredProvision.mock();
190
+ resources[declaredResource.id] = await declaredResource.mock();
168
191
  }
169
- return provisions;
192
+ return resources;
170
193
  };
171
194
 
172
- export const resolveMockProvisions = async (
195
+ export const createMockResources = async (
173
196
  app: Topo
174
- ): Promise<ProvisionOverrideMap> => await buildMockProvisions(app);
197
+ ): Promise<ResourceOverrideMap> => await buildMockResources(app);
198
+
199
+ // Re-export from core so existing consumers of this module continue to work.
200
+ export { buildCrossValidationSchema };
175
201
 
176
202
  /**
177
203
  * Merge a Partial<TrailContext> into a test context.
@@ -179,20 +205,20 @@ export const resolveMockProvisions = async (
179
205
  */
180
206
  export const mergeTestContext = (
181
207
  ctx?: Partial<TrailContext>,
182
- provisions?: ProvisionOverrideMap
208
+ resources?: ResourceOverrideMap
183
209
  ): TrailContext => {
184
210
  const base = createTestContext();
185
211
  const extensions = {
186
212
  ...base.extensions,
187
213
  ...ctx?.extensions,
188
- ...provisions,
214
+ ...resources,
189
215
  };
190
216
  const merged = {
191
217
  ...base,
192
218
  ...ctx,
193
219
  extensions: Object.keys(extensions).length === 0 ? undefined : extensions,
194
220
  } as MutableTrailContext;
195
- const lookup = createProvisionLookup(() => merged);
196
- merged.provision = lookup;
221
+ const lookup = createResourceLookup(() => merged);
222
+ merged.resource = lookup;
197
223
  return merged;
198
224
  };
package/src/contracts.ts CHANGED
@@ -14,12 +14,13 @@ import type { z } from 'zod';
14
14
 
15
15
  import { expectOk } from './assertions.js';
16
16
  import {
17
- mergeProvisionOverrides,
17
+ mergeResourceOverrides,
18
18
  mergeTestContext,
19
19
  normalizeTestExecutionOptions,
20
- resolveMockProvisions,
20
+ createMockResources,
21
21
  } from './context.js';
22
22
  import type { TestExecutionOptions } from './context.js';
23
+ import { deriveTrailExamples } from './effective-examples.js';
23
24
 
24
25
  // ---------------------------------------------------------------------------
25
26
  // Helpers
@@ -71,14 +72,19 @@ export const testContracts = (
71
72
  ): void => {
72
73
  const resolveInput =
73
74
  typeof ctxOrFactory === 'function' ? ctxOrFactory : () => ctxOrFactory;
74
- const allEntries = app.list() as Trail<unknown, unknown>[];
75
+ const allEntries = (app.list() as Trail<unknown, unknown, unknown>[]).map(
76
+ (trailDef) => ({
77
+ ...trailDef,
78
+ examples: deriveTrailExamples(trailDef),
79
+ })
80
+ );
75
81
 
76
82
  describe('contracts', () => {
77
83
  describe.each(allEntries)('$id', (t) => {
78
84
  if (t.output === undefined) {
79
85
  return;
80
86
  }
81
- if (t.examples === undefined || t.examples.length === 0) {
87
+ if (t.examples.length === 0) {
82
88
  return;
83
89
  }
84
90
  if (needsCrossContext(t, resolveInput)) {
@@ -92,10 +98,10 @@ export const testContracts = (
92
98
  'contract: $name',
93
99
  async (example: TrailExample<unknown, unknown>) => {
94
100
  const resolved = normalizeTestExecutionOptions(resolveInput());
95
- const provisions = mergeProvisionOverrides(
96
- await resolveMockProvisions(app),
101
+ const resources = mergeResourceOverrides(
102
+ await createMockResources(app),
97
103
  resolved.ctx,
98
- resolved.provisions
104
+ resolved.resources
99
105
  );
100
106
  const testCtx = mergeTestContext(resolved.ctx);
101
107
 
@@ -104,7 +110,7 @@ export const testContracts = (
104
110
 
105
111
  const result = await executeTrail(t, example.input, {
106
112
  ctx: testCtx,
107
- provisions,
113
+ resources,
108
114
  });
109
115
  const resultValue = expectOk(result);
110
116