import { beforeEach, describe, expect, it, vi } from "vitest"; import { PERMS } from "@/lib/permission-slugs"; import type { AuditEntry } from "@/lib/services/audit"; import type { HousekeepingCapabilityContext } from "../../../foundation/contracts"; const { alertUser, audit, resolveServerContext } = vi.hoisted(() => ({ alertUser: vi.fn(), audit: vi.fn(), resolveServerContext: vi.fn(), })); vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({ getHousekeepingCapabilityContext: resolveServerContext, })); vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() })); vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() })); vi.mock("@/lib/db", async (importOriginal) => ({ ...(await importOriginal()), db: { select: vi.fn(() => { throw new Error("alert must not query DB"); }), }, })); vi.mock("@/lib/services/audit", async (importOriginal) => ({ ...(await importOriginal()), logAudit: audit, })); vi.mock("@/lib/services/rcon", async (importOriginal) => ({ ...(await importOriginal()), rcon: { alertUser }, })); vi.mock("@/lib/services/webhook", () => ({ notify: vi.fn() })); import { peopleMutationService } from "./mutations"; function context(): HousekeepingCapabilityContext { return { actor: { id: 42, username: "operator", rank: 6 }, isSuperAdmin: false, has: (slug) => slug === PERMS.USERS_EDIT, hasAny: (...slugs) => slugs.includes(PERMS.USERS_EDIT), hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_EDIT), }; } const invocation = { correlationId: "audit-contract", expectedActorId: 42, }; beforeEach(() => { vi.clearAllMocks(); resolveServerContext.mockResolvedValue(context()); alertUser.mockResolvedValue(true); audit.mockResolvedValue(undefined); }); describe("People external audit contract", () => { it("blocks the external operation when intent persistence fails", async () => { audit.mockRejectedValueOnce(new Error("intent unavailable")); const result = await peopleMutationService.execute( invocation, "user.alert", { userId: 7, message: "Hello" }, ); expect(result).toMatchObject({ ok: false }); expect(alertUser).not.toHaveBeenCalled(); expect(audit).toHaveBeenCalledWith( expect.objectContaining({ outcome: "intent" }), ); }); it("persists a correlated failure outcome when a pre-mutation external operation fails", async () => { alertUser.mockResolvedValue(false); const result = await peopleMutationService.execute( invocation, "user.alert", { userId: 7, message: "Hello" }, ); expect(result).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" }, }); expect( audit.mock.calls.map((call) => { const entry = call[0] as AuditEntry; return { outcome: entry.outcome, correlationId: entry.correlationId, }; }), ).toEqual([ { outcome: "intent", correlationId: "audit-contract" }, { outcome: "failure", correlationId: "audit-contract" }, ]); }); it("returns serializable partial completion when final outcome persistence fails", async () => { audit.mockImplementation(async (entry: AuditEntry) => { if (entry.outcome === "success") throw new Error("outcome unavailable"); }); const result = await peopleMutationService.execute( invocation, "user.alert", { userId: 7, message: "Hello" }, ); expect(result).toMatchObject({ ok: true, completion: { status: "partial", external: "completed", audit: "persisted", }, data: { before: null, after: { userId: 7, alertDelivered: true }, }, correlationId: "audit-contract", }); expect(() => JSON.stringify(result)).not.toThrow(); expect(alertUser).toHaveBeenCalledTimes(1); expect( audit.mock.calls.map((call) => (call[0] as AuditEntry).outcome), ).toEqual(["intent", "success", "partial"]); }); it("attempts legacy alert without a target lookup when the database is unavailable", async () => { const result = await peopleMutationService.execute( { ...invocation, legacy: true }, "user.alert", { userId: 900719925, message: "Direct RCON" }, ); expect(result).toMatchObject({ ok: true, data: { before: null, after: { userId: 900719925, alertDelivered: true }, }, }); expect(alertUser).toHaveBeenCalledWith(900719925, "Direct RCON"); }); });