feat: 素材处理管线——自动处理、审核流程、6状态机

This commit is contained in:
2026-06-07 22:50:05 +08:00
parent a389888eb4
commit 90fdb44b20
30 changed files with 1452 additions and 55 deletions

View File

@@ -0,0 +1,277 @@
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);
});
});
});
});