2026-09-16 13:59:49 +00:00
|
|
|
import { describe, it, expect, beforeAll } from "vitest";
|
|
|
|
|
import PocketBase from "pocketbase";
|
|
|
|
|
|
|
|
|
|
import { APP_URL, DEMO, adminPb, requireServices, signIn, userPb } from "./helpers";
|
|
|
|
|
|
|
|
|
|
let admin: PocketBase;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
await requireServices();
|
|
|
|
|
admin = await adminPb();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("the AI key never leaves the server", () => {
|
|
|
|
|
it("is not readable by a client manager", async () => {
|
|
|
|
|
const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword);
|
|
|
|
|
// Every rule on `settings` is null — superuser only.
|
|
|
|
|
await expect(pb.collection("settings").getFullList()).rejects.toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("does not appear in the settings page HTML", async () => {
|
|
|
|
|
const marker = `sk-test-should-not-leak-${Date.now()}`;
|
|
|
|
|
|
|
|
|
|
const existing = await admin
|
|
|
|
|
.collection("settings")
|
|
|
|
|
.getFirstListItem("id != ''")
|
|
|
|
|
.catch(() => null);
|
2026-09-17 06:13:24 +00:00
|
|
|
|
|
|
|
|
// CAPTURE THE REAL KEY BEFORE OVERWRITING IT.
|
|
|
|
|
//
|
|
|
|
|
// An earlier version of this test restored to "" instead of to whatever
|
|
|
|
|
// was there, so every run of the integration suite silently destroyed the
|
|
|
|
|
// configured API key. It took a live debugging session to notice, because
|
|
|
|
|
// the only symptom is that AI features quietly stop working later.
|
|
|
|
|
//
|
|
|
|
|
// This suite runs against the shared instance — there is no separate test
|
|
|
|
|
// database — so anything it touches, it must put back exactly.
|
|
|
|
|
const previousKey: string = existing?.ai_api_key ?? "";
|
|
|
|
|
|
2026-09-16 13:59:49 +00:00
|
|
|
const record = existing
|
|
|
|
|
? await admin.collection("settings").update(existing.id, { ai_api_key: marker })
|
|
|
|
|
: await admin.collection("settings").create({ ai_api_key: marker });
|
|
|
|
|
|
2026-09-17 06:13:24 +00:00
|
|
|
try {
|
|
|
|
|
const cookie = await signIn(DEMO.ownerEmail, DEMO.ownerPassword);
|
|
|
|
|
const response = await fetch(`${APP_URL}/admin/settings`, { headers: { cookie } });
|
|
|
|
|
const html = await response.text();
|
2026-09-16 13:59:49 +00:00
|
|
|
|
2026-09-17 06:13:24 +00:00
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
expect(html).not.toContain(marker);
|
|
|
|
|
} finally {
|
|
|
|
|
// `finally`, so a failed assertion cannot leave the marker in place.
|
|
|
|
|
if (existing) {
|
|
|
|
|
await admin.collection("settings").update(record.id, { ai_api_key: previousKey });
|
|
|
|
|
} else {
|
|
|
|
|
// We created this record; remove it rather than leaving an empty one.
|
|
|
|
|
await admin.collection("settings").delete(record.id).catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-16 13:59:49 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("the admin settings page is owner-only", () => {
|
|
|
|
|
it("returns 404 for a client manager", async () => {
|
|
|
|
|
const cookie = await signIn(DEMO.managerEmail, DEMO.managerPassword);
|
|
|
|
|
const response = await fetch(`${APP_URL}/admin/settings`, { headers: { cookie } });
|
|
|
|
|
expect(response.status).toBe(404);
|
|
|
|
|
});
|
|
|
|
|
});
|