import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import path from "node:path"; import { catalogMethodSlugsForSection } from "../../lib/server/governanceCatalog"; import { FACET_GROUP_IDS, FACET_VALUE_IDS_BY_GROUP, SECTION_IDS, type SectionId, facetGroupsFileSchema, sectionFacetsSchema, } from "../../lib/server/validation/methodFacetsSchemas"; const REPO_ROOT = path.resolve(__dirname, "..", ".."); const SECTION_TO_MESSAGES_FILE: Record = { communication: "messages/en/create/customRule/communication.json", membership: "messages/en/create/customRule/membership.json", decisionApproaches: "messages/en/create/customRule/decisionApproaches.json", conflictManagement: "messages/en/create/customRule/conflictManagement.json", }; function readJson(rel: string): T { return JSON.parse(readFileSync(path.join(REPO_ROOT, rel), "utf8")) as T; } describe("data/create/customRule parity (CR-88)", () => { for (const section of SECTION_IDS) { const messagesPath = SECTION_TO_MESSAGES_FILE[section]; const dataPath = `data/create/customRule/${section}.json`; it(`${section}: facet file matches messages methods one-to-one`, () => { const messages = readJson<{ methods: { id: string }[] }>(messagesPath); const dataParsed = sectionFacetsSchema.safeParse(readJson(dataPath)); expect(dataParsed.success).toBe(true); if (!dataParsed.success) return; const messageSlugs = new Set(messages.methods.map((m) => m.id)); const dataSlugs = new Set(Object.keys(dataParsed.data)); const onlyInMessages = [...messageSlugs].filter((s) => !dataSlugs.has(s)); const onlyInData = [...dataSlugs].filter((s) => !messageSlugs.has(s)); expect(onlyInMessages, `${section} slugs missing from data/`).toEqual([]); expect(onlyInData, `${section} slugs missing from messages/`).toEqual([]); const catalogSlugs = catalogMethodSlugsForSection(section); expect(catalogSlugs).toEqual([...messageSlugs]); }); } }); describe("data/create/customRule/_facetGroups.json (CR-88)", () => { const groups = readJson("data/create/customRule/_facetGroups.json"); it("matches the facetGroupsFileSchema", () => { const parsed = facetGroupsFileSchema.safeParse(groups); expect(parsed.success).toBe(true); }); it("every chipId resolves to a real position in the referenced messages file", () => { const parsed = facetGroupsFileSchema.parse(groups); for (const group of FACET_GROUP_IDS) { const block = parsed[group]; // source is "messages/en/.../foo.json#/"; resolve relative to repo root. const [filePath, jsonPointer] = block.source.split("#"); const file = readJson>(filePath); const arrayKey = jsonPointer.replace(/^\//, ""); const arr = file[arrayKey]; expect(Array.isArray(arr), `${group}: ${block.source} → array`).toBe(true); const positions = new Set(arr.map((_, i) => String(i + 1))); for (const [valueId, { chipId }] of Object.entries(block.values)) { expect( positions.has(chipId), `${group}.${valueId} chipId ${chipId} should be a position in ${block.source} (have ${[...positions].join(",")})`, ).toBe(true); } } }); it("uses the canonical 19 value ids across the four groups", () => { const parsed = facetGroupsFileSchema.parse(groups); let total = 0; for (const group of FACET_GROUP_IDS) { const expected = new Set(FACET_VALUE_IDS_BY_GROUP[group]); const actual = new Set(Object.keys(parsed[group].values)); expect(actual).toEqual(expected); total += actual.size; } expect(total).toBe(19); }); });