@ontrails/testing 1.0.0-beta.39 → 1.0.0-beta.41
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/CHANGELOG.md +23 -0
- package/package.json +8 -8
- package/src/detours.ts +1 -1
- package/src/effective-examples.ts +79 -85
- package/src/examples.ts +1 -1
- package/src/scenario.ts +1 -1
- package/src/trail.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.41
|
|
4
|
+
|
|
5
|
+
## 1.0.0-beta.40
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [`5adb995`](https://github.com/outfitter-dev/trails/commit/5adb99551c2dda6190d46cce7f60bb08d63c99aa): Complete the v1 hard cutover from the authored `blaze` field to
|
|
10
|
+
`implementation` across trail contracts, surface projections, tests, examples,
|
|
11
|
+
and public source-analysis helpers. Existing applications must rename authored
|
|
12
|
+
trail behavior fields and direct trail-object access before upgrading.
|
|
13
|
+
- [`6712075`](https://github.com/outfitter-dev/trails/commit/67120754df3f614c7f4dd98be1fa0ba9d69b7765): Complete the v1 hard cutover from the `contour` domain-object declaration
|
|
14
|
+
vocabulary to `entity` across contracts, topo facts, store helpers, Warden,
|
|
15
|
+
Wayfinder, operator surfaces, examples, and generated locks. Existing
|
|
16
|
+
applications must rename contour APIs, run `trails dev reset --yes` to discard
|
|
17
|
+
pre-cutover local Topographer snapshots, and then recompile committed
|
|
18
|
+
`trails.lock` artifacts before upgrading. Those derived snapshots are
|
|
19
|
+
intentionally not read through a compatibility layer.
|
|
20
|
+
The entity-shaped wire contract advances `TopoGraph` and split lock manifests
|
|
21
|
+
from schema version 3 to 4; old split artifacts fail with regeneration guidance,
|
|
22
|
+
while the canonical root `trails.lock` remains schema version 5.
|
|
23
|
+
Wayfinder reports those stale rows as topo-store drift while keeping current
|
|
24
|
+
committed lock facts available for inspection.
|
|
25
|
+
|
|
3
26
|
## 1.0.0-beta.39
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.41",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*.ts",
|
|
6
6
|
"!src/**/__tests__/**",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ontrails/drizzle": "^1.0.0-beta.
|
|
31
|
-
"@ontrails/store": "^1.0.0-beta.
|
|
30
|
+
"@ontrails/drizzle": "^1.0.0-beta.41",
|
|
31
|
+
"@ontrails/store": "^1.0.0-beta.41"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
35
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
36
|
-
"@ontrails/http": "^1.0.0-beta.
|
|
37
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
38
|
-
"@ontrails/observe": "^1.0.0-beta.
|
|
34
|
+
"@ontrails/cli": "^1.0.0-beta.41",
|
|
35
|
+
"@ontrails/core": "^1.0.0-beta.41",
|
|
36
|
+
"@ontrails/http": "^1.0.0-beta.41",
|
|
37
|
+
"@ontrails/mcp": "^1.0.0-beta.41",
|
|
38
|
+
"@ontrails/observe": "^1.0.0-beta.41",
|
|
39
39
|
"zod": "^4.3.5"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
package/src/detours.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* testDetours — validate the live detour contract for every trail.
|
|
3
3
|
*
|
|
4
|
-
* Pure structural validation. No
|
|
4
|
+
* Pure structural validation. No implementation or detour recovery execution needed.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { describe, test } from 'bun:test';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyEntity, Trail, TrailExample } from '@ontrails/core';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
getEntityReferences,
|
|
4
4
|
getTrailVersionEntryKind,
|
|
5
5
|
isArchivedTrailVersionEntry,
|
|
6
6
|
} from '@ontrails/core';
|
|
@@ -23,7 +23,7 @@ const normalizeComposeRef = (value: string | { readonly id: string }): string =>
|
|
|
23
23
|
typeof value === 'string' ? value : value.id;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Tracks examples that `deriveTrailExamples` synthesizes from
|
|
26
|
+
* Tracks examples that `deriveTrailExamples` synthesizes from entity
|
|
27
27
|
* fixtures. Authored examples are passed through untouched and never
|
|
28
28
|
* appear here, so consumers can distinguish the two by identity.
|
|
29
29
|
*
|
|
@@ -34,15 +34,15 @@ const normalizeComposeRef = (value: string | { readonly id: string }): string =>
|
|
|
34
34
|
const derivedExamples = new WeakSet<TrailExample<unknown, unknown>>();
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Returns `true` if the given example was synthesized from
|
|
37
|
+
* Returns `true` if the given example was synthesized from entity fixtures
|
|
38
38
|
* by `deriveTrailExamples`, `false` if it was authored on the trail.
|
|
39
39
|
*/
|
|
40
40
|
export const isDerivedExample = (
|
|
41
41
|
example: TrailExample<unknown, unknown>
|
|
42
42
|
): boolean => derivedExamples.has(example);
|
|
43
43
|
|
|
44
|
-
interface
|
|
45
|
-
readonly
|
|
44
|
+
interface EntityFixture {
|
|
45
|
+
readonly entity: AnyEntity;
|
|
46
46
|
readonly example: ExampleRecord;
|
|
47
47
|
readonly index: number;
|
|
48
48
|
}
|
|
@@ -51,51 +51,51 @@ const capitalize = (value: string): string =>
|
|
|
51
51
|
value.length === 0 ? value : value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
52
52
|
|
|
53
53
|
const collectReferenceMap = (
|
|
54
|
-
|
|
55
|
-
): ReadonlyMap<string, ReturnType<typeof
|
|
56
|
-
const
|
|
54
|
+
entities: readonly AnyEntity[]
|
|
55
|
+
): ReadonlyMap<string, ReturnType<typeof getEntityReferences>> => {
|
|
56
|
+
const entityNames = new Set(entities.map((entity) => entity.name));
|
|
57
57
|
|
|
58
58
|
return new Map(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
entities.map((entity) => [
|
|
60
|
+
entity.name,
|
|
61
|
+
getEntityReferences(entity).filter((reference) =>
|
|
62
|
+
entityNames.has(reference.entity)
|
|
63
63
|
),
|
|
64
64
|
])
|
|
65
65
|
);
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
const getIdentityValue = (fixture:
|
|
69
|
-
fixture.example[fixture.
|
|
68
|
+
const getIdentityValue = (fixture: EntityFixture): unknown =>
|
|
69
|
+
fixture.example[fixture.entity.identity];
|
|
70
70
|
|
|
71
71
|
const candidateMatchesSelectedReference = (
|
|
72
|
-
candidate:
|
|
73
|
-
target:
|
|
74
|
-
reference: ReturnType<typeof
|
|
72
|
+
candidate: EntityFixture,
|
|
73
|
+
target: EntityFixture,
|
|
74
|
+
reference: ReturnType<typeof getEntityReferences>[number]
|
|
75
75
|
): boolean =>
|
|
76
76
|
Object.is(candidate.example[reference.field], getIdentityValue(target));
|
|
77
77
|
|
|
78
78
|
const selectedMatchesCandidateReference = (
|
|
79
|
-
fixture:
|
|
80
|
-
candidate:
|
|
81
|
-
reference: ReturnType<typeof
|
|
79
|
+
fixture: EntityFixture,
|
|
80
|
+
candidate: EntityFixture,
|
|
81
|
+
reference: ReturnType<typeof getEntityReferences>[number]
|
|
82
82
|
): boolean =>
|
|
83
83
|
Object.is(fixture.example[reference.field], getIdentityValue(candidate));
|
|
84
84
|
|
|
85
85
|
const matchesCandidateReferences = (
|
|
86
|
-
candidate:
|
|
87
|
-
selected: readonly
|
|
88
|
-
|
|
86
|
+
candidate: EntityFixture,
|
|
87
|
+
selected: readonly EntityFixture[],
|
|
88
|
+
referencesByEntity: ReadonlyMap<
|
|
89
89
|
string,
|
|
90
|
-
ReturnType<typeof
|
|
90
|
+
ReturnType<typeof getEntityReferences>
|
|
91
91
|
>
|
|
92
92
|
): boolean => {
|
|
93
93
|
const candidateReferences =
|
|
94
|
-
|
|
94
|
+
referencesByEntity.get(candidate.entity.name) ?? [];
|
|
95
95
|
|
|
96
96
|
for (const reference of candidateReferences) {
|
|
97
97
|
const target = selected.find(
|
|
98
|
-
(fixture) => fixture.
|
|
98
|
+
(fixture) => fixture.entity.name === reference.entity
|
|
99
99
|
);
|
|
100
100
|
if (target === undefined) {
|
|
101
101
|
continue;
|
|
@@ -109,18 +109,17 @@ const matchesCandidateReferences = (
|
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
const matchesSelectedReferences = (
|
|
112
|
-
candidate:
|
|
113
|
-
selected: readonly
|
|
114
|
-
|
|
112
|
+
candidate: EntityFixture,
|
|
113
|
+
selected: readonly EntityFixture[],
|
|
114
|
+
referencesByEntity: ReadonlyMap<
|
|
115
115
|
string,
|
|
116
|
-
ReturnType<typeof
|
|
116
|
+
ReturnType<typeof getEntityReferences>
|
|
117
117
|
>
|
|
118
118
|
): boolean => {
|
|
119
119
|
for (const fixture of selected) {
|
|
120
|
-
const fixtureReferences =
|
|
121
|
-
referencesByContour.get(fixture.contour.name) ?? [];
|
|
120
|
+
const fixtureReferences = referencesByEntity.get(fixture.entity.name) ?? [];
|
|
122
121
|
for (const reference of fixtureReferences) {
|
|
123
|
-
if (reference.
|
|
122
|
+
if (reference.entity !== candidate.entity.name) {
|
|
124
123
|
continue;
|
|
125
124
|
}
|
|
126
125
|
if (!selectedMatchesCandidateReference(fixture, candidate, reference)) {
|
|
@@ -133,43 +132,43 @@ const matchesSelectedReferences = (
|
|
|
133
132
|
};
|
|
134
133
|
|
|
135
134
|
const matchesKnownReferences = (
|
|
136
|
-
candidate:
|
|
137
|
-
selected: readonly
|
|
138
|
-
|
|
135
|
+
candidate: EntityFixture,
|
|
136
|
+
selected: readonly EntityFixture[],
|
|
137
|
+
referencesByEntity: ReadonlyMap<
|
|
139
138
|
string,
|
|
140
|
-
ReturnType<typeof
|
|
139
|
+
ReturnType<typeof getEntityReferences>
|
|
141
140
|
>
|
|
142
141
|
): boolean =>
|
|
143
|
-
matchesCandidateReferences(candidate, selected,
|
|
144
|
-
matchesSelectedReferences(candidate, selected,
|
|
142
|
+
matchesCandidateReferences(candidate, selected, referencesByEntity) &&
|
|
143
|
+
matchesSelectedReferences(candidate, selected, referencesByEntity);
|
|
145
144
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const selectEntityFixtures = (
|
|
146
|
+
entities: readonly AnyEntity[],
|
|
147
|
+
referencesByEntity: ReadonlyMap<
|
|
149
148
|
string,
|
|
150
|
-
ReturnType<typeof
|
|
149
|
+
ReturnType<typeof getEntityReferences>
|
|
151
150
|
>,
|
|
152
151
|
index = 0,
|
|
153
|
-
selected: readonly
|
|
154
|
-
): readonly (readonly
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
152
|
+
selected: readonly EntityFixture[] = []
|
|
153
|
+
): readonly (readonly EntityFixture[])[] => {
|
|
154
|
+
const entity = entities[index];
|
|
155
|
+
if (entity === undefined) {
|
|
157
156
|
return [selected];
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
const examples =
|
|
159
|
+
const examples = entity.examples ?? [];
|
|
161
160
|
const matchingFixtures = examples.flatMap((example, exampleIndex) => {
|
|
162
161
|
const fixture = {
|
|
163
|
-
|
|
162
|
+
entity,
|
|
164
163
|
example: example as ExampleRecord,
|
|
165
164
|
index: exampleIndex,
|
|
166
|
-
} satisfies
|
|
165
|
+
} satisfies EntityFixture;
|
|
167
166
|
|
|
168
|
-
if (!matchesKnownReferences(fixture, selected,
|
|
167
|
+
if (!matchesKnownReferences(fixture, selected, referencesByEntity)) {
|
|
169
168
|
return [];
|
|
170
169
|
}
|
|
171
170
|
|
|
172
|
-
return
|
|
171
|
+
return selectEntityFixtures(entities, referencesByEntity, index + 1, [
|
|
173
172
|
...selected,
|
|
174
173
|
fixture,
|
|
175
174
|
]);
|
|
@@ -179,38 +178,37 @@ const selectContourFixtures = (
|
|
|
179
178
|
};
|
|
180
179
|
|
|
181
180
|
/**
|
|
182
|
-
* Merge selected
|
|
181
|
+
* Merge selected entity fixtures into a single candidate input object.
|
|
183
182
|
*
|
|
184
183
|
* The resulting record contains:
|
|
185
|
-
* - `<
|
|
186
|
-
* - `<
|
|
187
|
-
* - `<
|
|
188
|
-
* - Unqualified `<field>` keys: first-write-wins across
|
|
184
|
+
* - `<entity>`: the full fixture payload keyed by entity name.
|
|
185
|
+
* - `<entity><Identity>`: the fixture's identity value on a prefixed key.
|
|
186
|
+
* - `<entity><Field>`: every fixture field on a prefixed key.
|
|
187
|
+
* - Unqualified `<field>` keys: first-write-wins across entities.
|
|
189
188
|
*
|
|
190
189
|
* The first-write-wins behaviour on unqualified keys is intentional but can
|
|
191
|
-
* silently drop a later
|
|
190
|
+
* silently drop a later entity's value when two entities share a field name
|
|
192
191
|
* (e.g. both declare `id`). The prefixed aliases above are unambiguous and
|
|
193
192
|
* always written, so schemas that consume the prefixed form are unaffected;
|
|
194
193
|
* schemas that rely on the bare field name should disambiguate via the
|
|
195
194
|
* prefixed alias instead.
|
|
196
195
|
*/
|
|
197
196
|
const buildDerivedInput = (
|
|
198
|
-
fixtures: readonly
|
|
197
|
+
fixtures: readonly EntityFixture[]
|
|
199
198
|
): Record<string, unknown> => {
|
|
200
199
|
const candidate: Record<string, unknown> = {};
|
|
201
200
|
|
|
202
201
|
for (const fixture of fixtures) {
|
|
203
|
-
candidate[fixture.
|
|
204
|
-
candidate[
|
|
205
|
-
|
|
206
|
-
] = getIdentityValue(fixture);
|
|
202
|
+
candidate[fixture.entity.name] = fixture.example;
|
|
203
|
+
candidate[`${fixture.entity.name}${capitalize(fixture.entity.identity)}`] =
|
|
204
|
+
getIdentityValue(fixture);
|
|
207
205
|
|
|
208
206
|
for (const [field, value] of Object.entries(fixture.example)) {
|
|
209
207
|
if (!Object.hasOwn(candidate, field)) {
|
|
210
208
|
candidate[field] = value;
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
candidate[`${fixture.
|
|
211
|
+
candidate[`${fixture.entity.name}${capitalize(field)}`] = value;
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
|
|
@@ -248,7 +246,7 @@ const projectInputForSchema = (
|
|
|
248
246
|
};
|
|
249
247
|
|
|
250
248
|
/**
|
|
251
|
-
* Derive an expected output value from the selected
|
|
249
|
+
* Derive an expected output value from the selected entity fixtures when
|
|
252
250
|
* exactly one fixture's payload satisfies the trail's output schema.
|
|
253
251
|
*
|
|
254
252
|
* Returns `undefined` when the trail has no output schema, when no fixture
|
|
@@ -261,22 +259,22 @@ const projectInputForSchema = (
|
|
|
261
259
|
*/
|
|
262
260
|
const deriveExpectedValue = (
|
|
263
261
|
trail: Trail<unknown, unknown, unknown>,
|
|
264
|
-
fixtures: readonly
|
|
262
|
+
fixtures: readonly EntityFixture[]
|
|
265
263
|
): unknown => {
|
|
266
264
|
if (trail.output === undefined) {
|
|
267
265
|
return undefined;
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
const outputSchema = trail.output;
|
|
271
|
-
const
|
|
269
|
+
const entityMatches = fixtures
|
|
272
270
|
.map((fixture) => outputSchema.safeParse(fixture.example))
|
|
273
271
|
.filter((candidate) => candidate.success);
|
|
274
272
|
|
|
275
|
-
if (
|
|
273
|
+
if (entityMatches.length !== 1) {
|
|
276
274
|
return undefined;
|
|
277
275
|
}
|
|
278
276
|
|
|
279
|
-
const [singleMatch] =
|
|
277
|
+
const [singleMatch] = entityMatches;
|
|
280
278
|
if (singleMatch === undefined) {
|
|
281
279
|
return undefined;
|
|
282
280
|
}
|
|
@@ -284,14 +282,14 @@ const deriveExpectedValue = (
|
|
|
284
282
|
};
|
|
285
283
|
|
|
286
284
|
const formatFixtureName = (
|
|
287
|
-
fixtures: readonly
|
|
285
|
+
fixtures: readonly EntityFixture[],
|
|
288
286
|
index: number
|
|
289
287
|
): string => {
|
|
290
288
|
const label = fixtures
|
|
291
289
|
.map((fixture) => {
|
|
292
290
|
const identity = getIdentityValue(fixture);
|
|
293
291
|
const fallback = fixture.index + 1;
|
|
294
|
-
return `${fixture.
|
|
292
|
+
return `${fixture.entity.name}:${String(identity ?? fallback)}`;
|
|
295
293
|
})
|
|
296
294
|
.join(', ');
|
|
297
295
|
|
|
@@ -301,7 +299,7 @@ const formatFixtureName = (
|
|
|
301
299
|
};
|
|
302
300
|
|
|
303
301
|
/**
|
|
304
|
-
* Prefer authored trail examples and fall back to
|
|
302
|
+
* Prefer authored trail examples and fall back to entity-derived fixtures.
|
|
305
303
|
*
|
|
306
304
|
* Examples returned by this helper come from one of two provenances:
|
|
307
305
|
* - **Authored.** When `trail.examples` is non-empty, its entries are
|
|
@@ -309,14 +307,14 @@ const formatFixtureName = (
|
|
|
309
307
|
* full invariants — including composing-coverage assertions in
|
|
310
308
|
* `testExamples`.
|
|
311
309
|
* - **Derived.** When there are no authored examples but the trail has
|
|
312
|
-
*
|
|
310
|
+
* entities with examples, candidate inputs are synthesized from entity
|
|
313
311
|
* fixtures and validated against `trail.input`. These are opportunistic
|
|
314
|
-
* coverage that exists to let `testAll(app)` exercise
|
|
312
|
+
* coverage that exists to let `testAll(app)` exercise entity-backed
|
|
315
313
|
* trails without per-test setup; they are not guaranteed to exercise
|
|
316
314
|
* every composition branch, so consumers should relax invariants that
|
|
317
315
|
* only make sense for authored inputs (see `isDerivedExample`).
|
|
318
316
|
*
|
|
319
|
-
*
|
|
317
|
+
* Entity examples stay as the raw input payload so Trails validation /
|
|
320
318
|
* transforms still happen exactly once inside the normal test execution
|
|
321
319
|
* path. Derived examples are additionally tagged via a module-level
|
|
322
320
|
* `WeakSet` so consumers can detect them without widening the public
|
|
@@ -329,24 +327,20 @@ export const deriveTrailExamples = (
|
|
|
329
327
|
return trail.examples;
|
|
330
328
|
}
|
|
331
329
|
|
|
332
|
-
if (trail.
|
|
330
|
+
if (trail.entities.length === 0) {
|
|
333
331
|
return [];
|
|
334
332
|
}
|
|
335
333
|
|
|
336
334
|
if (
|
|
337
|
-
trail.
|
|
338
|
-
(
|
|
339
|
-
contour.examples === undefined || contour.examples.length === 0
|
|
335
|
+
trail.entities.some(
|
|
336
|
+
(entity) => entity.examples === undefined || entity.examples.length === 0
|
|
340
337
|
)
|
|
341
338
|
) {
|
|
342
339
|
return [];
|
|
343
340
|
}
|
|
344
341
|
|
|
345
|
-
const
|
|
346
|
-
const fixtureSets =
|
|
347
|
-
trail.contours,
|
|
348
|
-
referencesByContour
|
|
349
|
-
);
|
|
342
|
+
const referencesByEntity = collectReferenceMap(trail.entities);
|
|
343
|
+
const fixtureSets = selectEntityFixtures(trail.entities, referencesByEntity);
|
|
350
344
|
|
|
351
345
|
return fixtureSets.flatMap((fixtures, index) => {
|
|
352
346
|
const merged = buildDerivedInput(fixtures);
|
package/src/examples.ts
CHANGED
|
@@ -375,7 +375,7 @@ export const testExamples = (
|
|
|
375
375
|
|
|
376
376
|
// Composition trails: use recording compose and check coverage.
|
|
377
377
|
//
|
|
378
|
-
// Composing coverage only runs against AUTHORED examples.
|
|
378
|
+
// Composing coverage only runs against AUTHORED examples. Entity-derived
|
|
379
379
|
// fixtures are opportunistic coverage that may not exercise every
|
|
380
380
|
// `ctx.compose()` branch in the trail, so asserting coverage against them
|
|
381
381
|
// would produce false failures for trails whose authored intent was a
|
package/src/scenario.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Scenarios express multi-trail flows as structured data — arrays of steps
|
|
5
5
|
* with compose-step references via `ref()`. Each step invokes a trail through
|
|
6
|
-
* the normal execution pipeline (validation, layers,
|
|
6
|
+
* the normal execution pipeline (validation, layers, implementation, Result).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, test } from 'bun:test';
|
package/src/trail.ts
CHANGED
|
@@ -82,7 +82,7 @@ const runScenario = async (
|
|
|
82
82
|
}
|
|
83
83
|
const validatedInput = expectOk(validated);
|
|
84
84
|
|
|
85
|
-
const result = await trailDef.
|
|
85
|
+
const result = await trailDef.implementation(validatedInput, testCtx);
|
|
86
86
|
assertScenarioResult(result, scenario, trailDef);
|
|
87
87
|
};
|
|
88
88
|
|