feat(housekeeping): compose operational inbox
This commit is contained in:
1 parent
bcb0ca977f
commit
300ac0ef95
13 files changed
+1594
-7
No files matched your search
@@ -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));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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
Reference in new issue
Block a user