import type Database from "bun:sqlite"; import { describe, expect, test } from "bun:test"; import { createEntity, deleteEntity, getEntity, listEntities, listEntityNames, updateEntity, } from "../../../src/server/db/entities"; import { createProject } from "../../../src/server/db/projects"; import { createNoopLogger } from "../../../src/server/logger"; import { createMigratedTestDatabase } from "../../helpers"; const LOG = createNoopLogger(); function withEntitiesDb(callback: (db: Database) => void): void { const handle = createMigratedTestDatabase("entities-dao-test"); try { callback(handle.db); handle.close(); } finally { handle.cleanup(); } } function setupProject(db: Database, name = "测试项目"): string { const result = createProject(db, { name }, LOG); if ("error" in result) throw new Error(result.error); return result.project.id; } function setupEntity( db: Database, projectId: string, overrides: Partial<{ aliases: string[]; description: string; name: string; type: string }> = {}, ): string { const result = createEntity( db, projectId, { aliases: overrides.aliases, description: overrides.description ?? "测试实体描述", name: overrides.name ?? "测试实体", type: (overrides.type ?? "person") as "person", }, LOG, ); if ("error" in result) throw new Error(result.error); return result.entity.id; } describe("实体数据访问层", () => { describe("createEntity", () => { test("创建实体成功,aliases 为数组", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const result = createEntity( db, projectId, { description: "描述", name: "张三", type: "person", }, LOG, ); expect("error" in result).toBe(false); const entity = (result as { entity: { aliases: string[]; description: string; name: string; type: string } }) .entity; expect(entity.name).toBe("张三"); expect(entity.description).toBe("描述"); expect(entity.type).toBe("person"); expect(entity.aliases).toEqual([]); }); }); test("创建实体时指定别名", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const result = createEntity( db, projectId, { aliases: ["小张", "张工"], name: "张三", type: "person", }, LOG, ); expect("error" in result).toBe(false); const entity = (result as { entity: { aliases: string[] } }).entity; expect(entity.aliases).toEqual(["小张", "张工"]); }); }); test("空名称返回 400", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const result = createEntity(db, projectId, { name: " ", type: "other" }, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(400); }); }); test("同项目下重名返回 409", () => { withEntitiesDb((db) => { const projectId = setupProject(db); createEntity(db, projectId, { name: "张三", type: "person" }, LOG); const result = createEntity(db, projectId, { name: "张三", type: "person" }, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); test("不同项目可重名", () => { withEntitiesDb((db) => { const p1 = setupProject(db, "项目一"); const p2 = setupProject(db, "项目二"); expect("error" in createEntity(db, p1, { name: "张三", type: "person" }, LOG)).toBe(false); expect("error" in createEntity(db, p2, { name: "张三", type: "person" }, LOG)).toBe(false); }); }); }); describe("getEntity", () => { test("获取实体成功", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const entityId = setupEntity(db, projectId); const result = getEntity(db, projectId, entityId); expect("error" in result).toBe(false); expect((result as { entity: { id: string } }).entity.id).toBe(entityId); }); }); test("不存在返回 404", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const result = getEntity(db, projectId, "nonexistent"); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(404); }); }); }); describe("listEntities", () => { test("分页列出实体", () => { withEntitiesDb((db) => { const projectId = setupProject(db); setupEntity(db, projectId, { name: "实体1", type: "person" }); setupEntity(db, projectId, { name: "实体2", type: "organization" }); const result = listEntities(db, projectId, { page: 1, pageSize: 10 }); expect(result.total).toBe(2); const types = result.items.map((i: { type: string }) => i.type).sort(); expect(types).toContain("person"); expect(types).toContain("organization"); }); }); test("按类型筛选", () => { withEntitiesDb((db) => { const projectId = setupProject(db); setupEntity(db, projectId, { name: "人", type: "person" }); setupEntity(db, projectId, { name: "公司", type: "organization" }); const result = listEntities(db, projectId, { page: 1, pageSize: 10, type: "person" }); expect(result.total).toBe(1); const firstItem = result.items[0]!; expect(firstItem.name).toBe("人"); }); }); test("软删除实体不出现在列表中", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const entityId = setupEntity(db, projectId); deleteEntity(db, projectId, entityId, LOG); const result = listEntities(db, projectId, { page: 1, pageSize: 10 }); expect(result.total).toBe(0); }); }); }); describe("listEntityNames", () => { test("返回所有实体名称和别名", () => { withEntitiesDb((db) => { const projectId = setupProject(db); setupEntity(db, projectId, { aliases: ["小张"], name: "张三", type: "person" }); setupEntity(db, projectId, { name: "李四", type: "person" }); const result = listEntityNames(db, projectId); expect(result).toHaveLength(2); const names = result.map((r) => r.name).sort(); expect(names).toEqual(["张三", "李四"]); const p3 = result.find((r) => r.name === "张三")!; expect(p3.aliases).toEqual(["小张"]); }); }); }); describe("updateEntity", () => { test("更新实体名称和别名", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const entityId = setupEntity(db, projectId, { name: "张三" }); const result = updateEntity(db, projectId, entityId, { aliases: ["张总", "老张"], name: "张三丰" }, LOG); expect("error" in result).toBe(false); const entity = (result as { entity: { aliases: string[]; name: string } }).entity; expect(entity.name).toBe("张三丰"); expect(entity.aliases).toEqual(["张总", "老张"]); }); }); test("更新时名称去重", () => { withEntitiesDb((db) => { const projectId = setupProject(db); setupEntity(db, projectId, { name: "已有实体" }); const entityId = setupEntity(db, projectId, { name: "张三" }); const result = updateEntity(db, projectId, entityId, { name: "已有实体" }, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); test("空参数返回原实体", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const entityId = setupEntity(db, projectId); const result = updateEntity(db, projectId, entityId, {}, LOG); expect("error" in result).toBe(false); const entity = (result as { entity: { id: string } }).entity; expect(entity.id).toBe(entityId); }); }); }); describe("deleteEntity", () => { test("软删除实体成功", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const entityId = setupEntity(db, projectId); const result = deleteEntity(db, projectId, entityId, LOG); expect("error" in result).toBe(false); const getResult = getEntity(db, projectId, entityId); expect("error" in getResult).toBe(true); expect((getResult as { status: number }).status).toBe(404); }); }); test("不存在返回 404", () => { withEntitiesDb((db) => { const projectId = setupProject(db); const result = deleteEntity(db, projectId, "nonexistent", LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(404); }); }); }); });