2026-08-11 20:02:04 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
2026-08-14 10:09:46 +08:00
import { normalizeModuleOrder , parseArguments } from '../admin-test-env-acceptance.mjs'
2026-10-22 23:06:00 +08:00
import { cleanupSucceeded , formalFeeItems , run } from './fee-items.mjs'
2026-08-11 20:02:04 +08:00
const source = readFileSync ( new URL ( './fee-items.mjs' , import . meta . url ), 'utf8' )
2026-08-19 09:42:07 +08:00
test ( 'fee detail binding does not send the removed settlement switch' , () => {
assert . match ( source , /const detailPayload = \{/ )
assert . doesNotMatch ( source , /settlementIncluded/ )
2026-08-11 20:02:04 +08:00
})
test ( 'referenced fee-item deletion remains a conflict assertion' , () => {
assert . match ( source , /'任意客户费用明细引用后公共项目不可删除'[\s\S]*conflict\(protectedDeleteResponse\)/ )
})
test ( 'cleanup treats successful deletion and an already absent resource as clean' , () => {
assert . equal ( cleanupSucceeded ({ http : 200 , body : { code : '200' } }), true )
assert . equal ( cleanupSucceeded ({ http : 200 , body : { code : 'A0404' } }), true )
assert . equal ( cleanupSucceeded ({ http : 404 , body : {} }), true )
assert . equal ( cleanupSucceeded ({ http : 200 , body : { code : 'A0422' } }), false )
})
2026-08-13 16:58:17 +08:00
2026-10-22 23:06:00 +08:00
test ( 'no automatic historical fee-item cleanup remains' , () => {
assert . doesNotMatch ( source , /cleanupStaleAcceptanceItems|isAcceptanceFeeItem/ )
2026-08-13 16:58:17 +08:00
})
2026-10-22 23:06:00 +08:00
for ( const missingBaseline of [ false , true ]) test ( `fixed fee catalogue preserves old test items and performs no master writes; missing= ${ missingBaseline } ` , async () => {
const codes = [ 'FBA_HEAD_FREIGHT' , 'PRIVATE_LAST_MILE_DELIVERY' , ... Array . from ({ length : 110 }, ( _ , i ) => `FEE_TYPE_ ${ String ( i + 1 ). padStart ( 3 , '0' ) } ` )]
const catalogue = codes . map (( feeCode , i ) => ({ id : String ( i + 1 ), feeCode , tenantId : '0' , status : 'ACTIVE' ,
tradeScope : i < 84 ? 'DOMESTIC' : 'INTERNATIONAL' , category : '标准' , feeTypeName : `费用 ${ i } ` }))
catalogue . push ({ id : '999' , feeCode : 'ACPT_123456' , feeTypeName : '全量验收费用-123456' , tenantId : '0' , status : 'DISABLED' , category : '临时' , tradeScope : 'DOMESTIC' })
if ( missingBaseline ) catalogue . splice ( 3 , 1 )
const calls = [], gaps = []
const context = { options : { fixedCustomerId : '101' }, marker : 'TEST' , createdResources : {},
async request ( method , path , body , token ) {
calls . push ({ method , path })
const response = data => ({ http : 200 , path , body : { code : '200' , data } })
if ( path === '/api/auth/login' ) return response ({ accessToken : 'client' , refreshToken : 'refresh' })
if ( path === '/api/auth/logout' ) return response ({})
assert . equal ( method , 'GET' )
if ( token === 'client' ) return { http : 403 , body : { code : 'A0403' } }
const params = new URL ( path , 'https://test.invalid' ). searchParams
const selected = catalogue . filter ( row => [ 'category' , 'tradeScope' , 'status' ]. every ( key => ! params . has ( key ) || row [ key ] === params . get ( key ))
&& ( ! params . has ( 'keyword' ) || row . feeTypeName . includes ( params . get ( 'keyword' ))))
const size = Number ( params . get ( 'size' )), page = Number ( params . get ( 'page' ))
return response ({ records : selected . slice (( page - 1 ) * size , page * size ), total : selected . length , page , size , pages : Math . ceil ( selected . length / size ) })
}, apiSuccess ( _m , _n , response ) { return response . body . data },
assert ( _m , name , _r , passed ) { assert . ok ( passed , name ) },
addCoverageGap (... args ) { gaps . push ( args ) },
}
const before = structuredClone ( catalogue )
if ( missingBaseline ) await assert . rejects ( run ( context ), /112条/ )
else {
await run ( context )
assert . equal ( context . createdResources . feeItems . mode , 'READ_ONLY' )
assert . equal ( gaps . length , 1 )
assert . equal ( calls . filter ( row => row . path === '/api/auth/logout' ). length , 1 )
}
assert . deepEqual ( catalogue , before )
assert . ok ( calls . every ( row => row . method === 'GET' || row . path . startsWith ( '/api/auth/' )))
2026-10-21 15:10:44 +08:00
})
test ( '正式费用按编码识别,扩展停用项目保留且不掩盖缺失正式编码' , () => {
const rows = [{ feeCode : 'FBA_HEAD_FREIGHT' }, { feeCode : 'FEE_TYPE_001' },
{ feeCode : 'PRIVATE_LAST_MILE_DELIVERY' }, { feeCode : 'FEE_EXT_OTHER' , status : 'DISABLED' }]
assert . deepEqual ( formalFeeItems ( rows ), rows . slice ( 0 , 3 ))
assert . equal ( rows . length , 4 )
assert . equal ( formalFeeItems ([{ feeCode : 'FEE_TYPE_111' }]). length , 0 )
2026-08-14 10:09:46 +08:00
})
2026-09-08 21:05:15 +08:00
test ( 'formal baseline includes both system fee items in addition to the 110 controlled items' , () => {
assert . match ( source , /const BASELINE_COUNT = 112/ )
assert . match ( source , /const INTERNATIONAL_COUNT = 28/ )
2026-08-14 10:09:46 +08:00
assert . match ( source , /'FBA_HEAD_FREIGHT'/ )
2026-09-08 21:05:15 +08:00
assert . match ( source , /'PRIVATE_LAST_MILE_DELIVERY'/ )
2026-08-14 10:09:46 +08:00
})
test ( 'fee-items injects its same-marker merchant and order fixtures for focused execution' , () => {
const expected = [ 'merchant-qualification-fixture' , 'orders' , 'fee-items' ]
assert . deepEqual ( normalizeModuleOrder ([ 'fee-items' ]), expected )
assert . deepEqual ( normalizeModuleOrder ([ 'fee-items' , 'orders' ]), expected )
assert . deepEqual ( parseArguments ([ '--modules' , 'fee-items' ]). modules , expected )
2026-08-13 16:58:17 +08:00
})