test: harden housekeeping registry contracts

This commit is contained in:
Simo committed 2026-08-25 19:49:31 +02:00
1 parent 2d5f03048a
commit 14cfad4029
4 files changed
+239 -11

No files matched your search

@@ -21,6 +21,39 @@ const requiredKeys = [
"pages.housekeeping.states.error.description",
];
const expectedDomainMessages = [
{
id: "operations",
labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description",
},
{
id: "people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
},
{
id: "content",
labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description",
},
{
id: "economy",
labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description",
},
{
id: "hotel",
labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description",
},
{
id: "system",
labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description",
},
] as const;
function resolveMessage(messages: unknown, key: string): unknown {
return key.split(".").reduce<unknown>((value, segment) => {
if (value === null || typeof value !== "object") return undefined;
@@ -44,14 +77,34 @@ describe("housekeeping localization contract", () => {
"states",
]);
for (const key of [
...HOUSEKEEPING_MANIFESTS.flatMap((manifest) => [
manifest.labelKey,
manifest.descriptionKey,
]),
...requiredKeys,
]) {
for (const key of requiredKeys) {
expect(resolveMessage(messages, key), key).toEqual(expect.any(String));
}
for (const [index, expected] of expectedDomainMessages.entries()) {
const manifest = HOUSEKEEPING_MANIFESTS[index];
expect(manifest).toMatchObject(expected);
expect(
resolveMessage(messages, expected.labelKey),
expected.labelKey,
).toEqual(expect.any(String));
expect(
resolveMessage(messages, expected.descriptionKey),
expected.descriptionKey,
).toEqual(expect.any(String));
}
});
it("uses idiomatic Italian copy for the housekeeping shell", () => {
expect(resolveMessage(itMessages, "pages.housekeeping.preview.badge")).toBe(
"Anteprima della struttura",
);
expect(
resolveMessage(itMessages, "pages.housekeeping.navigation.primary"),
).toBe("Aree housekeeping");
expect(
resolveMessage(itMessages, "pages.housekeeping.states.loading.title"),
).toBe("Caricamento in corso");
});
});
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import { anyCapability, type HousekeepingCapabilityContext } from "./contracts";
import { buildHousekeepingNavigation } from "./navigation";
import { createHousekeepingRegistry } from "./registry";
@@ -117,4 +118,34 @@ describe("housekeeping navigation", () => {
expect(translate).not.toHaveBeenCalledWith("system.title");
expect(translate).not.toHaveBeenCalledWith("system.description");
});
it("shows the real People manifest only for its view capabilities", () => {
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
const translate = (key: string) => key;
expect(
buildHousekeepingNavigation(
registry,
context([PERMS.MOD_CFH_VIEW]),
translate,
).map((domain) => domain.id),
).toContain("people");
expect(
buildHousekeepingNavigation(
registry,
context([PERMS.MOD_CFH_VIEW]),
translate,
).map((domain) => domain.id),
).not.toContain("economy");
for (const slug of [PERMS.MOD_ACTIONS, PERMS.MOD_CFH_EDIT]) {
const visibleDomainIds = buildHousekeepingNavigation(
registry,
context([slug]),
translate,
).map((domain) => domain.id);
expect(visibleDomainIds).not.toContain("people");
expect(visibleDomainIds).not.toContain("economy");
}
});
});
@@ -21,6 +21,82 @@ const manifest = (
routes: [],
});
const expectedManifests = [
{
id: "operations",
iconId: "inbox",
previewHref: "/admin-next/operations",
labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description",
slugs: [PERMS.ADMIN_DASHBOARD],
},
{
id: "people",
iconId: "users",
previewHref: "/admin-next/people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
slugs: [
PERMS.USERS_VIEW,
PERMS.MODERATION_VIEW,
PERMS.TICKETS_VIEW,
PERMS.BANS_VIEW,
PERMS.MOD_DASHBOARD,
PERMS.MOD_CFH_VIEW,
PERMS.MOD_TEAM_VIEW,
PERMS.MOD_TICKETS_VIEW,
PERMS.MOD_USERS_VIEW,
PERMS.MOD_BANS_VIEW,
],
},
{
id: "content",
iconId: "file-text",
previewHref: "/admin-next/content",
labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description",
slugs: [
PERMS.NEWS_VIEW,
PERMS.PAGES_VIEW,
PERMS.BANNERS_VIEW,
PERMS.EVENTS_VIEW,
PERMS.POLLS_VIEW,
PERMS.PREFIXES_VIEW,
],
},
{
id: "economy",
iconId: "gem",
previewHref: "/admin-next/economy",
labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description",
slugs: [PERMS.CATALOG_VIEW, PERMS.SHOP_VIEW],
},
{
id: "hotel",
iconId: "hotel",
previewHref: "/admin-next/hotel",
labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description",
slugs: [PERMS.ROOMS_VIEW, PERMS.RADIO_VIEW, PERMS.ASSETS_IMPORT],
},
{
id: "system",
iconId: "settings",
previewHref: "/admin-next/system",
labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description",
slugs: [
PERMS.SETTINGS_VIEW,
PERMS.LOGS_VIEW,
PERMS.ANALYTICS_VIEW,
PERMS.DEVOPS_VIEW,
PERMS.NOTIFICATIONS_VIEW,
PERMS.PERMISSIONS_MANAGE,
PERMS.RCON_EXECUTE,
],
},
] as const;
describe("housekeeping registry", () => {
it("registers the six approved domains in their locked order", () => {
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
@@ -150,4 +226,72 @@ describe("housekeeping registry", () => {
]),
).toThrow("unknown capability slug: admin.ghost.route");
});
it("locks every real manifest to its approved presentation and capability group", () => {
expect(HOUSEKEEPING_MANIFESTS).toHaveLength(6);
for (const [index, expected] of expectedManifests.entries()) {
const actual = HOUSEKEEPING_MANIFESTS[index];
expect(actual).toMatchObject({
id: expected.id,
iconId: expected.iconId,
previewHref: expected.previewHref,
labelKey: expected.labelKey,
descriptionKey: expected.descriptionKey,
});
expect(actual.routes).toEqual([]);
expect(actual.capability).toEqual({ mode: "any", slugs: expected.slugs });
}
});
it("rejects empty domain and route capability requirements", () => {
expect(() =>
createHousekeepingRegistry([
{ ...manifest("operations"), capability: { mode: "any", slugs: [] } },
]),
).toThrow("empty capability requirement");
expect(() =>
createHousekeepingRegistry([
{
...manifest("people"),
routes: [
{
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/people/users",
capability: { mode: "any", slugs: [] },
},
],
},
]),
).toThrow("empty capability requirement");
});
it("rejects duplicate route identities and hrefs across domains", () => {
const route = {
id: "shared",
labelKey: "pages.housekeeping.domains.people.title",
href: "/admin-next/shared",
capability: anyCapability(PERMS.USERS_VIEW),
};
expect(() =>
createHousekeepingRegistry([
{ ...manifest("people"), routes: [route] },
{
...manifest("content"),
routes: [{ ...route, href: "/admin-next/content/shared" }],
},
]),
).toThrow("duplicate route id");
expect(() =>
createHousekeepingRegistry([
{ ...manifest("people"), routes: [route] },
{
...manifest("content"),
routes: [{ ...route, id: "content-shared" }],
},
]),
).toThrow("duplicate route href");
});
});
+4 -4
View File
@@ -3263,13 +3263,13 @@
},
"housekeeping": {
"preview": {
"badge": "Anteprima della base",
"badge": "Anteprima della struttura",
"commandDisabled": "Ricerca e comandi saranno abilitati in un sottoprogetto successivo.",
"backToSite": "Torna al sito"
},
"navigation": {
"skipToContent": "Vai al contenuto",
"primary": "Domini housekeeping",
"primary": "Aree housekeeping",
"contextual": "Navigazione del dominio"
},
"domains": {
@@ -3300,8 +3300,8 @@
},
"states": {
"loading": {
"title": "Caricamento housekeeping",
"description": "Preparazione degli strumenti housekeeping disponibili."
"title": "Caricamento in corso",
"description": "Stiamo preparando gli strumenti housekeeping disponibili."
},
"empty": {
"title": "Nessun contenuto disponibile",