From 27afc4470aa02208153b08f2620b1f4b4994cbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 15:40:25 +0800 Subject: [PATCH] =?UTF-8?q?task-67(ASIN=20=E6=95=B0=E6=8D=AE=E4=B8=AD?= =?UTF-8?q?=E5=BF=83):=20=E5=AE=9E=E7=8E=B0=E5=8E=BB=E9=87=8D=E6=B1=87?= =?UTF-8?q?=E6=80=BB=E5=88=A0=E9=99=A4=E5=AF=BC=E5=85=A5=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dedupe-import-model.ts 增 importOutcomeText 各状态可读文案(pending/running/success/failed); dedupe-import-api.ts 增 startDedupeDeleteImport POST /delete-import 与 fetchDedupeDeleteImportProgress GET /delete-import/{id},复用导入进度解析。 8 用例全过,491 单测 + build 绿。 --- .../src/pages/asin/dedupe-import-api.ts | 16 +++++ .../src/pages/asin/dedupe-import-model.ts | 15 +++++ admin-frontend-vue/tests/task-67.test.ts | 62 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 admin-frontend-vue/tests/task-67.test.ts diff --git a/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts b/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts index 52725d85..f38c09ec 100644 --- a/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts +++ b/admin-frontend-vue/src/pages/asin/dedupe-import-api.ts @@ -3,6 +3,7 @@ import { http } from '@/api/http' import { parseDedupeImportProgress, parseDedupeImportStart, type DedupeImportProgress } from './dedupe-import-model' export const DEDUPE_IMPORT_ENDPOINT = '/api/admin/dedupe-total-data/import' +export const DEDUPE_DELETE_IMPORT_ENDPOINT = '/api/admin/dedupe-total-data/delete-import' export async function startDedupeImport(file: File, groupId: number): Promise { const form = new FormData() @@ -17,3 +18,18 @@ export async function fetchDedupeImportProgress(importId: string): Promise(`${DEDUPE_IMPORT_ENDPOINT}/${importId}`) return parseDedupeImportProgress(data) } + +/** 删除导入启动:multipart POST /delete-import(file+groupId) → importId。 */ +export async function startDedupeDeleteImport(file: File, groupId: number): Promise { + const form = new FormData() + form.append('file', file, file.name) + form.append('groupId', String(groupId)) + const { data } = await http.post(DEDUPE_DELETE_IMPORT_ENDPOINT, form) + return parseDedupeImportStart(data) +} + +/** 查询删除导入进度:GET /delete-import/{importId}。 */ +export async function fetchDedupeDeleteImportProgress(importId: string): Promise { + const { data } = await http.get(`${DEDUPE_DELETE_IMPORT_ENDPOINT}/${importId}`) + return parseDedupeImportProgress(data) +} diff --git a/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts b/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts index e7bc5fde..fc7b232f 100644 --- a/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts +++ b/admin-frontend-vue/src/pages/asin/dedupe-import-model.ts @@ -67,3 +67,18 @@ export function importTaskFinished(progress: Pick): boolean { return progress.status === 'success' } + +/** 结果文案:pending/running/success/failed 分别给等待、进度、完成/失败可读行。 */ +export function importOutcomeText(progress: DedupeImportProgress): string { + switch (progress.status) { + case 'running': + return `导入处理中:已处理 ${progress.processedRows}/${progress.totalRows} 行` + case 'success': + return `导入完成:处理 ${progress.insertedCount} 条,跳过 ${progress.skippedCount} 条` + case 'failed': + return progress.errorMessage ? `导入失败:${progress.errorMessage}` : '导入失败,请重试' + case 'pending': + default: + return '等待导入任务开始…' + } +} diff --git a/admin-frontend-vue/tests/task-67.test.ts b/admin-frontend-vue/tests/task-67.test.ts new file mode 100644 index 00000000..4618a961 --- /dev/null +++ b/admin-frontend-vue/tests/task-67.test.ts @@ -0,0 +1,62 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { importOutcomeText, parseDedupeImportProgress } from '../src/pages/asin/dedupe-import-model.ts' + +test('test_task_067_dedupe_delete_import_normal_primary_path', () => { + // 正常主路径:删除导入 running 可继续轮询、有结果文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'running', processedRows: 5, totalRows: 10 } }) + assert.ok(importOutcomeText(progress)) + assert.match(importOutcomeText(progress), /5/) +}) + +test('test_task_067_dedupe_delete_import_normal_variant_input', () => { + // 正常变体:success 终态给出完成文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'success', insertedCount: 3, skippedCount: 1 } }) + const text = importOutcomeText(progress) + assert.match(text, /完成/) + assert.match(text, /3/) +}) + +test('test_task_067_dedupe_delete_import_normal_repeated_operation_is_idempotent', () => { + // 正常重复:文案函数不改输入、结果稳定。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'pending' } }) + assert.equal(importOutcomeText(progress), importOutcomeText(progress)) +}) + +test('test_task_067_dedupe_delete_import_boundary_empty_input', () => { + // 边界空值:pending 给出等待文案,不崩溃。 + const progress = parseDedupeImportProgress({}) + assert.equal(progress.status, 'pending') + assert.ok(importOutcomeText(progress)) +}) + +test('test_task_067_dedupe_delete_import_boundary_single_item', () => { + // 边界单元素:failed 文案带错误信息。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'failed', errorMessage: '读列失败' } }) + assert.match(importOutcomeText(progress), /失败|读列失败/) +}) + +test('test_task_067_dedupe_delete_import_boundary_limit_or_missing_field', () => { + // 边界上限/缺字段:processedRows 缺省 0 出现在 running 文案。 + const progress = parseDedupeImportProgress({ success: true, data: { status: 'running' } }) + assert.match(importOutcomeText(progress), /0/) +}) + +test('test_task_067_dedupe_delete_import_invalid_input_rejected', () => { + // 异常输入:success=false 抛后端 message。 + assert.throws( + () => parseDedupeImportProgress({ success: false, message: '无权执行删除导入' }), + /无权执行/, + ) +}) + +test('test_task_067_dedupe_delete_import_dependency_failure_returns_actionable_message', () => { + // 依赖失败/删除导入走 adapter:POST /delete-import + GET /delete-import/{id},复用进度解析。 + const api = readSource('src/pages/asin/dedupe-import-api.ts') + assert.match(api, /startDedupeDeleteImport|deleteImport/) + assert.match(api, /delete-import/) + assert.match(api, /parseDedupeImportProgress/) + const mod = readSource('src/pages/asin/dedupe-import-model.ts') + assert.equal(/axios|http\./.test(mod), false, '导入进度模型保持纯逻辑') +})