docfast/src/__tests__/health.test.ts
OpenClaw Subagent bbc106f518
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 17m7s
test: improve health.ts and browser.ts coverage
- health.ts: Added tests for timeout race, version regex edge cases, non-Error catch blocks
- browser.ts: Added comprehensive edge case test for buildPdfOptions with all fields
- Branch coverage: health.ts improved from 50% to 83.33%
- Function coverage: health.ts improved from 75% to 100%
- Overall function coverage improved from 84.46% to 84.95%
- Total tests: 772 → 776 (+4 new tests)
2026-03-15 08:06:39 +01:00

138 lines
4.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import express from "express";
import request from "supertest";
import { getPoolStats } from "../services/browser.js";
import { pool } from "../services/db.js";
let app: express.Express;
beforeEach(async () => {
vi.clearAllMocks();
// Default: healthy DB
const mockClient = {
query: vi.fn()
.mockResolvedValueOnce({ rows: [{ 1: 1 }] }) // SELECT 1
.mockResolvedValueOnce({ rows: [{ version: "PostgreSQL 17.4 on x86_64" }] }), // SELECT version()
release: vi.fn(),
};
vi.mocked(pool.connect).mockResolvedValue(mockClient as any);
vi.mocked(getPoolStats).mockReturnValue({
poolSize: 16,
totalPages: 16,
availablePages: 14,
queueDepth: 0,
pdfCount: 5,
restarting: false,
uptimeMs: 60000,
browsers: [],
});
const { healthRouter } = await import("../routes/health.js");
app = express();
app.use("/health", healthRouter);
});
describe("GET /health", () => {
it("returns 200 with status ok when DB is healthy", async () => {
const res = await request(app).get("/health");
expect(res.status).toBe(200);
expect(res.body.status).toBe("ok");
expect(res.body.database.status).toBe("ok");
});
it("returns 503 with status degraded on DB error", async () => {
vi.mocked(pool.connect).mockRejectedValue(new Error("Connection refused"));
const res = await request(app).get("/health");
expect(res.status).toBe(503);
expect(res.body.status).toBe("degraded");
expect(res.body.database.status).toBe("error");
});
it("includes pool stats", async () => {
const res = await request(app).get("/health");
expect(res.body.pool).toMatchObject({
size: 16,
available: 14,
queueDepth: 0,
pdfCount: 5,
});
});
it("includes version", async () => {
const res = await request(app).get("/health");
expect(res.body.version).toBeDefined();
expect(typeof res.body.version).toBe("string");
});
it("returns 503 when client.query() throws and releases client with destroy flag", async () => {
const mockRelease = vi.fn();
const mockClient = {
query: vi.fn().mockRejectedValue(new Error("Query failed")),
release: mockRelease,
};
vi.mocked(pool.connect).mockResolvedValue(mockClient as any);
const res = await request(app).get("/health");
expect(res.status).toBe(503);
expect(res.body.status).toBe("degraded");
expect(res.body.database.status).toBe("error");
expect(res.body.database.message).toContain("Query failed");
// Verify client.release(true) was called to destroy the bad connection
expect(mockRelease).toHaveBeenCalledWith(true);
});
it("returns 503 when database health check times out (timeout race wins)", async () => {
// Make pool.connect() hang longer than HEALTH_CHECK_TIMEOUT_MS (3000ms)
const mockClient = {
query: vi.fn(),
release: vi.fn(),
};
vi.mocked(pool.connect).mockImplementation(() =>
new Promise((resolve) => {
// Resolve after 5000ms, which is longer than the 3000ms timeout
setTimeout(() => resolve(mockClient as any), 5000);
})
);
const res = await request(app).get("/health");
expect(res.status).toBe(503);
expect(res.body.status).toBe("degraded");
expect(res.body.database.status).toBe("error");
expect(res.body.database.message).toContain("Database health check timed out");
});
it("returns PostgreSQL for version string without PostgreSQL match", async () => {
const mockClient = {
query: vi.fn()
.mockResolvedValueOnce({ rows: [{ 1: 1 }] }) // SELECT 1
.mockResolvedValueOnce({ rows: [{ version: "MySQL 8.0.33" }] }), // No PostgreSQL in version string
release: vi.fn(),
};
vi.mocked(pool.connect).mockResolvedValue(mockClient as any);
const res = await request(app).get("/health");
expect(res.status).toBe(200);
expect(res.body.status).toBe("ok");
expect(res.body.database.status).toBe("ok");
expect(res.body.database.version).toBe("PostgreSQL"); // fallback when no regex match
});
it("returns 503 when non-Error is thrown in catch block", async () => {
// Make pool.connect() throw a non-Error object
vi.mocked(pool.connect).mockRejectedValue("String error message");
const res = await request(app).get("/health");
expect(res.status).toBe(503);
expect(res.body.status).toBe("degraded");
expect(res.body.database.status).toBe("error");
expect(res.body.database.message).toBe("Database connection failed");
});
});