fix(housekeeping): preserve radio key compatibility

This commit is contained in:
Simo committed 2026-09-05 16:04:10 +02:00
1 parent 9b83cdc3c3
commit 36ac116dc2
2 files changed
+55 -5

No files matched your search

@@ -1,3 +1,4 @@
import { readFileSync } from "node:fs";
import type { SQL } from "drizzle-orm"; import type { SQL } from "drizzle-orm";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts"; import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
@@ -52,6 +53,24 @@ const context = {
legacy: true, legacy: true,
}; };
function readCuratedRadioSettingKeys(): string[] {
const pagePath = new URL(
"../../../../../app/admin/radio/settings/page.tsx",
import.meta.url,
);
const source = readFileSync(pagePath, "utf8");
const groupsStart = source.indexOf("const GROUPS: Group[] = [");
const groupsEnd = source.indexOf("const CURATED_KEYS", groupsStart);
if (groupsStart < 0 || groupsEnd < 0) {
throw new Error("Radio settings GROUPS source contract was not found");
}
return Array.from(
source.slice(groupsStart, groupsEnd).matchAll(/\bkey:\s*"([^"]+)"/g),
(match) => match[1],
);
}
beforeEach(() => { beforeEach(() => {
state.rows = []; state.rows = [];
state.writes = []; state.writes = [];
@@ -69,6 +88,34 @@ describe("Hotel radio settings runtime", () => {
expect(state.writes).toEqual([]); expect(state.writes).toEqual([]);
}); });
it.each(["radio_custom-setting.v2", "auto_dj_vendor:key/variant"])(
"accepts an existing custom prefixed setting key: %s",
async (key) => {
await executeHotelMutationOperation(
"radio.settings.save-one",
{ key, value: "configured" },
context,
);
expect(state.writes).toEqual([
expect.objectContaining({ key, value: "configured" }),
]);
},
);
it.each(["Radio_name", "AUTO_DJ_mode"])(
"rejects an uppercase setting prefix: %s",
async (key) => {
await expect(
executeHotelMutationOperation(
"radio.settings.save-one",
{ key, value: "configured" },
context,
),
).rejects.toBeInstanceOf(HotelMutationFailure);
expect(state.writes).toEqual([]);
},
);
it("rejects duplicate and invalid bulk entries atomically", async () => { it("rejects duplicate and invalid bulk entries atomically", async () => {
for (const entries of [ for (const entries of [
[ [
@@ -92,10 +139,13 @@ describe("Hotel radio settings runtime", () => {
}); });
it("accepts the complete 102-entry curated form within the 500-entry bound", async () => { it("accepts the complete 102-entry curated form within the 500-entry bound", async () => {
const entries = Array.from({ length: 102 }, (_, index) => ({ const keys = readCuratedRadioSettingKeys();
key: `radio_curated_${index}`, const entries = keys.map((key, index) => ({
key,
value: `value-${index}`, value: `value-${index}`,
})); }));
expect(keys).toHaveLength(102);
expect(new Set(keys).size).toBe(102);
await executeHotelMutationOperation( await executeHotelMutationOperation(
"radio.settings.save-many", "radio.settings.save-many",
@@ -103,7 +153,7 @@ describe("Hotel radio settings runtime", () => {
context, context,
); );
expect(state.writes).toHaveLength(102); expect(state.writes.map(({ key }) => key)).toEqual(keys);
}); });
it("rejects a bulk request above 500 entries", async () => { it("rejects a bulk request above 500 entries", async () => {
@@ -127,8 +127,8 @@ const roomRuntimeSchema = z
}) })
.strict(); .strict();
const settingKey = requiredText(255).refine((key) => const settingKey = requiredText(255).refine(
/^(?:radio_|auto_dj_)[a-z0-9_]+$/i.test(key), (key) => key.startsWith("radio_") || key.startsWith("auto_dj_"),
); );
const settingSchema = z const settingSchema = z
.object({ .object({