task-73(ASIN 数据中心): 实现查询 ASIN 导入删除

POST /api/admin/query-asins/delete-import(file+可选groupId) 启动导入删除,
GET /delete-import/{importId} 轮询(复用 QueryAsinImportProgressVo 含
deletedCount), 新增删除结果文案, 9 个契约测试。
This commit is contained in:
2026-09-05 16:07:48 +08:00
parent 5058baffe0
commit a9b4a56986
3 changed files with 137 additions and 0 deletions
@@ -0,0 +1,26 @@
/** 查询 ASIN 导入删除适配(任务 73):multipart POST /delete-import(file[+groupId]) → importId,及进度轮询。 */
import { http } from '@/api/http'
import {
parseQueryAsinImportProgress,
parseQueryAsinImportStart,
type QueryAsinImportProgress,
} from './query-asin-import-model'
export const QUERY_ASIN_DELETE_IMPORT_ENDPOINT = '/api/admin/query-asins/delete-import'
/** 启动导入删除;groupId 为 Excel 未提供分组时的兜底,可选(>=1 才下发)。 */
export async function startQueryAsinDeleteImport(file: File, groupId?: number): Promise<string> {
const form = new FormData()
form.append('file', file, file.name)
if (typeof groupId === 'number' && Number.isFinite(groupId) && groupId >= 1) {
form.append('groupId', String(Math.floor(groupId)))
}
const { data } = await http.post<unknown>(QUERY_ASIN_DELETE_IMPORT_ENDPOINT, form)
return parseQueryAsinImportStart(data)
}
/** 查询导入删除进度:GET /delete-import/{importId}。 */
export async function fetchQueryAsinDeleteImportProgress(importId: string): Promise<QueryAsinImportProgress> {
const { data } = await http.get<unknown>(`${QUERY_ASIN_DELETE_IMPORT_ENDPOINT}/${importId}`)
return parseQueryAsinImportProgress(data)
}
@@ -84,3 +84,18 @@ export function queryAsinImportOutcomeText(progress: QueryAsinImportProgress): s
return '等待导入任务开始…'
}
}
/** 导入删除结果文案(任务 73):突出删除条数,成功/失败/等待各有可读行。 */
export function queryAsinDeleteOutcomeText(progress: QueryAsinImportProgress): string {
switch (progress.status) {
case 'running':
return `删除处理中:已处理 ${progress.processedRows}/${progress.totalRows}`
case 'success':
return `删除完成:删除 ${progress.deletedCount} 条,跳过 ${progress.skippedCount}`
case 'failed':
return progress.errorMessage ? `删除失败:${progress.errorMessage}` : '删除失败,请重试'
case 'pending':
default:
return '等待删除任务开始…'
}
}
+96
View File
@@ -0,0 +1,96 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readSource } from './helpers.ts'
import {
parseQueryAsinImportStart,
parseQueryAsinImportProgress,
queryAsinImportFinished,
queryAsinImportSucceeded,
queryAsinDeleteOutcomeText,
} from '../src/pages/asin/query-asin-import-model.ts'
test('test_task_073_query_asin_delete_start_normal_primary_path', () => {
// 正常主路径:删除导入启动结果解析出任务 id。
assert.equal(
parseQueryAsinImportStart({ success: true, data: { importId: 'job_del_1' } }),
'job_del_1',
)
})
test('test_task_073_query_asin_delete_start_normal_variant_input', () => {
// 正常变体:已解包对象直传同样解析。
assert.equal(parseQueryAsinImportStart({ importId: 'job_del_2' }), 'job_del_2')
})
test('test_task_073_query_asin_delete_progress_normal_deleted_count', () => {
// 正常主路径:删除进度统计 deletedCount(插入恒为 0)。
const progress = parseQueryAsinImportProgress({
success: true,
data: { status: 'running', processedRows: 50, deletedCount: 12, insertedCount: 0, skippedCount: 3 },
})
assert.equal(progress.status, 'running')
assert.equal(progress.deletedCount, 12)
assert.equal(progress.insertedCount, 0)
assert.equal(queryAsinImportFinished(progress), false)
})
test('test_task_073_query_asin_delete_success_terminal_and_text', () => {
// 正常终态:success 判定成功;结果文案突出删除条数与跳过。
const done = parseQueryAsinImportProgress({
success: true,
data: { status: 'success', deletedCount: 9, skippedCount: 2, insertedCount: 0 },
})
assert.equal(queryAsinImportFinished(done), true)
assert.equal(queryAsinImportSucceeded(done), true)
const text = queryAsinDeleteOutcomeText(done)
assert.match(text, /删除/)
assert.match(text, /9/)
assert.match(text, /2/)
})
test('test_task_073_query_asin_delete_failed_terminal_and_text', () => {
// 边界失败:failed 终态完成但未成功,错误信息进入文案。
const failed = parseQueryAsinImportProgress({
success: true,
data: { status: 'failed', errorMessage: 'Excel 缺少分组列' },
})
assert.equal(queryAsinImportFinished(failed), true)
assert.equal(queryAsinImportSucceeded(failed), false)
assert.match(queryAsinDeleteOutcomeText(failed), /分组/)
})
test('test_task_073_query_asin_delete_progress_boundary_empty', () => {
// 边界空值:空负载按 pending 处理,不崩溃、未完成。
const empty = parseQueryAsinImportProgress({})
assert.equal(empty.status, 'pending')
assert.equal(empty.deletedCount, 0)
assert.equal(queryAsinImportFinished(empty), false)
})
test('test_task_073_query_asin_delete_text_running', () => {
// 边界/过程:running 给行进度;缺 deletedCount 用 0。
assert.match(queryAsinDeleteOutcomeText(parseQueryAsinImportProgress({ data: { status: 'running', processedRows: 4, totalRows: 8 } })), /4\/8/)
})
test('test_task_073_query_asin_delete_invalid_input_rejected', () => {
// 异常输入:success=false 抛后端 message;未知状态按未完成处理。
assert.throws(
() => parseQueryAsinImportProgress({ success: false, message: '删除任务不存在' }),
/删除任务不存在/,
)
assert.throws(
() => parseQueryAsinImportStart({ success: false, message: '无权导入删除' }),
/无权导入删除/,
)
})
test('test_task_073_query_asin_delete_dependency_failure_returns_actionable_message', () => {
// 依赖失败/轮询走 adaptermultipart POST /delete-import + GET /delete-import/{importId}。
const api = readSource('src/pages/asin/query-asin-delete-import-api.ts')
assert.match(api, /delete-import/)
assert.match(api, /http\.post/)
assert.match(api, /http\.get/)
assert.match(api, /FormData/)
assert.match(api, /startQueryAsinDeleteImport/)
assert.match(api, /fetchQueryAsinDeleteImportProgress/)
})