From 818eed0cb5d313deed3b03fe3c313dabc7e48f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 17:28:13 +0800 Subject: [PATCH] =?UTF-8?q?task-120(=E4=BB=BB=E5=8A=A1=E4=B8=8E=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=88=86=E6=9E=90=E4=B8=AD=E5=BF=83):=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E4=BB=BB=E5=8A=A1=E4=B8=8E=E9=87=8D=E5=A4=8D=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E7=9C=9F=E5=AE=9E=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit live 验收(8/8, api.aishufu.top admin 账号, Node fetch 直连):登录可达、未登录 401、视频任务列表、店铺数据任务列表、重复检查总览/明细/详情、统一信封。 --- .../tests/live/task-120-task-live.test.ts | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 admin-frontend-vue/tests/live/task-120-task-live.test.ts diff --git a/admin-frontend-vue/tests/live/task-120-task-live.test.ts b/admin-frontend-vue/tests/live/task-120-task-live.test.ts new file mode 100644 index 00000000..dc938801 --- /dev/null +++ b/admin-frontend-vue/tests/live/task-120-task-live.test.ts @@ -0,0 +1,117 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { parseImageVideoPage } from '../../src/pages/tasks/image-video-model.ts' +import { parseShopDataTaskPage } from '../../src/pages/tasks/shop-data-model.ts' +import { parseDuplicateOverview } from '../../src/pages/tasks/duplicate-model.ts' +import { parseDuplicateItemsPage } from '../../src/pages/tasks/duplicate-model.ts' + +const BASE = process.env.AIIMAGE_LIVE_BASE || 'http://127.0.0.1:18080' +const USER = process.env.AIIMAGE_LIVE_USER || '' +const PASS = process.env.AIIMAGE_LIVE_PASS || '' +const HAS_CREDS = Boolean(USER && PASS) +const DEVICE = 'claude-task120-task-acceptance' + +let sessionCookie = '' +let bearerToken = '' + +async function ensureSession(): Promise { + if (sessionCookie || bearerToken) return + const res = await fetch(`${BASE}/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: USER, password: PASS, deviceId: DEVICE }), + }) + const body = (await res.json()) as { success?: boolean; message?: string; data?: { token?: string } } + assert.equal(body.success, true, `登录应成功: ${body.message || ''}`) + if (body.data?.token) bearerToken = body.data.token + const cookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [] + const setCookie = cookies[0] || res.headers.get('set-cookie') || '' + const match = /aiimage_token=([^;]+)/.exec(setCookie) + if (match) sessionCookie = `aiimage_token=${match[1]}` + assert.ok(sessionCookie || bearerToken, '登录应下发会话 cookie 或 token') +} + +async function authedFetch(path: string, init: RequestInit = {}): Promise { + const headers: Record = { ...(init.headers as Record) } + if (sessionCookie) headers.Cookie = sessionCookie + else if (bearerToken) headers.Authorization = `Bearer ${bearerToken}` + return fetch(`${BASE}${path}`, { ...init, headers }) +} + +test('test_task_120_task_live_reachability_normal_primary_path', async () => { + // 正常主路径(无需凭据):真实服务在线,登录页可访问。 + const res = await fetch(`${BASE}/login`) + assert.equal(res.status, 200) + const html = await res.text() + assert.ok(html.length > 0) +}) + +test('test_task_120_task_live_unauth_rejected', async () => { + // 异常输入(无需凭据):任务/重复检查列表未登录返回 401。 + for (const path of ['/api/admin/image-video-tasks?page=1&page_size=5', '/api/admin/shop-data-crawl-tasks?page=1&page_size=5']) { + const res = await fetch(`${BASE}${path}`) + const body = (await res.json()) as { success?: boolean; code?: number } + assert.equal(body.success, false, path) + assert.equal(body.code, 401, path) + } +}) + +test('test_task_120_task_live_video_tasks', { skip: !HAS_CREDS }, async () => { + // 真实视频任务列表:snake page_size 参数 + 解析归一。 + await ensureSession() + const res = await authedFetch('/api/admin/image-video-tasks?page=1&page_size=5') + const page = parseImageVideoPage(await res.json()) + assert.equal(res.status, 200) + assert.ok(Array.isArray(page.items)) + assert.equal(typeof page.total, 'number') +}) + +test('test_task_120_task_live_shop_data_tasks', { skip: !HAS_CREDS }, async () => { + // 真实店铺数据任务列表(按店铺分组)。 + await ensureSession() + const res = await authedFetch('/api/admin/shop-data-crawl-tasks?page=1&page_size=5') + const page = parseShopDataTaskPage(await res.json()) + assert.equal(res.status, 200) + assert.ok(Array.isArray(page.items)) + assert.equal(typeof page.total, 'number') +}) + +test('test_task_120_task_live_duplicate_overview', { skip: !HAS_CREDS }, async () => { + // 真实重复检查总览(pending 或已扫描均可解析)。 + await ensureSession() + const res = await authedFetch('/api/admin/shop-data-crawl/duplicate-check-overview') + const overview = parseDuplicateOverview(await res.json()) + assert.equal(res.status, 200) + assert.equal(typeof overview.pending, 'boolean') + assert.ok(Array.isArray(overview.shops)) +}) + +test('test_task_120_task_live_duplicate_items', { skip: !HAS_CREDS }, async () => { + // 真实重复检查明细列表(pending 空态或数据)。 + await ensureSession() + const res = await authedFetch('/api/admin/shop-data-crawl/duplicate-check-items?page=1&page_size=10') + const page = parseDuplicateItemsPage(await res.json()) + assert.equal(res.status, 200) + assert.equal(typeof page.pending, 'boolean') + assert.ok(Array.isArray(page.items)) +}) + +test('test_task_120_task_live_duplicate_detail', { skip: !HAS_CREDS }, async () => { + // 真实重复详情卡片分页。 + await ensureSession() + const res = await authedFetch('/api/admin/shop-data-crawl/duplicate-check-detail?page=1&page_size=3') + const detail = await res.json() + assert.equal(res.status, 200) + assert.ok(typeof detail === 'object') +}) + +test('test_task_120_task_live_contract_summary', { skip: !HAS_CREDS }, async () => { + // 正常主路径(读契约):统一 success 信封覆盖任务列表接口。 + await ensureSession() + for (const path of ['/api/admin/image-video-tasks?page=1&page_size=2', '/api/admin/shop-data-crawl-tasks?page=1&page_size=2']) { + const res = await authedFetch(path) + const body = (await res.json()) as { success?: boolean; data?: unknown } + assert.equal(body.success, true, path) + assert.ok(body.data && typeof body.data === 'object', path) + } +})