Testing
Framework
Vitest, configured at the repo root (vitest.config.ts) — one test runner for every workspace, not per-app config duplication.
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxMirrored layout, not colocated tests
Every test lives under tests/, at the same relative path as the source file it covers — not next to the source:
type ProviderAdapter = {
id: ProviderId;
createChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): Promise<ChatCompletionResponse>;
streamChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): AsyncGenerator<ChatCompletionChunk, void, void>;
};
type ProviderRequestContext = {
apiKey?: string;
providerModel: string;
signal: AbortSignal;
}; This keeps server/ and app/ directories free of test-file noise, and makes "does this file have a test" a pure path transformation — no searching for a colocated *.test.ts sibling.
Running tests
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1755878400,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 118,
"total_tokens": 160
}
}To run a subset, pass a path filter straight to Vitest:
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"Sure"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":42,"completion_tokens":118,"total_tokens":160}}
data: [DONE]Integration tests and the gateway's .env
vitest.config.ts explicitly loads apps/gateway/.env before the suite runs (guarded with existsSync, since CI injects real environment variables directly rather than checking in a .env file). This is required for integration tests that hit a real Postgres instance — middleware/auth.integration.test.ts, middleware/rate-limit.integration.test.ts, middleware/management-auth.integration.test.ts, database/repositories.integration.test.ts, and routing/state-sync.integration.test.ts — so they need a running, migrated database matching your apps/gateway/.env, not just installed dependencies.
What's covered
| Area | Test files |
|---|---|
| Auth | auth/api-keys.test.ts, middleware/auth.integration.test.ts, middleware/management-auth.integration.test.ts |
| Rate limiting | middleware/rate-limit.integration.test.ts, middleware/request-id.test.ts |
| Providers | One adapter/request/response/stream test file per provider under providers/<name>/ |
| Registry | registry/models.test.ts |
| Routing | routing/circuit-breaker.test.ts, routing/dispatch.test.ts, routing/latency-tracker.test.ts, routing/scoring.test.ts, routing/state-sync.integration.test.ts |
| Database | database/repositories.integration.test.ts |
| Shared packages | packages/api-contract/schemas/*.test.ts |
Mocking conventions
The anti-slop lint plugin's no-module-mocking rule (see Coding Standards) steers away from mocking whole modules. Provider adapters accept an injectable deps: { fetchImpl?: FetchLike } specifically so tests can substitute a fake fetch at the function-parameter level instead of mocking node:fetch/global fetch wholesale — see any providers/<name>/adapter.test.ts for the pattern.
Writing a new test
Match the mirrored path, and follow the nearest existing test file in the same directory for setup/teardown conventions — integration tests generally clean up the rows they write, unit tests generally don't touch the database at all.