优化
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url), 'utf8')
|
||||
|
||||
const loadingPickService = read('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java')
|
||||
const completionCommand = read('../../firstleg/src/main/java/com/baiyue/firstleg/entity/LoadingCompletionCommandEntity.java')
|
||||
const completionWorker = read('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingCompletionWorker.java')
|
||||
const loadingPickTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java')
|
||||
const settlementTests = read('../../firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java')
|
||||
const unifiedSql = read('../../deploy/sql/头程数据库统一增量-MySQL8.sql')
|
||||
const finalizeHttp = read('phase3-loading-finalize.mjs')
|
||||
const pdaPage = read('../../../apps/pda-uniapp/pages/picking/picking.vue')
|
||||
const pdaCompletionState = read('../../../apps/pda-uniapp/utils/loading-completion-state.js')
|
||||
const pdaCompletionTests = read('../../../apps/pda-uniapp/tests/loading-completion-state.test.mjs')
|
||||
|
||||
const RECOVERY_TASK_ID = '2092817774453780481'
|
||||
|
||||
function retryDecision({ operationStatus, sameRequest, finalTaskStatus }) {
|
||||
if (!sameRequest) return 'CONFLICT'
|
||||
if (operationStatus === 'PROCESSING') return 'PROCESSING'
|
||||
if (operationStatus === 'COMPLETED') return 'REPLAY'
|
||||
if (finalTaskStatus === 'COMPLETED') return 'RECONCILE_COMPLETE'
|
||||
return 'RESUME'
|
||||
}
|
||||
|
||||
test('offline orchestration model distinguishes processing replay recovery and conflict', () => {
|
||||
assert.equal(retryDecision({ operationStatus: 'PROCESSING', sameRequest: true }), 'PROCESSING')
|
||||
assert.equal(retryDecision({ operationStatus: 'COMPLETED', sameRequest: true }), 'REPLAY')
|
||||
assert.equal(retryDecision({ operationStatus: 'FAILED', sameRequest: true, finalTaskStatus: 'LOADING' }), 'RESUME')
|
||||
assert.equal(retryDecision({ operationStatus: 'FAILED', sameRequest: false }), 'CONFLICT')
|
||||
})
|
||||
|
||||
test('POST complete is reliable acceptance only and returns PROCESSING/COMPLETING', () => {
|
||||
const start = loadingPickService.indexOf('public Task complete(')
|
||||
const end = loadingPickService.indexOf('/** Called only by the completion worker', start)
|
||||
const block = loadingPickService.slice(start, end)
|
||||
assert.match(block, /LoadingCompletionCommandEntity/)
|
||||
assert.match(block, /acceptCompletion/)
|
||||
assert.match(block, /return view\(requiredTask\(taskId\), null\)/)
|
||||
assert.doesNotMatch(block, /settlementService|completeAfterPdaSettlement|publishCompletedEvent/)
|
||||
assert.match(loadingPickService, /"COMPLETING"\.equals\(task\.getTaskStatus\(\)\)[\s\S]{0,80}"PROCESSING"/)
|
||||
})
|
||||
|
||||
test('complete does not hold fl_loading_pick_task FOR UPDATE across remote settlement calls', () => {
|
||||
const start = loadingPickService.indexOf('public void processCompletion(')
|
||||
const end = loadingPickService.indexOf('private static void requireSameCompletionCommand', start)
|
||||
const block = loadingPickService.slice(start, end)
|
||||
assert.notEqual(start, -1)
|
||||
assert.match(block, /settlementService\.settleOwner/)
|
||||
assert.doesNotMatch(block, /requiredLoadingTaskForUpdate|requiredTaskForUpdate/)
|
||||
assert.match(loadingPickService, /completionCommands\.insert/)
|
||||
})
|
||||
|
||||
test('completion operation has durable PROCESSING and terminal response state', () => {
|
||||
assert.match(completionCommand, /commandStatus/)
|
||||
assert.match(completionCommand, /attemptCount/)
|
||||
assert.match(completionCommand, /nextRetryAt/)
|
||||
assert.match(completionCommand, /completedAt/)
|
||||
assert.match(completionCommand, /failureStage/)
|
||||
const commandTable = unifiedSql.slice(unifiedSql.indexOf('CREATE TABLE IF NOT EXISTS fl_loading_completion_command'),
|
||||
unifiedSql.indexOf(';', unifiedSql.indexOf('CREATE TABLE IF NOT EXISTS fl_loading_completion_command')) + 1)
|
||||
assert.match(commandTable, /command_status/i)
|
||||
assert.match(commandTable, /next_retry_at/i)
|
||||
assert.match(commandTable, /completed_at/i)
|
||||
assert.match(unifiedSql, /CREATE TABLE IF NOT EXISTS fl_loading_completion_checkpoint/)
|
||||
})
|
||||
|
||||
test('durable worker processes inbound settlements incrementally with retry backoff', () => {
|
||||
assert.match(completionWorker, /@Scheduled/)
|
||||
assert.match(completionWorker, /commands\.due/)
|
||||
assert.match(completionWorker, /commands\.claim/)
|
||||
assert.match(completionWorker, /15L <<|nextRetryAt|retryAt/)
|
||||
assert.match(completionWorker, /"FAILED"\s*:\s*"RETRYABLE"/)
|
||||
assert.match(loadingPickService, /checkpointAtLeast[\s\S]{0,1800}SETTLED/)
|
||||
assert.match(loadingPickTests, /(?:transient|瞬时)[\s\S]{0,1500}(?:backoff|retry|退避)/i)
|
||||
assert.match(loadingPickTests, /FAILED[\s\S]{0,1500}(?:same operationId|同operationId|resume|恢复)/i)
|
||||
})
|
||||
|
||||
test('same operationId concurrency returns PROCESSING promptly instead of waiting for the task row lock', () => {
|
||||
assert.match(loadingPickTests, /sameOperationId[\s\S]{0,1600}PROCESSING/i)
|
||||
assert.match(loadingPickTests, /(?:assertTimeout|timeout|elapsed|millis|seconds)[\s\S]{0,500}(?:CannotAcquireLock|FOR UPDATE|taskForUpdate)/i)
|
||||
assert.match(loadingPickTests, /never\(\)[\s\S]{0,180}requiredLoadingTaskForUpdate|never\(\)[\s\S]{0,180}taskForUpdate/i)
|
||||
})
|
||||
|
||||
test('a different operationId is rejected while the completion command is processing', () => {
|
||||
assert.match(loadingPickTests, /(?:different operationId|不同operationId|different key|不同键)[\s\S]{0,1200}(?:A0409|CONFLICT)/i)
|
||||
})
|
||||
|
||||
test('fifteen completed Warehouse settlements can be reconciled after Firstleg rollback', () => {
|
||||
assert.match(settlementTests, /15|fifteen/i)
|
||||
assert.match(settlementTests, /(?:partial success|部分成功|local rollback|本地回滚)/i)
|
||||
assert.match(settlementTests, /(?:settlementTarget|COMPLETED|replaySettlement)/)
|
||||
assert.match(loadingPickTests, /(?:resume|recover|reconcile|恢复|核平)[\s\S]{0,2000}COMPLETE_LOADING/i)
|
||||
})
|
||||
|
||||
test('recovery fixture preserves the observed fifteen-settlement partial success', () => {
|
||||
assert.equal(RECOVERY_TASK_ID, '2092817774453780481')
|
||||
assert.match(finalizeHttp, new RegExp(RECOVERY_TASK_ID))
|
||||
assert.match(finalizeHttp, /15[\s\S]{0,600}(?:Warehouse|settlement)/i)
|
||||
assert.match(finalizeHttp, /(?:LOADING|v1)[\s\S]{0,600}(?:恢复|recovery|resume)/i)
|
||||
})
|
||||
|
||||
test('final acceptance reconciles task allocation reservation occupancy and one completion audit', () => {
|
||||
for (const fact of ['task', 'allocation', 'reservation', 'occupancy']) {
|
||||
assert.match(finalizeHttp, new RegExp(fact, 'i'), `missing final ${fact} reconciliation`)
|
||||
}
|
||||
assert.match(finalizeHttp, /COMPLETE_LOADING/)
|
||||
assert.match(finalizeHttp, /(?:length === 1|恰有一次|只写一条)/)
|
||||
assert.match(finalizeHttp, /(?:PROCESSING|处理中)/)
|
||||
assert.match(finalizeHttp, /(?:elapsed|latency|耗时|毫秒|ms)/i)
|
||||
})
|
||||
|
||||
test('PDA treats accepted completion as processing, returns to list, and never claims completion early', () => {
|
||||
assert.match(pdaCompletionState, /PROCESSING_STATUSES = new Set\(\['COMPLETING', 'PROCESSING'\]\)/)
|
||||
assert.match(pdaCompletionTests, /accepted completion is processing/)
|
||||
assert.match(pdaPage, /loading-completion-state/)
|
||||
assert.match(pdaPage, /(?:PROCESSING|COMPLETING)[\s\S]{0,1200}navigateBackOr/)
|
||||
assert.doesNotMatch(pdaPage, /completeContainerPickingTask[\s\S]{0,700}showToast\('已确认装柜完成'/,
|
||||
'accepted PROCESSING response must not show a completed success toast')
|
||||
assert.match(pdaPage, /shouldReconcileCompletion/)
|
||||
})
|
||||
Reference in New Issue
Block a user