task-119(任务与重复分析中心): 实现重复检查导入/导出状态
新增 duplicate-import-export.ts:导入结果摘要/导入参数归一与导出查询构建。 TDD: task-119.test.ts 8 用例先 RED 后 GREEN。
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/** 重复检查导入/导出(任务 119):导入结果摘要、导入参数与导出查询构建;纯逻辑。
|
||||
* 导出走 GET /duplicate-check-export 直接下载 CSV,导入 POST /duplicate-check-import(multipart)。 */
|
||||
import { unwrap } from '../../api/envelope.ts'
|
||||
import type { DuplicateFilter } from './duplicate-filter.ts'
|
||||
|
||||
/** 导出端点路径(供下载 URL 构造)。 */
|
||||
export const DUPLICATE_EXPORT_PATH = '/duplicate-check-export'
|
||||
|
||||
function count(value: unknown): number {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : 0
|
||||
}
|
||||
|
||||
export type DuplicateImportMode = 'add' | 'delete'
|
||||
|
||||
/** 导入模式归一:delete/del/remove → delete,其余按 add。 */
|
||||
export function normalizeImportMode(mode: unknown): DuplicateImportMode {
|
||||
const text = typeof mode === 'string' ? mode.trim().toLowerCase() : ''
|
||||
return text === 'delete' || text === 'del' || text === 'remove' ? 'delete' : 'add'
|
||||
}
|
||||
|
||||
export interface DuplicateImportResult {
|
||||
totalRows: number
|
||||
asinCount: number
|
||||
mode: DuplicateImportMode
|
||||
}
|
||||
|
||||
/** 导入结果摘要解析(data: {total_rows, asin_count, ...})。 */
|
||||
export function parseDuplicateImportResult(payload: unknown): DuplicateImportResult {
|
||||
const data = unwrap<unknown>(payload)
|
||||
const record = data && typeof data === 'object' ? (data as Record<string, unknown>) : {}
|
||||
return {
|
||||
totalRows: count(record.total_rows ?? record.totalRows),
|
||||
asinCount: count(record.asin_count ?? record.asinCount),
|
||||
mode: normalizeImportMode(record.mode),
|
||||
}
|
||||
}
|
||||
|
||||
/** 导入查询参数(multipart 外的固定参数)。 */
|
||||
export function toDuplicateImportQuery(input: {
|
||||
mode?: unknown
|
||||
shopName?: string
|
||||
countryCodes?: readonly string[]
|
||||
}): { mode: DuplicateImportMode; shop_name?: string; country_codes?: string } {
|
||||
const query: { mode: DuplicateImportMode; shop_name?: string; country_codes?: string } = {
|
||||
mode: normalizeImportMode(input.mode),
|
||||
}
|
||||
const shopName = typeof input.shopName === 'string' ? input.shopName.trim() : ''
|
||||
if (shopName) query.shop_name = shopName
|
||||
const countryCodes = Array.isArray(input.countryCodes)
|
||||
? input.countryCodes.map((code) => String(code).trim()).filter(Boolean)
|
||||
: []
|
||||
if (countryCodes.length) query.country_codes = countryCodes.join(',')
|
||||
return query
|
||||
}
|
||||
|
||||
/** 导出查询参数(不含分页,直接下载 CSV)。 */
|
||||
export function toDuplicateExportQuery(filter: DuplicateFilter): Record<string, string> {
|
||||
const query: Record<string, string> = {}
|
||||
const put = (key: string, value: string) => {
|
||||
const text = value.trim()
|
||||
if (text) query[key] = text
|
||||
}
|
||||
put('view', filter.view)
|
||||
put('asin', filter.asin)
|
||||
put('shop_name', filter.shopName)
|
||||
put('country', filter.country)
|
||||
put('site', filter.site)
|
||||
put('date_from', filter.dateFrom)
|
||||
put('date_to', filter.dateTo)
|
||||
return query
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
import {
|
||||
normalizeImportMode,
|
||||
parseDuplicateImportResult,
|
||||
toDuplicateExportQuery,
|
||||
toDuplicateImportQuery,
|
||||
} from '../src/pages/tasks/duplicate-import-export.ts'
|
||||
|
||||
test('test_task_119_duplicate_import_export_normal_primary_path', () => {
|
||||
// 正常主路径:导入结果摘要解析。
|
||||
const summary = parseDuplicateImportResult({
|
||||
success: true,
|
||||
data: { total_rows: 120, asin_count: 45, mode: 'add' },
|
||||
})
|
||||
assert.equal(summary.totalRows, 120)
|
||||
assert.equal(summary.asinCount, 45)
|
||||
assert.equal(summary.mode, 'add')
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_normal_variant_input', () => {
|
||||
// 正常变体:导入参数归一(delete)。
|
||||
assert.equal(normalizeImportMode('DEL'), 'delete')
|
||||
assert.equal(normalizeImportMode('add'), 'add')
|
||||
assert.deepEqual(toDuplicateImportQuery({ mode: 'add', shopName: '店', countryCodes: ['DE', 'UK'] }), {
|
||||
mode: 'add',
|
||||
shop_name: '店',
|
||||
country_codes: 'DE,UK',
|
||||
})
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_repeated_is_idempotent', () => {
|
||||
// 正常重复:导出查询构建稳定、不改输入。
|
||||
const filter = { view: 'monitor', asin: 'A', shopName: '', country: 'DE', site: '', dateFrom: '', dateTo: '' }
|
||||
assert.deepEqual(toDuplicateExportQuery(filter), toDuplicateExportQuery(filter))
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_boundary_empty_input', () => {
|
||||
// 边界空值:无模式按 add;空 country 不下发。
|
||||
assert.equal(normalizeImportMode(''), 'add')
|
||||
const q = toDuplicateImportQuery({ mode: 'x', shopName: '', countryCodes: [] })
|
||||
assert.equal(q.mode, 'add')
|
||||
assert.equal('country_codes' in q, false)
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_boundary_single_item', () => {
|
||||
// 边界单元素:单国家导出。
|
||||
const query = toDuplicateExportQuery({ view: '', asin: '', shopName: '', country: 'DE', site: '', dateFrom: '', dateTo: '' })
|
||||
assert.equal(query.country, 'DE')
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_boundary_limit_or_missing_field', () => {
|
||||
// 边界上限/缺字段:导入结果缺省字段回 0。
|
||||
const summary = parseDuplicateImportResult({ data: {} })
|
||||
assert.equal(summary.totalRows, 0)
|
||||
assert.equal(summary.asinCount, 0)
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_invalid_input_rejected', () => {
|
||||
// 异常输入:success=false 抛后端 message。
|
||||
assert.throws(() => parseDuplicateImportResult({ success: false, message: '请上传 Excel 文件' }), /请上传 Excel 文件/)
|
||||
})
|
||||
|
||||
test('test_task_119_duplicate_import_export_dependency_failure_returns_actionable_message', () => {
|
||||
// 依赖失败/可操作:导入导出纯逻辑;导出走 GET /duplicate-check-export。
|
||||
const mod = readSource('src/pages/tasks/duplicate-import-export.ts')
|
||||
assert.equal(/axios|http\.|vue/.test(mod), false, '导入导出保持纯逻辑')
|
||||
assert.match(mod, /duplicate-check-export/)
|
||||
})
|
||||
Reference in New Issue
Block a user