feat(housekeeping): compose operational inbox
CI / check (pull_request) Successful in 37s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-30 16:26:20 +02:00
1 parent bcb0ca977f
commit 300ac0ef95
13 files changed
+1594 -7

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { HousekeepingCapabilityContext } from "@/features/housekeeping/foundation/contracts";
import { PERMS } from "@/lib/permission-slugs";
const { getContextMock, loadInboxMock } = vi.hoisted(() => ({
getContextMock: vi.fn(),
loadInboxMock: vi.fn(),
}));
vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
getHousekeepingCapabilityContext: getContextMock,
}));
vi.mock("@/features/housekeeping/foundation/inbox/inbox-service", () => ({
loadHousekeepingInbox: loadInboxMock,
}));
import { executeHousekeepingInbox } from "./housekeeping-inbox";
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.USERS_VIEW,
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
};
describe("housekeeping inbox action", () => {
beforeEach(() => {
getContextMock.mockReset().mockResolvedValue(context);
loadInboxMock.mockReset().mockResolvedValue({
items: [],
errors: [],
correlationId: "inbox-action",
});
});
it("binds inbox composition to a fresh server capability context", async () => {
const response = await executeHousekeepingInbox();
expect(getContextMock).toHaveBeenCalledOnce();
expect(loadInboxMock).toHaveBeenCalledWith(context);
expect(response.correlationId).toBe("inbox-action");
});
it("returns a typed partial envelope when the boundary throws", async () => {
loadInboxMock.mockRejectedValueOnce(new Error("inbox unavailable"));
const response = await executeHousekeepingInbox();
expect(response.items).toEqual([]);
expect(response.errors).toEqual([{ sourceId: "inbox", code: "INTERNAL" }]);
expect(response.correlationId).toEqual(expect.any(String));
});
});
+25
View File
@@ -0,0 +1,25 @@
"use server";
import { createCorrelationId } from "@/features/housekeeping/foundation/correlation";
import {
type HousekeepingInboxResponse,
loadHousekeepingInbox,
} from "@/features/housekeeping/foundation/inbox/inbox-service";
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
function failedInbox(): HousekeepingInboxResponse {
return {
items: [],
errors: [{ sourceId: "inbox", code: "INTERNAL" }],
correlationId: createCorrelationId(),
};
}
export async function executeHousekeepingInbox(): Promise<HousekeepingInboxResponse> {
try {
const context = await getHousekeepingCapabilityContext();
return await loadHousekeepingInbox(context);
} catch {
return failedInbox();
}
}
File diff suppressed because it is too large. Load diff
@@ -3,7 +3,10 @@ import {
anyCapability, anyCapability,
type HousekeepingDomainManifest, type HousekeepingDomainManifest,
} from "../../foundation/contracts"; } from "../../foundation/contracts";
import { SYSTEM_INBOX_SOURCES } from "./inbox";
import { SYSTEM_ROUTES } from "./routes"; import { SYSTEM_ROUTES } from "./routes";
import { SYSTEM_SEARCH_PROVIDERS } from "./search";
import { SYSTEM_WIDGETS } from "./widgets";
export const systemManifest = { export const systemManifest = {
id: "system", id: "system",
@@ -23,7 +26,7 @@ export const systemManifest = {
PERMS.NOTIFICATIONS_EDIT, PERMS.NOTIFICATIONS_EDIT,
), ),
routes: SYSTEM_ROUTES, routes: SYSTEM_ROUTES,
searchProviders: [], searchProviders: SYSTEM_SEARCH_PROVIDERS,
inboxSources: [], inboxSources: SYSTEM_INBOX_SOURCES,
widgets: [], widgets: SYSTEM_WIDGETS,
} satisfies HousekeepingDomainManifest; } satisfies HousekeepingDomainManifest;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -614,6 +614,27 @@ const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
"src/features/housekeeping/domains/system/commands/system-commands.ts", "src/features/housekeeping/domains/system/commands/system-commands.ts",
new Set(["src/features/housekeeping/domains/system/services/mutations"]), new Set(["src/features/housekeeping/domains/system/services/mutations"]),
], ],
[
"src/features/housekeeping/domains/system/search.ts",
new Set([
"src/features/housekeeping/domains/system/queries/observability",
"src/features/housekeeping/domains/system/queries/operations",
]),
],
[
"src/features/housekeeping/domains/system/inbox.ts",
new Set([
"src/features/housekeeping/domains/system/queries/observability",
"src/features/housekeeping/domains/system/queries/operations",
]),
],
[
"src/features/housekeeping/domains/system/widgets.ts",
new Set([
"src/features/housekeeping/domains/system/queries/observability",
"src/features/housekeeping/domains/system/queries/operations",
]),
],
[ [
"src/features/housekeeping/domains/system/pages/access.tsx", "src/features/housekeeping/domains/system/pages/access.tsx",
new Set(["src/features/housekeeping/domains/system/queries/access"]), new Set(["src/features/housekeeping/domains/system/queries/access"]),
@@ -703,7 +724,12 @@ const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
], ],
[ [
"src/features/housekeeping/domains/system/manifest.ts", "src/features/housekeeping/domains/system/manifest.ts",
new Set(["src/features/housekeeping/domains/system/routes"]), new Set([
"src/features/housekeeping/domains/system/inbox",
"src/features/housekeeping/domains/system/routes",
"src/features/housekeeping/domains/system/search",
"src/features/housekeeping/domains/system/widgets",
]),
], ],
[ [
"src/features/housekeeping/domains/system/route-handlers.ts", "src/features/housekeeping/domains/system/route-handlers.ts",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -16,7 +16,10 @@ import { PEOPLE_INBOX_SOURCES } from "../domains/people/inbox";
import { PEOPLE_ROUTES } from "../domains/people/routes"; import { PEOPLE_ROUTES } from "../domains/people/routes";
import { PEOPLE_SEARCH_PROVIDERS } from "../domains/people/search"; import { PEOPLE_SEARCH_PROVIDERS } from "../domains/people/search";
import { PEOPLE_WIDGETS } from "../domains/people/widgets"; import { PEOPLE_WIDGETS } from "../domains/people/widgets";
import { SYSTEM_INBOX_SOURCES } from "../domains/system/inbox";
import { SYSTEM_ROUTES } from "../domains/system/routes"; import { SYSTEM_ROUTES } from "../domains/system/routes";
import { SYSTEM_SEARCH_PROVIDERS } from "../domains/system/search";
import { SYSTEM_WIDGETS } from "../domains/system/widgets";
import { HOUSEKEEPING_MANIFESTS } from "../manifests"; import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix"; import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
import { import {
@@ -392,7 +395,9 @@ describe("housekeeping registry", () => {
? ECONOMY_SEARCH_PROVIDERS ? ECONOMY_SEARCH_PROVIDERS
: expected.id === "hotel" : expected.id === "hotel"
? HOTEL_SEARCH_PROVIDERS ? HOTEL_SEARCH_PROVIDERS
: [], : expected.id === "system"
? SYSTEM_SEARCH_PROVIDERS
: [],
); );
expect(actual.inboxSources).toEqual( expect(actual.inboxSources).toEqual(
expected.id === "people" expected.id === "people"
@@ -403,7 +408,9 @@ describe("housekeeping registry", () => {
? ECONOMY_INBOX_SOURCES ? ECONOMY_INBOX_SOURCES
: expected.id === "hotel" : expected.id === "hotel"
? HOTEL_INBOX_SOURCES ? HOTEL_INBOX_SOURCES
: [], : expected.id === "system"
? SYSTEM_INBOX_SOURCES
: [],
); );
expect(actual.widgets).toEqual( expect(actual.widgets).toEqual(
expected.id === "people" expected.id === "people"
@@ -414,7 +421,9 @@ describe("housekeeping registry", () => {
? ECONOMY_WIDGETS ? ECONOMY_WIDGETS
: expected.id === "hotel" : expected.id === "hotel"
? HOTEL_WIDGETS ? HOTEL_WIDGETS
: [], : expected.id === "system"
? SYSTEM_WIDGETS
: [],
); );
expect(actual.capability).toEqual({ mode: "any", slugs: expected.slugs }); expect(actual.capability).toEqual({ mode: "any", slugs: expected.slugs });
} }
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff