124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
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<string, unknown>;
|
|
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<string, unknown>;
|
|
expect(inserted.isSecret).toBe(false);
|
|
expect(inserted.value).toBe("gpt-4o");
|
|
});
|
|
});
|