@vinkius-core/testing 1.0.0
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/README.md +494 -0
- package/dist/FusionTester.d.ts +98 -0
- package/dist/FusionTester.d.ts.map +1 -0
- package/dist/FusionTester.js +123 -0
- package/dist/FusionTester.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
# @vinkius-core/testing
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<strong>The official test runner for MCP Fusion applications.</strong>
|
|
5
|
+
<br />
|
|
6
|
+
In-memory MVA lifecycle emulator — runs the full execution pipeline without network transport.
|
|
7
|
+
<br /><br />
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@vinkius-core/testing)
|
|
10
|
+
[](package.json)
|
|
11
|
+
[](package.json)
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Every MCP server today is tested with HTTP mocks, raw `JSON.stringify` assertions, and string matching. That's like testing a REST API by reading TCP packets.
|
|
19
|
+
|
|
20
|
+
**MCP Fusion** applications have **five auditable layers** (Zod Validation → Middleware Chain → Handler → Presenter Egress Firewall → System Rules). The `FusionTester` lets you assert each layer independently, in-memory, without starting a server.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────────────────┐
|
|
24
|
+
│ FusionTester │
|
|
25
|
+
│ │
|
|
26
|
+
│ ┌──────────┐ ┌────────────┐ ┌─────────┐ │
|
|
27
|
+
│ │ Zod │──▶│ Middleware │──▶│ Handler │ │
|
|
28
|
+
│ │ Input │ │ Chain │ │ │ │
|
|
29
|
+
│ └──────────┘ └────────────┘ └────┬────┘ │
|
|
30
|
+
│ │ │
|
|
31
|
+
│ ┌────▼────┐ │
|
|
32
|
+
│ │Presenter│ │
|
|
33
|
+
│ │ (Egress │ │
|
|
34
|
+
│ │Firewall)│ │
|
|
35
|
+
│ └────┬────┘ │
|
|
36
|
+
│ │ │
|
|
37
|
+
│ ┌────────────────────────────────────▼──────────────┐ │
|
|
38
|
+
│ │ MvaTestResult │ │
|
|
39
|
+
│ │ ┌──────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ │ │
|
|
40
|
+
│ │ │ data │ │systemRules│ │uiBlocks│ │rawResponse│ │ │
|
|
41
|
+
│ │ └──────┘ └───────────┘ └────────┘ └───────────┘ │ │
|
|
42
|
+
│ └───────────────────────────────────────────────────┘ │
|
|
43
|
+
└─────────────────────────────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### What you can audit
|
|
47
|
+
|
|
48
|
+
| MVA Layer | What FusionTester asserts | SOC2 Relevance |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| **Egress Firewall** | Hidden fields (`passwordHash`, `tenantId`) are physically absent from `result.data` | Data leak prevention |
|
|
51
|
+
| **OOM Guard** | Zod rejects `take: 10000` before it reaches the handler | Memory exhaustion protection |
|
|
52
|
+
| **System Rules** | `result.systemRules` contains the expected domain rules | Deterministic LLM governance |
|
|
53
|
+
| **UI Blocks** | SSR blocks (echarts, summaries) are correctly generated | Agent response quality |
|
|
54
|
+
| **Middleware** | Auth guards block unauthorized calls, isError is true | Access control verification |
|
|
55
|
+
| **Agent Limit** | Collections are truncated at cognitive guardrail bounds | Context window protection |
|
|
56
|
+
| **HATEOAS** | `suggestActions` produces correct next-step affordances | Agent navigation safety |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install @vinkius-core/testing
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Peer Dependencies
|
|
67
|
+
|
|
68
|
+
| Package | Version |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `@vinkius-core/mcp-fusion` | `^2.0.0` |
|
|
71
|
+
| `zod` | `^3.25.1 \|\| ^4.0.0` |
|
|
72
|
+
|
|
73
|
+
> **Zero runtime dependencies.** The package ships only TypeScript types and one class. Your test runner (Vitest, Jest, Mocha, `node:test`) is your choice.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { describe, it, expect } from 'vitest';
|
|
81
|
+
import { createFusionTester } from '@vinkius-core/testing';
|
|
82
|
+
import { registry } from './server/registry.js';
|
|
83
|
+
|
|
84
|
+
const tester = createFusionTester(registry, {
|
|
85
|
+
contextFactory: () => ({
|
|
86
|
+
prisma: mockPrisma,
|
|
87
|
+
tenantId: 't_enterprise_42',
|
|
88
|
+
role: 'ADMIN',
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('User MVA Audit', () => {
|
|
93
|
+
it('Egress Firewall strips sensitive fields', async () => {
|
|
94
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 10 });
|
|
95
|
+
|
|
96
|
+
expect(result.data[0]).not.toHaveProperty('passwordHash');
|
|
97
|
+
expect(result.data[0]).not.toHaveProperty('tenantId');
|
|
98
|
+
expect(result.data[0].email).toBe('ceo@acme.com');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('System rules are injected by Presenter', async () => {
|
|
102
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 5 });
|
|
103
|
+
|
|
104
|
+
expect(result.systemRules).toContain(
|
|
105
|
+
'Data originates from the database via Prisma ORM.'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('OOM Guard rejects unbounded queries', async () => {
|
|
110
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 99999 });
|
|
111
|
+
expect(result.isError).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## API Reference
|
|
119
|
+
|
|
120
|
+
### `createFusionTester(registry, options)`
|
|
121
|
+
|
|
122
|
+
Factory function — creates a `FusionTester` instance.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
function createFusionTester<TContext>(
|
|
126
|
+
registry: ToolRegistry<TContext>,
|
|
127
|
+
options: TesterOptions<TContext>,
|
|
128
|
+
): FusionTester<TContext>;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Parameter | Type | Description |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `registry` | `ToolRegistry<TContext>` | Your application's tool registry — the same one wired to the MCP server |
|
|
134
|
+
| `options` | `TesterOptions<TContext>` | Configuration object |
|
|
135
|
+
|
|
136
|
+
### `TesterOptions<TContext>`
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
interface TesterOptions<TContext> {
|
|
140
|
+
contextFactory: () => TContext | Promise<TContext>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Field | Type | Description |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `contextFactory` | `() => TContext \| Promise<TContext>` | Factory that produces the mock context for each call. Inject fake Prisma, auth tokens, tenant IDs here. Supports async (e.g., DB lookup). |
|
|
147
|
+
|
|
148
|
+
### `tester.callAction(toolName, actionName, args?, overrideContext?)`
|
|
149
|
+
|
|
150
|
+
Executes a single tool action through the **full MVA pipeline** and returns a decomposed result.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
async callAction<TArgs>(
|
|
154
|
+
toolName: string,
|
|
155
|
+
actionName: string,
|
|
156
|
+
args?: TArgs,
|
|
157
|
+
overrideContext?: Partial<TContext>,
|
|
158
|
+
): Promise<MvaTestResult>;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Parameter | Type | Required | Description |
|
|
162
|
+
|---|---|---|---|
|
|
163
|
+
| `toolName` | `string` | ✅ | The registered tool name (e.g. `'db_user'`, `'analytics'`) |
|
|
164
|
+
| `actionName` | `string` | ✅ | The action discriminator (e.g. `'find_many'`, `'create'`) |
|
|
165
|
+
| `args` | `object` | ❌ | Arguments for the action — omit the `action` discriminator, FusionTester injects it |
|
|
166
|
+
| `overrideContext` | `Partial<TContext>` | ❌ | Per-test context overrides. Shallow-merged with `contextFactory()` output |
|
|
167
|
+
|
|
168
|
+
### `MvaTestResult<TData>`
|
|
169
|
+
|
|
170
|
+
Decomposed MVA response — each field maps to a specific pipeline layer.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface MvaTestResult<TData = unknown> {
|
|
174
|
+
data: TData;
|
|
175
|
+
systemRules: readonly string[];
|
|
176
|
+
uiBlocks: readonly unknown[];
|
|
177
|
+
isError: boolean;
|
|
178
|
+
rawResponse: unknown;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Field | Type | Source | Description |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| `data` | `TData` | Presenter Zod schema | Validated data **after** the Egress Firewall. Hidden fields are physically absent. |
|
|
185
|
+
| `systemRules` | `string[]` | Presenter `.systemRules()` | JIT domain rules injected by the Presenter. Empty array if no Presenter. |
|
|
186
|
+
| `uiBlocks` | `unknown[]` | Presenter `.uiBlocks()` / `.collectionUiBlocks()` | SSR UI blocks (charts, summaries, markdown). Empty array if no Presenter. |
|
|
187
|
+
| `isError` | `boolean` | Pipeline | `true` if Zod rejected the input, middleware blocked, or handler returned `error()`. |
|
|
188
|
+
| `rawResponse` | `unknown` | Pipeline | The raw MCP `ToolResponse` for protocol-level inspection. |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Cookbook
|
|
193
|
+
|
|
194
|
+
### Egress Firewall Audit
|
|
195
|
+
|
|
196
|
+
Verify that the Presenter's Zod schema physically strips sensitive fields:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
it('strips PII from response', async () => {
|
|
200
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 5 });
|
|
201
|
+
|
|
202
|
+
const users = result.data as Array<Record<string, unknown>>;
|
|
203
|
+
for (const user of users) {
|
|
204
|
+
expect(user).not.toHaveProperty('passwordHash');
|
|
205
|
+
expect(user).not.toHaveProperty('tenantId');
|
|
206
|
+
expect(user).not.toHaveProperty('internalFlags');
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### OOM Guard (Input Validation)
|
|
212
|
+
|
|
213
|
+
Verify that Zod boundaries reject out-of-range values:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
it('rejects take > 50 (OOM Guard)', async () => {
|
|
217
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 10000 });
|
|
218
|
+
expect(result.isError).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('rejects non-integer take', async () => {
|
|
222
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 3.14 });
|
|
223
|
+
expect(result.isError).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('rejects invalid email format', async () => {
|
|
227
|
+
const result = await tester.callAction('db_user', 'create', {
|
|
228
|
+
email: 'not-an-email', name: 'Test',
|
|
229
|
+
});
|
|
230
|
+
expect(result.isError).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Agent Limit (Cognitive Guardrail)
|
|
235
|
+
|
|
236
|
+
Verify that collections are truncated to prevent context window exhaustion:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
it('truncates at agentLimit', async () => {
|
|
240
|
+
// Handler returns 100 items, Presenter has agentLimit(20)
|
|
241
|
+
const result = await tester.callAction('analytics', 'list', { limit: 100 });
|
|
242
|
+
expect((result.data as any[]).length).toBe(20);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('shows truncation warning in UI blocks', async () => {
|
|
246
|
+
const result = await tester.callAction('analytics', 'list', { limit: 100 });
|
|
247
|
+
const warning = result.uiBlocks.find((b: any) => b.content?.includes('Truncated'));
|
|
248
|
+
expect(warning).toBeDefined();
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Middleware Guards (RBAC)
|
|
253
|
+
|
|
254
|
+
Test authentication and authorization middleware using `overrideContext`:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
it('blocks GUEST role', async () => {
|
|
258
|
+
const result = await tester.callAction(
|
|
259
|
+
'db_user', 'find_many', { take: 5 },
|
|
260
|
+
{ role: 'GUEST' },
|
|
261
|
+
);
|
|
262
|
+
expect(result.isError).toBe(true);
|
|
263
|
+
expect(result.data).toContain('Unauthorized');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('allows ADMIN role', async () => {
|
|
267
|
+
const result = await tester.callAction(
|
|
268
|
+
'db_user', 'find_many', { take: 5 },
|
|
269
|
+
{ role: 'ADMIN' },
|
|
270
|
+
);
|
|
271
|
+
expect(result.isError).toBe(false);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('blocks across all actions (not just find_many)', async () => {
|
|
275
|
+
const result = await tester.callAction(
|
|
276
|
+
'db_user', 'create', { email: 'test@co.com', name: 'Test' },
|
|
277
|
+
{ role: 'VIEWER' },
|
|
278
|
+
);
|
|
279
|
+
expect(result.isError).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### System Rules Verification
|
|
284
|
+
|
|
285
|
+
Assert that the LLM receives correct domain directives:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
it('injects domain rules from Presenter', async () => {
|
|
289
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 1 });
|
|
290
|
+
|
|
291
|
+
expect(result.systemRules).toContain(
|
|
292
|
+
'Data originates from the database via Prisma ORM.'
|
|
293
|
+
);
|
|
294
|
+
expect(result.systemRules).toContain(
|
|
295
|
+
'Email addresses are PII. Mask when possible.'
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('injects contextual rules based on role', async () => {
|
|
300
|
+
const result = await tester.callAction(
|
|
301
|
+
'analytics', 'list', { limit: 1 },
|
|
302
|
+
{ role: 'ADMIN' },
|
|
303
|
+
);
|
|
304
|
+
expect(result.systemRules).toContain('User is ADMIN. Show full details.');
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### UI Block Inspection
|
|
309
|
+
|
|
310
|
+
Verify SSR blocks are generated for client rendering:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
it('generates collection summary', async () => {
|
|
314
|
+
const result = await tester.callAction('analytics', 'list', { limit: 5 });
|
|
315
|
+
|
|
316
|
+
const summary = result.uiBlocks.find((b: any) => b.type === 'summary') as any;
|
|
317
|
+
expect(summary).toBeDefined();
|
|
318
|
+
expect(summary.content).toContain('Total:');
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Manual `response()` Builder
|
|
323
|
+
|
|
324
|
+
Test actions that use the `response()` builder directly (without a Presenter):
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
it('extracts rules from manual builder', async () => {
|
|
328
|
+
const result = await tester.callAction('system', 'health');
|
|
329
|
+
|
|
330
|
+
expect(result.systemRules).toContain('System is operational.');
|
|
331
|
+
expect(result.data).toEqual({ status: 'healthy', uptime: 42 });
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Context Override Isolation
|
|
336
|
+
|
|
337
|
+
Verify that overrides don't leak between test calls:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
it('isolates context between sequential calls', async () => {
|
|
341
|
+
// Call 1 — GUEST (blocked)
|
|
342
|
+
const r1 = await tester.callAction('db_user', 'find_many', { take: 1 }, { role: 'GUEST' });
|
|
343
|
+
|
|
344
|
+
// Call 2 — default ADMIN (succeeds)
|
|
345
|
+
const r2 = await tester.callAction('db_user', 'find_many', { take: 1 });
|
|
346
|
+
|
|
347
|
+
expect(r1.isError).toBe(true);
|
|
348
|
+
expect(r2.isError).toBe(false);
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Async Context Factory
|
|
353
|
+
|
|
354
|
+
Simulate async DI resolution (e.g., reading JWT from a database):
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const tester = createFusionTester(registry, {
|
|
358
|
+
contextFactory: async () => {
|
|
359
|
+
const token = await fetchTestToken();
|
|
360
|
+
return {
|
|
361
|
+
prisma: mockPrisma,
|
|
362
|
+
tenantId: token.tenantId,
|
|
363
|
+
role: token.role,
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Protocol-Level Inspection
|
|
370
|
+
|
|
371
|
+
Inspect the raw MCP `ToolResponse` for transport-level assertions:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
it('raw response follows MCP shape', async () => {
|
|
375
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 1 });
|
|
376
|
+
const raw = result.rawResponse as { content: Array<{ type: string; text: string }> };
|
|
377
|
+
|
|
378
|
+
expect(raw.content).toBeInstanceOf(Array);
|
|
379
|
+
expect(raw.content[0].type).toBe('text');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('Symbol metadata is invisible to JSON.stringify', async () => {
|
|
383
|
+
const result = await tester.callAction('db_user', 'find_many', { take: 1 });
|
|
384
|
+
const json = JSON.stringify(result.rawResponse);
|
|
385
|
+
|
|
386
|
+
// Transport layer never sees MVA metadata
|
|
387
|
+
expect(json).not.toContain('systemRules');
|
|
388
|
+
expect(json).not.toContain('mva-meta');
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Architecture
|
|
395
|
+
|
|
396
|
+
### The Symbol Backdoor
|
|
397
|
+
|
|
398
|
+
The `FusionTester` runs the **real** execution pipeline — the exact same code path as your production MCP server:
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
ToolRegistry.routeCall()
|
|
402
|
+
→ Concurrency Semaphore
|
|
403
|
+
→ Discriminator Parsing
|
|
404
|
+
→ Zod Input Validation
|
|
405
|
+
→ Compiled Middleware Chain
|
|
406
|
+
→ Handler Execution
|
|
407
|
+
→ PostProcessor (Presenter auto-application)
|
|
408
|
+
→ Egress Guard
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The key insight: `ResponseBuilder.build()` attaches structured MVA metadata via a **global Symbol** (`MVA_META_SYMBOL`). Symbols are ignored by `JSON.stringify`, so the MCP transport never sees them — but the `FusionTester` reads them in RAM.
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// What the MCP transport sees (JSON.stringify):
|
|
415
|
+
{ "content": [{ "type": "text", "text": "{...}" }] }
|
|
416
|
+
|
|
417
|
+
// What FusionTester reads (Symbol key):
|
|
418
|
+
response[Symbol.for('mcp-fusion.mva-meta')] = {
|
|
419
|
+
data: { id: '1', name: 'Alice', email: 'alice@acme.com' },
|
|
420
|
+
systemRules: ['Data from Prisma ORM...'],
|
|
421
|
+
uiBlocks: [{ type: 'summary', content: 'User: Alice' }],
|
|
422
|
+
};
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**No XML regex. No string parsing. Zero coupling to response formatting.**
|
|
426
|
+
|
|
427
|
+
### Why not reimplement the pipeline?
|
|
428
|
+
|
|
429
|
+
The tester calls `ToolRegistry.routeCall()` — the same function your production server uses. If we reimplemented the pipeline, tests would pass in the tester but fail in production. Full fidelity means:
|
|
430
|
+
|
|
431
|
+
- ✅ Concurrency semaphore limits
|
|
432
|
+
- ✅ Mutation serialization
|
|
433
|
+
- ✅ Abort signal propagation
|
|
434
|
+
- ✅ Egress payload guards
|
|
435
|
+
- ✅ Zod error formatting
|
|
436
|
+
- ✅ Generator result draining
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## MVA Convention: `tests/` Layer
|
|
441
|
+
|
|
442
|
+
The testing package introduces a fourth layer to the MVA convention:
|
|
443
|
+
|
|
444
|
+
```text
|
|
445
|
+
src/
|
|
446
|
+
├── models/ ← M — Zod schemas
|
|
447
|
+
├── views/ ← V — Presenters
|
|
448
|
+
├── agents/ ← A — MCP tool definitions
|
|
449
|
+
├── index.ts ← Registry barrel
|
|
450
|
+
└── server.ts ← Server bootstrap
|
|
451
|
+
tests/
|
|
452
|
+
├── firewall/ ← Egress Firewall assertions
|
|
453
|
+
├── guards/ ← Middleware & OOM Guard tests
|
|
454
|
+
├── rules/ ← System Rules verification
|
|
455
|
+
└── setup.ts ← Shared tester instance
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Recommended `setup.ts`
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// tests/setup.ts
|
|
462
|
+
import { createFusionTester } from '@vinkius-core/testing';
|
|
463
|
+
import { registry } from '../src/index.js';
|
|
464
|
+
|
|
465
|
+
export const tester = createFusionTester(registry, {
|
|
466
|
+
contextFactory: () => ({
|
|
467
|
+
prisma: mockPrisma,
|
|
468
|
+
tenantId: 't_test',
|
|
469
|
+
role: 'ADMIN',
|
|
470
|
+
}),
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### File Naming
|
|
475
|
+
|
|
476
|
+
| Directory | Suffix | What it tests |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| `tests/firewall/` | `.firewall.test.ts` | Presenter Zod filtering (PII, hidden fields) |
|
|
479
|
+
| `tests/guards/` | `.guard.test.ts` | Middleware RBAC, OOM limits, input validation |
|
|
480
|
+
| `tests/rules/` | `.rules.test.ts` | System rules injection, contextual rules |
|
|
481
|
+
| `tests/blocks/` | `.blocks.test.ts` | UI blocks, collection summaries, truncation warnings |
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Requirements
|
|
486
|
+
|
|
487
|
+
- Node.js 18+
|
|
488
|
+
- TypeScript 5.7+
|
|
489
|
+
- `@vinkius-core/mcp-fusion ^2.0.0`
|
|
490
|
+
- `zod ^3.25.1 || ^4.0.0`
|
|
491
|
+
|
|
492
|
+
## Documentation
|
|
493
|
+
|
|
494
|
+
Full docs: **[vinkius-labs.github.io/mcp-fusion](https://vinkius-labs.github.io/mcp-fusion/)**.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FusionTester — In-Memory MVA Lifecycle Emulator
|
|
3
|
+
*
|
|
4
|
+
* Runs the **real** MCP Fusion execution pipeline in RAM:
|
|
5
|
+
* Zod Input Validation → Middleware Chain → Handler → PostProcessor → Egress Firewall
|
|
6
|
+
*
|
|
7
|
+
* Decomposes the `ToolResponse` into structured `MvaTestResult` objects
|
|
8
|
+
* using the Symbol Backdoor (`MVA_META_SYMBOL`) — zero XML parsing, zero regex.
|
|
9
|
+
*
|
|
10
|
+
* **Zero coupling to test runners.** Returns plain JS objects. Use with
|
|
11
|
+
* Vitest, Jest, Mocha, or Node's native `node:test`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { createFusionTester } from '@vinkius-core/testing';
|
|
16
|
+
* import { registry } from '../src/server/registry.js';
|
|
17
|
+
*
|
|
18
|
+
* const tester = createFusionTester(registry, {
|
|
19
|
+
* contextFactory: () => ({
|
|
20
|
+
* prisma: mockPrisma,
|
|
21
|
+
* tenantId: 't_777',
|
|
22
|
+
* }),
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const result = await tester.callAction('db_user', 'find_many', { take: 10 });
|
|
26
|
+
*
|
|
27
|
+
* expect(result.data[0]).not.toHaveProperty('passwordHash');
|
|
28
|
+
* expect(result.systemRules).toContain('Data originates from the database via Prisma ORM.');
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
import type { ToolRegistry } from '@vinkius-core/mcp-fusion';
|
|
34
|
+
import type { TesterOptions, MvaTestResult } from './types.js';
|
|
35
|
+
/**
|
|
36
|
+
* In-memory MVA lifecycle emulator.
|
|
37
|
+
*
|
|
38
|
+
* Delegates to `ToolRegistry.routeCall()` for full pipeline fidelity,
|
|
39
|
+
* then extracts structured MVA layers via the Symbol Backdoor.
|
|
40
|
+
*
|
|
41
|
+
* @typeParam TContext - Application context type (matches your ToolRegistry)
|
|
42
|
+
*/
|
|
43
|
+
export declare class FusionTester<TContext> {
|
|
44
|
+
private readonly registry;
|
|
45
|
+
private readonly options;
|
|
46
|
+
constructor(registry: ToolRegistry<TContext>, options: TesterOptions<TContext>);
|
|
47
|
+
/**
|
|
48
|
+
* Execute a tool action through the full MVA pipeline in-memory.
|
|
49
|
+
*
|
|
50
|
+
* @param toolName - The registered tool name (e.g. `'db_user'`)
|
|
51
|
+
* @param actionName - The action discriminator (e.g. `'find_many'`)
|
|
52
|
+
* @param args - Arguments for the action (excluding the discriminator)
|
|
53
|
+
* @param overrideContext - Partial context overrides for this specific test
|
|
54
|
+
* (e.g. `{ role: 'GUEST' }` to simulate a different JWT)
|
|
55
|
+
* @returns Decomposed MVA result with `data`, `systemRules`, `uiBlocks`, `isError`
|
|
56
|
+
*
|
|
57
|
+
* @throws {Error} If Zod input validation rejects the args (the ZodError propagates)
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // Egress Firewall test
|
|
62
|
+
* const result = await tester.callAction('db_user', 'find_many', { take: 10 });
|
|
63
|
+
* expect(result.data[0]).not.toHaveProperty('passwordHash');
|
|
64
|
+
*
|
|
65
|
+
* // OOM Guard test — Zod rejects take > 50
|
|
66
|
+
* await expect(
|
|
67
|
+
* tester.callAction('db_user', 'find_many', { take: 10000 })
|
|
68
|
+
* ).rejects.toThrow();
|
|
69
|
+
*
|
|
70
|
+
* // Middleware test via overrideContext
|
|
71
|
+
* const result = await tester.callAction('db_user', 'create',
|
|
72
|
+
* { email: 'test@co.com' },
|
|
73
|
+
* { role: 'GUEST' }
|
|
74
|
+
* );
|
|
75
|
+
* expect(result.isError).toBe(true);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
callAction<TArgs = Record<string, unknown>>(toolName: string, actionName: string, args?: TArgs, overrideContext?: Partial<TContext>): Promise<MvaTestResult>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a FusionTester for the given registry.
|
|
82
|
+
*
|
|
83
|
+
* @param registry - The application's ToolRegistry instance
|
|
84
|
+
* @param options - Context factory and configuration
|
|
85
|
+
* @returns A new FusionTester instance
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const tester = createFusionTester(registry, {
|
|
90
|
+
* contextFactory: () => ({
|
|
91
|
+
* prisma: mockPrisma,
|
|
92
|
+
* tenantId: 't_enterprise_42',
|
|
93
|
+
* }),
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function createFusionTester<TContext>(registry: ToolRegistry<TContext>, options: TesterOptions<TContext>): FusionTester<TContext>;
|
|
98
|
+
//# sourceMappingURL=FusionTester.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FusionTester.d.ts","sourceRoot":"","sources":["../src/FusionTester.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI/D;;;;;;;GAOG;AACH,qBAAa,YAAY,CAAC,QAAQ;IAE1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,EAChC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC;IAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACG,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,KAAK,EACZ,eAAe,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GACpC,OAAO,CAAC,aAAa,CAAC;CAsD5B;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EACvC,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,EAChC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,GACjC,YAAY,CAAC,QAAQ,CAAC,CAExB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { MVA_META_SYMBOL } from '@vinkius-core/mcp-fusion';
|
|
2
|
+
// ── FusionTester Class ───────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* In-memory MVA lifecycle emulator.
|
|
5
|
+
*
|
|
6
|
+
* Delegates to `ToolRegistry.routeCall()` for full pipeline fidelity,
|
|
7
|
+
* then extracts structured MVA layers via the Symbol Backdoor.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam TContext - Application context type (matches your ToolRegistry)
|
|
10
|
+
*/
|
|
11
|
+
export class FusionTester {
|
|
12
|
+
registry;
|
|
13
|
+
options;
|
|
14
|
+
constructor(registry, options) {
|
|
15
|
+
this.registry = registry;
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Execute a tool action through the full MVA pipeline in-memory.
|
|
20
|
+
*
|
|
21
|
+
* @param toolName - The registered tool name (e.g. `'db_user'`)
|
|
22
|
+
* @param actionName - The action discriminator (e.g. `'find_many'`)
|
|
23
|
+
* @param args - Arguments for the action (excluding the discriminator)
|
|
24
|
+
* @param overrideContext - Partial context overrides for this specific test
|
|
25
|
+
* (e.g. `{ role: 'GUEST' }` to simulate a different JWT)
|
|
26
|
+
* @returns Decomposed MVA result with `data`, `systemRules`, `uiBlocks`, `isError`
|
|
27
|
+
*
|
|
28
|
+
* @throws {Error} If Zod input validation rejects the args (the ZodError propagates)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Egress Firewall test
|
|
33
|
+
* const result = await tester.callAction('db_user', 'find_many', { take: 10 });
|
|
34
|
+
* expect(result.data[0]).not.toHaveProperty('passwordHash');
|
|
35
|
+
*
|
|
36
|
+
* // OOM Guard test — Zod rejects take > 50
|
|
37
|
+
* await expect(
|
|
38
|
+
* tester.callAction('db_user', 'find_many', { take: 10000 })
|
|
39
|
+
* ).rejects.toThrow();
|
|
40
|
+
*
|
|
41
|
+
* // Middleware test via overrideContext
|
|
42
|
+
* const result = await tester.callAction('db_user', 'create',
|
|
43
|
+
* { email: 'test@co.com' },
|
|
44
|
+
* { role: 'GUEST' }
|
|
45
|
+
* );
|
|
46
|
+
* expect(result.isError).toBe(true);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
async callAction(toolName, actionName, args, overrideContext) {
|
|
50
|
+
// 1. Context Hydration
|
|
51
|
+
const baseContext = await this.options.contextFactory();
|
|
52
|
+
const ctx = overrideContext
|
|
53
|
+
? { ...baseContext, ...overrideContext }
|
|
54
|
+
: baseContext;
|
|
55
|
+
// 2. Build args with discriminator
|
|
56
|
+
const builtArgs = {
|
|
57
|
+
action: actionName,
|
|
58
|
+
...(args || {}),
|
|
59
|
+
};
|
|
60
|
+
// 3. Run the REAL pipeline (validation → middleware → handler → presenter → egress)
|
|
61
|
+
const rawResponse = await this.registry.routeCall(ctx, toolName, builtArgs);
|
|
62
|
+
// 4. Error path — no MVA meta on error responses
|
|
63
|
+
if (rawResponse.isError) {
|
|
64
|
+
const errorText = rawResponse.content?.[0]?.text ?? 'Unknown error';
|
|
65
|
+
return {
|
|
66
|
+
data: errorText,
|
|
67
|
+
systemRules: [],
|
|
68
|
+
uiBlocks: [],
|
|
69
|
+
isError: true,
|
|
70
|
+
rawResponse,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// 5. Extract MVA Meta via Symbol Backdoor
|
|
74
|
+
const meta = rawResponse[MVA_META_SYMBOL];
|
|
75
|
+
if (meta) {
|
|
76
|
+
return {
|
|
77
|
+
data: meta.data,
|
|
78
|
+
systemRules: [...meta.systemRules],
|
|
79
|
+
uiBlocks: [...meta.uiBlocks],
|
|
80
|
+
isError: false,
|
|
81
|
+
rawResponse,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// 6. Fallback — tool without Presenter (raw data, no MVA layers)
|
|
85
|
+
const rawText = rawResponse.content?.[0]?.text ?? '';
|
|
86
|
+
let parsedData;
|
|
87
|
+
try {
|
|
88
|
+
parsedData = JSON.parse(rawText);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
parsedData = rawText;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
data: parsedData,
|
|
95
|
+
systemRules: [],
|
|
96
|
+
uiBlocks: [],
|
|
97
|
+
isError: false,
|
|
98
|
+
rawResponse,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// ── Factory ──────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Create a FusionTester for the given registry.
|
|
105
|
+
*
|
|
106
|
+
* @param registry - The application's ToolRegistry instance
|
|
107
|
+
* @param options - Context factory and configuration
|
|
108
|
+
* @returns A new FusionTester instance
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const tester = createFusionTester(registry, {
|
|
113
|
+
* contextFactory: () => ({
|
|
114
|
+
* prisma: mockPrisma,
|
|
115
|
+
* tenantId: 't_enterprise_42',
|
|
116
|
+
* }),
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function createFusionTester(registry, options) {
|
|
121
|
+
return new FusionTester(registry, options);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=FusionTester.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FusionTester.js","sourceRoot":"","sources":["../src/FusionTester.ts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAI3D,4DAA4D;AAE5D;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IAEA;IACA;IAFrB,YACqB,QAAgC,EAChC,OAAgC;QADhC,aAAQ,GAAR,QAAQ,CAAwB;QAChC,YAAO,GAAP,OAAO,CAAyB;IAClD,CAAC;IAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,KAAK,CAAC,UAAU,CACZ,QAAgB,EAChB,UAAkB,EAClB,IAAY,EACZ,eAAmC;QAEnC,uBAAuB;QACvB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG,eAAe;YACvB,CAAC,CAAC,EAAE,GAAG,WAAW,EAAE,GAAG,eAAe,EAAc;YACpD,CAAC,CAAC,WAAW,CAAC;QAElB,mCAAmC;QACnC,MAAM,SAAS,GAA4B;YACvC,MAAM,EAAE,UAAU;YAClB,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;SAClB,CAAC;QAEF,oFAAoF;QACpF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAE5E,iDAAiD;QACjD,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,eAAe,CAAC;YACpE,OAAO;gBACH,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,EAAE;gBACf,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,IAAI;gBACb,WAAW;aACd,CAAC;QACN,CAAC;QAED,0CAA0C;QAC1C,MAAM,IAAI,GAAI,WAAkD,CAAC,eAAe,CAAwB,CAAC;QAEzG,IAAI,IAAI,EAAE,CAAC;YACP,OAAO;gBACH,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;gBAClC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;gBAC5B,OAAO,EAAE,KAAK;gBACd,WAAW;aACd,CAAC;QACN,CAAC;QAED,iEAAiE;QACjE,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QACrD,IAAI,UAAmB,CAAC;QACxB,IAAI,CAAC;YAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,UAAU,GAAG,OAAO,CAAC;QAAC,CAAC;QAEzE,OAAO;YACH,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,EAAE;YACf,QAAQ,EAAE,EAAE;YACZ,OAAO,EAAE,KAAK;YACd,WAAW;SACd,CAAC;IACN,CAAC;CACJ;AAED,4DAA4D;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAC9B,QAAgC,EAChC,OAAgC;IAEhC,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vinkius-core/testing — Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Public API for the in-memory MVA lifecycle emulator.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
export { FusionTester, createFusionTester } from './FusionTester.js';
|
|
9
|
+
export type { TesterOptions, MvaTestResult } from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MVA Test Result Types — Structured Response Decomposition
|
|
3
|
+
*
|
|
4
|
+
* Zero coupling to any test runner. Returns plain JS/TS objects
|
|
5
|
+
* that any framework (Vitest, Jest, Mocha, node:test) can assert against.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for the FusionTester.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam TContext - Application context type (same as your ToolRegistry)
|
|
13
|
+
*/
|
|
14
|
+
export interface TesterOptions<TContext> {
|
|
15
|
+
/**
|
|
16
|
+
* Factory that produces the mock context for each test invocation.
|
|
17
|
+
* Inject fake Prisma, fake JWT, fake tenantId here.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* contextFactory: () => ({
|
|
22
|
+
* prisma: mockPrisma,
|
|
23
|
+
* tenantId: 't_777',
|
|
24
|
+
* auth: { role: 'ADMIN' },
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
contextFactory: () => TContext | Promise<TContext>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decomposed MVA response from the FusionTester.
|
|
32
|
+
*
|
|
33
|
+
* Each field maps to a specific MVA layer, allowing QA to assert
|
|
34
|
+
* each pillar independently without parsing XML or JSON strings.
|
|
35
|
+
*
|
|
36
|
+
* @typeParam TData - The validated data type (post-Egress Firewall)
|
|
37
|
+
*/
|
|
38
|
+
export interface MvaTestResult<TData = unknown> {
|
|
39
|
+
/**
|
|
40
|
+
* Validated data AFTER the Egress Firewall (Presenter Zod schema).
|
|
41
|
+
* Hidden fields (`@fusion.hide`) are physically absent here.
|
|
42
|
+
*/
|
|
43
|
+
data: TData;
|
|
44
|
+
/**
|
|
45
|
+
* JIT system rules from the Presenter's `.systemRules()`.
|
|
46
|
+
* The LLM reads these as domain-level directives.
|
|
47
|
+
*/
|
|
48
|
+
systemRules: readonly string[];
|
|
49
|
+
/**
|
|
50
|
+
* SSR UI blocks from the Presenter's `.uiBlocks()` / `.collectionUiBlocks()`.
|
|
51
|
+
* Contains echarts configs, markdown blocks, summary strings, etc.
|
|
52
|
+
*/
|
|
53
|
+
uiBlocks: readonly unknown[];
|
|
54
|
+
/**
|
|
55
|
+
* `true` if the pipeline returned an error response (`isError: true`).
|
|
56
|
+
* Useful for asserting OOM guard rejections, middleware blocks, and handler errors.
|
|
57
|
+
*/
|
|
58
|
+
isError: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The raw MCP `ToolResponse` from the pipeline.
|
|
61
|
+
* For protocol-level inspection (content blocks, XML formatting, etc.)
|
|
62
|
+
*/
|
|
63
|
+
rawResponse: unknown;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,QAAQ;IACnC;;;;;;;;;;;;OAYG;IACH,cAAc,EAAE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,OAAO;IAC1C;;;OAGG;IACH,IAAI,EAAE,KAAK,CAAC;IAEZ;;;OAGG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAE/B;;;OAGG;IACH,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAE7B;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,WAAW,EAAE,OAAO,CAAC;CACxB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MVA Test Result Types — Structured Response Decomposition
|
|
3
|
+
*
|
|
4
|
+
* Zero coupling to any test runner. Returns plain JS/TS objects
|
|
5
|
+
* that any framework (Vitest, Jest, Mocha, node:test) can assert against.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vinkius-core/testing",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "In-memory MVA lifecycle emulator for MCP Fusion. Runs the full pipeline (Zod Input → Middlewares → Handler → Egress Firewall) without network transport. Returns structured MvaTestResult objects — zero coupling to Jest/Vitest.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"mcp-fusion",
|
|
22
|
+
"testing",
|
|
23
|
+
"mva",
|
|
24
|
+
"emulator",
|
|
25
|
+
"ai",
|
|
26
|
+
"llm",
|
|
27
|
+
"soc2",
|
|
28
|
+
"compliance"
|
|
29
|
+
],
|
|
30
|
+
"author": "Vinkius Labs",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/vinkius-labs/mcp-fusion.git",
|
|
34
|
+
"directory": "packages/testing"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/vinkius-labs/mcp-fusion/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://vinkius-labs.github.io/mcp-fusion/",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@vinkius-core/mcp-fusion": "^2.0.0",
|
|
52
|
+
"zod": "^3.25.1 || ^4.0.0"
|
|
53
|
+
},
|
|
54
|
+
"license": "Apache-2.0"
|
|
55
|
+
}
|