初始提交:ai-review 项目当前版本(含赛道一/二提交规范修订与时间节点文档)

This commit is contained in:
hangshuo652
2026-08-23 11:52:45 +08:00
commit 4da7044c4b
152 changed files with 33490 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
import { test, expect, type Page } from '@playwright/test';
const PASSWORD = 'test123';
const UNIQUE = Date.now().toString(36);
async function login(page: Page) {
await page.goto('/login');
await page.fill('input[type="password"]', PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('/');
}
async function apiToken(request: any): Promise<string> {
const res = await request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
const { token } = await res.json();
return token;
}
// ══════════════════════════════════════
// 1. 成果物 TabDeliverablesView
// ══════════════════════════════════════
test.describe('成果物 Tab', () => {
let pid = '';
let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `deliv-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'd-std', content: '## 场景价值(8分)' },
});
await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'deliv-entry', repo_url: `file://C:\\deliv-${UNIQUE}` },
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('初始化一覧 → 勾选 → 提交率更新 → CSV 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.getByRole('button', { name: '成果物' }).click();
await expect(page.getByText('成果物确认')).toBeVisible();
// 初始化一覧
await page.getByRole('button', { name: '初始化一覧' }).click();
await expect(page.locator('.entry-table input[type="checkbox"]').first()).toBeVisible();
// 勾选第一个成果物 → 提交率 +1(经 API 验证)
const before = await page.request.get(`http://localhost:3002/api/projects/${pid}/entries/deliverables/summary`, {
headers: { Authorization: `Bearer ${token}` },
});
const beforeData = await before.json();
await page.locator('.entry-table input[type="checkbox"]').first().check();
await expect.poll(async () => {
const r = await page.request.get(`http://localhost:3002/api/projects/${pid}/entries/deliverables/summary`, {
headers: { Authorization: `Bearer ${token}` },
});
return (await r.json()).totalSubmitted;
}).toBe(beforeData.totalSubmitted + 1);
// CSV 下载
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载CSV' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('deliverables');
});
});
// ══════════════════════════════════════
// 2. 评审进度时间线(DetailPanelD7
// ══════════════════════════════════════
test.describe('评审进度时间线', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `prog-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'p-std', content: '## 场景价值(8分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'prog-entry', repo_url: `file://C:\\prog-${UNIQUE}` },
});
eid = (await entry.json()).id;
// 启动评审(外部路径 → 秒级 clone_fail),写入 progress_log
await request.post(`http://localhost:3002/api/projects/${pid}/entries/${eid}/start`, {
headers: { Authorization: `Bearer ${token}` },
});
await expect.poll(async () => {
const r = await request.get(`http://localhost:3002/api/projects/${pid}/entries/${eid}`, {
headers: { Authorization: `Bearer ${token}` },
});
return (await r.json()).status;
}).toBe('clone_fail');
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('详情面板显示评审进度时间线(含克隆步骤)', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
const progress = page.locator('details', { has: page.getByText('评审进度') });
await expect(progress).toBeVisible();
await expect(progress).toContainText('不允许克隆外部路径');
});
});
// ══════════════════════════════════════
// 3. PDF 下载(单条目报告 / 汇总报告)
// ══════════════════════════════════════
test.describe('PDF 下载', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `pdf-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'pdf-std', content: '## 架构设计(10分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'pdf-entry', repo_url: `file://C:\\pdf-${UNIQUE}` },
});
eid = (await entry.json()).id;
await request.put(`http://localhost:3002/api/projects/${pid}/entries/${eid}/force-review`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { dimensions: [{ name: '架构设计', score: 8, maxScore: 10 }] },
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('单条目报告 PDF 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载报告' }).click();
const download = await downloadPromise;
// 服务端 Content-Disposition 中文文件名在部分平台会被搅乱,这里只断言"触发了 .pdf 下载"
expect(download.suggestedFilename().toLowerCase()).toContain('.pdf');
});
test('汇总报告 PDF 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.getByRole('button', { name: '汇总' }).click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载汇总PDF' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename().toLowerCase()).toContain('.pdf');
});
});
// ══════════════════════════════════════
// 4. 人才测评 L2/L3 分表展示
// ══════════════════════════════════════
test.describe('人才测评 L2/L3 分表', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `l2l3-${UNIQUE}`, track: '人才测评' },
});
pid = (await proj.json()).id;
// 删除自动标准,替换为自定义 Q2 标准
const list = await request.get(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` },
});
for (const s of await list.json()) {
await request.delete(`http://localhost:3002/api/projects/${pid}/standards/${s.id}`, { headers: { Authorization: `Bearer ${token}` } });
}
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` },
data: { name: 'l2-std', category_tag: '人才测评', content: '## 功能完整性(40分)\n## 设计文档(10分)\n## [Q2] LLM生成问卷(15分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'l2l3-entry', repo_url: `file://C:\\l2-${UNIQUE}`, question_id: 'Q2' },
});
eid = (await entry.json()).id;
await request.put(`http://localhost:3002/api/projects/${pid}/entries/${eid}/force-review`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: {
dimensions: [
{ name: '功能完整性', score: 40, maxScore: 40, group: 'common' },
{ name: '设计文档', score: 10, maxScore: 10, group: 'common' },
{ name: 'LLM生成问卷', score: 15, maxScore: 15, group: 'Q2' },
],
},
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('详情面板分别展示 L2共通 与 L3追加 分表', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
await expect(page.getByText('L2共通评分')).toBeVisible();
await expect(page.getByText('L3追加评分')).toBeVisible();
});
});