80 lines
5.0 KiB
JavaScript
80 lines
5.0 KiB
JavaScript
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')
|
|
|
|
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']) {
|
|
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}`)
|
|
}
|
|
})
|