Files
xiaoxia-saas/apps/web/e2e/test_asset.spec.ts
T
CI Bot 330eb983d8 style: 引入Prettier后首次全量格式化(前端 + Python脚本)
- apps/web/: prettier --write . 全量格式化(98个文件)
- scripts/ci/auto_fix_formatting.py: black 格式化
纯格式改动,无业务逻辑变更
2026-07-18 17:46:20 +08:00

295 lines
8.9 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())
})
})