import { describe, expect, it, vi } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import type { HousekeepingCapabilityContext } from "../../../foundation/contracts"; import { createInMemoryStudioOperationRepository, createStudioOperationQuery, STUDIO_OPERATION_KINDS, } from "./studio"; 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("Studio operation query", () => { it("defines the exact ten operation kinds", () => { expect(STUDIO_OPERATION_KINDS).toEqual([ "badge", "clone", "clothing", "effect", "furni", "maintenance", "pet", "repair-icons", "sync", "upload", ]); }); it("fails closed before invoking an unauthorized adapter", async () => { const load = vi.fn(); const result = await createStudioOperationQuery({ load }).run(context([]), { routeId: "hotel.studio.furni", }); expect(result).toMatchObject({ ok: false, error: { code: "FORBIDDEN" } }); expect(load).not.toHaveBeenCalled(); }); it("normalizes filters and preserves truthful external-source outages", async () => { const load = vi.fn(async (input) => ({ kind: "studio" as const, items: [], total: 0, partialDependencies: ["external-sources"] as const, input, })); const result = await createStudioOperationQuery({ load }).run( context([PERMS.ASSETS_IMPORT]), { routeId: "hotel.studio.clone", list: { search: " sofa ", pageSize: 999, offset: 999_999 }, }, ); expect(load).toHaveBeenCalledWith({ routeId: "hotel.studio.clone", operationKind: "clone", list: { search: "sofa", pageSize: 100, offset: 100_000 }, }); expect(result).toMatchObject({ ok: true, data: { partialDependencies: ["external-sources"] }, }); }); it("loads the audit route across all operation kinds without inventing a command kind", async () => { const load = vi.fn(async () => ({ kind: "studio" as const, items: [], total: 0, partialDependencies: [] as const, })); const result = await createStudioOperationQuery({ load }).run( context([PERMS.ASSETS_IMPORT]), { routeId: "hotel.studio.audit" }, ); expect(result.ok).toBe(true); expect(load).toHaveBeenCalledWith( expect.objectContaining({ operationKind: null }), ); }); }); describe("Studio operation repository", () => { it("enforces monotonic progress and terminal event ordering", async () => { const repository = createInMemoryStudioOperationRepository(); await repository.persistIntent({ operationId: "op-1", kind: "furni", actorId: 42, correlationId: "corr-1", createdAt: "2026-08-30T10:00:00.000Z", }); await repository.appendEvent({ operationId: "op-1", kind: "furni", phase: "running", completed: 2, total: 4, messageKey: "pages.housekeeping.studio.running", correlationId: "corr-1", }); await expect( repository.appendEvent({ operationId: "op-1", kind: "furni", phase: "running", completed: 1, total: 4, messageKey: "pages.housekeeping.studio.running", correlationId: "corr-1", }), ).rejects.toThrow("non-monotonic Studio progress"); expect(await repository.get("op-1")).toMatchObject({ operationId: "op-1", completed: 2, }); }); });