138 lines
6.2 KiB
TypeScript
138 lines
6.2 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
const PASSWORD = 'test123';
|
|
const UNIQUE = Date.now().toString(36);
|
|
let pid = '';
|
|
let lastNewPwd = '';
|
|
|
|
async function login(page: Page, password = PASSWORD) {
|
|
await page.goto('/login');
|
|
await page.fill('input[type="password"]', password);
|
|
await page.click('button[type="submit"]');
|
|
await page.waitForURL('/');
|
|
}
|
|
|
|
// ══════════════════════════════════════
|
|
// K7: 改密流程(httpOnly cookie + 密钥轮换 → 需重登)
|
|
// ══════════════════════════════════════
|
|
|
|
test.describe('K7 改密流程', () => {
|
|
test.afterAll(async ({ request }) => {
|
|
// 崩溃安全:若改密测试中途失败,确保服务器密码还原,避免级联影响其他 spec
|
|
const ok = await request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
|
|
if (ok.ok()) return;
|
|
if (!lastNewPwd) return;
|
|
const loginNew = await request.post('http://localhost:3002/api/auth/login', { data: { password: lastNewPwd } });
|
|
if (!loginNew.ok()) return;
|
|
const { token } = await loginNew.json();
|
|
await request.post('http://localhost:3002/api/auth/password', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: { currentPassword: lastNewPwd, newPassword: PASSWORD },
|
|
});
|
|
});
|
|
|
|
test('改密:当前密码错误时显示错误且不跳转', async ({ page }) => {
|
|
await login(page);
|
|
await page.click('button[title="修改管理密码"]');
|
|
await page.fill('input[placeholder="当前密码"]', 'wrong-current');
|
|
await page.fill('input[placeholder="新密码(至少 6 位)"]', 'new-pass-123');
|
|
await page.click('.new-project-actions button:has-text("保存")');
|
|
await expect(page.locator('.new-project-form')).toContainText('当前密码错误');
|
|
await expect(page).toHaveURL('/');
|
|
});
|
|
|
|
test('改密成功后旧密码失效、新密码可登录,并还原原密码', async ({ page }) => {
|
|
const NEW_PWD = `new-${UNIQUE}-x9`;
|
|
lastNewPwd = NEW_PWD;
|
|
await login(page, PASSWORD);
|
|
await page.click('button[title="修改管理密码"]');
|
|
await page.fill('input[placeholder="当前密码"]', PASSWORD);
|
|
await page.fill('input[placeholder="新密码(至少 6 位)"]', NEW_PWD);
|
|
await page.click('.new-project-actions button:has-text("保存")');
|
|
await page.waitForURL('/login');
|
|
|
|
// 旧密码登录失败
|
|
await page.fill('input[type="password"]', PASSWORD);
|
|
await page.click('button[type="submit"]');
|
|
await expect(page.locator('.error')).toContainText('密码错误');
|
|
|
|
// 新密码登录成功
|
|
await login(page, NEW_PWD);
|
|
await expect(page.locator('.sidebar-header h2')).toHaveText('AI-Review');
|
|
|
|
// 还原原密码
|
|
await page.click('button[title="修改管理密码"]');
|
|
await page.fill('input[placeholder="当前密码"]', NEW_PWD);
|
|
await page.fill('input[placeholder="新密码(至少 6 位)"]', PASSWORD);
|
|
await page.click('.new-project-actions button:has-text("保存")');
|
|
await page.waitForURL('/login');
|
|
await login(page, PASSWORD);
|
|
await expect(page.locator('.sidebar-header h2')).toHaveText('AI-Review');
|
|
});
|
|
});
|
|
|
|
// ══════════════════════════════════════
|
|
// D2: CSV 导入模板下载
|
|
// ══════════════════════════════════════
|
|
|
|
test.describe('D2 CSV 模板下载', () => {
|
|
test.beforeAll(async ({ request }) => {
|
|
const loginRes = await request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
|
|
const { token } = await loginRes.json();
|
|
const proj = await request.post('http://localhost:3002/api/projects', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: { name: `csv-${UNIQUE}`, track: '赛道一' },
|
|
});
|
|
pid = (await proj.json()).id;
|
|
});
|
|
|
|
test('批量导入面板可下载 CSV 模板', async ({ page }) => {
|
|
await login(page);
|
|
await page.goto(`/project/${pid}`);
|
|
await page.click('button:has-text("批量导入")');
|
|
const downloadPromise = page.waitForEvent('download');
|
|
await page.click('button:has-text("下载模板")');
|
|
const download = await downloadPromise;
|
|
expect(download.suggestedFilename()).toBe('entry-import-template.csv');
|
|
});
|
|
});
|
|
|
|
// ══════════════════════════════════════
|
|
// K4: 条目编辑保存补齐 sub_type/question_id
|
|
// ══════════════════════════════════════
|
|
|
|
test.describe('K4 编辑保存字段', () => {
|
|
let pid = '';
|
|
let eid = '';
|
|
|
|
test.beforeAll(async ({ request }) => {
|
|
const loginRes = await request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
|
|
const { token } = await loginRes.json();
|
|
const proj = await request.post('http://localhost:3002/api/projects', {
|
|
headers: { Authorization: `Bearer ${token}` }, data: { name: `k4-${UNIQUE}`, track: '赛道一' },
|
|
});
|
|
pid = (await proj.json()).id;
|
|
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
|
|
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
data: { title: 'k4-entry', repo_url: `file://C:\\k4-${UNIQUE}` },
|
|
});
|
|
eid = (await entry.json()).id;
|
|
});
|
|
|
|
test('编辑保存 sub_type 持久化', async ({ page }) => {
|
|
await login(page);
|
|
await page.goto(`/project/${pid}`);
|
|
await page.click('button:has-text("编辑")');
|
|
await page.selectOption('.detail-overlay select', { label: '新規开发' });
|
|
await page.click('.detail-overlay button:has-text("保存")');
|
|
|
|
const loginRes = await page.request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
|
|
const { token } = await loginRes.json();
|
|
const detailRes = await page.request.get(`http://localhost:3002/api/projects/${pid}/entries/${eid}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
const detail = await detailRes.json();
|
|
expect(detail.sub_type).toBe('新規');
|
|
});
|
|
});
|