import { beforeEach, describe, expect, it, vi } from "vitest"; import { recordAuditLog } from "@/modules/core/server/audit"; import { requirePermission } from "@/modules/core/server/auth"; import type { AuthContext } from "@/modules/core/types"; import type { EmergencyIncident, EmergencyIncidentData } from "@/modules/emergency/types"; import { createEmergencyIncident, listEmergencyIncidentData, updateEmergencyIncidentStatus, } from "@/modules/emergency/server/operations"; vi.mock("@/modules/core/server/auth", () => ({ requirePermission: vi.fn(), })); vi.mock("@/modules/core/server/audit", () => ({ recordAuditLog: vi.fn(), })); vi.mock("@/modules/emergency/server/operations", () => ({ createEmergencyIncident: vi.fn(), isEmergencyMutationFailure: vi.fn((value: unknown) => typeof value === "object" && value !== null && "success" in value && value.success === false), listEmergencyIncidentData: vi.fn(), updateEmergencyIncidentStatus: vi.fn(), })); const account: AuthContext["account"] = { id: "account-1", name: "Admin", email: "admin@example.com", avatarUrl: "", role: "org_admin", status: "active", createdAt: "2026-07-02T00:00:00.000Z", updatedAt: "2026-07-02T00:00:00.000Z", }; function createAuthContext(overrides: Partial = {}): AuthContext { return { account, organization: { id: "org-1", name: "Demo Org", slug: "demo-org", status: "active", registrationEnabled: true, oidcEnabled: false, oidcIssuerUrl: "", oidcClientId: "", oidcHasClientSecret: false, oidcScopes: "openid profile email", oidcRedirectUri: "", oidcAvatarClaim: "picture", oidcAutoProvision: false, createdAt: "2026-07-02T00:00:00.000Z", updatedAt: "2026-07-02T00:00:00.000Z", }, organizations: [], permissions: ["incident:read", "incident:manage"], session: { id: "session-1", accountId: "account-1", activeOrganizationId: "org-1", createdAt: "2026-07-02T00:00:00.000Z", expiresAt: "2026-07-09T00:00:00.000Z", }, ...overrides, }; } const incident: EmergencyIncident = { id: "incident-1", organizationId: "org-1", severity: "critical", status: "open", title: "夜间跌倒风险", description: "重点照护房巡检发现老人离床。", source: "人工上报", acknowledgedAt: undefined, acknowledgedByAccountId: undefined, acknowledgedByName: undefined, resolvedAt: undefined, resolvedByAccountId: undefined, resolvedByName: undefined, createdAt: "2026-07-02T00:00:00.000Z", updatedAt: "2026-07-02T00:00:00.000Z", }; const emergencyData: EmergencyIncidentData = { incidents: [incident], metrics: { open: 1, acknowledged: 0, critical: 1, resolvedToday: 0, }, }; function allowAuth(): void { vi.mocked(requirePermission).mockResolvedValue({ success: true, context: createAuthContext(), }); } function jsonRequest(body: Record): Request { return new Request("http://localhost/api/emergency/incidents", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); } async function readJson(response: Response): Promise> { return (await response.json()) as Record; } beforeEach(() => { vi.clearAllMocks(); allowAuth(); }); describe("emergency incident API routes", () => { it("loads emergency incidents for the active organization", async () => { vi.mocked(listEmergencyIncidentData).mockResolvedValue(emergencyData); const { GET } = await import("./incidents/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(200); expect(body.success).toBe(true); expect(body.data).toEqual(emergencyData); expect(listEmergencyIncidentData).toHaveBeenCalledWith("org-1"); }); it("returns permission failures before loading incidents", async () => { const denied = Response.json({ success: false, reason: "权限不足" }, { status: 403 }); vi.mocked(requirePermission).mockResolvedValue({ success: false, response: denied }); const { GET } = await import("./incidents/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(403); expect(body.reason).toBe("权限不足"); expect(listEmergencyIncidentData).not.toHaveBeenCalled(); }); it("requires an active organization before loading incidents", async () => { vi.mocked(requirePermission).mockResolvedValue({ success: true, context: createAuthContext({ organization: undefined }), }); const { GET } = await import("./incidents/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(400); expect(body.reason).toBe("请选择机构后查看应急事件"); expect(listEmergencyIncidentData).not.toHaveBeenCalled(); }); it("creates a manual emergency incident", async () => { vi.mocked(createEmergencyIncident).mockResolvedValue(incident); const { POST } = await import("./incidents/route"); const response = await POST( jsonRequest({ severity: "critical", title: "夜间跌倒风险", description: "重点照护房巡检发现老人离床。", source: "人工上报", }), ); const body = await readJson(response); expect(response.status).toBe(201); expect(body.success).toBe(true); expect(createEmergencyIncident).toHaveBeenCalledWith(expect.objectContaining({ organizationId: "org-1" })); expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "emergency.incident.create" })); }); it("rejects invalid create input", async () => { const { POST } = await import("./incidents/route"); const response = await POST(jsonRequest({ severity: "critical", title: "" })); const body = await readJson(response); expect(response.status).toBe(400); expect(body.reason).toBe("事件标题和描述不能为空"); expect(createEmergencyIncident).not.toHaveBeenCalled(); expect(recordAuditLog).not.toHaveBeenCalled(); }); it("updates incident status", async () => { vi.mocked(updateEmergencyIncidentStatus).mockResolvedValue({ ...incident, status: "acknowledged", acknowledgedAt: "2026-07-02T01:00:00.000Z", acknowledgedByAccountId: "account-1", acknowledgedByName: "Admin", updatedAt: "2026-07-02T01:00:00.000Z", }); const { PATCH } = await import("./incidents/[id]/route"); const response = await PATCH(jsonRequest({ status: "acknowledged" }), { params: Promise.resolve({ id: "incident-1" }), }); const body = await readJson(response); expect(response.status).toBe(200); expect(body.success).toBe(true); expect(updateEmergencyIncidentStatus).toHaveBeenCalledWith(expect.objectContaining({ accountId: "account-1", id: "incident-1", organizationId: "org-1" })); expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "emergency.incident.update" })); }); it("rejects invalid incident status", async () => { const { PATCH } = await import("./incidents/[id]/route"); const response = await PATCH(jsonRequest({ status: "triaged" }), { params: Promise.resolve({ id: "incident-1" }), }); const body = await readJson(response); expect(response.status).toBe(400); expect(body.reason).toBe("事件状态无效"); expect(updateEmergencyIncidentStatus).not.toHaveBeenCalled(); }); it("returns structured failures for missing or cross-organization incidents", async () => { vi.mocked(updateEmergencyIncidentStatus).mockResolvedValue({ success: false, reason: "应急事件不存在", status: 404, }); const { PATCH } = await import("./incidents/[id]/route"); const response = await PATCH(jsonRequest({ status: "acknowledged" }), { params: Promise.resolve({ id: "incident-from-other-org" }), }); const body = await readJson(response); expect(response.status).toBe(404); expect(body.reason).toBe("应急事件不存在"); expect(recordAuditLog).not.toHaveBeenCalled(); }); });