import { describe, expect, test } from "bun:test"; import { archiveProject, createProject, deleteProject, fetchProject, fetchProjectList, restoreProject, updateProject, } from "../../../src/web/shared/hooks/use-projects"; import { installFetchMock, jsonResponse } from "../test-utils"; const PROJECT = { createdAt: "2024-01-01T00:00:00.000Z", description: "描述", id: "p1", name: "项目", status: "active" as const, updatedAt: "2024-01-01T00:00:00.000Z", }; async function expectRejectsWithMessage(action: () => Promise, message: string) { try { await action(); throw new Error("expected rejection"); } catch (error) { expect(error).toBeInstanceOf(Error); expect((error as Error).message).toBe(message); } } function jsonBody(body: BodyInit | null | undefined): unknown { return JSON.parse(typeof body === "string" ? body : "{}"); } describe("use-projects request helpers", () => { test("fetchProjectList 按协议拼接 query 参数", async () => { const calls = installFetchMock(() => jsonResponse({ items: [PROJECT], page: 2, pageSize: 10, total: 1 })); const result = await fetchProjectList({ keyword: "项目", page: 2, pageSize: 10, status: "active" }); expect(result.items).toHaveLength(1); expect(calls[0]?.method).toBe("GET"); expect(calls[0]?.url).toBe("/api/projects?page=2&pageSize=10&keyword=%E9%A1%B9%E7%9B%AE&status=active"); }); test("创建、更新、归档、恢复和删除使用正确 method 与 body", async () => { const calls = installFetchMock((call) => { if (call.method === "DELETE") return new Response(null, { status: 204 }); return jsonResponse( { project: PROJECT }, { status: call.method === "POST" && call.url === "/api/projects" ? 201 : 200 }, ); }); await createProject({ description: "描述", name: "项目" }); await updateProject("p1", { name: "新项目" }); await archiveProject("p1"); await restoreProject("p1"); await deleteProject("p1"); await fetchProject("p1"); expect(calls.map((call) => `${call.method} ${call.url}`)).toEqual([ "POST /api/projects", "PATCH /api/projects/p1", "POST /api/projects/p1/archive", "POST /api/projects/p1/restore", "DELETE /api/projects/p1", "GET /api/projects/p1", ]); expect(jsonBody(calls[0]?.body)).toEqual({ description: "描述", name: "项目" }); expect(jsonBody(calls[1]?.body)).toEqual({ name: "新项目" }); }); test("错误响应优先使用后端 error 字段", async () => { installFetchMock(() => jsonResponse({ error: "项目名称已存在" }, { status: 409 })); await expectRejectsWithMessage(() => createProject({ name: "重复项目" }), "项目名称已存在"); }); test("非 JSON 错误响应回退到 HTTP 状态", async () => { installFetchMock(() => new Response("broken", { status: 500 })); await expectRejectsWithMessage(() => fetchProject("p-missing"), "HTTP 500"); }); });