Files
xiaoxia-saas/apps/web/e2e/test_asset.spec.ts
T
灵应 87357c8bde
CI/CD Pipeline / Deploy Staging (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Failing after 71h21m22s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 71h22m9s
fix: 修复 CI Lint 全部 22 个 ESLint 警告
- e2e: request: any → APIRequestContext 类型替换(6个文件)
- e2e: 移除未使用变量 _sourceAssetId, _hasProgress
- src: react-refresh/only-export-components 添加 eslint-disable 注释
  (MainLayout, Input, router/index)
- src: EditingPlanner useEffect 补全 resetClips 依赖
- src: MyVoices okButtonProps 类型修正 as ButtonProps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:32:17 +08:00

315 lines
9.2 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 素材库流程 E2E 测试
*
* 覆盖:创建素材库、列出素材库、创建素材记录
* 每个测试独立,先注册登录获取 auth token。
*/
import { expect, test, type APIRequestContext } 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)}`;
}
/** 登录操作,遇到 429 限流自动等待重试 */
async function loginWithRetry(
request: APIRequestContext,
email: string,
password: string,
maxRetries = 2,
) {
for (let i = 0; i <= maxRetries; i++) {
const response = await request.post(`${apiBase}/auth/login`, {
data: { email, password },
});
if (response.status() !== 429) return response;
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, 65000));
}
return request.post(`${apiBase}/auth/login`, {
data: { email, password },
});
}
/** 注册并登录,返回 { headers, email, username, userId } */
async function createAuthedUser(request: APIRequestContext, 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 loginWithRetry(request, email, 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: APIRequestContext,
headers: Record<string, string>,
suffix: string,
): Promise<string> {
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("素材库流程", () => {
// 登录限流 10次/60s,测试可能触发限流等待,给足够超时
test.describe.configure({ timeout: 180_000 });
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: { kind: string }) => 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());
});
});