2026-08-13 11:57:33 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
2026-10-22 14:37:59 +08:00
import { run } from './suppliers.mjs'
import { AcceptanceRun } from '../admin-test-env-acceptance.mjs'
2026-08-13 11:57:33 +08:00
const source = readFileSync ( new URL ( './suppliers.mjs' , import . meta . url ), 'utf8' )
const orchestrator = readFileSync ( new URL ( '../admin-test-env-acceptance.mjs' , import . meta . url ), 'utf8' )
2026-08-15 09:57:46 +08:00
const openapi = readFileSync ( new URL ( '../../warehouse/src/main/resources/openapi/supplier-v1.yaml' , import . meta . url ), 'utf8' )
2026-08-13 11:57:33 +08:00
2026-08-27 10:38:55 +08:00
test ( 'supplier acceptance covers full CRUD-like lifecycle, carrier matrix and boundary failures' ,()=>{
for ( const marker of [ '列表分页与订舱筛选' , '查询四类供应商统计' , '新增供应商' , '查询供应商详情' , '编辑供应商' , '停用供应商' , '重复名称返回冲突' , 'tenantId非0被拒绝' , '非BOOKING携带carrierCodes返回A0400并保留链路ID' , '客户账号调用平台供应商接口被拒绝' , 'requestId' , 'traceId' ]) assert . match ( source , new RegExp ( marker ))
assert . match ( source , /carrierCodes:\['MATSON','COSCO'\]/ )
2026-09-04 15:19:45 +08:00
assert . match ( source , /sameCarrierCodes\(detail\?\.carrierCodes,\['MATSON','COSCO'\]\)/ )
2026-08-17 14:46:44 +08:00
assert . doesNotMatch ( source , /scope\s*:/ )
2026-08-13 11:57:33 +08:00
})
2026-08-15 09:57:46 +08:00
test ( 'supplier module is registered in the default full acceptance orchestration' ,()=> assert . match ( orchestrator , /CONFIGURED_MODULES = \[[^\]]*'suppliers'/s))
2026-08-13 11:57:33 +08:00
test ( 'supplier fixture is retained only as inactive because API has no delete contract' ,()=>{ assert . match ( source , /finalStatus:inactive\?'INACTIVE'/ ); assert . doesNotMatch ( source , /context\.request\('DELETE'/ )})
2026-08-17 14:46:44 +08:00
test ( 'supplier OpenAPI exposes the internal active category validation contract without scope' ,()=>{
2026-08-15 09:57:46 +08:00
for ( const marker of [ '/internal/warehouse/v1/suppliers/{supplierId}' , 'X-Internal-Service-Token' , 'requireActive' , 'InternalSnapshot' , 'SupplierPageResponse' , 'platform:supplier:list' ]) assert . match ( openapi , new RegExp ( marker . replace ( /[{}]/g , '\\$&' )))
2026-08-17 14:46:44 +08:00
assert . doesNotMatch ( openapi , /\bscope\b/ )
2026-08-15 09:57:46 +08:00
assert . doesNotMatch ( openapi , /platform:supplier:view/ )
})
2026-10-22 14:37:59 +08:00
function existingHarness ({ empty = false , wrongDetail = false , failStats = false } = {}) {
const runner = new AcceptanceRun ({ environment : 'test' , fixedCustomerId : '123' , quiet : true })
const calls = []
const categories = [ 'BOOKING' , 'CUSTOMS' , 'TRUCKING' , 'CERTIFICATE' ]
runner . request = async ( method , path ) => {
calls . push ({ method , path })
assert . equal ( method , 'GET' , 'fixed suppliers must never mutate master data' )
const url = new URL ( path , 'http://offline.invalid' )
let data
let code = '200'
if ( url . searchParams . get ( 'tenantId' ) === '1' ) code = 'A0400'
else if ( url . pathname . endsWith ( '/stats' )) {
data = { booking : 1 , customs : 1 , trucking : 1 , certificate : 1 }
if ( failStats ) code = 'S0500'
} else if ( url . searchParams . has ( 'category' )) {
const category = url . searchParams . get ( 'category' )
data = { records : empty ? [] : [{ id : String ( categories . indexOf ( category ) + 1 ), category , status : 'ACTIVE' }],
page : 1 , size : 20 , total : empty ? 0 : 1 , pages : empty ? 0 : 1 }
} else {
const id = url . pathname . split ( '/' ). at ( - 1 )
data = { id , tenantId : wrongDetail ? '88' : '0' , category : categories [ Number ( id ) - 1 ], status : 'ACTIVE' }
}
return { http : 200 , body : { code , data }, requestId : 'request' , traceId : 'trace' }
}
return { runner , calls }
}
test ( 'fixed supplier module runs through orchestrator using only existing read-only records' , async () => {
const { runner , calls } = existingHarness ()
await runner . executeModule ( 'suppliers' , () => run ( runner ))
assert . equal ( calls . length , 10 )
assert . equal ( runner . modules [ 0 ]. skipped , undefined )
assert . equal ( runner . modules [ 0 ]. success , true )
assert . equal ( runner . coverageGaps . length , 1 )
assert . equal ( runner . coverageGaps [ 0 ]. scenario , 'fixed-resource-lifecycle' )
})
test ( 'empty supplier lists retain detail gaps and never create replacements' , async () => {
const { runner , calls } = existingHarness ({ empty : true })
await run ( runner )
assert . equal ( calls . length , 6 )
assert . equal ( runner . coverageGaps . length , 5 )
assert . ok ( runner . steps . every ( step => step . passed ))
})
test ( 'supplier detail scope drift and failed statistics remain failures' , async () => {
for ( const options of [{ wrongDetail : true }, { failStats : true }]) {
const { runner } = existingHarness ( options )
await runner . executeModule ( 'suppliers' , () => run ( runner ))
assert . equal ( runner . modules [ 0 ]. success , false )
assert . ok ( runner . steps . some ( step => ! step . passed ))
}
})