Files
EpicNext-Cms/src/features/housekeeping/domains/content/services/mutations.test.ts
T

107 lines
3.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
import {
CONTENT_MUTATION_OPERATIONS,
createContentMutationInvocation,
createContentMutationService,
} from "./mutations";
function context(
granted: readonly string[],
actorId = 42,
): HousekeepingCapabilityContext {
const permissions = new Set(granted);
return {
actor: { id: actorId, 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("Content mutation service authority", () => {
it("rehydrates server authority and rejects forged actor identity", async () => {
const adapter = { execute: vi.fn() };
const service = createContentMutationService(adapter, async () =>
context([PERMS.NEWS_EDIT], 42),
);
const result = await service.execute(
{ correlationId: "forged", expectedActorId: 7 },
"article.change",
{ action: "create", title: "Forged" },
);
expect(result).toMatchObject({
ok: false,
error: { code: "FORBIDDEN" },
});
expect(adapter.execute).not.toHaveBeenCalled();
});
it("requires the exact operation ACL even after dispatcher authorization", async () => {
const adapter = { execute: vi.fn() };
const service = createContentMutationService(adapter, async () =>
context([PERMS.NEWS_EDIT]),
);
const result = await service.execute(
{ correlationId: "brand", expectedActorId: 42 },
"theme.update",
{},
);
expect(result).toMatchObject({
ok: false,
error: { code: "FORBIDDEN" },
});
expect(adapter.execute).not.toHaveBeenCalled();
});
it("executes every declared operation through the real service boundary", async () => {
const execute = vi.fn(async (_operation, _input, mutationContext) => ({
before: null,
after: { actorId: mutationContext.capability.actor.id },
}));
const service = createContentMutationService({ execute }, async () =>
context(Object.values(PERMS)),
);
const invocation = createContentMutationInvocation(
{ id: 42 },
"operation-matrix",
);
for (const operation of CONTENT_MUTATION_OPERATIONS) {
const result = await service.execute(invocation, operation, {});
expect(result.ok, operation).toBe(true);
}
expect(execute).toHaveBeenCalledTimes(CONTENT_MUTATION_OPERATIONS.length);
});
it("maps adapter failures without exposing storage or template payloads", async () => {
const service = createContentMutationService(
{
execute: async () => {
throw new Error("C:\\private\\template.json: secret body");
},
},
async () => context([PERMS.SETTINGS_EDIT]),
);
const result = await service.execute(
{ correlationId: "redacted", expectedActorId: 42 },
"translation.cms.save",
{ data: { secret: "body" } },
);
expect(result).toMatchObject({
ok: false,
error: {
code: "DEPENDENCY_UNAVAILABLE",
messageKey: "errors.housekeeping.dependencyUnavailable",
},
});
expect(JSON.stringify(result)).not.toContain("secret body");
});
});