task-64(ASIN 数据中心): 实现去重汇总导出等待状态
新增 dedupe-total-export.ts 纯模型:buildDedupeExportQuery/toExportUrl 按用户名/日期区间/ 分组/国家构造 /api/admin/dedupe-total-data/export 下载地址(空白/非法条件不下发), DedupeExportStage + exportStageHint 提供运行/完成/失败等待提示。8 用例全过,467 单测 + build 绿。
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
/** 去重汇总导出等待模型(任务 64):导出 URL 构建 + 运行等待态提示,纯逻辑。 */
|
||||||
|
import { validIsoDateOrUndefined } from './asin-filter.ts'
|
||||||
|
import type { DedupeTotalFilterState } from './dedupe-total-filter.ts'
|
||||||
|
|
||||||
|
export const DEDUPE_TOTAL_EXPORT_ENDPOINT = '/api/admin/dedupe-total-data/export'
|
||||||
|
|
||||||
|
export type DedupeExportStage = 'idle' | 'running' | 'done' | 'error'
|
||||||
|
|
||||||
|
/** 运行中/完成等待提示;idle 无提示。 */
|
||||||
|
export function exportStageHint(stage: DedupeExportStage): string | undefined {
|
||||||
|
switch (stage) {
|
||||||
|
case 'running':
|
||||||
|
return '正在生成并下载导出文件,请稍候…'
|
||||||
|
case 'done':
|
||||||
|
return '导出完成'
|
||||||
|
case 'error':
|
||||||
|
return '导出失败,请重试'
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按导出可用筛选(用户名/日期区间/分组/国家)构建查询串;空白/非法条件不下发。 */
|
||||||
|
export function buildDedupeExportQuery(state: DedupeTotalFilterState): string {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
const username = (state.username || '').trim()
|
||||||
|
const country = (state.country || '').trim()
|
||||||
|
if (username) params.set('username', username)
|
||||||
|
if (country) params.set('country', country)
|
||||||
|
const startDate = validIsoDateOrUndefined(state.startDate)
|
||||||
|
const endDate = validIsoDateOrUndefined(state.endDate)
|
||||||
|
if (startDate) params.set('start_date', startDate)
|
||||||
|
if (endDate) params.set('end_date', endDate)
|
||||||
|
if (typeof state.groupId === 'number' && state.groupId >= 1) params.set('group_id', String(state.groupId))
|
||||||
|
return params.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出下载地址:浏览器直接访问该 URL 触发流式下载。 */
|
||||||
|
export function toExportUrl(state: DedupeTotalFilterState): string {
|
||||||
|
const query = buildDedupeExportQuery(state)
|
||||||
|
return query ? `${DEDUPE_TOTAL_EXPORT_ENDPOINT}?${query}` : DEDUPE_TOTAL_EXPORT_ENDPOINT
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readSource } from './helpers.ts'
|
||||||
|
import {
|
||||||
|
DEDUPE_TOTAL_EXPORT_ENDPOINT,
|
||||||
|
buildDedupeExportQuery,
|
||||||
|
toExportUrl,
|
||||||
|
exportStageHint,
|
||||||
|
} from '../src/pages/asin/dedupe-total-export.ts'
|
||||||
|
import { createDedupeTotalFilterState } from '../src/pages/asin/dedupe-total-filter.ts'
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_normal_primary_path', () => {
|
||||||
|
// 正常主路径:完整日期区间+国家生成导出查询串。
|
||||||
|
const query = buildDedupeExportQuery({
|
||||||
|
...createDedupeTotalFilterState(),
|
||||||
|
startDate: '2026-01-01',
|
||||||
|
endDate: '2026-01-31',
|
||||||
|
country: 'DE',
|
||||||
|
})
|
||||||
|
const params = new URLSearchParams(query)
|
||||||
|
assert.equal(params.get('start_date'), '2026-01-01')
|
||||||
|
assert.equal(params.get('end_date'), '2026-01-31')
|
||||||
|
assert.equal(params.get('country'), 'DE')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_normal_variant_input', () => {
|
||||||
|
// 正常变体:按上传用户名/分组导出。
|
||||||
|
const query = buildDedupeExportQuery({
|
||||||
|
...createDedupeTotalFilterState(),
|
||||||
|
username: ' 张伟恒 ',
|
||||||
|
groupId: 3,
|
||||||
|
})
|
||||||
|
const params = new URLSearchParams(query)
|
||||||
|
assert.equal(params.get('username'), '张伟恒')
|
||||||
|
assert.equal(params.get('group_id'), '3')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_normal_repeated_operation_is_idempotent', () => {
|
||||||
|
// 正常重复:同一状态生成稳定查询串。
|
||||||
|
const state = { ...createDedupeTotalFilterState(), country: 'UK' }
|
||||||
|
assert.equal(buildDedupeExportQuery(state), buildDedupeExportQuery(state))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_boundary_empty_input', () => {
|
||||||
|
// 边界空值:无筛选导出 → 无查询参数但保留端点。
|
||||||
|
const url = toExportUrl(createDedupeTotalFilterState())
|
||||||
|
assert.ok(url.startsWith(DEDUPE_TOTAL_EXPORT_ENDPOINT))
|
||||||
|
assert.equal(url.includes('?'), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_boundary_single_item', () => {
|
||||||
|
// 边界单元素:单国家导出。
|
||||||
|
const query = buildDedupeExportQuery({ ...createDedupeTotalFilterState(), country: 'UK' })
|
||||||
|
assert.equal(new URLSearchParams(query).get('country'), 'UK')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_boundary_limit_or_missing_field', () => {
|
||||||
|
// 边界上限/缺字段:非法日期与空白条件不下发;negative groupId 忽略。
|
||||||
|
const query = buildDedupeExportQuery({
|
||||||
|
...createDedupeTotalFilterState(),
|
||||||
|
startDate: 'bad',
|
||||||
|
country: ' ',
|
||||||
|
groupId: -1,
|
||||||
|
username: ' ',
|
||||||
|
})
|
||||||
|
assert.equal(query, '')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_invalid_input_rejected', () => {
|
||||||
|
// 异常输入:导出等待状态文案与运行态语义一致。
|
||||||
|
assert.ok(exportStageHint('running'))
|
||||||
|
assert.equal(exportStageHint('idle'), undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_task_064_dedupe_export_dependency_failure_returns_actionable_message', () => {
|
||||||
|
// 依赖失败/可操作:导出走浏览器下载端点 + 等待态提示;URL 构建纯逻辑。
|
||||||
|
const mod = readSource('src/pages/asin/dedupe-total-export.ts')
|
||||||
|
assert.equal(/axios|http\.|vue/.test(mod), false, '导出 URL/等待模型保持纯逻辑')
|
||||||
|
assert.match(mod, /start_date/)
|
||||||
|
assert.match(mod, /export/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user