skillguard/.agents/memory/api-server-fetch-mocking-in-tests.md

25 lines
1.3 KiB
Markdown
Raw Permalink Normal View History

Add automated tests for model discovery (POST /providers/list-models) Task #25: the model-discovery capability (list available models, used by the guided provider setup) had no automated coverage. Added a new vitest suite that exercises the endpoint end-to-end against the in-process Express app. New file: - artifacts/api-server/src/routes/providers.listModels.test.ts Coverage (6 tests, all passing): - ok=false + clear German message when no token (empty token, no providerId), and the upstream provider is never called. - Falls back to the stored provider token when providerId is given and apiToken is empty (inserts a real provider row, asserts the Bearer header carries the stored token, cleans up afterward). - Normalizes the OpenAI-compatible response (data[].id) into a deduped, sorted model list; drops non-string ids. - Anthropic path: GET /models with x-api-key + anthropic-version headers (no Authorization), reads models[] with id/name fallback, dedupes. - Upstream failure returns ok=false (HTTP 200, not 500), empty models, and the token is redacted from the message ([REDACTED], never the raw token). - fetch throwing (network error) returns ok=false without leaking the token. Implementation note: the suite runs the app in-process and the test client also uses fetch, so global fetch is mocked with a passthrough — requests to the test server's baseUrl delegate to the captured real fetch; only upstream provider URLs are synthesized. Spy assertions filter out the localhost passthrough call. Saved this non-obvious testing lesson to memory. Deviation / note: pre-existing failures in relation.test.ts and compare.test.ts are unrelated to this task — the dev database's scans table is missing the fingerprint/relation/similarity/compared_scan_id columns (schema drift; needs a drizzle-kit push). Out of scope for this task; proposed as a follow-up. Replit-Task-Id: 7e8a3db2-0da7-40d9-b74d-132779a44d39
2026-06-10 21:20:17 +00:00
---
name: Mocking fetch in api-server route tests
description: How to mock upstream fetch without breaking the test client's own request to the in-process Express server
---
When testing an api-server route that itself calls `fetch` (e.g. provider model
discovery / connection tests), the route tests spin up the real Express app via
`app.listen(0)` and hit it with `fetch(`${baseUrl}/api/...`)`. The app runs in the
SAME process as the test, so `vi.spyOn(globalThis, "fetch")` intercepts BOTH the
test client's request to the server AND the route's upstream provider call.
**Rule:** capture `const realFetch = globalThis.fetch.bind(globalThis)` BEFORE
mocking, and in the mock implementation delegate any request whose URL starts with
the test server's `baseUrl` back to `realFetch`. Only synthesize a response for the
upstream (non-localhost) URL.
**Also:** the spy records the localhost passthrough call too, so don't assert on
`mock.calls[0]` — filter to the call whose URL does NOT start with `baseUrl` to find
the real upstream request. Likewise `not.toHaveBeenCalled()` / `toHaveBeenCalledTimes(1)`
must be expressed in terms of the filtered upstream calls, not the raw spy.
**Why:** without the passthrough, the test client's request to the in-process server
gets the synthetic upstream body (or the thrown error), so every test fails before the
route logic runs.