import { SystemRole } from "@capakraken/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { parseAiError } from "../ai-client.js"; import { logger } from "../lib/logger.js"; import { settingsRouter } from "../router/settings.js"; import { createCallerFactory } from "../trpc.js"; vi.mock("../lib/logger.js", () => ({ logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn(), }, })); const createCaller = createCallerFactory(settingsRouter); function createAdminCaller(db: Record) { return createCaller({ session: { user: { email: "admin@example.com", name: "Admin", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "admin_1", systemRole: SystemRole.ADMIN, permissionOverrides: null, }, }); } describe("runtime config hardening", () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.unstubAllGlobals(); vi.unstubAllEnvs(); }); it("sanitizes AI fallback diagnostics", () => { const error = parseAiError( new Error( "Provider failed at https://example.openai.azure.com/path?api-key=topsecret with Bearer sk-super-secret", ), ); expect(error).toContain(""); expect(error).toContain(""); expect(error).not.toContain("https://example.openai.azure.com"); expect(error).not.toContain("sk-super-secret"); expect(error).not.toContain("topsecret"); }); it("does not expose raw diagnostics from AI connection tests", async () => { const findUnique = vi.fn().mockResolvedValue({ aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", azureOpenAiApiKey: "sk-live-secret", }); vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, status: 500, text: vi.fn().mockResolvedValue( JSON.stringify({ error: { message: "upstream failure at https://api.openai.com/v1/chat/completions?api-key=hidden", }, }), ), })); const caller = createAdminCaller({ systemSettings: { findUnique, }, }); const result = await caller.testAiConnection(); expect(result.ok).toBe(false); expect(result).not.toHaveProperty("raw"); expect(result.error).toContain(""); expect(result.error).not.toContain("https://api.openai.com"); expect(result.error).not.toContain("hidden"); expect(logger.warn).toHaveBeenCalledWith( expect.objectContaining({ provider: "openai", diagnostic: expect.stringContaining(""), }), "AI connection test failed", ); }); it("reports secret presence flags when secrets come from environment overrides", async () => { vi.stubEnv("OPENAI_API_KEY", "env-openai-key"); vi.stubEnv("SMTP_PASSWORD", "env-smtp-password"); vi.stubEnv("GEMINI_API_KEY", "env-gemini-key"); const caller = createAdminCaller({ systemSettings: { findUnique: vi.fn().mockResolvedValue({ aiProvider: "openai", azureOpenAiApiKey: null, smtpPassword: null, geminiApiKey: null, }), }, }); const result = await caller.getSystemSettings(); expect(result.hasApiKey).toBe(true); expect(result.hasSmtpPassword).toBe(true); expect(result.hasGeminiApiKey).toBe(true); expect(result.runtimeSecrets.azureOpenAiApiKey.activeSource).toBe("environment"); expect(result.runtimeSecrets.smtpPassword.activeSource).toBe("environment"); expect(result.legacyStoredSecretFields).toEqual([]); }); it("prefers environment API keys during AI connection tests", async () => { vi.stubEnv("OPENAI_API_KEY", "env-openai-key"); const fetch = vi.fn().mockResolvedValue({ ok: true, text: vi.fn().mockResolvedValue("ok"), }); vi.stubGlobal("fetch", fetch); const caller = createAdminCaller({ systemSettings: { findUnique: vi.fn().mockResolvedValue({ aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", azureOpenAiApiKey: null, }), }, }); const result = await caller.testAiConnection(); expect(result).toEqual({ ok: true }); expect(fetch).toHaveBeenCalledWith( "https://api.openai.com/v1/chat/completions", expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer env-openai-key", }), }), ); }); it("does not persist incoming secret fields through updateSystemSettings", async () => { const findUnique = vi.fn().mockResolvedValue(null); const upsert = vi.fn().mockResolvedValue({}); const caller = createAdminCaller({ systemSettings: { findUnique, upsert, }, }); const result = await caller.updateSystemSettings({ azureOpenAiApiKey: "sk-should-not-store", smtpPassword: "smtp-should-not-store", geminiApiKey: "gemini-should-not-store", azureDalleApiKey: "dalle-should-not-store", anonymizationSeed: "seed-should-not-store", aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", }); expect(result).toEqual({ ok: true, ignoredSecretFields: [ "azureOpenAiApiKey", "smtpPassword", "anonymizationSeed", "azureDalleApiKey", "geminiApiKey", ], secretStorageMode: "environment-only", }); expect(upsert).toHaveBeenCalledWith({ where: { id: "singleton" }, create: { id: "singleton", aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", }, update: { aiProvider: "openai", azureOpenAiDeployment: "gpt-4o-mini", }, }); }); it("can clear legacy runtime secrets from database storage", async () => { const findUnique = vi.fn().mockResolvedValue({ azureOpenAiApiKey: "db-key", azureDalleApiKey: null, geminiApiKey: "db-gemini", smtpPassword: "db-smtp", anonymizationSeed: null, }); const update = vi.fn().mockResolvedValue({}); const caller = createAdminCaller({ systemSettings: { findUnique, update, }, }); const result = await caller.clearStoredRuntimeSecrets(); expect(result).toEqual({ ok: true, clearedFields: ["azureOpenAiApiKey", "geminiApiKey", "smtpPassword"], }); expect(update).toHaveBeenCalledWith({ where: { id: "singleton" }, data: { azureOpenAiApiKey: null, geminiApiKey: null, smtpPassword: null, }, }); }); });