diff --git a/src/__tests__/health.test.ts b/src/__tests__/health.test.ts index 230016d..fcd3014 100644 --- a/src/__tests__/health.test.ts +++ b/src/__tests__/health.test.ts @@ -84,4 +84,55 @@ describe("GET /health", () => { // 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"); + }); }); diff --git a/src/__tests__/pdf-options-builder.test.ts b/src/__tests__/pdf-options-builder.test.ts index 30641aa..5114e4f 100644 --- a/src/__tests__/pdf-options-builder.test.ts +++ b/src/__tests__/pdf-options-builder.test.ts @@ -65,4 +65,44 @@ describe("buildPdfOptions", () => { const r2 = buildPdfOptions({ displayHeaderFooter: false }); expect(r2.displayHeaderFooter).toBe(false); }); + + it("handles all optional fields set with various edge case values", () => { + const opts: PdfRenderOptions = { + format: "A3", + landscape: true, + printBackground: false, + margin: { top: "0", right: "0", bottom: "0", left: "0" }, + scale: 0.5, + pageRanges: "1-10", // non-empty pageRanges to ensure it gets included + preferCSSPageSize: false, + width: "210mm", // non-zero width to ensure it gets included + height: "297mm", // non-zero height to ensure it gets included + headerTemplate: "", // empty header edge case (still gets included via !== undefined check) + footerTemplate: "", // empty footer edge case (still gets included via !== undefined check) + displayHeaderFooter: false, + }; + const result = buildPdfOptions(opts); + + // Verify all fields are properly passed through + expect(result.format).toBe("A3"); + expect(result.landscape).toBe(true); + expect(result.printBackground).toBe(false); + expect(result.margin).toEqual({ top: "0", right: "0", bottom: "0", left: "0" }); + expect(result.scale).toBe(0.5); + expect(result.pageRanges).toBe("1-10"); + expect(result.preferCSSPageSize).toBe(false); + expect(result.width).toBe("210mm"); + expect(result.height).toBe("297mm"); + expect(result.headerTemplate).toBe(""); // empty string preserved via !== undefined + expect(result.footerTemplate).toBe(""); // empty string preserved via !== undefined + expect(result.displayHeaderFooter).toBe(false); + + // Verify result is a proper object with all expected properties + expect(typeof result).toBe("object"); + expect(Object.keys(result).sort()).toEqual([ + "format", "landscape", "printBackground", "margin", + "headerTemplate", "footerTemplate", "displayHeaderFooter", + "scale", "pageRanges", "preferCSSPageSize", "width", "height" + ].sort()); + }); });