import test from 'node:test' import assert from 'node:assert/strict' import { readSource } from './helpers.ts' import { parseQueryAsinPage, toQueryAsinItem, asinForCountry, queryAsinItemHasAnyAsin, QUERY_ASIN_COUNTRIES, } from '../src/pages/asin/query-asin-model.ts' import { createQueryAsinFilterState, queryAsinFilterActive, normalizeQueryAsinCountry, toQueryAsinParams, toQueryAsinQuery, } from '../src/pages/asin/query-asin-filter.ts' import { buildQueryAsinExportQuery, toQueryAsinExportUrl } from '../src/pages/asin/query-asin-export.ts' test('test_task_071_query_asin_list_normal_primary_path', () => { // 正常主路径:Java QueryAsinPageVo(camel) 宽行(每店铺 5 国家 ASIN)解析为前端分页结果。 const page = parseQueryAsinPage({ success: true, data: { items: [ { id: 11, groupId: 3, groupName: '华东组', shopName: 'BlueWave', asinDe: 'B0DE001', asinUk: 'B0UK001', asinFr: 'B0FR001', asinIt: 'B0IT001', asinEs: 'B0ES001', createdAt: '2026-01-01T10:00:00', updatedAt: '2026-01-02T11:00:00', }, ], total: 1, page: 1, pageSize: 15, }, }) assert.equal(page.items.length, 1) const item = page.items[0] assert.equal(item.id, 11) assert.equal(item.groupId, 3) assert.equal(item.groupName, '华东组') assert.equal(item.shopName, 'BlueWave') assert.equal(item.asinDe, 'B0DE001') assert.equal(item.asinUk, 'B0UK001') assert.equal(item.asinFr, 'B0FR001') assert.equal(item.asinIt, 'B0IT001') assert.equal(item.asinEs, 'B0ES001') assert.equal(item.createdAt, '2026-01-01T10:00:00') assert.equal(page.total, 1) assert.equal(page.pageSize, 15) }) test('test_task_071_query_asin_list_normal_variant_input', () => { // 正常变体:已解包 VO 直传归一;蛇形 asin_de 兜底;缺国家 ASIN/分组留空。 const page = parseQueryAsinPage({ items: [{ id: 12, shopName: 'RedSun', asin_de: 'B0DE002', asinUk: 'B0UK002' }], total: 4, page: 2, pageSize: 20, }) const item = page.items[0] assert.equal(item.shopName, 'RedSun') assert.equal(item.asinDe, 'B0DE002') assert.equal(item.asinUk, 'B0UK002') assert.equal(item.asinFr, '') assert.equal(item.groupId, null) assert.equal(item.groupName, '') assert.equal(page.page, 2) assert.equal(page.pageSize, 20) }) test('test_task_071_query_asin_list_normal_repeated_is_idempotent', () => { // 正常重复:解析与筛选参数生成不改输入、结果稳定。 const payload = { data: { items: [{ id: 13, shopName: 'A', asinDe: 'X' }], total: 1, page: 1, pageSize: 15 }, } assert.deepEqual(parseQueryAsinPage(payload), parseQueryAsinPage(payload)) const state = { groupId: null, shopName: ' 店 ', asin: '', country: '' } const first = toQueryAsinParams(state, 1, 15) const second = toQueryAsinParams(state, 1, 15) assert.deepEqual(first, second) assert.deepEqual(state, { groupId: null, shopName: ' 店 ', asin: '', country: '' }, '不修改调用方状态') }) test('test_task_071_query_asin_list_boundary_empty_input', () => { // 边界空值:空负载回默认分页结果(空列表/total 0/page 1/页大小 15)。 const page = parseQueryAsinPage({}) assert.deepEqual(page.items, []) assert.equal(page.total, 0) assert.equal(page.page, 1) assert.equal(page.pageSize, 10) }) test('test_task_071_query_asin_list_boundary_single_item', () => { // 边界单元素:单条记录;缺 id 的行被过滤。 const page = parseQueryAsinPage({ data: { items: [{ id: 7, shopName: 'Z' }, { shopName: 'no-id' }], total: 2, page: 1, pageSize: 15 }, }) assert.equal(page.items.length, 1) assert.equal(page.items[0].id, 7) }) test('test_task_071_query_asin_list_boundary_limit_or_missing_field', () => { // 边界上限/缺字段:页码下限 1、页大小上限 100 对齐后端;空白/非法/小于1的分组不下发。 const query = toQueryAsinQuery(toQueryAsinParams( { groupId: 0, shopName: ' ', asin: '', country: 'XX' }, 0, 999, )) assert.equal(query.page, 1) assert.equal(query.page_size, 100) assert.equal('group_id' in query, false, '分组 0 不应下发') assert.equal('shop_name' in query, false, '空白店铺名不应下发') assert.equal('asin' in query, false) assert.equal('country' in query, false, '不支持的国家码不应下发') // 正常非空值以 Java snake_case 参数名下发(QueryAsinController @RequestParam(page_size/group_id/shop_name) 契约)。 const filled = toQueryAsinQuery(toQueryAsinParams( { groupId: 5, shopName: ' 蓝店 ', asin: 'B0X', country: 'de' }, 3, 25, )) assert.equal(filled.page, 3) assert.equal(filled.page_size, 25) assert.equal(filled.group_id, 5) assert.equal(filled.shop_name, '蓝店') assert.equal(filled.asin, 'B0X') assert.equal(filled.country, 'DE') assert.equal('pageSize' in (filled as Record), false, '不应下发 pageSize(camel):后端收不到会导致筛选/分页失效') assert.equal('groupId' in (filled as Record), false, '不应下发 groupId(camel)') assert.equal('shopName' in (filled as Record), false, '不应下发 shopName(camel)') }) test('test_task_071_query_asin_country_semantics', () => { // 国家语义:每店铺行按国家取 ASIN 列、整行是否有任一国家 ASIN;国家码大小写归一与白名单。 const item = toQueryAsinItem({ id: 21, shopName: 'S', asinDe: 'DE1', asinEs: 'ES1' }) assert.ok(item) assert.equal(QUERY_ASIN_COUNTRIES.length, 5) assert.equal(asinForCountry(item!, 'DE'), 'DE1') assert.equal(asinForCountry(item!, 'UK'), '') assert.equal(asinForCountry(item!, 'ES'), 'ES1') assert.equal(asinForCountry(item!, 'XX'), '') assert.equal(queryAsinItemHasAnyAsin(item!), true) const empty = toQueryAsinItem({ id: 22, shopName: 'E' }) assert.equal(queryAsinItemHasAnyAsin(empty!), false) assert.equal(normalizeQueryAsinCountry('de'), 'DE') assert.equal(normalizeQueryAsinCountry('UK'), 'UK') assert.equal(normalizeQueryAsinCountry(''), '') assert.equal(normalizeQueryAsinCountry('CN'), '') assert.equal(queryAsinFilterActive({ groupId: null, shopName: '', asin: '', country: '' }), false) assert.equal(queryAsinFilterActive({ groupId: null, shopName: ' 店 ', asin: '', country: '' }), true) assert.equal(queryAsinFilterActive({ groupId: 2, shopName: '', asin: '', country: '' }), true) }) test('test_task_071_query_asin_list_invalid_input_rejected', () => { // 异常输入:success=false 抛后端 message;非对象/非数字 id 行不入列表;非法国家被归一丢弃。 assert.throws(() => parseQueryAsinPage({ success: false, message: '无权访问查询 ASIN 数据' }), /无权访问/) assert.equal(toQueryAsinItem('garbage'), null) assert.equal(toQueryAsinItem({ shopName: 'no id' }), null) assert.deepEqual(createQueryAsinFilterState(), { groupId: null, shopName: '', asin: '', country: '' }) }) test('test_task_071_query_asin_list_dependency_failure_returns_actionable_message', () => { // 依赖失败/加载走 adapter:GET /api/admin/query-asins + http.get + 解析,页面不内联。 const api = readSource('src/pages/asin/query-asin-api.ts') assert.match(api, /\/api\/admin\/query-asins/) assert.match(api, /http\.get/) assert.match(api, /parseQueryAsinPage/) assert.match(api, /toQueryAsinQuery/) const model = readSource('src/pages/asin/query-asin-model.ts') assert.equal(/axios|http\./.test(model), false, '查询 ASIN 列表解析保持纯逻辑') }) test('test_task_071_query_asin_export_url_follows_filter', () => { // 导出 URL 跟随当前筛选:非空条件以 snake 参数下发(后端 export @RequestParam 契约),空白/非法条件与无筛选时退回全量。 const url = toQueryAsinExportUrl({ groupId: 5, shopName: ' 蓝店 ', asin: ' B0X ', country: 'de' }) assert.equal(url.startsWith('/api/admin/query-asins/export?'), true) const params = new URLSearchParams(url.split('?')[1]) assert.equal(params.get('group_id'), '5') assert.equal(params.get('shop_name'), '蓝店') assert.equal(params.get('asin'), 'B0X') assert.equal(params.get('country'), 'DE') assert.equal(buildQueryAsinExportQuery({ groupId: 0, shopName: ' ', asin: '', country: 'XX' }), '', '空白/非法条件不下发') assert.equal(toQueryAsinExportUrl({ groupId: null, shopName: '', asin: '', country: '' }), '/api/admin/query-asins/export', '无筛选即全量导出') })