import { describe, it, expect, vi, beforeEach } from "vitest"; // Override the global setup.ts mock for keys — we need the REAL implementation vi.unmock("../services/keys.js"); // Keep db mocked (setup.ts already does this, but be explicit about our mock) vi.mock("../services/db.js", () => ({ default: { query: vi.fn(), connect: vi.fn(), on: vi.fn(), end: vi.fn() }, pool: { query: vi.fn(), connect: vi.fn(), on: vi.fn(), end: vi.fn() }, queryWithRetry: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }), connectWithRetry: vi.fn().mockResolvedValue(undefined), initDatabase: vi.fn().mockResolvedValue(undefined), cleanupStaleData: vi.fn(), isTransientError: vi.fn(), })); vi.mock("../services/logger.js", () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, })); import { queryWithRetry } from "../services/db.js"; import { createFreeKey, updateKeyEmail, updateEmailByCustomer, loadKeys, getAllKeys } from "../services/keys.js"; const mockQuery = vi.mocked(queryWithRetry); describe("keys.ts cache-hit coverage", () => { beforeEach(() => { vi.clearAllMocks(); // Reset cache by loading empty state mockQuery.mockResolvedValue({ rows: [], rowCount: 0 } as any); }); it("createFreeKey returns existing key when email has a free key in cache", async () => { // Pre-populate cache with a free key mockQuery.mockResolvedValueOnce({ rows: [ { key: "df_free_existing123", tier: "free", email: "existing@example.com", created_at: "2026-01-01T00:00:00.000Z", stripe_customer_id: null, }, ], rowCount: 1, } as any); // Load the cache with our test data await loadKeys(); // Clear mock calls from loadKeys mockQuery.mockClear(); // Now call createFreeKey with the same email - should hit cache and return existing const result = await createFreeKey("existing@example.com"); expect(result.key).toBe("df_free_existing123"); expect(result.tier).toBe("free"); expect(result.email).toBe("existing@example.com"); // Should NOT have called the database INSERT (cache hit path) const insertCalls = mockQuery.mock.calls.filter((call) => (call[0] as string).includes("INSERT") ); expect(insertCalls).toHaveLength(0); }); it("updateKeyEmail updates cache and DB when key is found in cache", async () => { // Pre-populate cache with a key mockQuery.mockResolvedValueOnce({ rows: [ { key: "df_pro_test123", tier: "pro", email: "old@example.com", created_at: "2026-01-01T00:00:00.000Z", stripe_customer_id: "cus_test123", }, ], rowCount: 1, } as any); // Load the cache await loadKeys(); // Clear mock calls mockQuery.mockClear(); // Mock the UPDATE query mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 } as any); // Call updateKeyEmail - should hit cache const result = await updateKeyEmail("df_pro_test123", "new@example.com"); expect(result).toBe(true); // Should have called the UPDATE query expect(mockQuery).toHaveBeenCalledWith( "UPDATE api_keys SET email = $1 WHERE key = $2", ["new@example.com", "df_pro_test123"] ); // Verify cache was updated const keys = getAllKeys(); const updatedKey = keys.find(k => k.key === "df_pro_test123"); expect(updatedKey?.email).toBe("new@example.com"); }); it("updateEmailByCustomer updates cache and DB when stripeCustomerId is found in cache", async () => { // Pre-populate cache with a key that has stripeCustomerId mockQuery.mockResolvedValueOnce({ rows: [ { key: "df_pro_customer123", tier: "pro", email: "customer@example.com", created_at: "2026-01-01T00:00:00.000Z", stripe_customer_id: "cus_customer123", }, ], rowCount: 1, } as any); // Load the cache await loadKeys(); // Clear mock calls mockQuery.mockClear(); // Mock the UPDATE query mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 } as any); // Call updateEmailByCustomer - should hit cache const result = await updateEmailByCustomer("cus_customer123", "newemail@example.com"); expect(result).toBe(true); // Should have called the UPDATE query expect(mockQuery).toHaveBeenCalledWith( "UPDATE api_keys SET email = $1 WHERE stripe_customer_id = $2", ["newemail@example.com", "cus_customer123"] ); // Verify cache was updated const keys = getAllKeys(); const updatedKey = keys.find(k => k.stripeCustomerId === "cus_customer123"); expect(updatedKey?.email).toBe("newemail@example.com"); }); });