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 { CareExecutionData, CareTask } from "@/modules/care/types"; import { listCareExecutionData, updateCareTaskStatus } from "@/modules/care/server/operations"; vi.mock("@/modules/core/server/auth", () => ({ requirePermission: vi.fn(), })); vi.mock("@/modules/core/server/audit", () => ({ recordAuditLog: vi.fn(), })); vi.mock("@/modules/care/server/operations", () => ({ isCareMutationFailure: vi.fn((value: unknown) => typeof value === "object" && value !== null && "success" in value && value.success === false), listCareExecutionData: vi.fn(), updateCareTaskStatus: 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: ["care:read", "care: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 careTask: CareTask = { id: "task-1", organizationId: "org-1", elderId: "elder-1", elderName: "王桂兰", title: "晨间协助洗漱", careType: "daily_care", priority: "normal", status: "pending", scheduledAt: "2026-07-02T08:00:00.000Z", assigneeLabel: "一号护理组", executionNotes: "", completedAt: undefined, completedByAccountId: undefined, completedByName: undefined, createdAt: "2026-07-02T00:00:00.000Z", updatedAt: "2026-07-02T00:00:00.000Z", }; const careExecutionData: CareExecutionData = { metrics: { pending: 1, inProgress: 1, completedToday: 1, highPriority: 1, }, tasks: [careTask], }; function allowAuth(): void { vi.mocked(requirePermission).mockResolvedValue({ success: true, context: createAuthContext(), }); } function jsonRequest(body: Record): Request { return new Request("http://localhost/api/care/tasks", { method: "PATCH", 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("care execution API routes", () => { it("loads care execution data for the active organization", async () => { vi.mocked(listCareExecutionData).mockResolvedValue(careExecutionData); const { GET } = await import("./tasks/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(200); expect(body.success).toBe(true); expect(body.data).toEqual(careExecutionData); expect(listCareExecutionData).toHaveBeenCalledWith("org-1"); }); it("returns permission failures before loading care tasks", async () => { const denied = Response.json({ success: false, reason: "权限不足" }, { status: 403 }); vi.mocked(requirePermission).mockResolvedValue({ success: false, response: denied }); const { GET } = await import("./tasks/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(403); expect(body.reason).toBe("权限不足"); expect(listCareExecutionData).not.toHaveBeenCalled(); }); it("requires an active organization before loading care tasks", async () => { vi.mocked(requirePermission).mockResolvedValue({ success: true, context: createAuthContext({ organization: undefined }), }); const { GET } = await import("./tasks/route"); const response = await GET(); const body = await readJson(response); expect(response.status).toBe(400); expect(body.reason).toBe("请选择机构后查看护理任务"); expect(listCareExecutionData).not.toHaveBeenCalled(); }); it("marks a care task as in progress", async () => { vi.mocked(updateCareTaskStatus).mockResolvedValue({ ...careTask, status: "in_progress", updatedAt: "2026-07-02T08:10:00.000Z", }); const { PATCH } = await import("./tasks/[id]/route"); const response = await PATCH(jsonRequest({ status: "in_progress" }), { params: Promise.resolve({ id: "task-1" }), }); const body = await readJson(response); expect(response.status).toBe(200); expect(body.success).toBe(true); expect(updateCareTaskStatus).toHaveBeenCalledWith(expect.objectContaining({ accountId: "account-1", id: "task-1", organizationId: "org-1" })); expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "care.task.update" })); }); it("completes a care task with notes", async () => { vi.mocked(updateCareTaskStatus).mockResolvedValue({ ...careTask, status: "completed", executionNotes: "已完成并观察无异常", completedAt: "2026-07-02T08:30:00.000Z", completedByAccountId: "account-1", completedByName: "Admin", updatedAt: "2026-07-02T08:30:00.000Z", }); const { PATCH } = await import("./tasks/[id]/route"); const response = await PATCH(jsonRequest({ status: "completed", executionNotes: "已完成并观察无异常" }), { params: Promise.resolve({ id: "task-1" }), }); const body = await readJson(response); expect(response.status).toBe(200); expect(body.success).toBe(true); expect(updateCareTaskStatus).toHaveBeenCalledWith(expect.objectContaining({ executionNotes: "已完成并观察无异常", status: "completed" })); expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "care.task.update" })); }); it("rejects invalid care task status", async () => { const { PATCH } = await import("./tasks/[id]/route"); const response = await PATCH(jsonRequest({ status: "done" }), { params: Promise.resolve({ id: "task-1" }), }); const body = await readJson(response); expect(response.status).toBe(400); expect(body.reason).toBe("护理任务状态无效"); expect(updateCareTaskStatus).not.toHaveBeenCalled(); expect(recordAuditLog).not.toHaveBeenCalled(); }); it("returns structured failures for missing or cross-organization care tasks", async () => { vi.mocked(updateCareTaskStatus).mockResolvedValue({ success: false, reason: "护理任务不存在", status: 404, }); const { PATCH } = await import("./tasks/[id]/route"); const response = await PATCH(jsonRequest({ status: "in_progress" }), { params: Promise.resolve({ id: "task-from-other-org" }), }); const body = await readJson(response); expect(response.status).toBe(404); expect(body.reason).toBe("护理任务不存在"); expect(recordAuditLog).not.toHaveBeenCalled(); }); });