2026-10-04 10:32:26 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
2026-10-04 10:44:33 +08:00
import { validateRollbackScope , rollbackSql , buildRollbackRemoteScript , assertMidTransactionObservation , runTripRollback , requestRollbackDeparture , assertDatabaseLockConflict } from './fleet-trip-rollback-test-env.mjs'
2026-10-04 10:32:26 +08:00
const scope = { marker : 'OWN_RUN' , tripNo : 'TR-OWN' , firstCustomerId : '101' , secondCustomerId : '102' , firstDispatchId : '201' , secondDispatchId : '202' , firstOutboundId : '301' , secondPalletId : '401' , firstDispatchNo : 'DS-OWN' , expectedImage : 'sha256:' + 'a' . repeat ( 64 )}
const rows = ()=>[{ collection : 'lease' , connectionId : 11 , lockId : '99' },{ collection : 'wait' , requester : 12 , blocker : 11 , lockId : '99' },
{ collection : 'firstDispatch' , id : '201' , status : 'DEPARTED' , shipped : 1 },{ collection : 'firstOutbound' , id : '301' , status : 'OUTBOUNDED' },{ collection : 'firstReplay' , count : 1 }]
2026-10-04 10:37:43 +08:00
test ( 'fault request allows database timeout and never retries an unknown write' , async ()=>{
const body = { recordVersion : '11' , idempotencyKey : 'ORIGINAL' }, calls = []
const context = { request : ()=>{ throw Error ( '30_SECOND_CHANNEL' )}, requestWithBody : async (... args )=>{ calls . push ( args ); return { http : 0 , body : null }}}
assert . equal (( await requestRollbackDeparture ( context , '/owned/depart' , body , 'owned-token' )). http , 0 )
assert . deepEqual ( calls ,[[ 'POST' , '/owned/depart' , body , 'owned-token' ]])
})
2026-10-04 10:44:33 +08:00
test ( 'database conflict requires exact handler response and trace, not arbitrary rejection' ,()=>{
const response = { http : 200 , body : { code : 'A0409' , message : '资源正在处理中,请稍后重试' }, requestId : 'request' , traceId : 'trace' }
assert . doesNotThrow (()=> assertDatabaseLockConflict ( response ))
for ( const wrong of [{ http : 0 },{ http : 500 },{ body : { code : 'S0500' , message : 'error' }},{ body : { code : 'A0409' , message : '请完成拣货' }},{ traceId : null }])
assert . throws (()=> assertDatabaseLockConflict ({... response ,... wrong }))
})
2026-10-04 10:32:26 +08:00
test ( 'plan and import are network-free; execution rejects missing TEST profile before any network' , async ()=>{
const plan = await runTripRollback ({ execute : false },{ env : {}})
assert . equal ( plan . businessSqlWrites , false ); assert . equal ( plan . deployment , false )
await assert . rejects ( runTripRollback ({ execute : true },{ env : { CHONGCHONG_ENV : 'prod' }}), /TEST/ )
})
test ( 'scope rejects reused customers, injection and unpinned runtime' ,()=>{
assert . equal ( validateRollbackScope ( scope ), scope )
for ( const changed of [{ secondCustomerId : '101' },{ secondDispatchId : '201' },{ firstDispatchId : '1 OR 1=1' },{ marker : "RUN';DELETE" },{ expectedImage : 'latest' }]) assert . throws (()=> validateRollbackScope ({... scope ,... changed }))
})
test ( 'lease contains only owned locking reads, fixed duration and rollback, without mutation or schema change' ,()=>{
const sql = rollbackSql ( scope , 'LEASE' )
assert . match ( sql , /dispatch_order_id=202/ ); assert . match ( sql , /warehouse_pallet_id=401/ ); assert . match ( sql , /FOR UPDATE;.*SLEEP\(65\)\);ROLLBACK;/ )
assert . doesNotMatch ( sql , /\b(?:INSERT|DELETE|ALTER|DROP|TRUNCATE|REPLACE|KILL|SET GLOBAL)\b/ )
assert . equal (( sql . match ( /UPDATE/g ) || []). length , 1 )
assert . throws (()=> rollbackSql ( scope , 'CUSTOM_SQL' ))
assert . throws (()=> buildRollbackRemoteScript ( scope , 'LEASE' , "agent');evil()" ))
})
test ( 'dirty observation is separate from final consistent cleanup evidence' ,()=>{
assert . match ( rollbackSql ( scope , 'OBSERVE' ), /READ UNCOMMITTED/ )
assert . match ( rollbackSql ( scope , 'RELEASED' ), /REPEATABLE READ/ )
assert . match ( rollbackSql ( scope , 'RELEASED' ), /READ ONLY/ )
assert . equal ( assertMidTransactionObservation ( rows ()). dirtyObservationOnly , true )
})
for ( const fault of [ 'foreign blocker' , 'no wait' , 'two waiters' , 'not yet written' , 'no outbound write' , 'no replay write' ]) test ( `refuses indirect mid-transaction evidence: ${ fault } ` ,()=>{
const value = rows ()
if ( fault === 'foreign blocker' ) value [ 1 ]. blocker = 77
if ( fault === 'no wait' ) value . splice ( 1 , 1 )
if ( fault === 'two waiters' ) value . push ({... value [ 1 ], requester : 13 })
if ( fault === 'not yet written' ) value [ 2 ]. status = 'READY_TO_DEPART'
if ( fault === 'no outbound write' ) value [ 3 ]. status = 'WAIT_OUTBOUND'
if ( fault === 'no replay write' ) value [ 4 ]. count = 0
assert . throws (()=> assertMidTransactionObservation ( value ))
})