Files
Alfred/tests/server/db/entities.test.ts
lanyuanxiaoyao 12edf0b545 feat: 实现阶段二实体体系——AI预处理真实化+实体CRUD+审核归一化
- 新增 entities 数据表(含迁移)、Entity 类型、DAO 层完整 CRUD
- AI 预处理管道接入真实模型(generateText),输出结构化 JSON(摘要+规范化内容+候选实体)
- 模板接口重构为 {systemPrompt, buildUserPrompt, parseOutput},general/meeting 模板真实化
- 新增 5 个实体路由端点 + 实体管理前端页面(列表/详情/编辑弹窗)
- 审核面板增强:展示 AI 预处理结构化结果+候选实体归一化面板(合并/新建/选择/放弃)
- 素材通过时根据用户确认的候选实体写入 entities 表
- 工作台菜单新增"实体"入口
- 新增 entities DAO 测试(16)、processor 测试(11)、路由测试(8),服务端 367 测试全部通过
- TypeScript 0 错误
2026-06-08 18:49:30 +08:00

265 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});
});
});