feat: 素材处理管线——自动处理、审核流程、6状态机
This commit is contained in:
@@ -27,6 +27,21 @@ async function deleteMaterialViaHandler(req: Request, db: Database): Promise<Res
|
||||
return h(req, db, MODE, LOG);
|
||||
}
|
||||
|
||||
async function approveMaterialViaHandler(req: Request, db: Database): Promise<Response> {
|
||||
const { handleApproveMaterial: h } = await import("../../../src/server/routes/materials/approve");
|
||||
return h(req, db, MODE, LOG);
|
||||
}
|
||||
|
||||
async function discardMaterialViaHandler(req: Request, db: Database): Promise<Response> {
|
||||
const { handleDiscardMaterial: h } = await import("../../../src/server/routes/materials/discard");
|
||||
return h(req, db, MODE, LOG);
|
||||
}
|
||||
|
||||
async function retryMaterialViaHandler(req: Request, db: Database): Promise<Response> {
|
||||
const { handleRetryMaterial: h } = await import("../../../src/server/routes/materials/retry");
|
||||
return h(req, db, MODE, LOG);
|
||||
}
|
||||
|
||||
async function getMaterialViaHandler(req: Request, db: Database): Promise<Response> {
|
||||
const { handleGetMaterial: h } = await import("../../../src/server/routes/materials/get");
|
||||
return h(req, db, MODE, LOG);
|
||||
@@ -280,4 +295,199 @@ describe("素材 API 路由", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/projects/:id/materials/:mid/approve", () => {
|
||||
test("review 状态通过返回 201", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
setMaterialStatusRaw(db, created.material.id, "review");
|
||||
|
||||
const req = new Request(
|
||||
`http://localhost/api/projects/${project.id}/materials/${created.material.id}/approve`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const res = await approveMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(201);
|
||||
const body = (await res.json()) as { material: Material };
|
||||
expect(body.material.status).toBe("approved");
|
||||
});
|
||||
});
|
||||
|
||||
test("非 review 状态返回 409", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
|
||||
const req = new Request(
|
||||
`http://localhost/api/projects/${project.id}/materials/${created.material.id}/approve`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const res = await approveMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
});
|
||||
|
||||
test("素材不存在返回 404", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const req = new Request(`http://localhost/api/projects/${project.id}/materials/nonexistent/approve`, {
|
||||
method: "POST",
|
||||
});
|
||||
const res = await approveMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/projects/:id/materials/:mid/discard", () => {
|
||||
test("review 状态放弃返回 201", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
setMaterialStatusRaw(db, created.material.id, "review");
|
||||
|
||||
const req = new Request(
|
||||
`http://localhost/api/projects/${project.id}/materials/${created.material.id}/discard`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const res = await discardMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(201);
|
||||
const body = (await res.json()) as { material: Material };
|
||||
expect(body.material.status).toBe("discarded");
|
||||
});
|
||||
});
|
||||
|
||||
test("非 review 状态返回 409", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
|
||||
const req = new Request(
|
||||
`http://localhost/api/projects/${project.id}/materials/${created.material.id}/discard`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const res = await discardMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/projects/:id/materials/:mid/retry", () => {
|
||||
test("failed 状态重试返回 201,processedContent 已清空", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
setMaterialStatusRaw(db, created.material.id, "failed", "之前的内容");
|
||||
|
||||
const req = new Request(`http://localhost/api/projects/${project.id}/materials/${created.material.id}/retry`, {
|
||||
method: "POST",
|
||||
});
|
||||
const res = await retryMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(201);
|
||||
const body = (await res.json()) as { material: Material };
|
||||
expect(body.material.status).toBe("pending");
|
||||
expect(body.material.processedContent).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test("非 failed 状态返回 409", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
|
||||
const req = new Request(`http://localhost/api/projects/${project.id}/materials/${created.material.id}/retry`, {
|
||||
method: "POST",
|
||||
});
|
||||
const res = await retryMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DELETE 状态校验扩展", () => {
|
||||
test("processing 状态返回 409", async () => {
|
||||
await withRouteDb(async (db) => {
|
||||
const project = createTestProject(db);
|
||||
const createRes = await createMaterialViaHandler(
|
||||
new Request(`http://localhost/api/projects/${project.id}/materials`, {
|
||||
body: JSON.stringify({ associatedDate: "2024-01-15", description: "测试" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: "POST",
|
||||
}),
|
||||
db,
|
||||
);
|
||||
const created = (await createRes.json()) as { material: Material };
|
||||
setMaterialStatusRaw(db, created.material.id, "processing");
|
||||
|
||||
const req = new Request(`http://localhost/api/projects/${project.id}/materials/${created.material.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const res = await deleteMaterialViaHandler(req, db);
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setMaterialStatusRaw(
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user