197 lines
4.6 KiB
TypeScript
197 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
allCapabilities,
|
|
anyCapability,
|
|
type CapabilityRequirement,
|
|
fail,
|
|
type HousekeepingCommand,
|
|
type HousekeepingDomainManifest,
|
|
type HousekeepingInboxSource,
|
|
type HousekeepingInboxSourceResult,
|
|
type HousekeepingQuery,
|
|
type HousekeepingSearchProvider,
|
|
type HousekeepingSearchResult,
|
|
type HousekeepingWidgetDefinition,
|
|
type HousekeepingWorkItem,
|
|
mapUnknownError,
|
|
ok,
|
|
} from ".";
|
|
|
|
const capability = anyCapability("admin.users.view");
|
|
|
|
const workItem = {
|
|
sourceId: "tickets",
|
|
itemId: "ticket-42",
|
|
deduplicationKey: "ticket:42",
|
|
domain: "people",
|
|
capability,
|
|
severity: "warning",
|
|
priority: "high",
|
|
ageMs: 60_000,
|
|
state: "open",
|
|
occurredAt: "2026-08-24T12:00:00.000Z",
|
|
titleKey: "pages.housekeeping.items.ticket",
|
|
context: { ticketId: "42" },
|
|
href: "/ase/people/tickets/42",
|
|
freshness: "fresh",
|
|
actions: [
|
|
{
|
|
id: "people.ticket.open",
|
|
labelKey: "pages.housekeeping.items.ticket.open",
|
|
commandId: "people.ticket.open",
|
|
capability,
|
|
},
|
|
],
|
|
} satisfies HousekeepingWorkItem;
|
|
|
|
const searchResult = {
|
|
id: "user-42",
|
|
domain: "people",
|
|
type: "entity",
|
|
title: "operator",
|
|
description: "A Housekeeping operator",
|
|
href: "/ase/people/users/42",
|
|
capability,
|
|
} satisfies HousekeepingSearchResult;
|
|
|
|
const searchProvider: HousekeepingSearchProvider = {
|
|
id: "people.users",
|
|
owner: "people",
|
|
capability,
|
|
search: async () => ok([searchResult], "search-1"),
|
|
};
|
|
|
|
const inboxSourceResult = {
|
|
items: [workItem],
|
|
availability: "available",
|
|
} satisfies HousekeepingInboxSourceResult;
|
|
|
|
const inboxSource: HousekeepingInboxSource = {
|
|
id: "people.tickets",
|
|
owner: "people",
|
|
capability,
|
|
getItems: async () => ok(inboxSourceResult, "inbox-1"),
|
|
};
|
|
|
|
const widget: HousekeepingWidgetDefinition = {
|
|
id: "people.queue",
|
|
owner: "people",
|
|
capability,
|
|
kind: "mandatory",
|
|
load: async () => ok({ count: 2 }, "widget-1"),
|
|
};
|
|
|
|
const query: HousekeepingQuery<{ id: number }, { id: number }> = {
|
|
id: "people.user",
|
|
owner: "people",
|
|
capability,
|
|
run: async (_context, input) => ok(input, "query-1"),
|
|
};
|
|
|
|
const command: HousekeepingCommand<{ id: number }, { id: number }> = {
|
|
id: "people.user.disable",
|
|
owner: "people",
|
|
risk: "sensitive",
|
|
capability,
|
|
requiresReason: true,
|
|
execute: async (_context, input) => ok(input, "command-1"),
|
|
};
|
|
|
|
const manifest: HousekeepingDomainManifest = {
|
|
id: "people",
|
|
labelKey: "pages.housekeeping.domains.people.title",
|
|
descriptionKey: "pages.housekeeping.domains.people.description",
|
|
iconId: "users",
|
|
canonicalHref: "/ase/people",
|
|
capability,
|
|
routes: [],
|
|
searchProviders: [searchProvider],
|
|
inboxSources: [inboxSource],
|
|
widgets: [widget],
|
|
};
|
|
|
|
describe("housekeeping foundation contracts", () => {
|
|
it("preserves correlation IDs for successful results", () => {
|
|
expect(ok({ count: 2 }, "corr-1")).toEqual({
|
|
ok: true,
|
|
data: { count: 2 },
|
|
correlationId: "corr-1",
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
"UNAUTHENTICATED",
|
|
"FORBIDDEN",
|
|
"VALIDATION",
|
|
"NOT_FOUND",
|
|
"CONFLICT",
|
|
"RATE_LIMITED",
|
|
"DEPENDENCY_UNAVAILABLE",
|
|
"TIMEOUT",
|
|
"INTERNAL",
|
|
] as const)("returns the approved %s error contract", (code) => {
|
|
expect(
|
|
fail(code, `errors.housekeeping.${code.toLowerCase()}`, "corr-2", {
|
|
name: ["errors.validation.required"],
|
|
}),
|
|
).toEqual({
|
|
ok: false,
|
|
error: {
|
|
code,
|
|
messageKey: `errors.housekeeping.${code.toLowerCase()}`,
|
|
fieldErrors: { name: ["errors.validation.required"] },
|
|
},
|
|
correlationId: "corr-2",
|
|
});
|
|
});
|
|
|
|
it("sanitizes unknown errors with the stable internal taxonomy", () => {
|
|
expect(mapUnknownError(new Error("database password exposed"))).toEqual({
|
|
code: "INTERNAL",
|
|
messageKey: "errors.housekeeping.internal",
|
|
});
|
|
});
|
|
|
|
it("rejects empty capability requirements", () => {
|
|
expect(() => anyCapability()).toThrow("capability requirement is empty");
|
|
expect(() => allCapabilities()).toThrow("capability requirement is empty");
|
|
});
|
|
|
|
it("exports assignable type-only contracts", () => {
|
|
const requirements: readonly CapabilityRequirement[] = [
|
|
capability,
|
|
allCapabilities("admin.users.view"),
|
|
];
|
|
|
|
expect([
|
|
workItem,
|
|
searchProvider,
|
|
inboxSource,
|
|
widget,
|
|
query,
|
|
command,
|
|
manifest,
|
|
requirements,
|
|
]).toHaveLength(8);
|
|
});
|
|
|
|
it("carries stable ownership and capability metadata for registry entries", () => {
|
|
expect(searchProvider).toMatchObject({
|
|
id: "people.users",
|
|
owner: "people",
|
|
capability,
|
|
});
|
|
expect(inboxSource).toMatchObject({
|
|
id: "people.tickets",
|
|
owner: "people",
|
|
capability,
|
|
});
|
|
expect(widget).toMatchObject({
|
|
id: "people.queue",
|
|
owner: "people",
|
|
capability,
|
|
kind: "mandatory",
|
|
});
|
|
});
|
|
});
|