fix(housekeeping): harden system workflow boundaries
CI / check (pull_request) Successful in 33s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-29 00:25:47 +02:00
1 parent 3788ecd9f1
commit c325c53774
18 files changed
+809 -79

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const doubles = vi.hoisted(() => ({
canAccess: vi.fn(),
getApiAdminContext: vi.fn(),
mutationExecute: vi.fn(),
reportError: vi.fn(),
revalidateTag: vi.fn(),
}));
vi.mock("@/features/housekeeping/domains/system/services/mutations", () => ({
systemMutationService: { execute: doubles.mutationExecute },
}));
vi.mock("@/lib/permissions", () => ({
canAccess: doubles.canAccess,
getApiAdminContext: doubles.getApiAdminContext,
}));
vi.mock("@/lib/admin/authorization-events", () => ({
logAuthorizationEvent: vi.fn(),
}));
vi.mock("@/lib/auth", () => ({ auth: vi.fn() }));
vi.mock("@/lib/rate-limit", () => ({ rateLimit: vi.fn() }));
vi.mock("@/lib/report-error", () => ({ reportError: doubles.reportError }));
vi.mock("@/lib/foundation/security", () => ({
extractClientIpAsync: vi.fn(async () => "198.51.100.8"),
}));
vi.mock("@/lib/foundation/request-context", () => ({
createStore: vi.fn(() => ({})),
getRequestId: vi.fn(() => "legacy-permissions-request"),
runWithStore: vi.fn((_store: unknown, callback: () => Promise<unknown>) =>
callback(),
),
setContextUserId: vi.fn(),
}));
vi.mock("next/cache", () => ({ revalidateTag: doubles.revalidateTag }));
import { deleteRank, setCmsPermissions } from "./permissions";
const permissions = {
has: () => true,
hasAny: () => true,
hasAll: () => true,
isSuperAdmin: false,
};
beforeEach(() => {
vi.clearAllMocks();
doubles.canAccess.mockReturnValue(true);
doubles.getApiAdminContext.mockResolvedValue({
session: {
expires: "2099-01-01T00:00:00.000Z",
user: {
id: 42,
name: "operator",
username: "operator",
rank: 7,
look: "hd-180-1",
mail: "[email protected]",
},
},
permissions,
});
});
describe("legacy permission action error parity", () => {
it("sanitizes typed infrastructure failure through the real adminAction boundary", async () => {
doubles.mutationExecute.mockResolvedValue({
ok: false,
error: {
code: "DEPENDENCY_UNAVAILABLE",
messageKey: "errors.housekeeping.dependencyUnavailable",
},
correlationId: "dependency-correlation",
});
await expect(deleteRank({ id: 7 })).resolves.toEqual({
ok: false,
error: "Internal server error",
fieldErrors: undefined,
});
});
it("preserves the established rank-in-use ActionError text", async () => {
doubles.mutationExecute.mockResolvedValue({
ok: false,
error: {
code: "CONFLICT",
messageKey: "errors.housekeeping.system.rankInUse",
fieldErrors: { rank: ["3"] },
},
correlationId: "rank-in-use-correlation",
});
await expect(deleteRank({ id: 7 })).resolves.toEqual({
ok: false,
error: "Cannot delete: 3 users have this rank",
fieldErrors: undefined,
});
});
it("preserves the established role-not-found ActionError text", async () => {
doubles.mutationExecute.mockResolvedValue({
ok: false,
error: {
code: "NOT_FOUND",
messageKey: "errors.housekeeping.system.roleNotFound",
},
correlationId: "role-not-found-correlation",
});
await expect(
setCmsPermissions({ roleId: 7, permissionSlugs: [] }),
).resolves.toEqual({
ok: false,
error: "Role not found",
fieldErrors: undefined,
});
});
});
+1 -1
View File
@@ -49,7 +49,7 @@ async function runAccessMutation(
if (result.error.messageKey === "errors.housekeeping.system.roleNotFound") {
throw new ActionError("Role not found");
}
throw new ActionError(result.error.messageKey);
throw new Error(result.error.messageKey);
}
const createRankSchema = z.object({