fix: close housekeeping foundation review findings
This commit is contained in:
1 parent
ebc263da35
commit
08f8d54888
21 files changed
+593
-46
No files matched your search
@@ -1,11 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
|
||||
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
|
||||
import {
|
||||
HOUSEKEEPING_DOMAIN_IDS,
|
||||
type HousekeepingDomainId,
|
||||
} from "../migration/types";
|
||||
import { anyCapability, type HousekeepingDomainManifest } from "./contracts";
|
||||
import {
|
||||
anyCapability,
|
||||
type CapabilityRequirement,
|
||||
type HousekeepingDomainManifest,
|
||||
type HousekeepingInboxSource,
|
||||
type HousekeepingSearchProvider,
|
||||
type HousekeepingWidgetDefinition,
|
||||
ok,
|
||||
} from "./contracts";
|
||||
import { createHousekeepingRegistry } from "./registry";
|
||||
|
||||
const manifest = (
|
||||
@@ -19,6 +28,45 @@ const manifest = (
|
||||
previewHref: `/admin-next/${id}`,
|
||||
capability: anyCapability(capabilitySlug),
|
||||
routes: [],
|
||||
searchProviders: [],
|
||||
inboxSources: [],
|
||||
widgets: [],
|
||||
});
|
||||
|
||||
const providerCapability = anyCapability(PERMS.USERS_VIEW);
|
||||
|
||||
const searchProvider = (
|
||||
id: string,
|
||||
owner: HousekeepingDomainId = "people",
|
||||
capability: CapabilityRequirement = providerCapability,
|
||||
): HousekeepingSearchProvider => ({
|
||||
id,
|
||||
owner,
|
||||
capability,
|
||||
search: async () => ok([], "search"),
|
||||
});
|
||||
|
||||
const inboxSource = (
|
||||
id: string,
|
||||
owner: HousekeepingDomainId = "people",
|
||||
capability: CapabilityRequirement = providerCapability,
|
||||
): HousekeepingInboxSource => ({
|
||||
id,
|
||||
owner,
|
||||
capability,
|
||||
getItems: async () => ok({ items: [], availability: "available" }, "inbox"),
|
||||
});
|
||||
|
||||
const widget = (
|
||||
id: string,
|
||||
owner: HousekeepingDomainId = "people",
|
||||
capability: CapabilityRequirement = providerCapability,
|
||||
): HousekeepingWidgetDefinition => ({
|
||||
id,
|
||||
owner,
|
||||
capability,
|
||||
kind: "optional",
|
||||
load: async () => ok(null, "widget"),
|
||||
});
|
||||
|
||||
const expectedManifests = [
|
||||
@@ -47,6 +95,18 @@ const expectedManifests = [
|
||||
PERMS.MOD_TICKETS_VIEW,
|
||||
PERMS.MOD_USERS_VIEW,
|
||||
PERMS.MOD_BANS_VIEW,
|
||||
PERMS.USERS_EDIT,
|
||||
PERMS.USERS_BAN,
|
||||
PERMS.USERS_RESET_PASSWORD,
|
||||
PERMS.MODERATION_EDIT,
|
||||
PERMS.TICKETS_EDIT,
|
||||
PERMS.SETTINGS_VIEW,
|
||||
PERMS.SETTINGS_EDIT,
|
||||
PERMS.WORDFILTER_VIEW,
|
||||
PERMS.WORDFILTER_EDIT,
|
||||
PERMS.MOD_ACTIONS,
|
||||
PERMS.MOD_CFH_EDIT,
|
||||
PERMS.MOD_TICKETS_EDIT,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -62,6 +122,14 @@ const expectedManifests = [
|
||||
PERMS.EVENTS_VIEW,
|
||||
PERMS.POLLS_VIEW,
|
||||
PERMS.PREFIXES_VIEW,
|
||||
PERMS.NEWS_EDIT,
|
||||
PERMS.PAGES_EDIT,
|
||||
PERMS.BANNERS_EDIT,
|
||||
PERMS.EVENTS_EDIT,
|
||||
PERMS.POLLS_EDIT,
|
||||
PERMS.PREFIXES_EDIT,
|
||||
PERMS.SETTINGS_VIEW,
|
||||
PERMS.SETTINGS_EDIT,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -70,7 +138,12 @@ const expectedManifests = [
|
||||
previewHref: "/admin-next/economy",
|
||||
labelKey: "pages.housekeeping.domains.economy.title",
|
||||
descriptionKey: "pages.housekeeping.domains.economy.description",
|
||||
slugs: [PERMS.CATALOG_VIEW, PERMS.SHOP_VIEW],
|
||||
slugs: [
|
||||
PERMS.CATALOG_VIEW,
|
||||
PERMS.SHOP_VIEW,
|
||||
PERMS.CATALOG_EDIT,
|
||||
PERMS.SHOP_EDIT,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "hotel",
|
||||
@@ -78,7 +151,16 @@ const expectedManifests = [
|
||||
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],
|
||||
slugs: [
|
||||
PERMS.ROOMS_VIEW,
|
||||
PERMS.RADIO_VIEW,
|
||||
PERMS.ASSETS_IMPORT,
|
||||
PERMS.ROOMS_EDIT,
|
||||
PERMS.ROOMS_DELETE,
|
||||
PERMS.RADIO_EDIT,
|
||||
PERMS.PAGES_VIEW,
|
||||
PERMS.CATALOG_EDIT,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "system",
|
||||
@@ -94,6 +176,8 @@ const expectedManifests = [
|
||||
PERMS.NOTIFICATIONS_VIEW,
|
||||
PERMS.PERMISSIONS_MANAGE,
|
||||
PERMS.RCON_EXECUTE,
|
||||
PERMS.SETTINGS_EDIT,
|
||||
PERMS.NOTIFICATIONS_EDIT,
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
@@ -240,10 +324,149 @@ describe("housekeeping registry", () => {
|
||||
descriptionKey: expected.descriptionKey,
|
||||
});
|
||||
expect(actual.routes).toEqual([]);
|
||||
expect(actual.searchProviders).toEqual([]);
|
||||
expect(actual.inboxSources).toEqual([]);
|
||||
expect(actual.widgets).toEqual([]);
|
||||
expect(actual.capability).toEqual({ mode: "any", slugs: expected.slugs });
|
||||
}
|
||||
});
|
||||
|
||||
it("covers every capability attributed to each domain's migration rows", () => {
|
||||
for (const manifest of HOUSEKEEPING_MANIFESTS) {
|
||||
const attributedCapabilities = new Set(
|
||||
HOUSEKEEPING_MIGRATION_MATRIX.filter(
|
||||
(entry) => entry.targetDomain === manifest.id,
|
||||
).flatMap((entry) => [
|
||||
...entry.capabilities.read,
|
||||
...entry.capabilities.mutate,
|
||||
]),
|
||||
);
|
||||
|
||||
for (const slug of attributedCapabilities) {
|
||||
expect(
|
||||
manifest.capability.slugs,
|
||||
`domain ${manifest.id}: ${slug}`,
|
||||
).toContain(slug);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects duplicate provider and widget ids across domain manifests", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), searchProviders: [searchProvider("shared")] },
|
||||
{
|
||||
...manifest("content"),
|
||||
searchProviders: [searchProvider("shared", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate search provider id: shared");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), inboxSources: [inboxSource("shared")] },
|
||||
{
|
||||
...manifest("content"),
|
||||
inboxSources: [inboxSource("shared", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate inbox source id: shared");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), widgets: [widget("shared")] },
|
||||
{
|
||||
...manifest("content"),
|
||||
widgets: [widget("shared", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate widget id: shared");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), searchProviders: [searchProvider("shared")] },
|
||||
{
|
||||
...manifest("content"),
|
||||
widgets: [widget("shared", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("duplicate widget id: shared");
|
||||
});
|
||||
|
||||
it("rejects provider and widget ownership outside their manifest", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
searchProviders: [searchProvider("people.users", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("search provider owner mismatch: people.users");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
inboxSources: [inboxSource("people.tickets", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("inbox source owner mismatch: people.tickets");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
widgets: [widget("people.queue", "content")],
|
||||
},
|
||||
]),
|
||||
).toThrow("widget owner mismatch: people.queue");
|
||||
});
|
||||
|
||||
it("rejects empty ids and invalid capability shapes for registry metadata", () => {
|
||||
const invalidCapability = {
|
||||
mode: "some",
|
||||
slugs: [PERMS.USERS_VIEW],
|
||||
} as unknown as CapabilityRequirement;
|
||||
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{ ...manifest("people"), searchProviders: [searchProvider(" ")] },
|
||||
]),
|
||||
).toThrow("empty search provider id");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
inboxSources: [
|
||||
inboxSource("people.tickets", "people", invalidCapability),
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("invalid capability requirement");
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
widgets: [
|
||||
widget(
|
||||
"people.queue",
|
||||
"people",
|
||||
anyCapability("admin.ghost.widget"),
|
||||
),
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("unknown capability slug: admin.ghost.widget");
|
||||
});
|
||||
|
||||
it("rejects widget kinds outside the mandatory or optional contract", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
{
|
||||
...manifest("people"),
|
||||
widgets: [
|
||||
{ ...widget("people.queue"), kind: "fixed" as "mandatory" },
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toThrow("invalid widget kind: people.queue");
|
||||
});
|
||||
|
||||
it("rejects empty domain and route capability requirements", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
|
||||
Reference in new issue
Block a user