import type Database from "bun:sqlite"; import { describe, expect, test } from "bun:test"; import { approveMaterial, createMaterial, deleteMaterial, discardMaterial, getMaterial, retryMaterial, } from "../../../src/server/db/materials"; import { createProject } from "../../../src/server/db/projects"; import { createNoopLogger } from "../../../src/server/logger"; import { createMigratedTestDatabase } from "../../helpers"; const LOG = createNoopLogger(); function withMaterialsDb(callback: (db: Database) => void): void { const handle = createMigratedTestDatabase("materials-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 setupMaterial( db: Database, projectId: string, overrides: Partial<{ associatedDate: string; description: string; materialType: "general" | "meeting"; }> = {}, ): string { const result = createMaterial( db, projectId, { associatedDate: overrides.associatedDate ?? "2024-01-15", description: overrides.description ?? "测试素材", materialType: overrides.materialType, }, LOG, ); if ("error" in result) throw new Error(result.error); return result.material.id; } function setMaterialStatus( db: Database, materialId: string, status: "approved" | "discarded" | "failed" | "pending" | "processing" | "review", processedContent: string | null = null, ): void { db.prepare("UPDATE materials SET status = ?, processed_content = ?, updated_at = ? WHERE id = ?").run( status, processedContent, new Date().toISOString(), materialId, ); } describe("素材数据访问层", () => { describe("createMaterial", () => { test("默认 materialType 为 general", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const result = createMaterial(db, projectId, { associatedDate: "2024-01-15", description: "测试" }, LOG); expect("error" in result).toBe(false); const material = (result as { material: { materialType: string } }).material; expect(material.materialType).toBe("general"); }); }); test("显式指定 materialType 为 meeting", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const result = createMaterial( db, projectId, { associatedDate: "2024-01-15", description: "会议素材", materialType: "meeting" }, LOG, ); expect("error" in result).toBe(false); const material = (result as { material: { materialType: string } }).material; expect(material.materialType).toBe("meeting"); }); }); test("非法 materialType 返回 400", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const result = createMaterial( db, projectId, { associatedDate: "2024-01-15", description: "测试", materialType: "invalid" as "general", }, LOG, ); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(400); }); }); }); describe("getMaterial", () => { test("返回包含 materialType 和 processedContent 字段", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); const result = getMaterial(db, projectId, materialId); expect("error" in result).toBe(false); const material = (result as { material: { materialType: string; processedContent: null | string } }).material; expect(material.materialType).toBe("general"); expect(material.processedContent).toBeNull(); }); }); }); describe("approveMaterial", () => { test("review 状态通过成功", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "review"); const result = approveMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); const material = (result as { material: { status: string } }).material; expect(material.status).toBe("approved"); }); }); test("非 review 状态拒绝(pending)", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); const result = approveMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); test("素材不存在返回 404", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const result = approveMaterial(db, projectId, "nonexistent", LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(404); }); }); }); describe("discardMaterial", () => { test("review 状态放弃成功", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "review"); const result = discardMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); const material = (result as { material: { status: string } }).material; expect(material.status).toBe("discarded"); }); }); test("非 review 状态拒绝", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); const result = discardMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); }); describe("retryMaterial", () => { test("failed 状态重试成功并清空 processedContent", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "failed", "之前的内容"); const result = retryMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); const material = (result as { material: { processedContent: null | string; status: string } }).material; expect(material.status).toBe("pending"); expect(material.processedContent).toBeNull(); }); }); test("非 failed 状态拒绝", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "review"); const result = retryMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); }); describe("deleteMaterial", () => { test("processing 状态禁止删除返回 409", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "processing"); const result = deleteMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(true); expect((result as { status: number }).status).toBe(409); }); }); test("pending 状态可正常删除", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); const result = deleteMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); }); }); test("review 状态可正常删除", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "review"); const result = deleteMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); }); }); test("failed 状态可正常删除", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "failed"); const result = deleteMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); }); }); test("approved 状态可正常删除", () => { withMaterialsDb((db) => { const projectId = setupProject(db); const materialId = setupMaterial(db, projectId); setMaterialStatus(db, materialId, "approved"); const result = deleteMaterial(db, projectId, materialId, LOG); expect("error" in result).toBe(false); }); }); }); });