Files
xiaoxia-saas/apps/web/e2e/test_asset.spec.ts
xiaoxia 0fcb77b991
CI Build & Deploy Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI Build & Deploy Pipeline / Production Browser E2E (push) Failing after 1437h19m24s
CI Build & Deploy Pipeline / Build Production Web Image (push) Failing after 1437h19m26s
CI Build & Deploy Pipeline / Deploy Production (push) Failing after 1437h19m25s
CI Build & Deploy Pipeline / Build Production API Image (push) Failing after 1437h19m26s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1437h21m16s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Failing after 1437h51m29s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
ci: Prettier纳入两层防御体系 (#520)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-18 18:08:19 +08:00

295 lines
8.9 KiB
TypeScript
Executable File
Raw Permalink 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())
})
})