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/
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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)
|
||||
})
|
||||
Reference in New Issue
Block a user