Files
crawler-plugin/admin-frontend-vue/tests/duplicate-console.test.ts
T
huangzd1997 4c88964473 task-283(后台迁移收尾/发布): admin-vue 后台工程入库 + Flask 后台整体退役
- 新后台 admin-frontend-vue 整工程入库(base=/admin-vue/、History 路由、Nginx 静态托管方案与 verify-dist 校验)
- Java AdminConsoleController 改为入口重定向: /admin|/admin.html、/login|/login.html -> /admin-vue/; 删除 classpath:static 旧 admin.html/login.html 单页与 admin.js 副本
- Flask 后台整体退役: 删除 admin_api/auth/main 蓝图、web_source 页面、static 脚本与 admin 相关测试; app.py 收敛为仅注册 version_bp(/api/version、/api/version/latest, 供桌面端更新检查)
- AdminApiGuardFilterTest 豁免样例路径 /admin.html -> /admin-vue/
2026-09-06 10:41:47 +08:00

121 lines
5.6 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { parseDuplicateConsole, parseLedgerPage, parseConsoleGroup, parseLedgerRow } from '../src/pages/tasks/duplicate-console-model.ts'
import {
indexShopGroups,
dupWithinGroup,
storeColumnsOf,
} from '../src/pages/tasks/duplicate-console-logic.ts'
function occurrence(asin: string, shop: string, group: string, country: string, date: string) {
return { asin, date, price: '', brand: 'B', shop_name: shop, group_name: group, country_codes: [country], country }
}
/** 已解析模型形态(camel)的 occurrence,供纯逻辑派生入参。 */
function camelOcc(asin: string, shop: string, group: string, country: string, date: string) {
return { asin, date, price: '', brand: 'B', shopName: shop, groupName: group, countryCodes: [country], country }
}
test('test_dup_console_model_parses_overview_dup_and_groups', () => {
const consoleData = parseDuplicateConsole({
success: true,
data: {
pending: false,
scanned_at: '2026-09-04 03:10:00',
summary: { shop_count: 4, asin_total: 6, record_total: 10, duplicate_asin_total: 3, duplicate_shop_count: 4, site_count: 3, asin_per_shop: 2.5, source: 'job' },
shops: [
{ shop_name: 'ShopA', group_name: 'GroupA', country_codes: ['UK', 'DE'], asin_count: 3, record_count: 3 },
{ shop_name: 'ShopB', group_name: 'GroupA', country_codes: ['UK'], asin_count: 3, record_count: 3 },
{ shop_name: 'ShopC', group_name: 'GroupC', country_codes: ['FR'], asin_count: 2, record_count: 2 },
],
dup: [
{ asin: 'E0000001', shop_count: 3, record_count: 3, occurrences: [
occurrence('E0000001', 'ShopA', 'GroupA', 'UK', '2026-08-29'),
occurrence('E0000001', 'ShopB', 'GroupA', 'UK', '2026-08-30'),
occurrence('E0000001', 'ShopC', 'GroupC', 'FR', '2026-08-31'),
] },
],
groups: [{ name: 'GroupA', shop_count: 2, asin_unique: 4, record_count: 6, dup_count: 2 }],
total_dup: 1,
},
})
assert.equal(consoleData.overview.pending, false)
assert.equal(consoleData.overview.summary?.asinTotal, 6)
assert.equal(consoleData.overview.shops.length, 3)
assert.equal(consoleData.totalDup, 1)
assert.equal(consoleData.dup[0].asin, 'E0000001')
assert.equal(consoleData.dup[0].occurrences.length, 3)
const group = consoleData.groups[0]
assert.deepEqual(group, { name: 'GroupA', shopCount: 2, asinUnique: 4, recordCount: 6, dupCount: 2 })
})
test('test_dup_console_parse_group_ignores_missing_name', () => {
assert.equal(parseConsoleGroup({ shop_count: 1 }), null)
assert.ok(parseConsoleGroup({ name: 'G', shop_count: 2 }))
})
test('test_dup_console_group_derivation_restricts_to_members', () => {
const dupA = { asin: 'A', shopCount: 2, recordCount: 2, occurrences: [
camelOcc('A', 'ShopA', 'GroupA', 'UK', '2026-08-01'),
camelOcc('A', 'ShopB', 'GroupA', 'UK', '2026-08-02'),
] }
const dupE = { asin: 'E', shopCount: 3, recordCount: 3, occurrences: [
camelOcc('E', 'ShopA', 'GroupA', 'UK', '2026-08-01'),
camelOcc('E', 'ShopB', 'GroupA', 'UK', '2026-08-02'),
camelOcc('E', 'ShopC', 'GroupC', 'FR', '2026-08-03'),
] }
const groups = indexShopGroups([
{ shopName: 'ShopA', groupName: 'GroupA', countryCodes: [], asinCount: 0, recordCount: 0 },
{ shopName: 'ShopB', groupName: 'GroupA', countryCodes: [], asinCount: 0, recordCount: 0 },
{ shopName: 'ShopC', groupName: 'GroupC', countryCodes: [], asinCount: 0, recordCount: 0 },
])
assert.deepEqual(groups.members.GroupA, ['ShopA', 'ShopB'])
assert.deepEqual(groups.shopGroupNames.ShopC, ['GroupC'])
const inGroupA = dupWithinGroup([dupA, dupE], groups.members.GroupA)
assert.equal(inGroupA.length, 2)
// E 组内只剩 ShopA/ShopB 两条记录。
const e = inGroupA.find((item) => item.asin === 'E')
assert.equal(e?.shopCount, 2)
assert.equal(e?.recordCount, 2)
// 单店成员组内不会命中(ShopD 不在数据里)。
assert.deepEqual(dupWithinGroup([dupA, dupE], ['ShopD']), [])
})
test('test_dup_console_store_columns_order_and_restriction', () => {
const dupA = { asin: 'A', shopCount: 2, recordCount: 2, occurrences: [
camelOcc('A', 'ShopB', 'GroupA', 'UK', '2026-08-01'),
camelOcc('A', 'ShopA', 'GroupA', 'UK', '2026-08-02'),
] }
assert.deepEqual(storeColumnsOf([dupA]), ['ShopB', 'ShopA'])
assert.deepEqual(storeColumnsOf([dupA], ['ShopA', 'ShopB', 'ShopC']), ['ShopB', 'ShopA'])
// 空行 + 显式成员列:仍输出成员店铺。
assert.deepEqual(storeColumnsOf([], ['ShopA', 'ShopB']), ['ShopA', 'ShopB'])
})
test('test_dup_console_ledger_parse_rows_and_paging', () => {
const page = parseLedgerPage({
success: true,
data: {
pending: false,
scanned_at: '2026-09-04 03:10:00',
items: [
{ asin: 'E0000001', brand: 'BrandE', store_count: 3, stores: ['ShopA', 'ShopB', 'ShopC'], groups: ['GroupA', 'GroupC'], countries: ['UK', 'FR'], record_count: 3, earliest: '2026-08-29 00:00:00', latest: '2026-08-31 00:00:00', occurrences: [occurrence('E0000001', 'ShopA', 'GroupA', 'UK', '2026-08-29')] },
],
total: 1,
page: 1,
page_size: 200,
},
})
assert.equal(page.pending, false)
assert.equal(page.total, 1)
assert.equal(page.pageSize, 200)
const row = page.items[0]
assert.equal(row.asin, 'E0000001')
assert.equal(row.storeCount, 3)
assert.equal(row.occurrences.length, 1)
const single = parseLedgerRow({ asin: 'X0000001', store_count: 0, stores: [], groups: [], countries: [], record_count: 0, earliest: '', latest: '', occurrences: [] })
assert.equal(single?.storeCount, 0)
assert.equal(parseLedgerRow({}), null)
})