test(ai): settings page never exposes the API key
Three tests cover the storage and access boundary: - A client manager cannot list the settings collection at all (rules are superuser-only — verified by the rejection, not by silent skip). - The settings page HTML returned to the owner does not contain the key value, even when the key is freshly set to a unique sentinel. - A client manager gets 404 on /admin/settings (admin layout gates on role, not on session presence). helpers.ts gained ownerEmail / ownerPassword — needed by the settings test and reusable for any owner-only integration test going forward.
This commit is contained in:
parent
70cb355762
commit
2401dbd68f
2 changed files with 50 additions and 0 deletions
48
tests/integration/ai-settings.test.ts
Normal file
48
tests/integration/ai-settings.test.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
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);
|
||||||
|
const record = existing
|
||||||
|
? await admin.collection("settings").update(existing.id, { ai_api_key: marker })
|
||||||
|
: await admin.collection("settings").create({ ai_api_key: marker });
|
||||||
|
|
||||||
|
const cookie = await signIn(DEMO.ownerEmail, DEMO.ownerPassword);
|
||||||
|
const response = await fetch(`${APP_URL}/admin/settings`, { headers: { cookie } });
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(html).not.toContain(marker);
|
||||||
|
|
||||||
|
await admin.collection("settings").update(record.id, { ai_api_key: "" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -8,6 +8,8 @@ export const DEMO = {
|
||||||
projectSlug: "wfdash-webapp",
|
projectSlug: "wfdash-webapp",
|
||||||
managerEmail: "manager@wf.example.com",
|
managerEmail: "manager@wf.example.com",
|
||||||
managerPassword: "manager-password-123",
|
managerPassword: "manager-password-123",
|
||||||
|
ownerEmail: "owner@example.com",
|
||||||
|
ownerPassword: "owner-password-123",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue