Skip to content

Testing

handover uses Vitest for unit tests and a small set of stable patterns for mocking providers and filesystem behavior. This guide documents those patterns so new tests are consistent and maintainable.

Run the full unit test suite:

Terminal window
npm test

Run coverage locally:

Terminal window
npm test -- --coverage

Run a single test file:

Terminal window
npm test -- src/auth/token-store.test.ts

Use createMockProvider() from src/providers/__mocks__/index.ts for round/provider unit tests.

Why this pattern exists:

  • real providers require API keys and network access
  • provider constructors can pull in SDK-specific runtime behavior
  • unit tests need deterministic, fully isolated provider behavior

The helper returns a type-checked LLMProvider shape with vi.fn() defaults for complete, estimateTokens, and maxContextTokens.

Example:

import { vi } from 'vitest';
import { createMockProvider } from '../providers/__mocks__/index.js';
const provider = createMockProvider();
(provider.complete as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
data: {},
usage: {
inputTokens: 100,
outputTokens: 50,
cacheReadTokens: 0,
cacheCreationTokens: 0,
},
model: 'mock',
duration: 0,
});

You can override defaults per test:

const provider = createMockProvider({ name: 'custom-mock' });

Use memfs when tests need file I/O without touching the real machine.

Canonical example: src/auth/token-store.test.ts.

Pattern:

import { vol } from 'memfs';
vi.mock('node:fs/promises', async () => {
const memfs = await import('memfs');
return memfs.fs.promises;
});
vi.mock('node:fs', async () => {
const memfs = await import('memfs');
return memfs.fs;
});
beforeEach(() => {
vol.reset();
});
vol.fromJSON({
'/mock-home/.handover/credentials.json': JSON.stringify({ token: 'abc' }),
});

This keeps tests fast, isolated, and deterministic.

Coverage config lives in vitest.config.ts under coverage.exclude.

Policy rules:

  • The exclusion list is FROZEN (see Last frozen: 2026-03-01 comment).
  • Every excluded path must include a written justification comment.
  • Do not enable thresholds.autoUpdate.
  • Do not add new exclusions without explicit rationale.

Current thresholds:

  • lines: 90%
  • functions: 90%
  • statements: 90%
  • branches: 85%

Common exclusion categories include CLI entrypoints, provider SDK wrappers, semantic search runtime modules, MCP server runtime, and analyzer/pipeline integration surfaces.

Notably, some MCP files are intentionally testable and stay included: src/mcp/tools.ts, src/mcp/errors.ts, and src/mcp/http-security.ts.