DOCS / DEVELOPMENT / TESTING

Testing

Framework

Vitest, configured at the repo root (vitest.config.ts) — one test runner for every workspace, not per-app config duplication.

ts
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Mirrored 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:

text
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

bash
{
  "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:

bash
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

AreaTest files
Authauth/api-keys.test.ts, middleware/auth.integration.test.ts, middleware/management-auth.integration.test.ts
Rate limitingmiddleware/rate-limit.integration.test.ts, middleware/request-id.test.ts
ProvidersOne adapter/request/response/stream test file per provider under providers/<name>/
Registryregistry/models.test.ts
Routingrouting/circuit-breaker.test.ts, routing/dispatch.test.ts, routing/latency-tracker.test.ts, routing/scoring.test.ts, routing/state-sync.integration.test.ts
Databasedatabase/repositories.integration.test.ts
Shared packagespackages/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.

LATTICE — INTERNAL AI INFRASTRUCTURE
GITHUB © 2026