feat(housekeeping): integrate studio operations
CI / check (pull_request) Successful in 38s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-30 15:29:58 +02:00
1 parent 020c06f172
commit 2e95a106e0
36 files changed
+2296 -60

No files matched your search

@@ -0,0 +1,125 @@
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,
});
});
});