import type Database from "bun:sqlite"; import { describe, expect, test } from "bun:test"; import type { Entity, RuntimeMode } from "../../../src/shared/api"; import { createProject } from "../../../src/server/db/projects"; import { createNoopLogger } from "../../../src/server/logger"; import { createMigratedMemoryTestDatabase } from "../../helpers"; const MODE: RuntimeMode = "test"; const LOG = createNoopLogger(); async function createEntityViaHandler(req: Request, db: Database): Promise { const { handleCreateEntity: h } = await import("../../../src/server/routes/entities/create"); return h(req, db, MODE, LOG); } async function listEntitiesViaHandler(req: Request, db: Database): Promise { const { handleListEntities: h } = await import("../../../src/server/routes/entities/list"); return h(req, db, MODE, LOG); } async function getEntityViaHandler(req: Request, db: Database): Promise { const { handleGetEntity: h } = await import("../../../src/server/routes/entities/get"); return h(req, db, MODE, LOG); } async function updateEntityViaHandler(req: Request, db: Database): Promise { const { handleUpdateEntity: h } = await import("../../../src/server/routes/entities/update"); return h(req, db, MODE, LOG); } async function deleteEntityViaHandler(req: Request, db: Database): Promise { const { handleDeleteEntity: h } = await import("../../../src/server/routes/entities/delete"); return h(req, db, MODE, LOG); } async function withRouteDb(callback: (db: Database) => Promise): Promise { const handle = createMigratedMemoryTestDatabase("entity-route-test"); try { await callback(handle.db); handle.close(); } finally { handle.cleanup(); } } function createTestProject(db: Database) { const result = createProject(db, { name: "测试项目" }, LOG); if ("error" in result) throw new Error(result.error); return result.project; } describe("实体 API 路由", () => { describe("POST /api/projects/:id/entities", () => { test("正常创建实体", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const req = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "张三", type: "person", description: "描述", aliases: ["小张"] }), headers: { "Content-Type": "application/json" }, method: "POST", }); const res = await createEntityViaHandler(req, db); expect(res.status).toBe(201); const body = (await res.json()) as { entity: Entity }; expect(body.entity.name).toBe("张三"); expect(body.entity.type).toBe("person"); expect(body.entity.aliases).toEqual(["小张"]); }); }); test("无 name 返回 400", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const req = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ type: "other" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const res = await createEntityViaHandler(req, db); expect(res.status).toBe(400); }); }); }); describe("GET /api/projects/:id/entities", () => { test("分页列出实体", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const req1 = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "实体1", type: "person" }), headers: { "Content-Type": "application/json" }, method: "POST", }); await createEntityViaHandler(req1, db); const req = new Request(`http://localhost/api/projects/${project.id}/entities?page=1&pageSize=10`, { method: "GET", }); const res = await listEntitiesViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { items: Entity[]; total: number }; expect(body.total).toBe(1); }); }); test("按类型筛选", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const req1 = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "人", type: "person" }), headers: { "Content-Type": "application/json" }, method: "POST", }); await createEntityViaHandler(req1, db); const req2 = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "公司", type: "organization" }), headers: { "Content-Type": "application/json" }, method: "POST", }); await createEntityViaHandler(req2, db); const req = new Request(`http://localhost/api/projects/${project.id}/entities?type=person`, { method: "GET" }); const res = await listEntitiesViaHandler(req, db); const body = (await res.json()) as { items: Entity[]; total: number }; expect(body.total).toBe(1); const firstItem = body.items[0]!; expect(firstItem.name).toBe("人"); }); }); }); describe("GET /api/projects/:id/entities/:eid", () => { test("获取实体详情", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const createReq = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "张三", type: "person" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const createRes = await createEntityViaHandler(createReq, db); const created = (await createRes.json()) as { entity: Entity }; const req = new Request(`http://localhost/api/projects/${project.id}/entities/${created.entity.id}`, { method: "GET", }); const res = await getEntityViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { entity: Entity }; expect(body.entity.name).toBe("张三"); }); }); test("不存在的实体返回 404", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const req = new Request(`http://localhost/api/projects/${project.id}/entities/nonexistent`, { method: "GET" }); const res = await getEntityViaHandler(req, db); expect(res.status).toBe(404); }); }); }); describe("PATCH /api/projects/:id/entities/:eid", () => { test("更新实体名称", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const createReq = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "张三", type: "person" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const createRes = await createEntityViaHandler(createReq, db); const created = (await createRes.json()) as { entity: Entity }; const req = new Request(`http://localhost/api/projects/${project.id}/entities/${created.entity.id}`, { body: JSON.stringify({ name: "张三丰" }), headers: { "Content-Type": "application/json" }, method: "PATCH", }); const res = await updateEntityViaHandler(req, db); expect(res.status).toBe(200); const body = (await res.json()) as { entity: Entity }; expect(body.entity.name).toBe("张三丰"); }); }); }); describe("DELETE /api/projects/:id/entities/:eid", () => { test("软删除实体", async () => { await withRouteDb(async (db) => { const project = createTestProject(db); const createReq = new Request(`http://localhost/api/projects/${project.id}/entities`, { body: JSON.stringify({ name: "张三", type: "person" }), headers: { "Content-Type": "application/json" }, method: "POST", }); const createRes = await createEntityViaHandler(createReq, db); const created = (await createRes.json()) as { entity: Entity }; const deleteReq = new Request(`http://localhost/api/projects/${project.id}/entities/${created.entity.id}`, { method: "DELETE", }); const res = await deleteEntityViaHandler(deleteReq, db); expect(res.status).toBe(200); const getReq = new Request(`http://localhost/api/projects/${project.id}/entities/${created.entity.id}`, { method: "GET", }); const getRes = await getEntityViaHandler(getReq, db); expect(getRes.status).toBe(404); }); }); }); });