fix(housekeeping): harden system workflow boundaries
This commit is contained in:
1 parent
3788ecd9f1
commit
c325c53774
18 files changed
+809
-79
No files matched your search
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
Reference in new issue
Block a user