Files
0pt/tests/api/health.test.ts
T
admin a88ad04867
Security Scan (SIAX Cloud) / security (push) Successful in 21s
CI (SIAX Cloud) / security (push) Successful in 21s
CI (SIAX Cloud) / quality (push) Successful in 41s
CI (SIAX Cloud) / security (pull_request) Successful in 22s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 21s
CI (SIAX Cloud) / quality (pull_request) Successful in 41s
fix(health): report real per-dependency status instead of hardcoded ok:true
BACKLOG P0-3: /api/health only ever checked *presence* of DATABASE_URL/
REDIS_URL and always returned ok:true regardless of whether anything was
reachable -- ARCHITECTURE.md's own words, "an uptime monitor watching HTTP
200 would show green for a dying instance".

lib/health-checks.ts adds real dependency probes:
  - db: a live `select 1` round trip (not just DATABASE_URL presence)
  - engine: a real HTTP call to the FastAPI engine's own GET /health
  - stripe: Stripe's documented lightweight balance.retrieve() liveness call
  - queue: honestly reports "not_integrated" when REDIS_URL is set but
    unused -- no Redis client is wired anywhere in this repo, so reporting
    "ok" would fabricate a check that doesn't exist

/api/health stays a liveness check (always 200 while the process can
respond; the body's `ok` field is the honest signal). New GET /api/ready
fails closed with 503 when the load-bearing dependency (db) isn't both
configured and reachable -- the "no /ready route" gap.

contracts/openapi.yaml, docs/architecture/ARCHITECTURE.md, and docs/slo.md
updated to match. 17 new unit tests (dependency-injected fakes, no real
network/DB) + 5 route-level tests.
2026-08-11 13:49:23 +02:00

71 lines
2.6 KiB
TypeScript

/**
* Route-level regression test for GET /api/health (BACKLOG P0-3): the old
* handler hardcoded `ok: true` regardless of dependency state. These tests
* cover the default (nothing configured) baseline against the real route
* handler; per-dependency ok/error branching is covered at the unit level in
* tests/lib/health-checks.test.ts (dependency-injected, no network/DB
* needed).
*/
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { GET as healthGET } from "@/app/api/health/route";
describe("GET /api/health", () => {
const savedEnv = {
DATABASE_URL: process.env.DATABASE_URL,
POSTGRES_URL: process.env.POSTGRES_URL,
ENGINE_URL: process.env.ENGINE_URL,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
REDIS_URL: process.env.REDIS_URL,
};
beforeEach(() => {
delete process.env.DATABASE_URL;
delete process.env.POSTGRES_URL;
delete process.env.ENGINE_URL;
delete process.env.STRIPE_SECRET_KEY;
delete process.env.REDIS_URL;
});
afterEach(() => {
for (const [k, v] of Object.entries(savedEnv)) {
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
});
it("REGRESSION: always returns HTTP 200 (liveness), but `ok` now reflects a real per-dependency check rather than a hardcoded true", async () => {
const res = await healthGET();
expect(res.status).toBe(200);
const body = await res.json();
// With nothing configured, nothing can be degraded -- ok:true here is
// now an honest conclusion, not a hardcoded literal.
expect(body.ok).toBe(true);
expect(body.service).toBe("0pt-web");
});
it("reports not_configured (not the old flat 'configured'/'not_configured' guess) for every unset dependency", async () => {
const res = await healthGET();
const body = await res.json();
expect(body.db).toBe("not_configured");
expect(body.engine).toBe("not_configured");
expect(body.stripe).toBe("not_configured");
expect(body.queue).toBe("not_configured");
});
it("reports queue as not_integrated (never a fabricated ok) when REDIS_URL is set but unused", async () => {
process.env.REDIS_URL = "redis://localhost:6379";
const res = await healthGET();
const body = await res.json();
expect(body.queue).toBe("not_integrated");
// Not wiring a real check for an unused integration is not itself a
// degradation -- overall liveness stays ok.
expect(body.ok).toBe(true);
});
it("response includes a ts timestamp", async () => {
const res = await healthGET();
const body = await res.json();
expect(new Date(body.ts).toString()).not.toBe("Invalid Date");
});
});