/** * 素材库流程 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, }; } /** 创建一个项目并返回 project id */ async function createProject(request: any, headers: Record, suffix: string): Promise { const resp = await request.post(`${apiBase}/projects`, { headers, data: { name: `Asset Test Proj ${suffix}`, description: 'E2E asset test' }, }); expect(resp.ok(), `创建项目应成功: ${await resp.text()}`).toBeTruthy(); const data = await resp.json(); return data.id; } test.describe('素材库流程', () => { test('创建素材库', async ({ request }) => { const { headers } = await createAuthedUser(request, 'lib-create'); const projectId = await createProject(request, headers, Date.now().toString()); const response = await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: `视频素材库 ${Date.now()}`, kind: 'video', }, }); expect(response.ok(), `创建素材库应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy(); const data = await response.json(); expect(data.id, '应返回素材库 ID').toBeTruthy(); expect(data.name).toContain('视频素材库'); expect(data.kind).toBe('video'); expect(data.project_id).toBe(projectId); }); test('创建素材库 - 无效 kind 反向', async ({ request }) => { const { headers } = await createAuthedUser(request, 'lib-badkind'); const projectId = await createProject(request, headers, Date.now().toString()); const response = await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: 'Bad Kind Library', kind: 'invalid_kind', }, }); // kind 有 pattern 校验 ^(video|voice|image)$,应返回 422 expect([400, 422]).toContain(response.status()); }); test('创建素材库 - 不存在的项目反向', async ({ request }) => { const { headers } = await createAuthedUser(request, 'lib-nopj'); const response = await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: 'nonexistent-project-999', name: 'Orphan Library', kind: 'video', }, }); expect(response.status(), '不存在的项目应返回 404').toBe(404); }); test('列出素材库', async ({ request }) => { const { headers } = await createAuthedUser(request, 'lib-list'); const projectId = await createProject(request, headers, Date.now().toString()); // 创建 2 个不同类型的素材库 await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: `Video Lib ${Date.now()}`, kind: 'video' }, }); await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: `Image Lib ${Date.now()}`, kind: 'image' }, }); // 列出(按 project_id 过滤) const response = await request.get(`${apiBase}/asset-libraries`, { headers, params: { project_id: projectId }, }); expect(response.ok(), `列出素材库应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy(); const data = await response.json(); const items = data.items || []; expect(items.length, '应至少有 2 个素材库').toBeGreaterThanOrEqual(2); const kinds = items.map((i: any) => i.kind); expect(kinds).toContain('video'); expect(kinds).toContain('image'); }); test('创建素材记录', async ({ request }) => { const { headers, userId } = await createAuthedUser(request, 'asset-create'); const projectId = await createProject(request, headers, Date.now().toString()); // 创建素材库 const lib = await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: `Asset Lib ${Date.now()}`, kind: 'video' }, }); expect(lib.ok()).toBeTruthy(); const libData = await lib.json(); // 创建素材记录 const response = await request.post(`${apiBase}/assets`, { headers, data: { project_id: projectId, library_id: libData.id, name: `test_video_${Date.now()}.mp4`, storage_key: `uploads/e2e/test_${Date.now()}.mp4`, mime_type: 'video/mp4', metadata: { duration: 15.5, resolution: '1080p' }, file_size: 1024000, status: 'ready', uploaded_by_user_id: userId, }, }); expect(response.ok(), `创建素材应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy(); const data = await response.json(); expect(data.id, '应返回素材 ID').toBeTruthy(); expect(data.name).toContain('test_video'); expect(data.mime_type).toBe('video/mp4'); expect(data.library_id).toBe(libData.id); }); test('列出素材', async ({ request }) => { const { headers, userId } = await createAuthedUser(request, 'asset-list'); const projectId = await createProject(request, headers, Date.now().toString()); // 创建素材库 const lib = await request.post(`${apiBase}/asset-libraries`, { headers, data: { project_id: projectId, name: `List Lib ${Date.now()}`, kind: 'video' }, }); expect(lib.ok(), `创建素材库应成功: ${await lib.text()}`).toBeTruthy(); const libData = await lib.json(); // 创建 2 个素材 await request.post(`${apiBase}/assets`, { headers, data: { project_id: projectId, library_id: libData.id, name: `clip_a_${Date.now()}.mp4`, storage_key: `uploads/e2e/clip_a.mp4`, mime_type: 'video/mp4', status: 'ready', uploaded_by_user_id: userId, }, }); await request.post(`${apiBase}/assets`, { headers, data: { project_id: projectId, library_id: libData.id, name: `clip_b_${Date.now()}.mp4`, storage_key: `uploads/e2e/clip_b.mp4`, mime_type: 'video/mp4', status: 'ready', uploaded_by_user_id: userId, }, }); // 列出素材 const response = await request.get(`${apiBase}/assets`, { headers, params: { library_id: libData.id }, }); expect(response.ok(), `列出素材应返回 2xx,实际: ${response.status()} ${await response.text()}`).toBeTruthy(); const data = await response.json(); const items = data.items || []; expect(items.length, '应至少有 2 个素材').toBeGreaterThanOrEqual(2); }); test('未登录创建素材库 - 反向', async ({ request }) => { const response = await request.post(`${apiBase}/asset-libraries`, { data: { project_id: 'some-project', name: 'Unauthorized Library', kind: 'video', }, }); expect([401, 403]).toContain(response.status()); }); });