import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { ledgerEvents, type Db } from "@parking/db"; import { createTestDb } from "@parking/db/testing"; import type { FastifyInstance } from "fastify"; import { buildServer } from "../server.js"; import { seedUser, login } from "../test-helpers.js"; // PUT /api/site-config/presence-bypass toggles the entry presence-gate bypass. It's a // DEDICATED, SIGNED endpoint: each signal that actually changes appends a config_change to // the ledger (attributed), and it persists to site_config. Admin-only. let db: Db; let close: () => void; let app: FastifyInstance; beforeEach(async () => { const t = createTestDb(); db = t.db; close = t.close; app = await buildServer({ db }); await app.ready(); }); afterEach(async () => { await app.close(); close(); }); const configChanges = () => db.select().from(ledgerEvents).all().filter((r) => r.type === "config_change"); async function put(body: unknown, auth: { cookie: string; csrf: string }) { return app.inject({ method: "PUT", url: "/api/site-config/presence-bypass", headers: { cookie: auth.cookie, "x-csrf-token": auth.csrf }, payload: body as Record, }); } describe("PUT /api/site-config/presence-bypass", () => { it("is admin-only: a non-site:update user is 403", async () => { await seedUser(db, { username: "op", password: "pw", roleId: "operator", permissions: ["shift:read"] }); const auth = await login(app, "op", "pw"); const res = await put({ camera: true }, auth); expect(res.statusCode).toBe(403); }); it("enabling a signal persists it AND signs an attributed config_change", async () => { await seedUser(db, { username: "admin", password: "pw" }); const auth = await login(app, "admin", "pw"); const res = await put({ camera: true }, auth); expect(res.statusCode).toBe(200); expect(res.json()).toMatchObject({ bypassPresenceCamera: true, bypassPresenceRadar: false }); const changes = configChanges(); expect(changes).toHaveLength(1); expect(changes[0].source).toBe("manual"); expect(changes[0].signature.length).toBeGreaterThan(0); expect(changes[0].payload).toMatchObject({ setting: "entryPresenceBypass.camera", value: true, prev: false, operator: "admin", }); }); it("a no-op toggle (already in that state) signs nothing", async () => { await seedUser(db, { username: "admin", password: "pw" }); const auth = await login(app, "admin", "pw"); await put({ camera: true }, auth); // 1st: on → 1 event await put({ camera: true }, auth); // 2nd: still on → no new event expect(configChanges()).toHaveLength(1); }); it("disabling signs the off transition too (auditable both ways)", async () => { await seedUser(db, { username: "admin", password: "pw" }); const auth = await login(app, "admin", "pw"); await put({ radar: true }, auth); await put({ radar: false }, auth); const changes = configChanges(); expect(changes).toHaveLength(2); expect(changes[1].payload).toMatchObject({ setting: "entryPresenceBypass.radar", value: false, prev: true }); }); it("rejects a non-boolean and an empty body", async () => { await seedUser(db, { username: "admin", password: "pw" }); const auth = await login(app, "admin", "pw"); expect((await put({ camera: "yes" }, auth)).statusCode).toBe(400); expect((await put({}, auth)).statusCode).toBe(400); }); });