4c88964473
- 新后台 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/
121 lines
5.6 KiB
TypeScript
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)
|
|
})
|