2026-08-08 11:33:18 +08:00
// Admin HTTP acceptance gaps that are intentionally independent from the CRUD
// modules. This module is read-only: it never creates or mutates business data.
const HEALTH_ENDPOINTS = [
{ path : '/api/platform/v1/orders/status' , service : 'order-service' },
{ path : '/api/platform/v1/first-leg/status' , service : 'firstleg-service' },
{ path : '/api/platform/v1/files/status' , service : 'file-service' },
{ path : '/api/platform/v1/warehouses/status' , service : 'warehouse-service' , allows503 : true },
]
function codeOf ( response ) {
return response ? . body ? . code == null ? 'missing' : String ( response . body . code )
}
function isMissingRoute ( response ) {
return response ? . http === 404 || response ? . http === 405
|| /Not Found|No handler|No static resource/i . test ( String ( response ? . raw || response ? . body ? . message || '' ))
}
function isBusinessFailure ( response ) {
const code = codeOf ( response )
return [ 400 , 401 , 403 , 404 , 409 ]. includes ( response ? . http )
|| ( response ? . http === 200 && code !== '200' )
}
function pageShape ( data ) {
return data && Array . isArray ( data . records )
&& Number ( data . current ?? data . page ?? 1 ) >= 1
&& Number ( data . size ?? 0 ) >= 0
}
function xlsx ( response ) {
return response ? . http === 200 && response . bytes ? . length > 4
&& response . bytes [ 0 ] === 0x50 && response . bytes [ 1 ] === 0x4b
}
async function checkPage ( context , module , name , path , options = {}) {
const response = await context . request ( 'GET' , path )
const data = context . apiSuccess ( module , ` ${ name } 分页查询` , response , `path= ${ path } ` , options )
context . assert ( module , ` ${ name } 分页结构与记录数组正确` , response , pageShape ( data ),
`records= ${ data ? . records ? . length ?? 'n/a' } , current= ${ data ? . current ?? data ? . page ?? 'n/a' } , size= ${ data ? . size ?? 'n/a' } ` , options )
return { response , data }
}
export async function run ( context ) {
const module = 'supplemental'
const scenario = 'service-health-and-auth-boundary'
const blocker = { scenario , deploymentBlocker : true }
2026-08-26 11:10:42 +08:00
const customerId = String ( context . context . merchantQualificationFixture ? . tenantId || '' )
context . assert ( module , '客户作用域负向检查使用本轮正式客户' , null , Boolean ( customerId ),
`customerId= ${ customerId || 'missing' } ` , blocker )
if ( ! customerId ) return
2026-08-08 11:33:18 +08:00
// Health endpoints are part of the deployed service contract. Warehouse is
// special: a schema/readiness failure is intentionally HTTP 503, not a fake
// success, so retain that signal in the report.
for ( const endpoint of HEALTH_ENDPOINTS ) {
const response = await context . request ( 'GET' , endpoint . path )
const notReadyEnvelope = endpoint . allows503 && response . http === 503 && codeOf ( response ) === '200'
const data = notReadyEnvelope
? response . body ? . data
: context . apiSuccess ( module , ` ${ endpoint . service } 状态响应使用统一成功体` , response ,
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , isMissingRoute ( response ) ? blocker : {})
if ( notReadyEnvelope ) {
context . addStep ( module , ` ${ endpoint . service } readiness 失败仍返回统一响应体` , response , true ,
`HTTP=503, code= ${ codeOf ( response ) } , status= ${ data ? . status || 'missing' } ` )
}
const shape = data && data . service === endpoint . service && typeof data . status === 'string'
&& ( endpoint . service !== 'warehouse-service' || typeof data . message === 'string' )
const ready = response . http === 200 && codeOf ( response ) === '200' && data ? . status === 'UP'
const notReady = endpoint . allows503 && response . http === 503 && codeOf ( response ) === '200'
&& data && data . service === endpoint . service && data . status !== 'UP' && typeof data . message === 'string'
context . assert ( module , ` ${ endpoint . service } 状态字段完整且 readiness 语义明确` , response ,
shape && ( ready || notReady ),
`service= ${ data ? . service || 'missing' } , status= ${ data ? . status || 'missing' } , readiness= ${ ready ? 'UP' : notReady ? 'NOT_READY' : 'invalid' } ` ,
isMissingRoute ( response ) ? blocker : {})
}
// Gateway must reject both absent and malformed bearer credentials before a
// platform handler is reached. This specifically guards against accidental
// route-level permitAll changes. No token or response body is persisted.
for ( const [ name , token ] of [[ '未携带令牌' , '' ], [ '伪造令牌' , 'invalid-admin-token' ]]) {
const response = await context . request ( 'GET' , '/api/platform/v1/orders/status' , undefined , token )
const rejected = response . http === 401 && [ 'A0401' , '401' ]. includes ( codeOf ( response ))
context . addStep ( module , `平台接口 ${ name } 返回 401` , response , rejected ,
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` )
}
// A route that exists but is not protected is an authorization regression;
// keep the check separate so a service outage does not look like a pass.
const protectedResponse = await context . request ( 'GET' , '/api/platform/v1/orders/status' , undefined , '' )
context . assert ( module , '未认证请求不返回业务成功码' , protectedResponse ,
! ( protectedResponse . http === 200 && codeOf ( protectedResponse ) === '200' ),
`HTTP= ${ protectedResponse . http } , code= ${ codeOf ( protectedResponse ) } ` )
// Warehouse/master-data read-only boundary checks. All resources are
// platform-public (tenantId=0); passing a customer scope must fail instead
// of silently widening or narrowing the result set.
const warehousePage = await checkPage ( context , module , '仓库' ,
'/api/platform/v1/warehouse-admin?page=1&size=5&status=ACTIVE' , { scenario : 'master-data-readonly' })
const warehouseId = String ( warehousePage . data ? . records ? .[ 0 ] ? . id || '' )
if ( warehouseId ) {
await checkPage ( context , module , '仓库筛选' ,
`/api/platform/v1/warehouse-admin?page=1&size=5&tenantId=0&warehouseType= ${ encodeURIComponent ( warehousePage . data . records [ 0 ]. warehouseType || 'DOMESTIC' ) } ` ,
{ scenario : 'master-data-readonly' })
}
for ( const [ name , path ] of [
[ '仓库非法状态' , '/api/platform/v1/warehouse-admin?page=1&size=5&status=NOT_A_STATUS' ],
2026-08-26 11:10:42 +08:00
[ '仓库客户作用域' , `/api/platform/v1/warehouse-admin?page=1&size=5&tenantId= ${ encodeURIComponent ( customerId ) } ` ],
2026-08-08 11:33:18 +08:00
]) {
const response = await context . request ( 'GET' , path )
context . addStep ( module , ` ${ name } 安全失败` , response , isBusinessFailure ( response ),
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , { scenario : 'master-data-readonly' , deploymentBlocker : isMissingRoute ( response ) })
}
const locationBase = '/api/platform/v1/locations?page=1&size=5&tenantId=0'
const palletBase = '/api/platform/v1/pallets?page=1&size=5&tenantId=0'
const locations = await checkPage ( context , module , '库位' , locationBase , { scenario : 'warehouse-resources' })
const pallets = await checkPage ( context , module , '托盘' , palletBase , { scenario : 'warehouse-resources' })
if ( warehouseId ) {
await checkPage ( context , module , '库位仓库筛选' , ` ${ locationBase } &warehouseId= ${ warehouseId } ` , { scenario : 'warehouse-resources' })
await checkPage ( context , module , '托盘仓库筛选' , ` ${ palletBase } &warehouseId= ${ warehouseId } ` , { scenario : 'warehouse-resources' })
}
const resourceScopeChecks = [
2026-08-26 11:10:42 +08:00
[ '库位客户作用域' , `/api/platform/v1/locations?page=1&size=5&tenantId= ${ encodeURIComponent ( customerId ) } ` ],
[ '托盘客户作用域' , `/api/platform/v1/pallets?page=1&size=5&tenantId= ${ encodeURIComponent ( customerId ) } ` ],
2026-08-08 11:33:18 +08:00
[ '库位非法状态' , ` ${ locationBase } &status=ALLOCATED` ],
]
for ( const [ name , path ] of resourceScopeChecks ) {
const response = await context . request ( 'GET' , path )
context . addStep ( module , ` ${ name } 安全失败` , response , isBusinessFailure ( response ),
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , { scenario : 'warehouse-resources' , deploymentBlocker : isMissingRoute ( response ) })
}
// A stale version is checked without identifying or mutating an existing
// resource; the sentinel ID must be rejected as not found/invalid.
for ( const [ name , path ] of [
[ '库位过期版本/不存在资源' , '/api/platform/v1/locations/999999999999?tenantId=0&warehouseId=999999999999' ],
[ '托盘占用资源保护边界' , '/api/platform/v1/pallets/999999999999?tenantId=0&warehouseId=999999999999' ],
]) {
const response = await context . request ( 'GET' , path )
context . addStep ( module , ` ${ name } 不泄露或误修改数据` , response , isBusinessFailure ( response ),
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , { scenario : 'warehouse-resources' , deploymentBlocker : isMissingRoute ( response ) })
}
// Shared express/partner master data: status/type filters and safe template
// downloads. We do not upload arbitrary or real customer workbooks.
for ( const [ label , base , invalid ] of [
[ '快递公司' , '/api/platform/v1/express-companies' , 'UNKNOWN' ],
[ '往来单位' , '/api/platform/v1/partners' , 'UNKNOWN_TYPE' ],
]) {
await checkPage ( context , module , label , ` ${ base } ?page=1&size=5&tenantId=0&status=ACTIVE` , { scenario : 'shared-master-data' })
const inactive = await checkPage ( context , module , ` ${ label } 停用筛选` , ` ${ base } ?page=1&size=5&tenantId=0&status=INACTIVE` , { scenario : 'shared-master-data' })
context . assert ( module , ` ${ label } 状态枚举过滤不混入其他状态` , inactive . response ,
inactive . data ? . records ? . every ? .(( item ) => item . status === 'INACTIVE' ), `records= ${ inactive . data ? . records ? . length ?? 'n/a' } ` , { scenario : 'shared-master-data' })
const invalidPath = label === '往来单位'
? ` ${ base } ?page=1&size=5&tenantId=0&types= ${ invalid } `
: ` ${ base } ?page=1&size=5&tenantId=0&status= ${ invalid } `
const invalidResponse = await context . request ( 'GET' , invalidPath )
context . addStep ( module , ` ${ label } 无效筛选参数安全失败` , invalidResponse , isBusinessFailure ( invalidResponse ),
`HTTP= ${ invalidResponse . http } , code= ${ codeOf ( invalidResponse ) } ` , { scenario : 'shared-master-data' , deploymentBlocker : isMissingRoute ( invalidResponse ) })
}
for ( const [ label , path ] of [
[ '仓库导入模板' , '/api/platform/v1/warehouse-admin/import-template' ],
[ '快递公司导入模板' , '/api/platform/v1/express-companies/import-template' ],
[ '往来单位导入模板' , '/api/platform/v1/partners/import-template' ],
]) {
const response = await context . requestBinary ( 'GET' , path )
context . addStep ( module , ` ${ label } 为 XLSX 且不记录二进制` , response , xlsx ( response ),
`HTTP= ${ response . http } , bytes= ${ response . bytes ? . length ?? 0 } , contentType= ${ response . contentType || 'missing' } ` ,
{ scenario : 'shared-master-data' , deploymentBlocker : isMissingRoute ( response ) })
}
// Price-book lifecycle is read-only here; pricing.mjs remains the sole owner
// of uploads/publication. Verify all supported status filters and source
// download evidence from an already existing active/history version.
const pricePath = '/api/platform/v1/fba-pricing/carrier-rates/price-books'
for ( const status of [ 'PENDING_CONFIRMATION' , 'ACTIVE' , 'SUPERSEDED' ]) {
const response = await context . request ( 'GET' , ` ${ pricePath } ?status= ${ status } ` )
const versions = context . apiSuccess ( module , `船期价格表 ${ status } 状态过滤` , response , `count= ${ Array . isArray ( response . body ? . data ) ? response . body . data . length : 'n/a' } ` , { scenario : 'price-book-readonly' , deploymentBlocker : isMissingRoute ( response ) })
context . assert ( module , `船期价格表 ${ status } 结果状态一致` , response ,
Array . isArray ( versions ) && versions . every (( item ) => item . status === status ), `count= ${ versions ? . length ?? 'n/a' } ` , { scenario : 'price-book-readonly' , deploymentBlocker : isMissingRoute ( response ) })
}
const activeVersionsResponse = await context . request ( 'GET' , ` ${ pricePath } ?status=ACTIVE` )
const activeVersions = context . apiSuccess ( module , '读取现有生效价格版本用于下载安全检查' , activeVersionsResponse , '' , { scenario : 'price-book-readonly' , deploymentBlocker : isMissingRoute ( activeVersionsResponse ) })
const activeVersionId = String ( activeVersions ? .[ 0 ] ? . id || '' )
if ( activeVersionId ) {
const sourceResponse = await context . requestBinary ( 'GET' , ` ${ pricePath } / ${ encodeURIComponent ( activeVersionId ) } /source` )
context . addStep ( module , '现有生效价格源文件下载为 XLSX' , sourceResponse , xlsx ( sourceResponse ),
`versionId= ${ activeVersionId } , bytes= ${ sourceResponse . bytes ? . length ?? 0 } , contentType= ${ sourceResponse . contentType || 'missing' } ` , { scenario : 'price-book-readonly' })
} else {
context . assert ( module , '存在可供安全下载核验的生效价格版本' , activeVersionsResponse , false , '当前无 ACTIVE 价格版本,无法安全执行源文件下载核验' , { scenario : 'price-book-readonly' , deploymentBlocker : true })
}
// Download center: list/status pagination and negative access checks only;
// downloads.mjs owns task creation and successful-file polling.
for ( const status of [ 'PENDING' , 'RUNNING' , 'SUCCEEDED' , 'FAILED' , 'EXPIRED' ]) {
const response = await context . request ( 'GET' , `/api/platform/v1/files/export-tasks?page=1&size=5&status= ${ status } ` )
const data = context . apiSuccess ( module , `下载中心 ${ status } 状态分页` , response , '' , { scenario : 'download-readonly' , deploymentBlocker : isMissingRoute ( response ) })
context . assert ( module , `下载中心 ${ status } 过滤结果状态一致` , response ,
pageShape ( data ) && data . records . every (( item ) => item . status === status ), `records= ${ data ? . records ? . length ?? 'n/a' } ` , { scenario : 'download-readonly' , deploymentBlocker : isMissingRoute ( response ) })
}
for ( const [ name , path ] of [
[ '不存在下载任务详情' , '/api/platform/v1/files/export-tasks/999999999999' ],
[ '不存在下载任务文件' , '/api/platform/v1/files/export-tasks/999999999999/download' ],
]) {
const response = await context . request ( 'GET' , path , undefined , context . token )
context . addStep ( module , ` ${ name } 安全失败` , response , isBusinessFailure ( response ), `HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , { scenario : 'download-readonly' , deploymentBlocker : isMissingRoute ( response ) })
}
const unauthDownload = await context . request ( 'GET' , '/api/platform/v1/files/export-tasks/999999999999/download' , undefined , '' )
context . addStep ( module , '未授权下载请求被拒绝' , unauthDownload , unauthDownload . http === 401 ,
`HTTP= ${ unauthDownload . http } , code= ${ codeOf ( unauthDownload ) } ` , { scenario : 'download-readonly' })
// IAM lists are intentionally read-only. The RBAC endpoints are list-shaped
// arrays (not paginated in the current contract); retain that fact instead of
// pretending pagination exists, and verify built-in markers are exposed.
for ( const [ label , path , builtInField ] of [
[ '平台用户' , '/api/platform/v1/iam/users?page=1&size=5' , 'protectedAccount' ],
[ '平台角色' , '/api/platform/v1/iam/roles' , 'roleType' ],
[ '菜单权限' , '/api/platform/v1/iam/permissions' , 'builtIn' ],
]) {
const response = await context . request ( 'GET' , path )
const data = context . apiSuccess ( module , ` ${ label } 只读列表` , response , '' , { scenario : 'iam-readonly' , deploymentBlocker : isMissingRoute ( response ) })
const records = label === '平台用户' ? data ? . records : data
context . assert ( module , ` ${ label } 列表结构正确且包含内置保护标识` , response ,
label === '平台用户' ? pageShape ( data )
: Array . isArray ( records ) && records . some (( item ) => label === '平台角色'
? item . roleType === 'SYSTEM'
: Object . hasOwn ( item , builtInField )),
`records= ${ records ? . length ?? 'n/a' } , builtInField= ${ builtInField } ` , { scenario : 'iam-readonly' , deploymentBlocker : isMissingRoute ( response ) })
}
for ( const [ name , path ] of [
[ '角色列表伪造令牌' , '/api/platform/v1/iam/roles' ],
[ '菜单权限未携带令牌' , '/api/platform/v1/iam/permissions' ],
]) {
const response = await context . request ( 'GET' , path , undefined , name . includes ( '伪造' ) ? 'invalid-admin-token' : '' )
context . addStep ( module , ` ${ name } 拒绝访问` , response , response . http === 401 ,
`HTTP= ${ response . http } , code= ${ codeOf ( response ) } ` , { scenario : 'iam-readonly' })
}
}