refactor(api): extract settings procedures
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import { SystemRole } from "@capakraken/shared";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const {
|
||||
createAuditEntry,
|
||||
testSmtpConnection,
|
||||
generateGeminiImage,
|
||||
parseGeminiError,
|
||||
} = vi.hoisted(() => ({
|
||||
createAuditEntry: vi.fn(),
|
||||
testSmtpConnection: vi.fn(),
|
||||
generateGeminiImage: vi.fn(),
|
||||
parseGeminiError: vi.fn((error: unknown) => `parsed:${String(error)}`),
|
||||
}));
|
||||
|
||||
vi.mock("../lib/audit.js", () => ({
|
||||
createAuditEntry,
|
||||
}));
|
||||
|
||||
vi.mock("../lib/email.js", () => ({
|
||||
testSmtpConnection,
|
||||
}));
|
||||
|
||||
vi.mock("../gemini-client.js", () => ({
|
||||
generateGeminiImage,
|
||||
parseGeminiError,
|
||||
}));
|
||||
|
||||
import {
|
||||
clearStoredRuntimeSecrets,
|
||||
getAiConfiguredStatus,
|
||||
getSystemSettingsView,
|
||||
testSettingsGeminiConnection,
|
||||
testSettingsSmtpConnection,
|
||||
updateSystemSettings,
|
||||
} from "../router/settings-procedure-support.js";
|
||||
|
||||
function createAdminContext(db: Record<string, unknown>) {
|
||||
return {
|
||||
db: db as never,
|
||||
dbUser: {
|
||||
id: "admin_1",
|
||||
systemRole: SystemRole.ADMIN,
|
||||
permissionOverrides: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("settings procedure support", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("builds the system settings view model from db and runtime state", async () => {
|
||||
vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
|
||||
vi.stubEnv("SMTP_PASSWORD", "env-smtp-password");
|
||||
|
||||
const result = await getSystemSettingsView(createAdminContext({
|
||||
systemSettings: {
|
||||
findUnique: vi.fn().mockResolvedValue({
|
||||
aiProvider: "openai",
|
||||
azureOpenAiApiKey: null,
|
||||
smtpPassword: null,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result.hasApiKey).toBe(true);
|
||||
expect(result.hasSmtpPassword).toBe(true);
|
||||
expect(result.aiProvider).toBe("openai");
|
||||
expect(result.defaultSummaryPrompt).toBeTypeOf("string");
|
||||
});
|
||||
|
||||
it("updates settings without persisting incoming secret fields and writes an audit entry", async () => {
|
||||
const findUnique = vi.fn().mockResolvedValue({
|
||||
id: "singleton",
|
||||
aiProvider: "azure",
|
||||
azureOpenAiApiKey: "secret",
|
||||
});
|
||||
const upsert = vi.fn().mockResolvedValue({});
|
||||
|
||||
const result = await updateSystemSettings(createAdminContext({
|
||||
systemSettings: {
|
||||
findUnique,
|
||||
upsert,
|
||||
},
|
||||
}), {
|
||||
aiProvider: "openai",
|
||||
azureOpenAiDeployment: "gpt-4o-mini",
|
||||
azureOpenAiApiKey: "should-be-ignored",
|
||||
smtpPassword: "also-ignored",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
ignoredSecretFields: ["azureOpenAiApiKey", "smtpPassword"],
|
||||
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",
|
||||
},
|
||||
});
|
||||
expect(createAuditEntry).toHaveBeenCalledWith(expect.objectContaining({
|
||||
entityType: "SystemSettings",
|
||||
action: "UPDATE",
|
||||
after: expect.objectContaining({
|
||||
aiProvider: "openai",
|
||||
azureOpenAiDeployment: "gpt-4o-mini",
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it("clears only legacy runtime secrets that are still stored", async () => {
|
||||
const update = vi.fn().mockResolvedValue({});
|
||||
|
||||
const result = await clearStoredRuntimeSecrets(createAdminContext({
|
||||
systemSettings: {
|
||||
findUnique: vi.fn().mockResolvedValue({
|
||||
azureOpenAiApiKey: "db-key",
|
||||
azureDalleApiKey: null,
|
||||
geminiApiKey: "db-gemini",
|
||||
smtpPassword: null,
|
||||
anonymizationSeed: "seed",
|
||||
}),
|
||||
update,
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
clearedFields: ["azureOpenAiApiKey", "geminiApiKey", "anonymizationSeed"],
|
||||
});
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
where: { id: "singleton" },
|
||||
data: {
|
||||
azureOpenAiApiKey: null,
|
||||
geminiApiKey: null,
|
||||
anonymizationSeed: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("tests smtp and gemini connections with audited outcomes", async () => {
|
||||
testSmtpConnection.mockResolvedValue({ ok: true });
|
||||
generateGeminiImage.mockResolvedValue("data:image/png;base64,abc123");
|
||||
|
||||
const db = {
|
||||
systemSettings: {
|
||||
findUnique: vi.fn().mockResolvedValue({
|
||||
geminiApiKey: "gem-key",
|
||||
geminiModel: "gem-model",
|
||||
}),
|
||||
},
|
||||
};
|
||||
const ctx = createAdminContext(db);
|
||||
|
||||
expect(await testSettingsSmtpConnection(ctx)).toEqual({ ok: true });
|
||||
expect(await testSettingsGeminiConnection(ctx)).toEqual({
|
||||
ok: true,
|
||||
model: "gem-model",
|
||||
preview: "data:image/png;base64,abc123...",
|
||||
});
|
||||
expect(createAuditEntry).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("reads the ai configured status from runtime-resolved settings", async () => {
|
||||
vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
|
||||
|
||||
const result = await getAiConfiguredStatus(createAdminContext({
|
||||
systemSettings: {
|
||||
findUnique: vi.fn().mockResolvedValue({
|
||||
aiProvider: "openai",
|
||||
azureOpenAiDeployment: "gpt-4o-mini",
|
||||
azureOpenAiApiKey: null,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result).toEqual({ configured: true });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user