feat(housekeeping): integrate studio operations
This commit is contained in:
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user