English · 简体中文
Server suite
Layout
All server tests live under server/src/__tests__/, mirroring src/:
| Directory | Contents |
|---|---|
db/ |
DB init/encryption/hardening, intelligence tiers, node-sqlite behavior, and db/migrate/ — migration runner tests, registry drift, endpoint identity, and the roundtrip.test.ts that test:migrations runs in isolation. |
lib/ |
Pure-module units: error classification, crypto, budget, Gemini wire/schema, fallback loop (incl. client-abort, hedge, lease, model-bench variants), env drift, module purity (see below). |
providers/ |
Per-adapter tests: google (params/auth-header/schema), openai-compat, cohere, cloudflare, zhipu, aihorde, modelscope, pollinations, cn-providers batch, plus cross-cutting behaviors — abort signals, reasoning timeouts, stream first-byte, stated-retry parsing (#798), max-token caps. |
routes/ |
HTTP surface tests: proxy/retry/fallback, anthropic messages shim, gemini /v1beta, ollama emulation, url-tokens, auth, analytics, custom providers/endpoints, compression/CSP security headers. |
services/ |
Router/scoring/fusion, health (scheduler, pacing, error/log/transport), cooldown-probe, catalog-sync (scheduler/source), key concurrency, learn-limits, degradation, embeddings/media/transcription. |
integration/ |
full-flow.test.ts — request lifecycle through the assembled app. |
helpers/ |
Shared test utilities (auth.ts, acl.ts). |
fixtures/ |
Golden files (e.g. compression-golden.json). |
Serial forked execution
npm run test -w server runs vitest run --pool=forks --fileParallelism=false. Two deliberate choices:
--pool=forksisolates each test file in a fresh process. The suite manipulates real state — better-sqlite3 databases,process.env, module-level caches (cooldown maps, health schedulers) — that must not leak between files.--fileParallelism=falseserializes files so shared resources (ports, temp data dirs) cannot collide across concurrently executing files.
The tradeoff is wall-clock time; the payoff is that the ~2300-test suite is deterministic. The client workspace does not need this: its standalone vitest config exists mainly to avoid loading the React/Tailwind vite plugins, and its tests are plain TypeScript in the default node environment.
Pure-lib import guard (#858)
src/__tests__/lib/module-purity.test.ts enforces an architectural invariant: modules declared pure in server/src/lib/ (functions over their arguments — no I/O, no DB, no config) must stay free of value imports. import type stays legal because type imports are erased before they can create a cycle.
Why it exists: pure leaf modules like error-classify.ts are imported by the proxy chat path, the responses path, AND the fusion panel precisely because they depend on nothing. One added import { getDb } compiles cleanly, passes every behavioral test, and quietly turns a leaf into a hub — surfacing later as an import cycle (the fusion↔proxy case) or a unit test that suddenly needs a database. The guard also detects dynamic imports, re-exports, and require().
The list maintains itself: any new import-free lib module fails the suite until it is classified as guarded or deliberately not — a decision made out loud in a diff rather than by omission.
Loopback binding convention (#888)
Every route/integration test starts its server as:
const server = app.listen(0, '127.0.0.1');
if (!server.listening) await new Promise<void>(r => server.once('listening', () => r()));
Two details, both load-bearing (#888):
- Bind to
127.0.0.1explicitly. The olderapp.listen(0)bound the IPv6 wildcard::, while tests fetchedhttp://127.0.0.1:PORTover IPv4. Different addresses means an unrelated local process holding an IPv4 listener on the same ephemeral port wins the connection without triggeringEADDRINUSE— requests were answered by that process (observed withwrangler devworkerd processes replying 404/501), producing random assertion failures, broken JSON parses, and 5s timeouts roughly every second-to-fourth run. - Wait for the
listeningevent before readingaddress().listen(port, host)resolves the host asynchronously (unlikelisten(port)), soserver.address().portis only reliable after the event fires.
Verified fix: ten consecutive full suite runs clean against a machine with the colliding processes still running.