@ontrails/testing 1.0.0-beta.15 → 1.0.0-beta.17
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 +70 -1
- package/README.md +47 -3
- package/package.json +16 -7
- package/src/all.ts +32 -7
- package/src/context.ts +3 -0
- package/src/contracts.ts +1 -16
- package/src/examples.ts +19 -11
- package/src/harness-cli.ts +16 -3
- package/src/harness-http.ts +263 -0
- package/src/harness-mcp.ts +2 -0
- package/src/index.ts +18 -1
- package/src/logger.ts +3 -1
- package/src/scenario.ts +3 -5
- package/src/signals.ts +221 -0
- package/src/surface-parity.ts +356 -0
- package/src/types.ts +136 -1
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/all.d.ts +0 -36
- package/dist/all.d.ts.map +0 -1
- package/dist/all.js +0 -99
- package/dist/all.js.map +0 -1
- package/dist/assertions.d.ts +0 -72
- package/dist/assertions.d.ts.map +0 -1
- package/dist/assertions.js +0 -238
- package/dist/assertions.js.map +0 -1
- package/dist/context.d.ts +0 -76
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -118
- package/dist/context.js.map +0 -1
- package/dist/contracts.d.ts +0 -17
- package/dist/contracts.d.ts.map +0 -1
- package/dist/contracts.js +0 -75
- package/dist/contracts.js.map +0 -1
- package/dist/crosses.d.ts +0 -38
- package/dist/crosses.d.ts.map +0 -1
- package/dist/crosses.js +0 -223
- package/dist/crosses.js.map +0 -1
- package/dist/detours.d.ts +0 -13
- package/dist/detours.d.ts.map +0 -1
- package/dist/detours.js +0 -109
- package/dist/detours.js.map +0 -1
- package/dist/effective-examples.d.ts +0 -30
- package/dist/effective-examples.d.ts.map +0 -1
- package/dist/effective-examples.js +0 -227
- package/dist/effective-examples.js.map +0 -1
- package/dist/examples.d.ts +0 -23
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js +0 -240
- package/dist/examples.js.map +0 -1
- package/dist/harness-cli.d.ts +0 -21
- package/dist/harness-cli.d.ts.map +0 -1
- package/dist/harness-cli.js +0 -205
- package/dist/harness-cli.js.map +0 -1
- package/dist/harness-mcp.d.ts +0 -21
- package/dist/harness-mcp.d.ts.map +0 -1
- package/dist/harness-mcp.js +0 -54
- package/dist/harness-mcp.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -19
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -15
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -87
- package/dist/logger.js.map +0 -1
- package/dist/scenario.d.ts +0 -37
- package/dist/scenario.d.ts.map +0 -1
- package/dist/scenario.js +0 -235
- package/dist/scenario.js.map +0 -1
- package/dist/trail.d.ts +0 -20
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -80
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -113
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/src/__tests__/all.test.ts +0 -254
- package/src/__tests__/context.test.ts +0 -170
- package/src/__tests__/contracts.test.ts +0 -239
- package/src/__tests__/crosses.test.ts +0 -587
- package/src/__tests__/detours.test.ts +0 -212
- package/src/__tests__/effective-examples.test.ts +0 -203
- package/src/__tests__/examples.test.ts +0 -636
- package/src/__tests__/harness-cli.test.ts +0 -90
- package/src/__tests__/harness-mcp.test.ts +0 -37
- package/src/__tests__/logger.test.ts +0 -136
- package/src/__tests__/partial-match.test.ts +0 -126
- package/src/__tests__/scenario.test.ts +0 -381
- package/src/__tests__/trail.test.ts +0 -99
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example-driven parity checks across shipped surfaces.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'bun:test';
|
|
6
|
+
|
|
7
|
+
import { deriveCliPath, filterSurfaceTrails } from '@ontrails/core';
|
|
8
|
+
import type { Topo, Trail, TrailContext, TrailExample } from '@ontrails/core';
|
|
9
|
+
import { deriveHttpInputSource, deriveHttpMethod } from '@ontrails/http';
|
|
10
|
+
import type { HttpMethod } from '@ontrails/http';
|
|
11
|
+
import { MCP_TOOL_ERROR_META_KEY, deriveToolName } from '@ontrails/mcp';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createMockResources,
|
|
15
|
+
defaultCreatePermit,
|
|
16
|
+
mergeResourceOverrides,
|
|
17
|
+
mergeTestContext,
|
|
18
|
+
} from './context.js';
|
|
19
|
+
import { deriveTrailExamples } from './effective-examples.js';
|
|
20
|
+
import { createCliHarness } from './harness-cli.js';
|
|
21
|
+
import { createHttpHarness } from './harness-http.js';
|
|
22
|
+
import { createMcpHarness } from './harness-mcp.js';
|
|
23
|
+
import type {
|
|
24
|
+
CliHarnessResult,
|
|
25
|
+
HttpHarnessResult,
|
|
26
|
+
McpHarnessResult,
|
|
27
|
+
NormalizedSurfaceParityResult,
|
|
28
|
+
SurfaceParityExclusion,
|
|
29
|
+
SurfaceParityOptions,
|
|
30
|
+
} from './types.js';
|
|
31
|
+
|
|
32
|
+
type ParityTrail = Trail<unknown, unknown, unknown>;
|
|
33
|
+
|
|
34
|
+
export interface SurfaceParityComparison {
|
|
35
|
+
readonly cli: NormalizedSurfaceParityResult;
|
|
36
|
+
readonly http: NormalizedSurfaceParityResult;
|
|
37
|
+
readonly mcp: NormalizedSurfaceParityResult;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const httpPathForTrail = (trailId: string): string =>
|
|
41
|
+
`/${trailId.replaceAll('.', '/')}`;
|
|
42
|
+
|
|
43
|
+
const escapeWhitespaceForCliToken = (value: string): string =>
|
|
44
|
+
value.replaceAll(/\s/gu, (char) => {
|
|
45
|
+
let escaped = '';
|
|
46
|
+
|
|
47
|
+
for (let index = 0; index < char.length; index += 1) {
|
|
48
|
+
const codePoint = char.codePointAt(index);
|
|
49
|
+
|
|
50
|
+
if (codePoint !== undefined) {
|
|
51
|
+
escaped += `\\u${codePoint.toString(16).padStart(4, '0')}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return escaped;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const cliCommandForExample = (
|
|
59
|
+
trail: ParityTrail,
|
|
60
|
+
example: TrailExample<unknown, unknown>
|
|
61
|
+
): string => {
|
|
62
|
+
const path = deriveCliPath(trail.id).join(' ');
|
|
63
|
+
const inputJson = escapeWhitespaceForCliToken(
|
|
64
|
+
JSON.stringify(example.input ?? {})
|
|
65
|
+
);
|
|
66
|
+
return `${path} --input-json ${inputJson} --output json`;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
|
70
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
71
|
+
|
|
72
|
+
const readTextContent = (content: unknown): string | undefined => {
|
|
73
|
+
if (!Array.isArray(content)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const firstText = content.find(
|
|
77
|
+
(item): item is { readonly text: string; readonly type: string } =>
|
|
78
|
+
isObjectRecord(item) &&
|
|
79
|
+
item['type'] === 'text' &&
|
|
80
|
+
typeof item['text'] === 'string'
|
|
81
|
+
);
|
|
82
|
+
return firstText?.text;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const parseJsonText = (text: string | undefined): unknown => {
|
|
86
|
+
if (text === undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(text);
|
|
91
|
+
} catch {
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const structuredContentValue = (
|
|
97
|
+
structuredContent: Record<string, unknown> | undefined
|
|
98
|
+
): unknown => {
|
|
99
|
+
if (structuredContent === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const keys = Object.keys(structuredContent);
|
|
103
|
+
return keys.length === 1 && keys[0] === 'data'
|
|
104
|
+
? structuredContent['data']
|
|
105
|
+
: structuredContent;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const normalizeCliResult = (
|
|
109
|
+
result: CliHarnessResult
|
|
110
|
+
): NormalizedSurfaceParityResult =>
|
|
111
|
+
result.exitCode === 0
|
|
112
|
+
? { ok: true, value: result.json }
|
|
113
|
+
: {
|
|
114
|
+
error: {
|
|
115
|
+
category: result.error?.category ?? 'internal',
|
|
116
|
+
code: result.error?.code ?? 'InternalError',
|
|
117
|
+
},
|
|
118
|
+
ok: false,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const normalizeHttpResult = (
|
|
122
|
+
result: HttpHarnessResult
|
|
123
|
+
): NormalizedSurfaceParityResult =>
|
|
124
|
+
result.ok
|
|
125
|
+
? { ok: true, value: result.data }
|
|
126
|
+
: {
|
|
127
|
+
error: {
|
|
128
|
+
category: result.error?.category ?? 'internal',
|
|
129
|
+
code: result.error?.code ?? 'InternalError',
|
|
130
|
+
},
|
|
131
|
+
ok: false,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const readMcpError = (
|
|
135
|
+
result: McpHarnessResult
|
|
136
|
+
): { readonly category: string; readonly code: string } => {
|
|
137
|
+
const errorMeta = result.meta?.[MCP_TOOL_ERROR_META_KEY];
|
|
138
|
+
if (isObjectRecord(errorMeta)) {
|
|
139
|
+
return {
|
|
140
|
+
category:
|
|
141
|
+
typeof errorMeta['category'] === 'string'
|
|
142
|
+
? errorMeta['category']
|
|
143
|
+
: 'internal',
|
|
144
|
+
code:
|
|
145
|
+
typeof errorMeta['name'] === 'string'
|
|
146
|
+
? errorMeta['name']
|
|
147
|
+
: 'InternalError',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { category: 'internal', code: 'InternalError' };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const normalizeMcpResult = (
|
|
154
|
+
result: McpHarnessResult
|
|
155
|
+
): NormalizedSurfaceParityResult =>
|
|
156
|
+
result.isError
|
|
157
|
+
? { error: readMcpError(result), ok: false }
|
|
158
|
+
: {
|
|
159
|
+
ok: true,
|
|
160
|
+
value:
|
|
161
|
+
result.structuredContent === undefined
|
|
162
|
+
? parseJsonText(readTextContent(result.content))
|
|
163
|
+
: structuredContentValue(result.structuredContent),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const applyAutoPermit = (
|
|
167
|
+
ctx: TrailContext,
|
|
168
|
+
trail: ParityTrail,
|
|
169
|
+
options: SurfaceParityOptions
|
|
170
|
+
): TrailContext => {
|
|
171
|
+
if (options.strictPermits || ctx.permit !== undefined) {
|
|
172
|
+
return ctx;
|
|
173
|
+
}
|
|
174
|
+
const permit = (options.createPermit ?? defaultCreatePermit)(trail);
|
|
175
|
+
return permit === undefined ? ctx : { ...ctx, permit };
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const createInvocationContext = async (
|
|
179
|
+
app: Topo,
|
|
180
|
+
trail: ParityTrail,
|
|
181
|
+
options: SurfaceParityOptions
|
|
182
|
+
) => {
|
|
183
|
+
const autoResources =
|
|
184
|
+
options.createResources === undefined
|
|
185
|
+
? await createMockResources(app)
|
|
186
|
+
: await options.createResources();
|
|
187
|
+
const resources = mergeResourceOverrides(
|
|
188
|
+
autoResources,
|
|
189
|
+
options.ctx,
|
|
190
|
+
options.resources
|
|
191
|
+
);
|
|
192
|
+
const ctx = applyAutoPermit(mergeTestContext(options.ctx), trail, options);
|
|
193
|
+
return { ctx, resources };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const mergeSurfaceContext = (
|
|
197
|
+
ctx: TrailContext,
|
|
198
|
+
surfaceCtx: Partial<TrailContext> | undefined
|
|
199
|
+
): TrailContext =>
|
|
200
|
+
surfaceCtx === undefined ? ctx : mergeTestContext({ ...ctx, ...surfaceCtx });
|
|
201
|
+
|
|
202
|
+
const runCliExample = async (
|
|
203
|
+
app: Topo,
|
|
204
|
+
trail: ParityTrail,
|
|
205
|
+
example: TrailExample<unknown, unknown>,
|
|
206
|
+
options: SurfaceParityOptions
|
|
207
|
+
): Promise<NormalizedSurfaceParityResult> => {
|
|
208
|
+
const { ctx, resources } = await createInvocationContext(app, trail, options);
|
|
209
|
+
const cliOptions = options.cli;
|
|
210
|
+
const harness = createCliHarness({
|
|
211
|
+
graph: app,
|
|
212
|
+
...cliOptions,
|
|
213
|
+
ctx: mergeSurfaceContext(ctx, cliOptions?.ctx),
|
|
214
|
+
resources,
|
|
215
|
+
});
|
|
216
|
+
return normalizeCliResult(
|
|
217
|
+
await harness.run(cliCommandForExample(trail, example))
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const runMcpExample = async (
|
|
222
|
+
app: Topo,
|
|
223
|
+
trail: ParityTrail,
|
|
224
|
+
example: TrailExample<unknown, unknown>,
|
|
225
|
+
options: SurfaceParityOptions
|
|
226
|
+
): Promise<NormalizedSurfaceParityResult> => {
|
|
227
|
+
const { ctx, resources } = await createInvocationContext(app, trail, options);
|
|
228
|
+
const mcpOptions = options.mcp;
|
|
229
|
+
const harness = createMcpHarness({
|
|
230
|
+
graph: app,
|
|
231
|
+
...mcpOptions,
|
|
232
|
+
createContext: async () =>
|
|
233
|
+
mergeSurfaceContext(ctx, await mcpOptions?.createContext?.()),
|
|
234
|
+
resources,
|
|
235
|
+
});
|
|
236
|
+
const toolName = deriveToolName(app.name, trail.id);
|
|
237
|
+
return normalizeMcpResult(
|
|
238
|
+
await harness.callTool(
|
|
239
|
+
toolName,
|
|
240
|
+
isObjectRecord(example.input) ? example.input : {}
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const httpRequestForExample = (
|
|
246
|
+
method: HttpMethod,
|
|
247
|
+
trailId: string,
|
|
248
|
+
example: TrailExample<unknown, unknown>
|
|
249
|
+
) => {
|
|
250
|
+
const input = isObjectRecord(example.input) ? example.input : {};
|
|
251
|
+
return deriveHttpInputSource(method) === 'query'
|
|
252
|
+
? { method, path: httpPathForTrail(trailId), query: input }
|
|
253
|
+
: { body: input, method, path: httpPathForTrail(trailId) };
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const runHttpExample = async (
|
|
257
|
+
app: Topo,
|
|
258
|
+
trail: ParityTrail,
|
|
259
|
+
example: TrailExample<unknown, unknown>,
|
|
260
|
+
options: SurfaceParityOptions
|
|
261
|
+
): Promise<NormalizedSurfaceParityResult> => {
|
|
262
|
+
const { ctx, resources } = await createInvocationContext(app, trail, options);
|
|
263
|
+
const httpOptions = options.http;
|
|
264
|
+
const harness = createHttpHarness({
|
|
265
|
+
graph: app,
|
|
266
|
+
...httpOptions,
|
|
267
|
+
ctx: mergeSurfaceContext(ctx, httpOptions?.ctx),
|
|
268
|
+
resources,
|
|
269
|
+
});
|
|
270
|
+
const method = deriveHttpMethod(trail.intent);
|
|
271
|
+
return normalizeHttpResult(
|
|
272
|
+
await harness.request(httpRequestForExample(method, trail.id, example))
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const runSurfaceParityExample = async (
|
|
277
|
+
app: Topo,
|
|
278
|
+
trail: ParityTrail,
|
|
279
|
+
example: TrailExample<unknown, unknown>,
|
|
280
|
+
options: SurfaceParityOptions = {}
|
|
281
|
+
): Promise<SurfaceParityComparison> => {
|
|
282
|
+
// CLI harness output capture is process-scoped, so keep surface execution
|
|
283
|
+
// ordered even though MCP and HTTP do not share that constraint.
|
|
284
|
+
const cli = await runCliExample(app, trail, example, options);
|
|
285
|
+
const mcp = await runMcpExample(app, trail, example, options);
|
|
286
|
+
const http = await runHttpExample(app, trail, example, options);
|
|
287
|
+
return { cli, http, mcp };
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const findExclusion = (
|
|
291
|
+
exclusions: readonly SurfaceParityExclusion[] | undefined,
|
|
292
|
+
trail: ParityTrail,
|
|
293
|
+
example: TrailExample<unknown, unknown>
|
|
294
|
+
): SurfaceParityExclusion | undefined =>
|
|
295
|
+
exclusions?.find(
|
|
296
|
+
(exclusion) =>
|
|
297
|
+
exclusion.trailId === trail.id &&
|
|
298
|
+
(exclusion.example === undefined || exclusion.example === example.name)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const parityTrails = (app: Topo): readonly ParityTrail[] =>
|
|
302
|
+
filterSurfaceTrails(app.list()).filter(
|
|
303
|
+
(trail) => deriveTrailExamples(trail).length > 0
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Register example-driven parity tests for CLI, MCP, and HTTP.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* import { testSurfaceParity } from '@ontrails/testing';
|
|
312
|
+
* import { graph } from '../src/app.js';
|
|
313
|
+
*
|
|
314
|
+
* testSurfaceParity(graph);
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
export const testSurfaceParity = (
|
|
318
|
+
app: Topo,
|
|
319
|
+
optionsOrFactory?:
|
|
320
|
+
| SurfaceParityOptions
|
|
321
|
+
| (() => SurfaceParityOptions | undefined)
|
|
322
|
+
): void => {
|
|
323
|
+
const resolveOptions =
|
|
324
|
+
typeof optionsOrFactory === 'function'
|
|
325
|
+
? optionsOrFactory
|
|
326
|
+
: () => optionsOrFactory;
|
|
327
|
+
|
|
328
|
+
describe('surface parity', () => {
|
|
329
|
+
for (const trail of parityTrails(app)) {
|
|
330
|
+
describe(trail.id, () => {
|
|
331
|
+
for (const example of deriveTrailExamples(trail)) {
|
|
332
|
+
const options = resolveOptions() ?? {};
|
|
333
|
+
const exclusion = findExclusion(options.exclusions, trail, example);
|
|
334
|
+
const testName = `example: ${example.name}`;
|
|
335
|
+
if (exclusion !== undefined) {
|
|
336
|
+
test.skip(`${testName} (excluded: ${exclusion.reason})`, () => {
|
|
337
|
+
throw new Error('Skipped parity exclusion should not execute');
|
|
338
|
+
});
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
test(testName, async () => {
|
|
343
|
+
const comparison = await runSurfaceParityExample(
|
|
344
|
+
app,
|
|
345
|
+
trail,
|
|
346
|
+
example,
|
|
347
|
+
resolveOptions() ?? {}
|
|
348
|
+
);
|
|
349
|
+
expect(comparison.mcp).toEqual(comparison.cli);
|
|
350
|
+
expect(comparison.http).toEqual(comparison.cli);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { DeriveCliCommandsOptions } from '@ontrails/cli';
|
|
6
|
+
import type {
|
|
7
|
+
DeriveHttpRoutesOptions,
|
|
8
|
+
HttpHeaderSource,
|
|
9
|
+
HttpMethod,
|
|
10
|
+
} from '@ontrails/http';
|
|
6
11
|
import type { McpExtra, DeriveMcpToolsOptions } from '@ontrails/mcp';
|
|
7
12
|
import type {
|
|
8
13
|
AnyTrail,
|
|
9
14
|
Logger,
|
|
15
|
+
ResourceOverrideMap,
|
|
10
16
|
Topo,
|
|
11
17
|
TraceFn,
|
|
12
18
|
TrailContext,
|
|
13
19
|
} from '@ontrails/core';
|
|
14
|
-
import type { LogLevel, LogRecord } from '@ontrails/
|
|
20
|
+
import type { LogLevel, LogRecord } from '@ontrails/observe';
|
|
15
21
|
|
|
16
22
|
// ---------------------------------------------------------------------------
|
|
17
23
|
// Test Scenario (for testTrail)
|
|
@@ -98,6 +104,13 @@ export interface CliHarness {
|
|
|
98
104
|
|
|
99
105
|
/** The result of a CLI harness command execution. */
|
|
100
106
|
export interface CliHarnessResult {
|
|
107
|
+
readonly error?:
|
|
108
|
+
| {
|
|
109
|
+
readonly category: string;
|
|
110
|
+
readonly code: string;
|
|
111
|
+
readonly message: string;
|
|
112
|
+
}
|
|
113
|
+
| undefined;
|
|
101
114
|
readonly exitCode: number;
|
|
102
115
|
/** Parsed JSON output if --output json was used. */
|
|
103
116
|
readonly json?: unknown | undefined;
|
|
@@ -128,8 +141,129 @@ export interface McpHarness {
|
|
|
128
141
|
export interface McpHarnessResult {
|
|
129
142
|
readonly content: unknown;
|
|
130
143
|
readonly isError: boolean;
|
|
144
|
+
readonly meta?: Record<string, unknown> | undefined;
|
|
145
|
+
readonly structuredContent?: Record<string, unknown> | undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// HTTP Harness
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/** Options for creating an HTTP harness. */
|
|
153
|
+
export interface HttpHarnessOptions extends DeriveHttpRoutesOptions {
|
|
154
|
+
readonly ctx?: Partial<TrailContext> | undefined;
|
|
155
|
+
readonly graph: Topo;
|
|
131
156
|
}
|
|
132
157
|
|
|
158
|
+
export interface HttpHarnessRequest {
|
|
159
|
+
readonly abortSignal?: AbortSignal | undefined;
|
|
160
|
+
readonly body?: unknown | undefined;
|
|
161
|
+
readonly headers?: HttpHeaderSource | undefined;
|
|
162
|
+
readonly method: HttpMethod;
|
|
163
|
+
readonly path: string;
|
|
164
|
+
readonly query?: Record<string, unknown> | undefined;
|
|
165
|
+
readonly requestId?: string | undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface HttpHarnessRequestOptions extends Omit<
|
|
169
|
+
HttpHarnessRequest,
|
|
170
|
+
'body' | 'method' | 'path' | 'query'
|
|
171
|
+
> {
|
|
172
|
+
readonly query?: Record<string, unknown> | undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A test harness for HTTP route projections. */
|
|
176
|
+
export interface HttpHarness {
|
|
177
|
+
/** Execute a raw HTTP-style harness request. */
|
|
178
|
+
request(request: HttpHarnessRequest): Promise<HttpHarnessResult>;
|
|
179
|
+
/** Execute a GET request, reading input from query params. */
|
|
180
|
+
get(
|
|
181
|
+
path: string,
|
|
182
|
+
query?: Record<string, unknown>,
|
|
183
|
+
options?: HttpHarnessRequestOptions
|
|
184
|
+
): Promise<HttpHarnessResult>;
|
|
185
|
+
/** Execute a POST request, reading input from the JSON-like body value. */
|
|
186
|
+
post(
|
|
187
|
+
path: string,
|
|
188
|
+
body?: unknown,
|
|
189
|
+
options?: HttpHarnessRequestOptions
|
|
190
|
+
): Promise<HttpHarnessResult>;
|
|
191
|
+
/** Execute a PUT request. */
|
|
192
|
+
put(
|
|
193
|
+
path: string,
|
|
194
|
+
body?: unknown,
|
|
195
|
+
options?: HttpHarnessRequestOptions
|
|
196
|
+
): Promise<HttpHarnessResult>;
|
|
197
|
+
/** Execute a PATCH request. */
|
|
198
|
+
patch(
|
|
199
|
+
path: string,
|
|
200
|
+
body?: unknown,
|
|
201
|
+
options?: HttpHarnessRequestOptions
|
|
202
|
+
): Promise<HttpHarnessResult>;
|
|
203
|
+
/** Execute a DELETE request. */
|
|
204
|
+
delete(
|
|
205
|
+
path: string,
|
|
206
|
+
body?: unknown,
|
|
207
|
+
options?: HttpHarnessRequestOptions
|
|
208
|
+
): Promise<HttpHarnessResult>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface HttpHarnessErrorBody {
|
|
212
|
+
readonly error: {
|
|
213
|
+
readonly category: string;
|
|
214
|
+
readonly code: string;
|
|
215
|
+
readonly message: string;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface HttpHarnessSuccessBody {
|
|
220
|
+
readonly data: unknown;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The result of an HTTP harness request. */
|
|
224
|
+
export interface HttpHarnessResult {
|
|
225
|
+
readonly body: HttpHarnessErrorBody | HttpHarnessSuccessBody;
|
|
226
|
+
readonly data?: unknown | undefined;
|
|
227
|
+
readonly error?: HttpHarnessErrorBody['error'] | undefined;
|
|
228
|
+
readonly ok: boolean;
|
|
229
|
+
readonly status: number;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Surface parity
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
export type SurfaceParitySurface = 'cli' | 'mcp' | 'http';
|
|
237
|
+
|
|
238
|
+
export interface SurfaceParityExclusion {
|
|
239
|
+
/** Optional example name. Omit to exclude every example for the trail. */
|
|
240
|
+
readonly example?: string | undefined;
|
|
241
|
+
/** Human-readable reason shown in the skipped test name. */
|
|
242
|
+
readonly reason: string;
|
|
243
|
+
/** Trail ID to exclude. */
|
|
244
|
+
readonly trailId: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface SurfaceParityOptions extends TestAllEstablishedOptions {
|
|
248
|
+
readonly createResources?:
|
|
249
|
+
| (() => ResourceOverrideMap | Promise<ResourceOverrideMap>)
|
|
250
|
+
| undefined;
|
|
251
|
+
readonly exclusions?: readonly SurfaceParityExclusion[] | undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export type NormalizedSurfaceParityResult =
|
|
255
|
+
| {
|
|
256
|
+
readonly ok: true;
|
|
257
|
+
readonly value: unknown;
|
|
258
|
+
}
|
|
259
|
+
| {
|
|
260
|
+
readonly error: {
|
|
261
|
+
readonly category: string;
|
|
262
|
+
readonly code: string;
|
|
263
|
+
};
|
|
264
|
+
readonly ok: false;
|
|
265
|
+
};
|
|
266
|
+
|
|
133
267
|
// ---------------------------------------------------------------------------
|
|
134
268
|
// Established verification
|
|
135
269
|
// ---------------------------------------------------------------------------
|
|
@@ -150,6 +284,7 @@ export interface TestAllEstablishedOptions {
|
|
|
150
284
|
| undefined)
|
|
151
285
|
| undefined;
|
|
152
286
|
readonly ctx?: Partial<TrailContext> | undefined;
|
|
287
|
+
readonly http?: Omit<HttpHarnessOptions, 'graph'> | undefined;
|
|
153
288
|
readonly mcp?: Omit<McpHarnessOptions, 'graph'> | undefined;
|
|
154
289
|
readonly resources?: Record<string, unknown> | undefined;
|
|
155
290
|
readonly strictPermits?: boolean | undefined;
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc -b
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|
package/dist/all.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* testAll — single-line governance suite for any Topo.
|
|
3
|
-
*
|
|
4
|
-
* Wraps topo validation, example execution, contract checks, and detour
|
|
5
|
-
* contract validation into one describe block.
|
|
6
|
-
*/
|
|
7
|
-
import type { Topo, TrailContext } from '@ontrails/core';
|
|
8
|
-
import type { TestExecutionOptions } from './context.js';
|
|
9
|
-
import type { TestAllEstablishedOptions } from './types.js';
|
|
10
|
-
/**
|
|
11
|
-
* Run the full governance test suite for a Topo.
|
|
12
|
-
*
|
|
13
|
-
* Generates a `governance` describe block containing:
|
|
14
|
-
* - Structural validation via `validateTopo`
|
|
15
|
-
* - Example execution via `testExamples`
|
|
16
|
-
* - Output contract checks via `testContracts`
|
|
17
|
-
* - Detour contract validation via `testDetours`
|
|
18
|
-
*
|
|
19
|
-
* Accepts either a static context or a factory function that produces a
|
|
20
|
-
* fresh context per test (useful when the context contains mutable state
|
|
21
|
-
* like an in-memory store).
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* import { testAll } from '@ontrails/testing';
|
|
26
|
-
* import { graph } from '../src/app.js';
|
|
27
|
-
*
|
|
28
|
-
* testAll(graph);
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
type TestAllInput = Partial<TrailContext> | TestExecutionOptions | (() => Partial<TrailContext> | TestExecutionOptions);
|
|
32
|
-
export declare const testAll: (topo: Topo, ctxOrFactory?: TestAllInput) => void;
|
|
33
|
-
type EstablishedInput = Partial<TrailContext> | TestAllEstablishedOptions | (() => Partial<TrailContext> | TestAllEstablishedOptions);
|
|
34
|
-
export declare const testAllEstablished: (topo: Topo, optionsOrFactory?: EstablishedInput) => void;
|
|
35
|
-
export {};
|
|
36
|
-
//# sourceMappingURL=all.d.ts.map
|
package/dist/all.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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;AAMzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,KAAK,YAAY,GACb,OAAO,CAAC,YAAY,CAAC,GACrB,oBAAoB,GACpB,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,oBAAoB,CAAC,CAAC;AAkDzD,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,EAAE,eAAe,YAAY,KAAG,IAEjE,CAAC;AAEF,KAAK,gBAAgB,GACjB,OAAO,CAAC,YAAY,CAAC,GACrB,yBAAyB,GACzB,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,yBAAyB,CAAC,CAAC;AA+E9D,eAAO,MAAM,kBAAkB,GAC7B,MAAM,IAAI,EACV,mBAAmB,gBAAgB,KAClC,IAYF,CAAC"}
|
package/dist/all.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* testAll — single-line governance suite for any Topo.
|
|
3
|
-
*
|
|
4
|
-
* Wraps topo validation, example execution, contract checks, and detour
|
|
5
|
-
* contract validation into one describe block.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
import { validateEstablishedTopo, validateTopo } from '@ontrails/core';
|
|
9
|
-
import { createCliHarness } from './harness-cli.js';
|
|
10
|
-
import { createMcpHarness } from './harness-mcp.js';
|
|
11
|
-
import { testContracts } from './contracts.js';
|
|
12
|
-
import { testDetours } from './detours.js';
|
|
13
|
-
import { testExamples } from './examples.js';
|
|
14
|
-
const formatValidationFailure = (error) => {
|
|
15
|
-
const issues = error.context?.issues;
|
|
16
|
-
if (issues === undefined || issues.length === 0) {
|
|
17
|
-
return error.message;
|
|
18
|
-
}
|
|
19
|
-
const details = issues.map((issue) => {
|
|
20
|
-
const id = typeof issue['id'] === 'string' ? issue['id'] : undefined;
|
|
21
|
-
const message = typeof issue['message'] === 'string' ? issue['message'] : undefined;
|
|
22
|
-
const rule = typeof issue['rule'] === 'string' ? issue['rule'] : undefined;
|
|
23
|
-
return [rule, id, message].filter(Boolean).join(': ');
|
|
24
|
-
});
|
|
25
|
-
return [error.message, ...details].join('\n');
|
|
26
|
-
};
|
|
27
|
-
const assertValidTopo = (result) => {
|
|
28
|
-
if (result.isErr()) {
|
|
29
|
-
throw new Error(formatValidationFailure(result.error));
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
const registerGovernanceSuite = (topo, ctxOrFactory, validate) => {
|
|
33
|
-
describe('governance', () => {
|
|
34
|
-
test('topo validates', () => {
|
|
35
|
-
expect(() => assertValidTopo(validate(topo))).not.toThrow();
|
|
36
|
-
});
|
|
37
|
-
// oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
|
|
38
|
-
testExamples(topo, ctxOrFactory);
|
|
39
|
-
// oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
|
|
40
|
-
testContracts(topo, ctxOrFactory);
|
|
41
|
-
// oxlint-disable-next-line jest/require-hook -- these generate describe/test blocks, not setup code
|
|
42
|
-
testDetours(topo);
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
export const testAll = (topo, ctxOrFactory) => {
|
|
46
|
-
registerGovernanceSuite(topo, ctxOrFactory, validateTopo);
|
|
47
|
-
};
|
|
48
|
-
const isEstablishedOptions = (input) => input !== undefined &&
|
|
49
|
-
(Object.hasOwn(input, 'cli') ||
|
|
50
|
-
Object.hasOwn(input, 'createPermit') ||
|
|
51
|
-
Object.hasOwn(input, 'ctx') ||
|
|
52
|
-
Object.hasOwn(input, 'mcp') ||
|
|
53
|
-
Object.hasOwn(input, 'resources') ||
|
|
54
|
-
Object.hasOwn(input, 'strictPermits'));
|
|
55
|
-
const normalizeEstablishedOptions = (input) => isEstablishedOptions(input) ? input : { ctx: input };
|
|
56
|
-
const toExecutionOptions = (options) => ({
|
|
57
|
-
...(options.createPermit === undefined
|
|
58
|
-
? {}
|
|
59
|
-
: { createPermit: options.createPermit }),
|
|
60
|
-
...(options.ctx === undefined ? {} : { ctx: options.ctx }),
|
|
61
|
-
...(options.resources === undefined ? {} : { resources: options.resources }),
|
|
62
|
-
...(options.strictPermits === undefined
|
|
63
|
-
? {}
|
|
64
|
-
: { strictPermits: options.strictPermits }),
|
|
65
|
-
});
|
|
66
|
-
const toCliHarnessOptions = (topo, options) => {
|
|
67
|
-
const cliOptions = {
|
|
68
|
-
graph: topo,
|
|
69
|
-
...options.cli,
|
|
70
|
-
};
|
|
71
|
-
if (options.ctx !== undefined) {
|
|
72
|
-
cliOptions.ctx = options.ctx;
|
|
73
|
-
}
|
|
74
|
-
return cliOptions;
|
|
75
|
-
};
|
|
76
|
-
const toMcpHarnessOptions = (topo, options) => ({
|
|
77
|
-
graph: topo,
|
|
78
|
-
...options.mcp,
|
|
79
|
-
});
|
|
80
|
-
const registerEstablishedSurfaceSuite = (topo, resolveInput) => {
|
|
81
|
-
describe('surfaces', () => {
|
|
82
|
-
test('CLI projection validates established topo', () => {
|
|
83
|
-
const options = normalizeEstablishedOptions(resolveInput());
|
|
84
|
-
expect(() => createCliHarness(toCliHarnessOptions(topo, options))).not.toThrow();
|
|
85
|
-
});
|
|
86
|
-
test('MCP projection validates established topo', () => {
|
|
87
|
-
const options = normalizeEstablishedOptions(resolveInput());
|
|
88
|
-
expect(() => createMcpHarness(toMcpHarnessOptions(topo, options))).not.toThrow();
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
export const testAllEstablished = (topo, optionsOrFactory) => {
|
|
93
|
-
const resolveInput = typeof optionsOrFactory === 'function'
|
|
94
|
-
? optionsOrFactory
|
|
95
|
-
: () => optionsOrFactory;
|
|
96
|
-
registerGovernanceSuite(topo, () => toExecutionOptions(normalizeEstablishedOptions(resolveInput())), validateEstablishedTopo);
|
|
97
|
-
registerEstablishedSurfaceSuite(topo, resolveInput);
|
|
98
|
-
};
|
|
99
|
-
//# sourceMappingURL=all.js.map
|
package/dist/all.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"all.js","sourceRoot":"","sources":["../src/all.ts"],"names":[],"mappings":"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"}
|