Files
baiyue-firstleg/scripts/admin-test-env-modules/pda-complete-location-release-convergence-contract.test.mjs
T

80 lines
5.0 KiB
JavaScript
Raw Normal View History

2026-08-27 14:30:23 +08:00
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 locationService = read('../../warehouse/src/main/java/com/baiyue/warehouse/service/LocationResourceService.java')
const locationRepository = read('../../warehouse/src/main/java/com/baiyue/warehouse/repository/LocationRepository.java')
const locationMapper = read('../../warehouse/src/main/java/com/baiyue/warehouse/mapper/LocationOccupancyMapper.java')
const locationTests = read('../../warehouse/src/test/java/com/baiyue/warehouse/service/LocationResourceServiceTest.java')
const settlementService = read('../../warehouse/src/main/java/com/baiyue/warehouse/service/PalletLocationAllocationService.java')
const settlementTests = read('../../warehouse/src/test/java/com/baiyue/warehouse/service/PalletLocationAllocationServiceTest.java')
const finalizeHttp = read('phase3-loading-finalize.mjs')
const loadingPickService = read('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java')
2026-08-27 14:30:23 +08:00
function releaseDecision({ sharedOverflow, locationStatus, activeOccupanciesAfterRelease }) {
if (sharedOverflow) return 'SHARED_STATUS_UNCHANGED'
if (activeOccupanciesAfterRelease > 0) return 'KEEP_OCCUPIED'
if (locationStatus === 'AVAILABLE') return 'ALREADY_AVAILABLE'
if (locationStatus === 'OCCUPIED') return 'TRANSITION_TO_AVAILABLE'
return 'CONFLICT'
}
test('offline release decision converges active-occupancy/location drift', () => {
assert.equal(releaseDecision({ sharedOverflow: false, locationStatus: 'AVAILABLE', activeOccupanciesAfterRelease: 0 }),
'ALREADY_AVAILABLE')
assert.equal(releaseDecision({ sharedOverflow: false, locationStatus: 'OCCUPIED', activeOccupanciesAfterRelease: 0 }),
'TRANSITION_TO_AVAILABLE')
assert.equal(releaseDecision({ sharedOverflow: false, locationStatus: 'OCCUPIED', activeOccupanciesAfterRelease: 1 }),
'KEEP_OCCUPIED')
assert.equal(releaseDecision({ sharedOverflow: true, locationStatus: 'AVAILABLE', activeOccupanciesAfterRelease: 5 }),
'SHARED_STATUS_UNCHANGED')
})
test('releaseSelectedReservations accepts AVAILABLE drift only after the last active occupancy is released', () => {
const block = locationService.slice(locationService.indexOf('releaseSelectedReservations('),
locationService.indexOf('transferToChild(', locationService.indexOf('releaseSelectedReservations(')))
assert.match(block, /releaseOccupancy/)
assert.match(block, /(?:count|has|exists|remaining)[A-Za-z]*(?:Active|Occupied|Occupanc)/i)
assert.match(block, /"AVAILABLE"\.equals\(location\.getStatus\(\)\)|location\.getStatus\(\).*"AVAILABLE"/s)
assert.match(block, /"OCCUPIED"/)
assert.match(block, /changeStatus/)
assert.doesNotMatch(block,
/releaseOccupancy[\s\S]{0,400}changeStatus\([^;]+"OCCUPIED",\s*"AVAILABLE"[^;]+\)\s*!=\s*1\)\s*\{/,
'release must not unconditionally require OCCUPIED -> AVAILABLE after occupancy release')
})
test('repository can count or lock remaining active occupancies for one location', () => {
assert.match(locationRepository, /(?:count|has|exists|lock)[A-Za-z]*(?:Active|Occupied|Occupanc)[A-Za-z]*\(/i)
assert.match(locationMapper, /occupancy_status\s*=\s*'OCCUPIED'/i)
assert.match(locationMapper, /location_id/)
})
test('Warehouse unit tests lock all three non-shared convergence cases', () => {
assert.match(locationTests, /AVAILABLE[\s\S]{0,1200}(?:active occupancy|活动占用|drift|漂移)/i)
assert.match(locationTests, /(?:last|最后)[\s\S]{0,1200}(?:AVAILABLE|changeStatus)/i)
assert.match(locationTests, /(?:other|remaining|其他|仍有)[\s\S]{0,1200}(?:occupancy|占用)[\s\S]{0,600}(?:never\(\)\.changeStatus|OCCUPIED)/i)
})
test('same settlement key replays without a second release or transfer', () => {
assert.match(settlementService, /"COMPLETED"\.equals\(operation\.getOperationStatus\(\)\)[\s\S]{0,180}replaySettlementResult/)
assert.match(settlementTests, /shouldReplayCompletedPdaSettlementForSameIdempotencyKey/)
assert.match(settlementTests, /verify\(locations,\s*never\(\)\)\.releaseSelectedReservations/)
assert.match(settlementTests, /verify\(locations,\s*never\(\)\)\.transferToChildByReservation/)
})
test('acceptance failure is zero-write before worker and final local convergence is transactional', () => {
for (const fact of ['task', 'plan', 'inbound', 'operation']) {
2026-08-27 14:30:23 +08:00
assert.match(finalizeHttp, new RegExp(fact, 'i'), `missing ${fact} failure baseline`)
}
assert.match(finalizeHttp, /A0409/)
assert.match(finalizeHttp, /零写入|零漂移/)
assert.match(loadingPickService, /completionTransaction\.executeWithoutResult/)
const finalizer = loadingPickService.slice(loadingPickService.indexOf('private void finalizeCompletion'),
loadingPickService.indexOf('private void requireColoadConservation'))
for (const fact of ['COMPLETE_LOADING', 'finishFromPda', 'publishCompletedEvent', 'completeAccepted']) {
assert.match(finalizer, new RegExp(fact), `missing transactional ${fact}`)
}
2026-08-27 14:30:23 +08:00
})