Files
delivery-desk/scripts/test-sqlite-review-rounds.ts
yuzhe 6091d61612 feat(review): 支持多候选稿验收轮次
- 支持每轮提交 1–5 个候选稿并按指定稿验收
- 保留历史轮次只读并兼容单候选稿版本接口
- 同步 SQLite/PostgreSQL schema、迁移验证、测试与项目文档
2026-07-21 20:25:52 +08:00

46 lines
3.7 KiB
TypeScript

process.env.NODE_ENV = 'test';
delete process.env.DATABASE_URL;
const { db } = await import('../api/db.js');
const { database, closeDatabase } = await import('../api/database.js');
const { decideCandidateInTransaction, ReviewDecisionError } = await import('../api/services/reviewService.js');
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
db.exec('BEGIN IMMEDIATE');
try {
const project = await database.one<{ id: number }>('SELECT id FROM projects ORDER BY id LIMIT 1');
assert(project, 'SQLite 测试需要至少一个项目');
const collectionId = await database.insertId("INSERT INTO collections (project_id, name, status) VALUES (?, ?, 'reviewing')", [project.id, `候选稿测试 ${Date.now()}`]);
const noteId = await database.insertId("INSERT INTO notes (collection_id, title, review_status) VALUES (?, ?, 'pending')", [collectionId, '候选稿 A']);
const roundId = await database.insertId("INSERT INTO review_rounds (note_id, round_number, status) VALUES (?, 1, 'reviewing')", [noteId]);
await database.execute('UPDATE notes SET active_round_id = ? WHERE id = ?', [roundId, noteId]);
await database.execute("INSERT INTO work_versions (note_id, version_number, title, review_status, review_round_id, candidate_name, candidate_status) VALUES (?, 1, ?, 'pending', ?, ?, 'pending')", [noteId, '候选稿 A', roundId, '方案 A']);
await database.execute("INSERT INTO work_versions (note_id, version_number, title, review_status, review_round_id, candidate_name, candidate_status) VALUES (?, 2, ?, 'pending', ?, ?, 'pending')", [noteId, '候选稿 B', roundId, '方案 B']);
await decideCandidateInTransaction(database, { noteId, versionNumber: 1, projectId: project.id, decision: 'changes_requested', reason: 'A 需调整', actorName: '测试客户', actorRole: 'client' });
assert((await database.one<{ review_status: string }>('SELECT review_status FROM notes WHERE id = ?', [noteId]))?.review_status === 'pending', '仍有待验收候选稿时作品应保持待验收');
await decideCandidateInTransaction(database, { noteId, versionNumber: 2, projectId: project.id, decision: 'approved', reason: '', actorName: '测试客户', actorRole: 'client' });
const note = await database.one<{ version_number: number; approved_version_number: number; review_status: string }>('SELECT version_number, approved_version_number, review_status FROM notes WHERE id = ?', [noteId]);
assert(note?.version_number === 2 && note.approved_version_number === 2 && note.review_status === 'approved', '作品未指向选中候选稿');
const candidates = await database.all<Array<{ version_number: number; candidate_status: string }>[number]>('SELECT version_number, candidate_status FROM work_versions WHERE review_round_id = ? ORDER BY version_number', [roundId]);
assert(candidates[0]?.candidate_status === 'not_selected' && candidates[1]?.candidate_status === 'selected', '同轮候选稿结果未正确收口');
assert((await database.one<{ status: string }>('SELECT status FROM collections WHERE id = ?', [collectionId]))?.status === 'completed', '作品通过后作品交付集未自动完成');
let historicalRejected = false;
try {
await decideCandidateInTransaction(database, { noteId, versionNumber: 1, projectId: project.id, decision: 'approved', reason: '', actorName: '测试客户', actorRole: 'client' });
} catch (error) {
historicalRejected = error instanceof ReviewDecisionError && error.statusCode === 409;
}
assert(historicalRejected, '已完成轮次的候选稿不应被重复验收');
process.stdout.write('SQLite 多候选稿验证通过:单稿退修、其他候选继续验收、选中收口与历史只读\n');
} finally {
db.exec('ROLLBACK');
await closeDatabase();
}