Six new unit suites (102 tests) for the highest-risk untested libs: userDataCleanup (destructive account/project deletes), documentVersions (document integrity), chat/citations (legal-citation parsing), safeError (secret redaction), llm/models (model resolution), and userLookup (profile email sync). Coverage floors ratchet up 2/2/4/2 -> 11/10/14/10 (measured 11.18/10.98/14.43/10.91), and docs/testing-coverage.md gives contributors a prioritized backlog for the remaining untested libs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.3 KiB
Backend unit-test coverage
The backend has a Vitest unit-test harness over backend/src/lib/**. This doc
tracks what is covered, what still needs tests, and how the coverage ratchet
works — so you can pick up a checkbox below and land it as a small PR.
Running the tests
cd backend
npm install
npm test # run all unit tests
npm run test:coverage # same, plus the per-file coverage table + floor check
Tests live in backend/src/lib/__tests__/*.test.ts. Read a couple of the
existing suites first (access.test.ts, userDataCleanup.test.ts) and match
their conventions: plain in-memory Supabase query mocks (no network, no real
database), one describe block per exported function, and tests that assert
current behavior.
Current coverage (measured 2026-07)
Per-area statement coverage from npm run test:coverage:
| Lib area | % statements | Tested? |
|---|---|---|
lib/safeError.ts |
100 | ✓ |
lib/userDataCleanup.ts |
100 | ✓ |
lib/llm/models.ts |
100 | ✓ |
lib/documentVersions.ts |
98 | ✓ |
lib/chat/citations.ts |
98 | ✓ |
lib/userLookup.ts |
91 | ✓ |
lib/downloadTokens.ts |
87 | ✓ |
lib/chat/types.ts |
85 | ✓ |
lib/access.ts |
76 | ✓ |
lib/storage.ts |
33 | partial — key/disposition helpers only |
lib/userApiKeys.ts |
13 | partial — provider/env helpers only |
lib/documentTypes.ts, lib/userSettings.ts, lib/upload.ts, lib/officeText.ts |
0 | ✗ |
lib/convert.ts, lib/spreadsheet.ts, lib/docxTrackedChanges.ts |
0 | ✗ |
lib/userDataExport.ts |
0 | ✗ |
lib/courtlistener.ts, lib/systemWorkflows.ts |
0 | ✗ |
lib/chat/prompts.ts, lib/chat/contextBuilders.ts, lib/chat/streaming.ts |
0 | ✗ |
lib/chat/tools/** (schemas, documentOps, toolDispatcher) |
0 | ✗ |
lib/llm/** (providers, tools, index, rawStreamLog) |
~4 | ✗ (only models.ts) |
lib/mcp/** (client, servers, oauth, types) |
0 | ✗ |
Global: 11.18% statements / 10.98% branches / 14.43% functions / 10.91%
lines. The global number is low because src/lib/** includes several very
large feature libs (toolDispatcher, documentOps, systemWorkflows,
courtlistener, docxTrackedChanges) that dominate the line count.
TODO — untested libs, in priority order
Each item is meant to be one self-contained PR: add the suite, then raise the
floors in backend/vitest.config.mts to just below the new measured numbers.
Size is a rough guess: S ≈ an hour, M ≈ an afternoon.
lib/documentTypes.ts— pure catalog/lookup of document types; assert known types resolve and unknown inputs fall back sanely. (S)lib/chat/prompts.ts— pure prompt builders; assert key instructions and interpolated values appear in the output strings. (S)lib/userSettings.ts— title/tabular model resolution from which API keys a user has; reuse the Supabase mock pattern fromuserLookup.test.ts. (S)lib/upload.ts— multer wrapper: assert LIMIT_FILE_SIZE maps to a 413 with the right message and other errors pass through. (S)lib/officeText.ts— office XML text extraction; build a tiny in-memory zip fixture with JSZip and assert extracted/decoded text. (S)lib/chat/tools/toolSchemas.ts— assert every tool schema has a name, description, and well-formed parameters (guards against schema drift). (S)lib/userApiKeys.ts(rest) — encrypt/decrypt round-trip and DB load/store paths with a mocked Supabase client. (M)lib/storage.ts(rest) — S3 upload/download/list/delete wrappers with a mocked AWS SDK client. (M)lib/userDataExport.ts— export assembly: given seeded mock tables, assert the export contains the user's data and nobody else's. (M)lib/spreadsheet.ts— parse a small in-memory xlsx fixture; assert sheet and cell extraction, including empty/edge cells. (M)lib/chat/contextBuilders.ts— context assembly from doc stores; assert doc labels, truncation, and ordering. (M)lib/docxTrackedChanges.ts— tracked-changes XML round-trip on a minimal docx fixture: insert/delete runs, accept/reject. High value: document integrity. (M)lib/courtlistener.ts— API client with mocked fetch: query building, pagination, and error paths. Legal-research correctness. (M)lib/systemWorkflows.ts— mostly data: assert workflow definitions are well-formed (unique ids, non-empty skill markdown). (S)lib/llm/tools.ts+lib/llm/index.ts— provider-neutral tool plumbing and provider selection with mocked provider modules. (M)lib/mcp/types.ts+lib/mcp/servers.ts— server config validation and allow-listing logic; security relevant. (M)lib/mcp/client.ts+lib/mcp/oauth.ts— connection lifecycle and OAuth token handling with a mocked MCP SDK; security relevant. (M)lib/llm/rawStreamLog.ts— log path construction and redaction with a mocked fs. (S)lib/chat/tools/documentOps.ts— start with the pure helpers (diff/match utilities), not the full tool handlers. (M)lib/chat/tools/toolDispatcher.ts— dispatch table routing and argument validation with stubbed tools; don't try to cover every tool body. (M)lib/chat/streaming.ts+lib/llm/{claude,gemini,openai}.ts— streaming loops and provider adapters; hardest to unit test, consider extracting pure chunk-parsing helpers first. (M)
Not worth unit testing directly: lib/supabase.ts and lib/convert.ts are
thin wrappers around external services (Supabase auth, LibreOffice); they are
better exercised by the e2e suite.
Ratchet policy
backend/vitest.config.mts enforces global coverage floors (currently
statements 11 / branches 10 / functions 14 / lines 10). They are a
no-regression ratchet, not a target:
- Floors only go up. Never lower them to get a PR green — that means your change removed tested behavior or added a large untested lib; add tests instead.
- Raise them in the same PR that adds tests. After your suite passes, run
npm run test:coverage, take the new global numbers, and set each floor to the measured value rounded down to a whole percent. - Keep the measured numbers in the config comment and the table above honest when you do.