task-80(ASIN 数据中心): 完成 ASIN 数据中心真实流程验收
live 验收(8/8 通过, 对 api.aishufu.top:18080 admin 账号): 登录可达性、 未登录 401、query-asin 列表+筛选(camel)、skip-price 列表(snake)、去重汇总 列表、商品类目树、类目新建→删除可逆写循环, 均经真实响应与项目解析器验证。
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { parseQueryAsinPage } from '../../src/pages/asin/query-asin-model.ts'
|
||||
import { parseSkipPricePage } from '../../src/pages/asin/skip-price-model.ts'
|
||||
import { parseDedupeTotalPage } from '../../src/pages/asin/dedupe-total-model.ts'
|
||||
import { parseProductCategoryList } from '../../src/pages/asin/product-category-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-task80-asin-acceptance'
|
||||
|
||||
let sessionCookie = ''
|
||||
let bearerToken = ''
|
||||
|
||||
async function ensureSession(): Promise<void> {
|
||||
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<Response> {
|
||||
const headers: Record<string, string> = { ...(init.headers as Record<string, string>) }
|
||||
if (sessionCookie) headers.Cookie = sessionCookie
|
||||
else if (bearerToken) headers.Authorization = `Bearer ${bearerToken}`
|
||||
if (init.body && !headers['Content-Type']) headers['Content-Type'] = 'application/json'
|
||||
return fetch(`${BASE}${path}`, { ...init, headers })
|
||||
}
|
||||
|
||||
test('test_task_080_asin_live_reachability_normal_primary_path', async () => {
|
||||
// 正常主路径(无需凭据):真实服务在线,登录页可访问(200 + HTML)。
|
||||
const res = await fetch(`${BASE}/login`)
|
||||
assert.equal(res.status, 200)
|
||||
const html = await res.text()
|
||||
assert.ok(html.length > 0)
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_unauth_rejected', async () => {
|
||||
// 异常输入(无需凭据):无会话请求各 ASIN 中心列表 → 401 未登录。
|
||||
for (const path of ['/api/admin/query-asins', '/api/admin/skip-price-asins', '/api/admin/product-categories']) {
|
||||
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_080_asin_live_query_asins_list', { skip: !HAS_CREDS }, async () => {
|
||||
// 真实查询 ASIN 列表:camel pageSize 参数;经同一解析器归一。
|
||||
await ensureSession()
|
||||
const res = await authedFetch('/api/admin/query-asins?page=1&pageSize=15')
|
||||
const page = parseQueryAsinPage(await res.json())
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(typeof page.total, 'number')
|
||||
assert.ok(Array.isArray(page.items))
|
||||
if (page.items.length) {
|
||||
const item = page.items[0]
|
||||
assert.equal(typeof item.id, 'number')
|
||||
assert.equal(typeof item.shopName, 'string')
|
||||
}
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_query_asins_filter', { skip: !HAS_CREDS }, async () => {
|
||||
// 真实筛选变体:非空 shopName 过滤返回也是合法分页(经共享解析)。
|
||||
await ensureSession()
|
||||
const res = await authedFetch('/api/admin/query-asins?page=1&pageSize=15&shopName=%E5%BA%97')
|
||||
const page = parseQueryAsinPage(await res.json())
|
||||
assert.equal(res.status, 200)
|
||||
assert.ok(Array.isArray(page.items))
|
||||
if (page.items.length) assert.equal(typeof page.items[0].shopName, 'string')
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_skip_price_list', { skip: !HAS_CREDS }, async () => {
|
||||
// 真实最低价 ASIN 列表:snake page_size 参数;宽行解析。
|
||||
await ensureSession()
|
||||
const res = await authedFetch('/api/admin/skip-price-asins?page=1&page_size=15')
|
||||
const page = parseSkipPricePage(await res.json())
|
||||
assert.equal(res.status, 200)
|
||||
assert.ok(Array.isArray(page.items))
|
||||
if (page.items.length) assert.equal(typeof page.items[0].shopName, 'string')
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_dedupe_list', { skip: !HAS_CREDS }, async () => {
|
||||
// 真实去重汇总列表。
|
||||
await ensureSession()
|
||||
const res = await authedFetch('/api/admin/dedupe-total-data?page=1&pageSize=15')
|
||||
const page = parseDedupeTotalPage(await res.json())
|
||||
assert.equal(res.status, 200)
|
||||
assert.ok(Array.isArray(page.items))
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_product_categories_tree', { skip: !HAS_CREDS }, async () => {
|
||||
// 真实商品类目树:GET /product-categories 返回嵌套树。
|
||||
await ensureSession()
|
||||
const res = await authedFetch('/api/admin/product-categories')
|
||||
const { tree } = parseProductCategoryList(await res.json())
|
||||
assert.equal(res.status, 200)
|
||||
assert.ok(Array.isArray(tree))
|
||||
})
|
||||
|
||||
test('test_task_080_asin_live_category_cycle', { skip: !HAS_CREDS }, async () => {
|
||||
// 正常主路径(写):真实新建类目 → 列表含新名 → 删除清理(结果来自真实业务)。
|
||||
await ensureSession()
|
||||
const name = `acc_cat_${Date.now()}`
|
||||
let createdId: number | null = null
|
||||
try {
|
||||
const create = await authedFetch('/api/admin/product-category', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ parentId: null, name, sortOrder: 0, description: 'live-acceptance' }),
|
||||
})
|
||||
const created = (await create.json()) as { success?: boolean; message?: string; data?: { id?: number } }
|
||||
assert.equal(create.status, 200)
|
||||
assert.equal(created.success, true, created.message || '')
|
||||
assert.equal(typeof created.data?.id, 'number')
|
||||
createdId = created.data!.id as number
|
||||
const { tree } = parseProductCategoryList(await (await authedFetch('/api/admin/product-categories')).json())
|
||||
assert.ok(tree.some((n) => n.id === createdId), '新建类目应出现在树中')
|
||||
} finally {
|
||||
if (createdId !== null) await authedFetch(`/api/admin/product-category/${createdId}`, { method: 'DELETE' })
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user