Skip to content

Testing

@vurb/testing runs the full execution pipeline in RAM — same code path as production — and returns structured MvaTestResult objects with each MVA layer decomposed into its own field. Zero tokens, zero servers, deterministic on every CI run.

bash
npm install @vurb/testing

Works with Vitest, Jest, Mocha, or node:test. The tester returns plain objects — your runner, your choice. Ideal for validating hand-written tools and auto-generated tools from @vurb/openapi-gen or @vurb/prisma-gen.

Create a Tester

typescript
import { createVurbTester } from '@vurb/testing';
import { registry } from './server/registry.js';

const tester = createVurbTester(registry, {
  contextFactory: () => ({
    prisma: mockPrisma,
    tenantId: 't_enterprise_42',
    role: 'ADMIN',
  }),
});

createVurbTester wraps your real ToolRegistry and calls routeCall() — the same function production uses. No pipeline reimplementation, no mock transport.

Assert Every MVA Layer

typescript
import { describe, it, expect } from 'vitest';

describe('SOC2 Data Governance', () => {
  it('strips PII before it reaches the LLM', async () => {
    const result = await tester.callAction('db_user', 'find_many', { take: 10 });

    for (const user of result.data) {
      expect(user).not.toHaveProperty('passwordHash');
      expect(user).not.toHaveProperty('tenantId');
    }
  });

  it('rejects unbounded queries', async () => {
    const result = await tester.callAction('db_user', 'find_many', { take: 99999 });
    expect(result.isError).toBe(true);
  });

  it('sends governance rules with data', async () => {
    const result = await tester.callAction('db_user', 'find_many', { take: 5 });
    expect(result.systemRules).toContain('Email addresses are PII. Mask when possible.');
  });

  it('blocks guest access', async () => {
    const result = await tester.callAction(
      'db_user', 'find_many', { take: 5 },
      { role: 'GUEST' },
    );
    expect(result.isError).toBe(true);
  });
});

Four tests, 8 ms, zero tokens.

What MvaTestResult Exposes

FieldWhat you assertCompliance mapping
result.dataPresenter schema stripped undeclared fieldsSOC2 CC6.1 — data leak prevention
result.isErrorMiddleware rejected the requestSOC2 CC6.3 — access control
result.systemRulesDomain directives present in responseContext governance
result.uiBlocksServer-rendered charts and summaries correctResponse quality
result.data.lengthagentLimit capped the collectionContext window protection
rawResponse<action_suggestions> HATEOAS hints presentAgent navigation

How It Works

ResponseBuilder.build() attaches MVA metadata via Symbol.for('Vurb.ts.mva-meta'). Symbols are invisible to JSON.stringify, so the MCP transport never sees them — but VurbTester reads them in RAM:

typescript
// MCP transport sees:
{ "content": [{ "type": "text", "text": "<data>...</data>" }] }

// VurbTester reads (Symbol key):
response[Symbol.for('Vurb.ts.mva-meta')] = {
  data: { id: '1', name: 'Alice', email: 'alice@acme.com' },
  rules: ['Data from Prisma ORM. Do not infer outside this response.'],
  ui: [{ type: 'summary', content: 'User: Alice (alice@acme.com)' }],
};

The tester exercises the full pipeline — Zod validation, compiled middleware chain, concurrency semaphore, mutation serialization, abort signal propagation, egress guards, agent limit truncation, and HATEOAS suggestions.

Guides

GuideDescription
Quick StartBuild your first VurbTester in 5 minutes
Egress FirewallAudit PII stripping and field-level security
System RulesVerify LLM governance directives
UI BlocksAssert SSR blocks, charts, and cognitive guardrails
Middleware GuardsTest RBAC, auth gates, and context derivation
OOM GuardValidate Zod input boundaries and agent limits
Error HandlingAssert isError, error messages, empty MVA layers
Raw ResponseProtocol-level MCP transport inspection
Conventiontests/ folder structure in the MVA convention