import { test, expect, request as requestFactory, type APIRequestContext } from '@playwright/test' /** * UGC 作品状态字段拆分验证测试 * * 所有 API 请求直接发到后端 localhost:8580,绕过前端代理。 */ // ── 配置 ── const API_BASE = process.env.API_BASE_URL || 'http://localhost:8580/api' const AUTH = { username: process.env.TEST_USERNAME || 'demo', password: process.env.TEST_PASSWORD || 'demo123456', tenantCode: process.env.TEST_TENANT_CODE || 'gdlib', } const VALID_STATUSES = ['draft', 'unpublished', 'pending_review', 'published', 'rejected'] // ── Helper ── function url(path: string) { return `${API_BASE}${path}` } // ── Fixture ── type Fixtures = { api: APIRequestContext } const apiTest = test.extend({ api: async ({}, use) => { // 用 requestFactory(顶层 import)创建独立上下文来登录 const loginCtx = await requestFactory.newContext({}) const loginResp = await loginCtx.post(url('/public/auth/login'), { data: { username: AUTH.username, password: AUTH.password, tenantCode: AUTH.tenantCode }, }) const loginJson = await loginResp.json() await loginCtx.dispose() if (loginJson.code !== 200 || !loginJson.data?.token) { throw new Error(`登录失败: ${JSON.stringify(loginJson)}`) } const token = loginJson.data.token // 创建带 auth header 的 API 上下文 const api = await requestFactory.newContext({ extraHTTPHeaders: { Authorization: `Bearer ${token}` }, }) await use(api) await api.dispose() }, }) // ══════════════════════════════════════════════════════ // 1. 我的作品列表 — status 是字符串枚举 // ══════════════════════════════════════════════════════ apiTest('S-01 我的作品列表 — status 字段为字符串', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=5')) const json = await resp.json() expect(json.code).toBe(200) expect(json.data.list).toBeInstanceOf(Array) for (const work of json.data.list) { expect( VALID_STATUSES.includes(work.status), `作品 id=${work.id} 的 status="${work.status}" 不是合法字符串枚举值`, ).toBe(true) } console.log(`✓ S-01: 返回 ${json.data.list.length} 条作品,status 全部为字符串`) }) apiTest('S-02 我的作品列表 — 按 status=draft 筛选', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=50&status=draft')) const json = await resp.json() expect(json.code).toBe(200) for (const work of json.data.list) { expect(work.status).toBe('draft') } console.log(`✓ S-02: draft 筛选返回 ${json.data.list.length} 条`) }) apiTest('S-03 我的作品列表 — 按 status=unpublished 筛选', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=50&status=unpublished')) const json = await resp.json() expect(json.code).toBe(200) for (const work of json.data.list) { expect(work.status).toBe('unpublished') } console.log(`✓ S-03: unpublished 筛选返回 ${json.data.list.length} 条`) }) apiTest('S-04 我的作品列表 — 按 status=published 筛选', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=50&status=published')) const json = await resp.json() expect(json.code).toBe(200) for (const work of json.data.list) { expect(work.status).toBe('published') } console.log(`✓ S-04: published 筛选返回 ${json.data.list.length} 条`) }) apiTest('S-05 我的作品列表 — 按 status=pending_review 筛选', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=50&status=pending_review')) const json = await resp.json() expect(json.code).toBe(200) for (const work of json.data.list) { expect(work.status).toBe('pending_review') } console.log(`✓ S-05: pending_review 筛选返回 ${json.data.list.length} 条`) }) apiTest('S-06 我的作品列表 — 按 status=rejected 筛选', async ({ api }) => { const resp = await api.get(url('/public/works?page=1&pageSize=50&status=rejected')) const json = await resp.json() expect(json.code).toBe(200) for (const work of json.data.list) { expect(work.status).toBe('rejected') } console.log(`✓ S-06: rejected 筛选返回 ${json.data.list.length} 条`) }) // ══════════════════════════════════════════════════════ // 2. 创建作品 — 初始 status 为 draft // ══════════════════════════════════════════════════════ apiTest('S-07 创建作品 — 初始 status 为 draft', async ({ api }) => { const resp = await api.post(url('/public/works'), { data: { title: `[E2E] 状态验证_${Date.now()}`, description: 'Playwright 自动创建', visibility: 'private', }, }) const json = await resp.json() expect(json.code).toBe(200) expect(json.data.status).toBe('draft') expect(typeof json.data.status).toBe('string') console.log(`✓ S-07: 创建作品 id=${json.data.id}, status="${json.data.status}"`) // 清理 if (json.data.id) { await api.delete(url(`/public/works/${json.data.id}`)) } }) // ══════════════════════════════════════════════════════ // 3. draft 不可发布 // ══════════════════════════════════════════════════════ apiTest('S-08 发布作品 — draft 状态不可发布', async ({ api }) => { const createResp = await api.post(url('/public/works'), { data: { title: `[E2E] 发布测试_${Date.now()}`, visibility: 'private' }, }) const createJson = await createResp.json() expect(createJson.code).toBe(200) const workId = createJson.data.id const publishResp = await api.post(url(`/public/works/${workId}/publish`)) const publishJson = await publishResp.json() expect(publishJson.code).toBe(400) console.log(`✓ S-08: draft 发布被拒绝,code=${publishJson.code}, msg="${publishJson.message}"`) await api.delete(url(`/public/works/${workId}`)) }) // ══════════════════════════════════════════════════════ // 4. 创作历史 // ══════════════════════════════════════════════════════ apiTest('S-09 创作历史 — 作品 status 为字符串', async ({ api }) => { const resp = await api.get(url('/public/creation/history?page=1&pageSize=5')) const json = await resp.json() expect(json.code).toBe(200) if (json.data?.list?.length > 0) { for (const work of json.data.list) { expect( VALID_STATUSES.includes(work.status), `创作历史 id=${work.id} status="${work.status}" 不合法`, ).toBe(true) } } console.log(`✓ S-09: 创作历史 ${json.data?.list?.length ?? 0} 条`) }) // ══════════════════════════════════════════════════════ // 5. 作品详情 // ══════════════════════════════════════════════════════ apiTest('S-10 作品详情 — status + leaiStatus 字段', async ({ api }) => { const listResp = await api.get(url('/public/works?page=1&pageSize=1')) const listJson = await listResp.json() if (listJson.data?.list?.length > 0) { const workId = listJson.data.list[0].id const detailResp = await api.get(url(`/public/works/${workId}`)) const detailJson = await detailResp.json() expect(detailJson.code).toBe(200) const work = detailJson.data.work // status 必须是合法字符串 expect(VALID_STATUSES.includes(work.status)).toBe(true) // leaiStatus 应为数字 if (work.leaiStatus != null) { expect(typeof work.leaiStatus).toBe('number') } console.log(`✓ S-10: 作品详情 status="${work.status}", leaiStatus=${work.leaiStatus}`) } else { console.log('⚠ S-10: 没有作品可测试详情') } }) // ══════════════════════════════════════════════════════ // 6. 作品广场 // ══════════════════════════════════════════════════════ apiTest('S-11 作品广场 — 只返回已发布作品', async ({ api }) => { const resp = await api.get(url('/public/gallery?page=1&pageSize=10')) const json = await resp.json() expect(json.code).toBe(200) if (json.data?.list?.length > 0) { for (const work of json.data.list) { expect(work.status).toBe('published') } } console.log(`✓ S-11: 广场 ${json.data?.list?.length ?? 0} 条已发布作品`) }) // ══════════════════════════════════════════════════════ // 7. 收藏列表 // ══════════════════════════════════════════════════════ apiTest('S-12 我的收藏 — status 为字符串', async ({ api }) => { const resp = await api.get(url('/public/mine/favorites?page=1&pageSize=5')) const json = await resp.json() expect(json.code).toBe(200) if (json.data?.list?.length > 0) { for (const item of json.data.list) { if (item.status) { expect(VALID_STATUSES.includes(item.status)).toBe(true) } } } console.log(`✓ S-12: 收藏 ${json.data?.list?.length ?? 0} 条`) })