21fc2869ff
Deploy / Staging E2E Tests (push) Has been skipped
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 141h54m7s
CI/CD Pipeline / Frontend Lint (push) Failing after 141h54m17s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 141h54m17s
186 lines
6.6 KiB
TypeScript
Executable File
186 lines
6.6 KiB
TypeScript
Executable File
/**
|
|
* 项目流程 E2E 测试
|
|
*
|
|
* 覆盖:创建项目、列出项目、获取项目详情
|
|
* 每个测试独立,先注册登录获取 auth token。
|
|
*/
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
const PASSWORD = 'Test123456!';
|
|
const apiBase = process.env.E2E_API_BASE || '/api/v1';
|
|
|
|
function uniqueEmail(prefix: string): string {
|
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}@example.com`;
|
|
}
|
|
|
|
function uniqueUsername(prefix: string): string {
|
|
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
}
|
|
|
|
/** 注册并登录,返回 { headers, email, username, userId } */
|
|
async function createAuthedUser(request: any, label: string) {
|
|
const email = uniqueEmail(label);
|
|
const username = uniqueUsername(label);
|
|
|
|
const reg = await request.post(`${apiBase}/auth/register`, {
|
|
data: { email, password: PASSWORD, username, display_name: `E2E ${label}` },
|
|
});
|
|
expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy();
|
|
const regData = await reg.json();
|
|
|
|
const login = await request.post(`${apiBase}/auth/login`, {
|
|
data: { email, password: PASSWORD },
|
|
});
|
|
expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy();
|
|
const loginData = await login.json();
|
|
|
|
return {
|
|
headers: { Authorization: `Bearer ${loginData.access_token}` },
|
|
email,
|
|
username,
|
|
userId: regData.user_id,
|
|
};
|
|
}
|
|
|
|
test.describe('项目流程', () => {
|
|
test('创建项目', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-create');
|
|
const projectName = `E2E 测试项目 ${Date.now()}`;
|
|
|
|
const response = await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: {
|
|
name: projectName,
|
|
description: 'Playwright E2E 回归测试创建',
|
|
},
|
|
});
|
|
|
|
expect(response.ok(), `创建项目应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy();
|
|
|
|
const data = await response.json();
|
|
expect(data.id, '应返回项目 ID').toBeTruthy();
|
|
expect(data.name).toBe(projectName);
|
|
expect(data.owner_user_id, '应返回所有者 ID').toBeTruthy();
|
|
});
|
|
|
|
test('创建项目名称为空 - 反向', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-empty');
|
|
|
|
const response = await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: { name: '', description: 'Should fail' },
|
|
});
|
|
|
|
// name 有 min_length=1 约束,应返回 422
|
|
expect([400, 422]).toContain(response.status());
|
|
});
|
|
|
|
test('列出项目', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-list');
|
|
|
|
// 先创建 2 个项目
|
|
await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: { name: `List Proj A ${Date.now()}` },
|
|
});
|
|
await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: { name: `List Proj B ${Date.now()}` },
|
|
});
|
|
|
|
// 列出
|
|
const response = await request.get(`${apiBase}/projects`, { headers });
|
|
|
|
expect(response.ok(), `列出项目应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy();
|
|
|
|
const data = await response.json();
|
|
const items = data.items || data.projects || data || [];
|
|
expect(Array.isArray(items)).toBeTruthy();
|
|
expect(items.length, '应至少有 2 个项目').toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
test('获取项目详情', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-detail');
|
|
|
|
// 先创建
|
|
const created = await request.post(`${apiBase}/projects`, {
|
|
headers,
|
|
data: { name: `Detail Proj ${Date.now()}`, description: 'Detail test' },
|
|
});
|
|
expect(created.ok(), `创建应成功: ${await created.text()}`).toBeTruthy();
|
|
const { id: projectId } = await created.json();
|
|
|
|
// 获取详情
|
|
const response = await request.get(`${apiBase}/projects/${projectId}`, { headers });
|
|
|
|
expect(response.ok(), `获取详情应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy();
|
|
|
|
const data = await response.json();
|
|
expect(data.id).toBe(projectId);
|
|
expect(data.name).toBeTruthy();
|
|
expect(data.owner_user_id).toBeTruthy();
|
|
});
|
|
|
|
test('获取不存在的项目 - 反向', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-404');
|
|
|
|
const response = await request.get(`${apiBase}/projects/nonexistent-project-id-999`, { headers });
|
|
|
|
expect(response.status(), '不存在的项目应返回 404').toBe(404);
|
|
});
|
|
|
|
test('未登录列出项目 - 反向', async ({ request }) => {
|
|
const response = await request.get(`${apiBase}/projects`);
|
|
|
|
expect([401, 403]).toContain(response.status());
|
|
});
|
|
|
|
test('未授权访问他人项目 - 反向', async ({ request }) => {
|
|
// 用户 A 创建项目
|
|
const { headers: headersA } = await createAuthedUser(request, 'proj-owner');
|
|
const created = await request.post(`${apiBase}/projects`, {
|
|
headers: headersA,
|
|
data: { name: `Owner Proj ${Date.now()}`, description: 'Owner test' },
|
|
});
|
|
expect(created.ok(), '用户 A 创建项目应成功').toBeTruthy();
|
|
const { id: projectId } = await created.json();
|
|
|
|
// 用户 B 尝试访问用户 A 的项目
|
|
const { headers: headersB } = await createAuthedUser(request, 'proj-intruder');
|
|
const response = await request.get(`${apiBase}/projects/${projectId}`, {
|
|
headers: headersB,
|
|
});
|
|
|
|
// 应返回 403 (Forbidden) 或 404 (Not Found) — 不应泄露资源存在性
|
|
expect([403, 404]).toContain(response.status());
|
|
});
|
|
|
|
test('未授权删除他人项目 - 反向', async ({ request }) => {
|
|
// 用户 A 创建项目
|
|
const { headers: headersA } = await createAuthedUser(request, 'proj-del-owner');
|
|
const created = await request.post(`${apiBase}/projects`, {
|
|
headers: headersA,
|
|
data: { name: `Delete Test Proj ${Date.now()}` },
|
|
});
|
|
expect(created.ok(), '用户 A 创建项目应成功').toBeTruthy();
|
|
const { id: projectId } = await created.json();
|
|
|
|
// 用户 B 尝试删除用户 A 的项目
|
|
const { headers: headersB } = await createAuthedUser(request, 'proj-del-attempt');
|
|
const response = await request.delete(`${apiBase}/projects/${projectId}`, {
|
|
headers: headersB,
|
|
});
|
|
|
|
expect([403, 404]).toContain(response.status());
|
|
});
|
|
|
|
test('使用无效项目 ID 获取详情 - 反向', async ({ request }) => {
|
|
const { headers } = await createAuthedUser(request, 'proj-badid');
|
|
|
|
const response = await request.get(`${apiBase}/projects/`, { headers });
|
|
|
|
// 空 ID 或无效格式应返回 404 或 422
|
|
expect([400, 404, 422]).toContain(response.status());
|
|
});
|
|
});
|