Files
EpicNext-Cms/src/features/housekeeping/domains/hotel/services/mutations-runtime-radio.test.ts
T

166 lines
4.0 KiB
TypeScript

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<Record<string, unknown>>,
writes: [] as Array<Record<string, unknown>>,
}));
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<Record<string, unknown>>
> & {
limit: (count: number) => Promise<Array<Record<string, unknown>>>;
};
result.limit = vi.fn(() => result);
return result;
}),
};
return query;
}),
insert: vi.fn(() => ({
values: (values: Record<string, unknown>) => ({
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);
}
},
);
});