import { describe, it, expect, vi, beforeEach } from "vitest"; import { encrypt } from "../../encrypt"; const mockUserAiKeysFindMany = vi.fn(); const mockUserModelPrefsFindFirst = vi.fn(); const mockSiteSettingFindFirst = vi.fn(); vi.mock("@epicure/db", () => ({ db: { query: { userAiKeys: { findMany: mockUserAiKeysFindMany }, userModelPrefs: { findFirst: mockUserModelPrefsFindFirst }, siteSettings: { findFirst: mockSiteSettingFindFirst }, }, }, userAiKeys: {}, userModelPrefs: {}, siteSettings: {}, eq: vi.fn((a, b) => ({ a, b })), and: vi.fn((...args) => args), })); const { getDefaultProviderWithKey, getModelConfigForUseCase, withUserKey } = await import("../resolve-user-key"); beforeEach(() => { vi.clearAllMocks(); delete process.env["OPENAI_API_KEY"]; delete process.env["ANTHROPIC_API_KEY"]; delete process.env["OPENROUTER_API_KEY"]; // Make getSiteSetting return null by default mockSiteSettingFindFirst.mockResolvedValue(null); }); describe("getDefaultProviderWithKey", () => { it("returns empty config when no keys at all", async () => { mockUserAiKeysFindMany.mockResolvedValue([]); const config = await getDefaultProviderWithKey("user1"); expect(config).toEqual({}); }); it("returns openrouter key first (priority order)", async () => { const encOpenrouter = encrypt("or-key"); const encOpenai = encrypt("sk-test"); mockUserAiKeysFindMany.mockResolvedValue([ { provider: "openai", encryptedKey: encOpenai }, { provider: "openrouter", encryptedKey: encOpenrouter }, ]); const config = await getDefaultProviderWithKey("user1"); expect(config.provider).toBe("openrouter"); expect(config.apiKey).toBe("or-key"); }); it("falls back to openai when no openrouter", async () => { const encOpenai = encrypt("sk-openai"); mockUserAiKeysFindMany.mockResolvedValue([ { provider: "openai", encryptedKey: encOpenai }, ]); const config = await getDefaultProviderWithKey("user1"); expect(config.provider).toBe("openai"); expect(config.apiKey).toBe("sk-openai"); }); it("uses site settings when no BYOK key exists", async () => { mockUserAiKeysFindMany.mockResolvedValue([]); // Mock getSiteSetting via siteSettings.findFirst to return encrypted key for OPENAI const encKey = encrypt("sk-from-site-settings"); mockSiteSettingFindFirst.mockResolvedValueOnce(null) // openrouter .mockResolvedValueOnce({ value: encKey, isSecret: true }); // openai const config = await getDefaultProviderWithKey("user1"); expect(config.provider).toBe("openai"); expect(config.apiKey).toBe("sk-from-site-settings"); }); it("skips corrupted BYOK key and tries next provider", async () => { mockUserAiKeysFindMany.mockResolvedValue([ { provider: "openrouter", encryptedKey: "CORRUPT:NOT:VALID" }, { provider: "openai", encryptedKey: encrypt("sk-valid") }, ]); const config = await getDefaultProviderWithKey("user1"); expect(config.provider).toBe("openai"); expect(config.apiKey).toBe("sk-valid"); }); }); describe("withUserKey", () => { it("injects BYOK key when user has one for this provider", async () => { const encKey = encrypt("sk-user-key"); vi.mocked(mockUserAiKeysFindMany); // just ensure mock is ready // withUserKey uses findFirst via userAiKeys const mockFindFirst = vi.fn().mockResolvedValue({ encryptedKey: encKey }); ((await import("@epicure/db")).db.query.userAiKeys as unknown as { findFirst: typeof mockFindFirst }).findFirst = mockFindFirst; const config = await withUserKey("user1", { provider: "openai" }); expect(config.apiKey).toBe("sk-user-key"); }); it("returns config unchanged when no user key for provider", async () => { const mockFindFirst = vi.fn().mockResolvedValue(null); ((await import("@epicure/db")).db.query.userAiKeys as unknown as { findFirst: typeof mockFindFirst }).findFirst = mockFindFirst; const config = await withUserKey("user1", { provider: "anthropic" }); expect(config.apiKey).toBeUndefined(); }); }); describe("getModelConfigForUseCase", () => { it("uses user model prefs when set", async () => { mockUserModelPrefsFindFirst.mockResolvedValue({ textProvider: "anthropic", textModel: "claude-sonnet-4-6", visionProvider: null, visionModel: null, mealPlanProvider: null, mealPlanModel: null, }); const mockFindFirst = vi.fn().mockResolvedValue(null); // no BYOK key ((await import("@epicure/db")).db.query.userAiKeys as unknown as { findFirst: typeof mockFindFirst }).findFirst = mockFindFirst; const config = await getModelConfigForUseCase("user1", "text"); expect(config.provider).toBe("anthropic"); expect(config.model).toBe("claude-sonnet-4-6"); }); it("falls back to getDefaultProviderWithKey when no prefs", async () => { mockUserModelPrefsFindFirst.mockResolvedValue(null); mockUserAiKeysFindMany.mockResolvedValue([ { provider: "openai", encryptedKey: encrypt("sk-default") }, ]); const config = await getModelConfigForUseCase("user1", "vision"); expect(config.provider).toBe("openai"); expect(config.apiKey).toBe("sk-default"); }); });