Free Provider ships a node:test suite covering the server, both providers, and the underlying clients — plus a coverage gate that fails if any tracked file drops below 85% line coverage.
npm test # run all suites (test/*.test.mjs)
npm run test:coverage # run the suite with per-file coverage reporting
npm run coverage:check # run the suite and enforce the coverage gatecoverage:check runs everything through scripts/check-coverage.mjs, which fails on:
- any test failure, or
- any tracked source file with line coverage below 85%.
On Node 20.12+ no extra dependencies are needed (native node:test, fetch, and V8 coverage); the --experimental-test-coverage / --test flags come from the runtime itself.
Important: the test glob is
"test/*.test.mjs"— runningnode --test test/(the bare directory) fails on Node 24.
| Suite | Focus |
|---|---|
test/server-index.test.mjs |
Express app wiring, auth mount, startup banner, startServer on an ephemeral port |
test/server-routes.test.mjs |
/v1/models, /v1/chat/completions (stream + non-stream), auto-model alternation, cooldown 429, /v1/health, /v1/pool |
test/server-providers.test.mjs |
Router: isAutoModel, nextAutoSlot pairing/sequence, explicit-model routing; pinned agentIndex for both providers |
test/server-auth.test.mjs |
Bearer-token auth middleware (valid/missing/invalid keys) |
test/server-config.test.mjs |
server/config.mjs parsing: env overrides, .env loading, proxy list, model list |
test/gemini-client.test.mjs |
Gemini guest client: init token grabbing, StreamGenerate frames, delta aggregation (prefix/tail/full-LCS), error codes (e.g. 1037 usage limit, 1060 IP block), flagged/plain stream failures |
test/cloudflare.test.mjs |
CFBypass transport: retries, backoff, cookie jar, extractCookies, pacing/throttle, proxy rotation + bench, and socks4/socks5/dispatcher handling |
test/chatgpt.test.mjs, test/chatgpt-auth.test.mjs |
Anonymous + authenticated ChatGPT client chat/auth flows and their edge cases |
test/client-index.test.mjs |
The ChatGPT client class the server uses (ask/streamAsk, init/token caching, upload paths) |
test/upload.test.mjs, test/sentinel.test.mjs, test/pow.test.mjs, test/turnstile.test.mjs, test/sse.test.mjs, test/tempmail.test.mjs |
Shared client internals: Azure upload builder, sentinel token, FNV-1a POW, Turnstile VM, SSE parser, temp-mail helper |
Auto-mode scheduling is deterministic and easy to assert:
const { isAutoModel, nextAutoSlot, resetAutoScheduler } = await import("../server/providers/router.mjs");
resetAutoScheduler();
nextAutoSlot(); // → { provider: "chatgpt", agentIndex: 0 }
nextAutoSlot(); // → { provider: "gemini", agentIndex: 0 }
nextAutoSlot(); // → { provider: "chatgpt", agentIndex: 1 }
nextAutoSlot(); // → { provider: "gemini", agentIndex: 1 }
// … wraps after AGENT_COUNT pairsisAutoModel("auto") → true, isAutoModel("gpt-4o") → false. Route-level tests assert the same sequence comes out of /v1/chat/completions by injecting stub providers.
- Don't dynamic-re-import a module with a cache-busting query (e.g.
await import("../server/index.mjs?id=again")) inside a test — it corrupts V8's per-file coverage attribution and can crater a file's reported percentage to ~0%. Reuse the top-level import instead. - Unreachable-by-design code stays uncovered: the
getNestedValuestring-key branch inclient/gemini.mjs(all call sites use numeric keys) and theserver/index.mjsmain-guard (only runs as an entrypoint). Both are accepted rows below 100% so they don't fail the gate.