import { describe, it, expect, vi, beforeEach } from "vitest"; // Unmock keys service — we want to test the real implementation vi.unmock("../services/keys.js"); // DB is still mocked by setup.ts import { queryWithRetry } from "../services/db.js"; describe("keys service", () => { let keys: typeof import("../services/keys.js"); beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); // Re-import to get fresh cache keys = await import("../services/keys.js"); }); describe("after loadKeys", () => { const mockRows = [ { key: "df_free_abc", tier: "free", email: "a@b.com", created_at: "2025-01-01T00:00:00Z", stripe_customer_id: null }, { key: "df_pro_xyz", tier: "pro", email: "pro@b.com", created_at: "2025-01-01T00:00:00Z", stripe_customer_id: "cus_123" }, ]; beforeEach(async () => { vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: mockRows, rowCount: 2 } as any); await keys.loadKeys(); }); it("isValidKey returns true for cached keys", () => { expect(keys.isValidKey("df_free_abc")).toBe(true); expect(keys.isValidKey("df_pro_xyz")).toBe(true); }); it("isValidKey returns false for unknown keys", () => { expect(keys.isValidKey("unknown")).toBe(false); }); it("isProKey returns true for pro tier, false for free", () => { expect(keys.isProKey("df_pro_xyz")).toBe(true); expect(keys.isProKey("df_free_abc")).toBe(false); }); it("getKeyInfo returns correct ApiKey object", () => { const info = keys.getKeyInfo("df_pro_xyz"); expect(info).toEqual({ key: "df_pro_xyz", tier: "pro", email: "pro@b.com", createdAt: "2025-01-01T00:00:00Z", stripeCustomerId: "cus_123", }); }); it("getKeyInfo returns undefined for unknown key", () => { expect(keys.getKeyInfo("nope")).toBeUndefined(); }); }); describe("createFreeKey", () => { beforeEach(async () => { vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 0 } as any); await keys.loadKeys(); }); it("creates key with df_free prefix", async () => { vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 1 } as any); const result = await keys.createFreeKey("new@test.com"); expect(result.key).toMatch(/^df_free_/); expect(result.tier).toBe("free"); expect(result.email).toBe("new@test.com"); }); it("returns existing key for same email", async () => { vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 1 } as any); const first = await keys.createFreeKey("dup@test.com"); const second = await keys.createFreeKey("dup@test.com"); expect(second.key).toBe(first.key); }); }); describe("createProKey", () => { beforeEach(async () => { vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 0 } as any); await keys.loadKeys(); }); it("uses UPSERT and returns key", async () => { const returnedRow = { key: "df_pro_newkey", tier: "pro", email: "pro@test.com", created_at: "2025-06-01T00:00:00Z", stripe_customer_id: "cus_new", }; vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [returnedRow], rowCount: 1 } as any); const result = await keys.createProKey("pro@test.com", "cus_new"); expect(result.tier).toBe("pro"); expect(result.stripeCustomerId).toBe("cus_new"); const call = vi.mocked(queryWithRetry).mock.calls.find( (c) => typeof c[0] === "string" && c[0].includes("ON CONFLICT") ); expect(call).toBeTruthy(); }); }); });