优化
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
COMPLETION_PHASE,
|
||||
completionPresentation,
|
||||
refreshCompletionAttempt,
|
||||
resolveCompletionPhase,
|
||||
shouldReconcileCompletion
|
||||
} from '../utils/loading-completion-state.js'
|
||||
|
||||
test('accepted completion is processing and blocks every repeated mutation', () => {
|
||||
for (const facts of [
|
||||
{ taskStatus: 'COMPLETING' },
|
||||
{ commandStatus: 'PROCESSING' },
|
||||
{ taskStatus: 'COMPLETING', commandStatus: 'PROCESSING' }
|
||||
]) {
|
||||
const phase = resolveCompletionPhase(facts)
|
||||
assert.equal(phase, COMPLETION_PHASE.PROCESSING)
|
||||
assert.deepEqual(completionPresentation(phase), { label: '完成处理中', action: '', blocked: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('submission and uncertain-result reconciliation block duplicate completion immediately', () => {
|
||||
assert.equal(resolveCompletionPhase({ submitting: true }), COMPLETION_PHASE.SUBMITTING)
|
||||
assert.equal(resolveCompletionPhase({ reconciling: true }), COMPLETION_PHASE.RECONCILING)
|
||||
assert.equal(completionPresentation(COMPLETION_PHASE.SUBMITTING).blocked, true)
|
||||
assert.equal(completionPresentation(COMPLETION_PHASE.RECONCILING).blocked, true)
|
||||
})
|
||||
|
||||
test('completion failure exposes a readable stage and retry entry', () => {
|
||||
const phase = resolveCompletionPhase({ taskStatus: 'COMPLETION_FAILED' })
|
||||
assert.equal(phase, COMPLETION_PHASE.FAILED)
|
||||
assert.deepEqual(completionPresentation(phase, '费用结算'), {
|
||||
label: '完成失败:费用结算', action: '重试完成', blocked: true, retryable: true
|
||||
})
|
||||
})
|
||||
|
||||
test('retry reuses the original operation id with the latest server version and command facts', () => {
|
||||
const attempt = refreshCompletionAttempt(
|
||||
{ operationId: 'complete-task-8-v3', expectedTaskVersion: 3, commandId: 'cmd-old' },
|
||||
{ taskVersion: 7, commandId: 'cmd-latest', commandStatus: 'COMPLETION_FAILED' }
|
||||
)
|
||||
assert.equal(attempt.operationId, 'complete-task-8-v3')
|
||||
assert.equal(attempt.expectedTaskVersion, 7)
|
||||
assert.equal(attempt.commandId, 'cmd-latest')
|
||||
assert.throws(() => refreshCompletionAttempt({}, { taskVersion: 7 }), /缺少原完成操作标识/)
|
||||
assert.throws(() => refreshCompletionAttempt({ operationId: 'stable-id' }), /缺少服务端最新任务版本/)
|
||||
})
|
||||
|
||||
test('network and timeout failures reconcile before any retry while business conflicts stay intact', () => {
|
||||
assert.equal(shouldReconcileCompletion({ network: true }), true)
|
||||
assert.equal(shouldReconcileCompletion({ timeout: true }), true)
|
||||
assert.equal(shouldReconcileCompletion({ code: 'REQUEST_TIMEOUT' }), true)
|
||||
assert.equal(shouldReconcileCompletion({ code: 'A0409', apiErrorMeta: { requestId: 'req-1' } }), false)
|
||||
})
|
||||
|
||||
test('completed facts win over stale processing facts', () => {
|
||||
const phase = resolveCompletionPhase({ taskStatus: 'COMPLETED', commandStatus: 'PROCESSING' })
|
||||
assert.equal(phase, COMPLETION_PHASE.COMPLETED)
|
||||
assert.equal(completionPresentation(phase).label, '装柜已完成')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
export const COMPLETION_PHASE = Object.freeze({
|
||||
READY: 'READY',
|
||||
SUBMITTING: 'SUBMITTING',
|
||||
RECONCILING: 'RECONCILING',
|
||||
PROCESSING: 'PROCESSING',
|
||||
FAILED: 'FAILED',
|
||||
COMPLETED: 'COMPLETED'
|
||||
})
|
||||
|
||||
const PROCESSING_STATUSES = new Set(['COMPLETING', 'PROCESSING'])
|
||||
const COMPLETED_STATUSES = new Set(['COMPLETED'])
|
||||
const FAILED_STATUSES = new Set(['COMPLETION_FAILED'])
|
||||
|
||||
function normalizedStatus(value) {
|
||||
return String(value || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve already-normalized task/command facts into a UI phase. DTO field
|
||||
* extraction deliberately stays outside this module until the backend schema
|
||||
* is frozen.
|
||||
*/
|
||||
export function resolveCompletionPhase({ taskStatus, commandStatus, submitting = false, reconciling = false } = {}) {
|
||||
if (reconciling) return COMPLETION_PHASE.RECONCILING
|
||||
if (submitting) return COMPLETION_PHASE.SUBMITTING
|
||||
const statuses = [taskStatus, commandStatus].map(normalizedStatus)
|
||||
if (statuses.some((status) => COMPLETED_STATUSES.has(status))) return COMPLETION_PHASE.COMPLETED
|
||||
if (statuses.some((status) => FAILED_STATUSES.has(status))) return COMPLETION_PHASE.FAILED
|
||||
if (statuses.some((status) => PROCESSING_STATUSES.has(status))) return COMPLETION_PHASE.PROCESSING
|
||||
return COMPLETION_PHASE.READY
|
||||
}
|
||||
|
||||
export function completionPresentation(phase, failureStage = '') {
|
||||
if (phase === COMPLETION_PHASE.SUBMITTING) return { label: '正在提交', action: '', blocked: true }
|
||||
if (phase === COMPLETION_PHASE.RECONCILING) return { label: '正在确认提交结果', action: '', blocked: true }
|
||||
if (phase === COMPLETION_PHASE.PROCESSING) return { label: '完成处理中', action: '', blocked: true }
|
||||
if (phase === COMPLETION_PHASE.COMPLETED) return { label: '装柜已完成', action: '', blocked: true }
|
||||
if (phase === COMPLETION_PHASE.FAILED) {
|
||||
return { label: failureStage ? `完成失败:${failureStage}` : '完成失败', action: '重试完成', blocked: true, retryable: true }
|
||||
}
|
||||
return { label: '待确认装柜', action: '确认装柜完成', blocked: false }
|
||||
}
|
||||
|
||||
export function shouldReconcileCompletion(error = {}) {
|
||||
const code = normalizedStatus(error.code || error.errCode)
|
||||
return Boolean(error.network || error.timeout || code === 'TIMEOUT' || code === 'REQUEST_TIMEOUT')
|
||||
}
|
||||
|
||||
/**
|
||||
* A retry keeps the original operationId while taking the latest server
|
||||
* version/command facts. It must never mint a new idempotency identity.
|
||||
*/
|
||||
export function refreshCompletionAttempt(previousAttempt = {}, latestFacts = {}) {
|
||||
if (!previousAttempt.operationId) throw new Error('缺少原完成操作标识,不能安全重试')
|
||||
const expectedTaskVersion = latestFacts.expectedTaskVersion ?? latestFacts.taskVersion ?? previousAttempt.expectedTaskVersion
|
||||
if (expectedTaskVersion === null || expectedTaskVersion === undefined) throw new Error('缺少服务端最新任务版本,不能安全重试')
|
||||
return {
|
||||
...previousAttempt,
|
||||
...latestFacts,
|
||||
operationId: previousAttempt.operationId,
|
||||
expectedTaskVersion: Number(expectedTaskVersion)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user