import { describe, expect, it, vi } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import type { HousekeepingCapabilityContext } from "../../foundation/contracts"; import { createHotelInboxSources } from "./inbox"; import { createHotelSearchProviders } from "./search"; import { createHotelWidgets } from "./widgets"; function context(granted: readonly string[]): HousekeepingCapabilityContext { const permissions = new Set(granted); return { actor: { id: 42, username: "operator", rank: 7 }, isSuperAdmin: false, has: (slug) => permissions.has(slug), hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)), hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)), }; } describe("Hotel Studio providers", () => { it("publishes searchable Studio operations with capability filtering", async () => { const [provider] = createHotelSearchProviders({ studio: vi.fn(async () => [ { id: "op-1", title: "Furniture import", description: "running", href: "/ase/hotel/studio/furni", }, ]), }); const result = await provider.search(context([PERMS.ASSETS_IMPORT]), { term: " furni ", limit: 50, }); expect(result).toMatchObject({ ok: true, data: [{ id: "op-1", domain: "hotel" }], }); }); it("exposes an operational inbox source and mandatory active widget", async () => { const operation = { operationId: "op-1", kind: "sync" as const, phase: "failed" as const, completed: 3, total: 5, messageKey: "errors.housekeeping.dependencyUnavailable", correlationId: "corr-1", actorId: 42, createdAt: "2026-08-30T10:00:00.000Z", updatedAt: "2026-08-30T10:01:00.000Z", }; const [source] = createHotelInboxSources({ operations: vi.fn(async () => [operation]), }); const [widget] = createHotelWidgets({ activeOperations: vi.fn(async () => ({ running: 2, failed: 1 })), }); expect(widget.kind).toBe("mandatory"); expect( await source.getItems( context([PERMS.ASSETS_IMPORT]), new AbortController().signal, ), ).toMatchObject({ ok: true, data: { items: [{ state: "failed" }] } }); expect( await widget.load( context([PERMS.ASSETS_IMPORT]), new AbortController().signal, ), ).toMatchObject({ ok: true, data: { running: 2, failed: 1 } }); }); });