diff --git a/src/routes/insurance.ts b/src/routes/insurance.ts index e41eb16..30c83cf 100644 --- a/src/routes/insurance.ts +++ b/src/routes/insurance.ts @@ -8,6 +8,7 @@ */ import { Hono } from "hono"; import { validateSlab } from "../middleware/validateSlab.js"; +import { cacheMiddleware } from "../middleware/cache.js"; import { getSupabase, createLogger, truncateErrorMessage } from "@percolator/shared"; const logger = createLogger("api:insurance"); @@ -31,7 +32,7 @@ export function insuranceRoutes(): Hono { * ] * } */ - app.get("/insurance/:slab", validateSlab, async (c) => { + app.get("/insurance/:slab", cacheMiddleware(15), validateSlab, async (c) => { const slab = c.req.param("slab"); try { diff --git a/tests/routes/insurance.test.ts b/tests/routes/insurance.test.ts index 1e3d7f7..46225d8 100644 --- a/tests/routes/insurance.test.ts +++ b/tests/routes/insurance.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { insuranceRoutes } from "../../src/routes/insurance.js"; +import { clearCache } from "../../src/middleware/cache.js"; // Mock @percolator/shared vi.mock("@percolator/shared", () => ({ @@ -29,6 +30,7 @@ describe("insurance routes", () => { beforeEach(() => { vi.clearAllMocks(); + clearCache(); mockSupabase = { from: vi.fn(() => mockSupabase), @@ -98,6 +100,50 @@ describe("insurance routes", () => { expect(data.history).toHaveLength(2); }); + it("caches the response so a second request for the same slab does not re-query Supabase (BUG-106)", async () => { + const mockStats = { + insurance_balance: "1000000000", + insurance_fee_revenue: "50000000", + total_open_interest: "5000000000", + }; + let fromCallCount = 0; + mockSupabase.from.mockImplementation((table: string) => { + fromCallCount++; + if (table === "market_stats") { + return { + select: vi.fn(() => ({ + eq: vi.fn(() => ({ + single: vi.fn().mockResolvedValue({ data: mockStats, error: null }), + })), + })), + }; + } else if (table === "insurance_history") { + return { + select: vi.fn(() => ({ + eq: vi.fn(() => ({ + order: vi.fn(() => ({ + limit: vi.fn().mockResolvedValue({ data: [], error: null }), + })), + })), + })), + }; + } + return mockSupabase; + }); + + const app = insuranceRoutes(); + const res1 = await app.request("/insurance/5gX6nn6Jhh3Sxsb6FMvbVGdFspKT2vtdXxkWi2zwXmHp"); + expect(res1.status).toBe(200); + const callsAfterFirst = fromCallCount; + expect(callsAfterFirst).toBeGreaterThan(0); + + const res2 = await app.request("/insurance/5gX6nn6Jhh3Sxsb6FMvbVGdFspKT2vtdXxkWi2zwXmHp"); + expect(res2.status).toBe(200); + expect(res2.headers.get("X-Cache")).toBe("HIT"); + // No new Supabase calls — served entirely from cache. + expect(fromCallCount).toBe(callsAfterFirst); + }); + it("should return 404 when market not found", async () => { mockSupabase.from.mockImplementation((table: string) => { if (table === "market_stats") {