import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; const mockFindFirst = vi.fn(); const mockFindMany = vi.fn(); const mockInsert = vi.fn(); const mockDelete = vi.fn(); vi.mock("@epicure/db", () => ({ db: { query: { siteSettings: { findFirst: mockFindFirst, findMany: mockFindMany, }, }, insert: mockInsert, delete: mockDelete, }, siteSettings: { key: "key", value: "value", isSecret: "is_secret", updatedAt: "updated_at", updatedById: "updated_by_id" }, eq: vi.fn((a, b) => ({ a, b })), })); const insertChain = { values: vi.fn().mockReturnThis(), onConflictDoUpdate: vi.fn().mockResolvedValue(undefined), }; const deleteChain = { where: vi.fn().mockResolvedValue(undefined), }; const { getSiteSetting, setSiteSetting, isSecretKey } = await import("../site-settings"); beforeEach(() => { vi.clearAllMocks(); mockInsert.mockReturnValue(insertChain); mockDelete.mockReturnValue(deleteChain); }); afterEach(() => { delete process.env["OPENAI_API_KEY"]; delete process.env["ANTHROPIC_API_KEY"]; }); describe("isSecretKey", () => { it("marks API keys as secret", () => { expect(isSecretKey("OPENAI_API_KEY")).toBe(true); expect(isSecretKey("ANTHROPIC_API_KEY")).toBe(true); expect(isSecretKey("VAPID_PRIVATE_KEY")).toBe(true); }); it("marks non-secret settings as public", () => { expect(isSecretKey("OPENROUTER_DEFAULT_MODEL")).toBe(false); expect(isSecretKey("OLLAMA_BASE_URL")).toBe(false); }); }); describe("getSiteSetting", () => { it("returns DB value when present (plain)", async () => { mockFindFirst.mockResolvedValue({ value: "gpt-4o", isSecret: false }); const val = await getSiteSetting("OPENROUTER_DEFAULT_MODEL"); expect(val).toBe("gpt-4o"); }); it("decrypts DB value when secret", async () => { // Pre-encrypt something using the real encrypt function const { encrypt } = await import("../encrypt"); const ciphertext = encrypt("sk-real-key"); mockFindFirst.mockResolvedValue({ value: ciphertext, isSecret: true }); const val = await getSiteSetting("OPENAI_API_KEY"); expect(val).toBe("sk-real-key"); }); it("falls back to env var when no DB row", async () => { mockFindFirst.mockResolvedValue(null); process.env["OPENAI_API_KEY"] = "sk-from-env"; const val = await getSiteSetting("OPENAI_API_KEY"); expect(val).toBe("sk-from-env"); }); it("returns null when neither DB nor env is set", async () => { mockFindFirst.mockResolvedValue(null); const val = await getSiteSetting("OPENAI_API_KEY"); expect(val).toBeNull(); }); it("falls back to env when DB throws", async () => { mockFindFirst.mockRejectedValue(new Error("DB error")); process.env["ANTHROPIC_API_KEY"] = "sk-anthropic-env"; const val = await getSiteSetting("ANTHROPIC_API_KEY"); expect(val).toBe("sk-anthropic-env"); }); }); describe("setSiteSetting", () => { it("deletes the row when value is null", async () => { await setSiteSetting("OPENAI_API_KEY", null, "admin-user"); expect(mockDelete).toHaveBeenCalled(); expect(mockInsert).not.toHaveBeenCalled(); }); it("deletes the row when value is empty string", async () => { await setSiteSetting("OPENAI_API_KEY", "", "admin-user"); expect(mockDelete).toHaveBeenCalled(); }); it("inserts encrypted value for secret key", async () => { await setSiteSetting("OPENAI_API_KEY", "sk-test", "admin-user"); expect(mockInsert).toHaveBeenCalled(); const inserted = insertChain.values.mock.calls[0]![0] as Record; expect(inserted.isSecret).toBe(true); // stored value should not be plain text expect(inserted.value).not.toBe("sk-test"); expect(typeof inserted.value).toBe("string"); }); it("inserts plain value for non-secret key", async () => { await setSiteSetting("OPENROUTER_DEFAULT_MODEL", "gpt-4o", "admin-user"); const inserted = insertChain.values.mock.calls[0]![0] as Record; expect(inserted.isSecret).toBe(false); expect(inserted.value).toBe("gpt-4o"); }); });