88 lines
4.5 KiB
JavaScript
88 lines
4.5 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import {
|
|
COMPLETION_PHASE,
|
|
completionPresentation,
|
|
refreshCompletionAttempt,
|
|
resolveCompletionPhase,
|
|
shouldReconcileCompletion
|
|
} from '../utils/loading-completion-state.js'
|
|
|
|
test('reopened processing and completed tasks keep evidence and completion read-only', async () => {
|
|
const page = await readFile(new URL('../pages/picking/picking.vue', import.meta.url), 'utf8')
|
|
const methods = ['loadingCompletionState', 'evidenceReadOnly', 'requireEvidenceStage'].map(name => {
|
|
const method = page.match(new RegExp(`${name}\\(\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},`))?.[0]
|
|
assert.ok(method, name)
|
|
return method
|
|
})
|
|
const behavior = new Function('resolveCompletionPhase', 'completionPresentation', `return ({${methods.join('\n')}})`)(resolveCompletionPhase, completionPresentation)
|
|
for (const workflowStatus of ['PROCESSING', 'COMPLETED']) {
|
|
const messages = []
|
|
const context = { remotePick: { workflowStatus, completionCommand: { status: workflowStatus } }, submitting: false, pickingCompleted: true, loadingStageReady: true, showToast(message) { messages.push(message) } }
|
|
context.loadingCompletionState = behavior.loadingCompletionState.call(context)
|
|
context.evidenceReadOnly = behavior.evidenceReadOnly.call(context)
|
|
assert.equal(context.evidenceReadOnly, true)
|
|
assert.equal(behavior.requireEvidenceStage.call(context), false)
|
|
assert.deepEqual(messages, [workflowStatus === 'COMPLETED' ? '装柜已完成' : '完成处理中'])
|
|
}
|
|
const retry = { remotePick: { workflowStatus: 'COMPLETION_FAILED' }, submitting: false }
|
|
retry.loadingCompletionState = behavior.loadingCompletionState.call(retry)
|
|
assert.equal(behavior.evidenceReadOnly.call(retry), false)
|
|
assert.match(page, /v-if="evidenceReadOnly"[^>]*role="status"/)
|
|
assert.match(page, /v-else class="loading-complete-btn"/)
|
|
assert.match(page, /class="evidence-btn evidence-add-btn" :disabled="evidenceReadOnly"/)
|
|
})
|
|
|
|
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 and request-hashed task version', () => {
|
|
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, 3)
|
|
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, '装柜已完成')
|
|
})
|