import type { SQL } from "drizzle-orm"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { HousekeepingCapabilityContext } from "../../../foundation/contracts"; import { HotelMutationFailure } from "./mutations"; const state = vi.hoisted(() => ({ rows: [] as Array>, writes: [] as Array>, })); vi.mock("@/lib/db", async () => { const schema = await import("@/db/schema"); const database = { select: vi.fn(() => { const query = { from: vi.fn(() => query), where: vi.fn((_condition: SQL | undefined) => { const result = Promise.resolve(state.rows) as Promise< Array> > & { limit: (count: number) => Promise>>; }; result.limit = vi.fn(() => result); return result; }), }; return query; }), insert: vi.fn(() => ({ values: (values: Record) => ({ onDuplicateKeyUpdate: async () => { state.writes.push(values); }, }), })), }; return { ...schema, db: database }; }); import { executeHotelMutationOperation } from "./mutations-runtime"; const capability: HousekeepingCapabilityContext = { actor: { id: 42, username: "operator", rank: 7 }, isSuperAdmin: false, has: () => true, hasAny: () => true, hasAll: () => true, }; const context = { capability, correlationId: "radio-runtime", legacy: true, }; beforeEach(() => { state.rows = []; state.writes = []; }); describe("Hotel radio settings runtime", () => { it("rejects an unrelated setting key before any write", async () => { await expect( executeHotelMutationOperation( "radio.settings.save-one", { key: "cms_secret", value: "private" }, context, ), ).rejects.toBeInstanceOf(HotelMutationFailure); expect(state.writes).toEqual([]); }); it("rejects duplicate and invalid bulk entries atomically", async () => { for (const entries of [ [ { key: "radio_name", value: "one" }, { key: "radio_name", value: "two" }, ], [ { key: "radio_name", value: "one" }, { key: "site_name", value: "forged" }, ], ]) { await expect( executeHotelMutationOperation( "radio.settings.save-many", { entries }, context, ), ).rejects.toBeInstanceOf(HotelMutationFailure); expect(state.writes).toEqual([]); } }); it("accepts the complete 102-entry curated form within the 500-entry bound", async () => { const entries = Array.from({ length: 102 }, (_, index) => ({ key: `radio_curated_${index}`, value: `value-${index}`, })); await executeHotelMutationOperation( "radio.settings.save-many", { entries }, context, ); expect(state.writes).toHaveLength(102); }); it("rejects a bulk request above 500 entries", async () => { const entries = Array.from({ length: 501 }, (_, index) => ({ key: `radio_setting_${index}`, value: "value", })); await expect( executeHotelMutationOperation( "radio.settings.save-many", { entries }, context, ), ).rejects.toBeInstanceOf(HotelMutationFailure); expect(state.writes).toEqual([]); }); it.each([ [ "radio.settings.save-one" as const, { key: "radio_stream_url", value: "https://private.example/stream" }, ], [ "radio.settings.save-many" as const, { entries: [{ key: "radio_name", value: "private station name" }], }, ], [ "radio.points.save" as const, { radio_points_enabled: "1", radio_points_per_minute: "17", radio_points_currency: "credits", radio_points_max_per_day: "34", radio_points_min_listeners: "2", }, ], ])( "does not expose private values in %s audit snapshots", async (operation, input) => { const snapshot = await executeHotelMutationOperation( operation, input, context, ); const serialized = JSON.stringify(snapshot); for (const privateValue of [ "https://private.example/stream", "private station name", "17", "34", ]) { expect(serialized).not.toContain(privateValue); } }, ); });