import type Database from "bun:sqlite"; import { describe, expect, test } from "bun:test"; import type { Project, RuntimeMode } from "../../../src/shared/api"; import { createMigratedMemoryTestDatabase } from "../../helpers"; const MODE: RuntimeMode = "test"; async function archiveProjectViaHandler(req: Request, db: Database): Promise { const { handleArchiveProject: h } = await import("../../../src/server/routes/projects/archive"); return h(req, db, MODE); } // Inline imports for actual route handler tests (each handler is in separate file) async function createProjectViaHandler(req: Request, db: Database): Promise { const { handleCreateProject: h } = await import("../../../src/server/routes/projects/create"); return h(req, db, MODE); } function createTestProject(db: Database, name = "测试项目"): Project { const result = createProject(db, { name }); if ("error" in result) throw new Error(result.error); return result.project; } async function deleteProjectViaHandler(req: Request, db: Database): Promise { const { handleDeleteProject: h } = await import("../../../src/server/routes/projects/delete"); return h(req, db, MODE); } async function getProjectViaHandler(req: Request, db: Database): Promise { const { handleGetProject: h } = await import("../../../src/server/routes/projects/get"); return h(req, db, MODE); } async function listProjectsViaHandler(req: Request, db: Database): Promise { const { handleListProjects: h } = await import("../../../src/server/routes/projects/list"); return h(req, db, MODE); } async function restoreProjectViaHandler(req: Request, db: Database): Promise { const { handleRestoreProject: h } = await import("../../../src/server/routes/projects/restore"); return h(req, db, MODE); } async function updateProjectViaHandler(req: Request, db: Database): Promise { const { handleUpdateProject: h } = await import("../../../src/server/routes/projects/update"); return h(req, db, MODE); } // Need db/projects for setup import { archiveProject, createProject, getProject } from "../../../src/server/db/projects"; async function withRouteDb(callback: (db: Database) => Promise): Promise { const handle = createMigratedMemoryTestDatabase("route-test"); try { await callback(handle.db); handle.close(); } finally { handle.cleanup(); } } describe("项目 API 路由", () => { test("POST /api/projects 创建项目", async () => { await withRouteDb(async (db) => { const req = new Request("http://localhost/api/projects", { body: JSON.stringify({ description: "路由测试", name: "路由项目" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const res = await createProjectViaHandler(req, db); expect(res.status).toBe(201); const body = (await res.json()) as { project: Project }; expect(body.project.name).toBe("路由项目"); }); }); test("GET /api/projects 列表查询", async () => { await withRouteDb(async (db) => { createTestProject(db, "A项目"); createTestProject(db, "B项目"); const req = new Request("http://localhost/api/projects?page=1&pageSize=20"); const res = await listProjectsViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { items: Project[]; total: number }; expect(body.total).toBe(2); expect(body.items.length).toBe(2); }); }); test("GET /api/projects/:id 获取详情", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "详情路由"); const req = new Request(`http://localhost/api/projects/${project.id}`); const res = await getProjectViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { project: Project }; expect(body.project.name).toBe("详情路由"); }); }); test("PATCH /api/projects/:id 更新项目", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "更新路由"); const req = new Request(`http://localhost/api/projects/${project.id}`, { body: JSON.stringify({ name: "已更新" }), headers: { "Content-Type": "application/json" }, method: "PATCH", }); const res = await updateProjectViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { project: Project }; expect(body.project.name).toBe("已更新"); }); }); test("POST /api/projects/:id/archive 归档项目", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "归档路由"); const req = new Request(`http://localhost/api/projects/${project.id}/archive`, { method: "POST" }); const res = await archiveProjectViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { project: Project }; expect(body.project.status).toBe("archived"); }); }); test("POST /api/projects/:id/restore 恢复项目", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "恢复路由"); archiveProject(db, project.id); const req = new Request(`http://localhost/api/projects/${project.id}/restore`, { method: "POST" }); const res = await restoreProjectViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { project: Project }; expect(body.project.status).toBe("active"); }); }); test("DELETE /api/projects/:id 永久删除已归档项目", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "删除路由"); archiveProject(db, project.id); const req = new Request(`http://localhost/api/projects/${project.id}`, { method: "DELETE" }); const res = await deleteProjectViaHandler(req, db); expect(res.status).toBe(204); const after = getProject(db, project.id); expect("error" in after).toBe(true); }); }); test("创建同名项目返回 409", async () => { await withRouteDb(async (db) => { const req1 = new Request("http://localhost/api/projects", { body: JSON.stringify({ name: "重复名" }), headers: { "Content-Type": "application/json" }, method: "POST", }); await createProjectViaHandler(req1, db); const req2 = new Request("http://localhost/api/projects", { body: JSON.stringify({ name: "重复名" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const res = await createProjectViaHandler(req2, db); expect(res.status).toBe(409); }); }); test("删除 active 项目返回 409", async () => { await withRouteDb(async (db) => { const project = createTestProject(db, "活项目"); const req = new Request(`http://localhost/api/projects/${project.id}`, { method: "DELETE" }); const res = await deleteProjectViaHandler(req, db); expect(res.status).toBe(409); }); }); });