2026-08-17 14:46:44 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const supplierFixture = readFileSync ( new URL ( './suppliers.mjs' , import . meta . url ), 'utf8' )
const financeFixture = readFileSync ( new URL ( './finance-full-fixtures.mjs' , import . meta . url ), 'utf8' )
2026-08-17 19:13:08 +08:00
const oceanBookingFixture = readFileSync ( new URL ( './ocean-bookings.mjs' , import . meta . url ), 'utf8' )
2026-08-17 14:46:44 +08:00
const supplierOpenapi = readFileSync ( new URL ( '../../warehouse/src/main/resources/openapi/supplier-v1.yaml' , import . meta . url ), 'utf8' )
const firstlegSnapshot = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/service/WarehouseSupplierSnapshotService.java' , import . meta . url ), 'utf8' )
const firstlegClient = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java' , import . meta . url ), 'utf8' )
2026-08-27 10:38:55 +08:00
const supplierDtos = readFileSync ( new URL ( '../../warehouse/src/main/java/com/baiyue/warehouse/dto/SupplierDtos.java' , import . meta . url ), 'utf8' )
const supplierService = readFileSync ( new URL ( '../../warehouse/src/main/java/com/baiyue/warehouse/service/SupplierService.java' , import . meta . url ), 'utf8' )
const supplierMapper = readFileSync ( new URL ( '../../warehouse/src/main/java/com/baiyue/warehouse/mapper/SupplierMapper.java' , import . meta . url ), 'utf8' )
const supplierSchema = readFileSync ( new URL ( '../../warehouse/src/main/resources/db/warehouse-supplier.sql' , import . meta . url ), 'utf8' )
const unifiedSql = readFileSync ( new URL ( '../../deploy/sql/头程数据库统一增量-MySQL8.sql' , import . meta . url ), 'utf8' )
const oceanBookingController = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/oceanbooking/controller/PlatformOceanBookingController.java' , import . meta . url ), 'utf8' )
const oceanBookingService = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/oceanbooking/service/OceanBookingService.java' , import . meta . url ), 'utf8' )
const oceanBookingDtos = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/oceanbooking/dto/OceanBookingDtos.java' , import . meta . url ), 'utf8' )
const oceanBookingOpenapi = readFileSync ( new URL ( '../../firstleg/src/main/resources/openapi/ocean-bookings-v1.yaml' , import . meta . url ), 'utf8' )
const carrierScheduleService = readFileSync ( new URL ( '../../firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/CarrierScheduleQueryService.java' , import . meta . url ), 'utf8' )
const adminOceanPage = readFileSync ( new URL ( '../../../apps/admin-web/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue' , import . meta . url ), 'utf8' )
const adminOceanApi = readFileSync ( new URL ( '../../../apps/admin-web/src/modules/ocean-bookings/api/platformOceanBookings.ts' , import . meta . url ), 'utf8' )
const adminOceanTypes = readFileSync ( new URL ( '../../../apps/admin-web/src/modules/ocean-bookings/types.ts' , import . meta . url ), 'utf8' )
const adminBookingAgents = readFileSync ( new URL ( '../../../apps/admin-web/src/modules/ocean-bookings/bookingAgents.ts' , import . meta . url ), 'utf8' )
const CARRIER_CODES = [ 'MATSON' , 'SML' , 'ZIM' , 'COSCO' , 'EMC' , 'HEDE' , 'OTHER' ]
const matrix = [
{ id : 'A' , carrierCodes : [ 'MATSON' , 'COSCO' ] },
{ id : 'B' , carrierCodes : [ 'MATSON' ] },
{ id : 'C' , carrierCodes : [ 'COSCO' ] },
]
const candidates = ( carrierCode ) => matrix . filter (( agent ) => agent . carrierCodes . includes ( carrierCode )). map (( agent ) => agent . id )
2026-08-17 14:46:44 +08:00
test ( 'booking supplier fixtures keep ACTIVE and BOOKING but never invent a geographic scope gate' , () => {
assert . match ( supplierFixture , /category:'BOOKING'/ )
assert . match ( supplierFixture , /status:'ACTIVE'/ )
assert . doesNotMatch ( supplierFixture , /scope\s*:/ )
2026-10-27 17:44:41 +08:00
const selector = financeFixture . slice ( financeFixture . indexOf ( 'export async function loadSupplier' ), financeFixture . indexOf ( 'async function uploadVoucher' ))
assert . match ( selector , /status === 'ACTIVE'/ )
assert . match ( selector , /item.category === category/ )
assert . match ( selector , /item.truckingScope === truckingScope/ )
assert . doesNotMatch ( selector , /bookingScope/ )
2026-08-17 14:46:44 +08:00
})
test ( 'Warehouse supplier contract filters by active category without scope in query, save or snapshot' , () => {
assert . match ( supplierOpenapi , /name: requireActive/ )
assert . match ( supplierOpenapi , /name: category/ )
2026-08-27 10:38:55 +08:00
assert . match ( supplierOpenapi , /required: \[supplierId, name, category, status, carrierCodes\]/ )
2026-08-17 14:46:44 +08:00
assert . doesNotMatch ( supplierOpenapi , /\bScope\b|name: scope|\bscope:/ )
})
test ( 'Firstleg booking supplier snapshot requires ACTIVE BOOKING and has no domestic/international argument' , () => {
assert . match ( firstlegSnapshot , /return resolve\(supplierId, suppliedName, "BOOKING"\)/ )
assert . match ( firstlegSnapshot , /!"ACTIVE"\.equals\(response\.data\(\)\.status\(\)\)/ )
assert . match ( firstlegSnapshot , /!category\.equals\(response\.data\(\)\.category\(\)\)/ )
2026-09-20 22:18:59 +08:00
const bookingBlock = firstlegSnapshot . slice ( firstlegSnapshot . indexOf ( 'public WarehouseSupplierClient.Snapshot booking(' ),
firstlegSnapshot . indexOf ( 'public WarehouseSupplierClient.Snapshot domesticTrucking(' ))
assert . doesNotMatch ( bookingBlock , /"INTERNATIONAL"|"DOMESTIC"|String scope/ )
2026-08-17 14:46:44 +08:00
assert . doesNotMatch ( firstlegClient , /String scope|String category,\s*String scope/s)
})
2026-08-17 19:13:08 +08:00
test ( 'HTTP ocean booking acceptance selects an ACTIVE BOOKING supplier without a geographic scope' , () => {
2026-09-02 16:38:44 +08:00
assert . match ( oceanBookingFixture , /resolveOceanBookingAgent\(context, MODULE, 'MATSON', OPTIONS\)/ )
2026-08-24 10:33:05 +08:00
assert . match ( oceanBookingFixture , /bookingAgentId: id\(agent\.id\), bookingAgentName: agent\.name/ )
2026-08-28 09:19:18 +08:00
assert . match ( oceanBookingFixture , /shippingCompanyId: id\(carrier\.id\), shippingCompanyName: carrier\.name, carrierCode: 'MATSON'/ )
2026-08-24 10:33:05 +08:00
assert . match ( oceanBookingFixture , /使用 ACTIVE BOOKING 订舱代理事实源/ )
2026-09-02 16:38:44 +08:00
assert . doesNotMatch ( oceanBookingFixture , /scope=/ )
2026-08-17 19:13:08 +08:00
})
2026-08-27 10:38:55 +08:00
test ( 'frozen MATSON and COSCO matrix is many-to-many and changing B to COSCO clears the selection' , () => {
assert . deepEqual ( candidates ( 'MATSON' ), [ 'A' , 'B' ])
assert . deepEqual ( candidates ( 'COSCO' ), [ 'A' , 'C' ])
let selectedAgentId = 'B'
const nextCarrierCode = 'COSCO'
if ( ! candidates ( nextCarrierCode ). includes ( selectedAgentId )) selectedAgentId = null
assert . equal ( selectedAgentId , null )
})
test ( 'supplier DTO, service and HTTP acceptance freeze normalized multi-carrier save and non-BOOKING rejection' , () => {
assert . match ( supplierDtos , /List<@Pattern\(regexp = "MATSON\|SML\|ZIM\|COSCO\|EMC\|HEDE\|OTHER"\) String> carrierCodes/ )
assert . match ( supplierService , /normalizeCarrierCodes\(request\.category\(\), request\.carrierCodes\(\)\)/ )
assert . match ( supplierService , /非BOOKING供应商禁止携带carrierCodes/ )
assert . match ( supplierService , /BOOKING供应商至少关联一个船司标准编码/ )
assert . match ( supplierService , /List\.copyOf\(normalized\)/ )
assert . match ( supplierFixture , /carrierCodes:\['MATSON','COSCO'\]/ )
assert . match ( supplierFixture , /非BOOKING携带carrierCodes返回A0400并保留链路ID/ )
assert . match ( supplierFixture , /code\(nonBookingCarrierResponse,'A0400'\)&&traced\(nonBookingCarrierResponse\)/ )
})
test ( 'normalized relation table and lookup index exist in module baseline and unified SQL' , () => {
for ( const sql of [ supplierSchema , unifiedSql ]) {
2026-09-20 22:18:59 +08:00
assert . match ( sql , /CREATE TABLE(?: IF NOT EXISTS)? wh_supplier_carrier/ )
2026-08-27 10:38:55 +08:00
assert . match ( sql , /PRIMARY KEY\s*\(?\s*supplier_id\s*,\s*carrier_code\s*\)?/i )
assert . match ( sql , /idx_wh_supplier_carrier_code\s*\(?\s*tenant_id\s*,\s*carrier_code\s*,\s*supplier_id\s*\)?/i )
2026-09-20 22:18:59 +08:00
assert . match ( sql , /carrier_code IN\s*\(?\s*(?:_utf8mb4)?'MATSON'\s*,\s*(?:_utf8mb4)?'SML'\s*,\s*(?:_utf8mb4)?'ZIM'\s*,\s*(?:_utf8mb4)?'COSCO'\s*,\s*(?:_utf8mb4)?'EMC'\s*,\s*(?:_utf8mb4)?'HEDE'\s*,\s*(?:_utf8mb4)?'OTHER'\s*\)?/i )
2026-08-27 10:38:55 +08:00
}
assert . match ( supplierMapper , /FROM wh_supplier_carrier[\s\S]*tenant_id=#\{tenantId\} AND carrier_code=#\{carrierCode\}/ )
})
test ( 'carrier-filtered booking-agent endpoint returns only ACTIVE BOOKING rows linked to the selected code' , () => {
assert . match ( oceanBookingController , /@GetMapping\("\/booking-agents"\)[\s\S]*@RequestParam String carrierCode/ )
assert . match ( firstlegClient , /candidates\([\s\S]*@RequestParam String category,[\s\S]*@RequestParam String carrierCode/ )
assert . match ( firstlegSnapshot , /client\.candidates\(internalToken, "BOOKING", carrierCode\)/ )
assert . match ( oceanBookingService , /bookingAgents\(String carrierCode\)[\s\S]*"ACTIVE"\.equals\(value\.status\(\)\)[\s\S]*"BOOKING"\.equals\(value\.category\(\)\)[\s\S]*value\.carrierCodes\(\)\.contains\(normalized\)/ )
assert . match ( oceanBookingDtos , /record BookingAgentOption\(Long supplierId, String name, List<String> carrierCodes\)/ )
assert . match ( oceanBookingOpenapi , /\/api\/platform\/v1\/ocean-bookings\/booking-agents:/ )
assert . match ( oceanBookingOpenapi , /name: carrierCode[\s\S]*enum: \[MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER\]/ )
})
test ( 'Admin loads candidates by carrier and clears an invalid selected agent when the carrier changes' , () => {
assert . match ( adminOceanApi , /listOceanBookingAgents/ )
assert . match ( adminOceanApi , /\$\{basePath\}\/booking-agents/ )
assert . match ( adminOceanTypes , /carrierCodes:\s*OceanBookingCarrierCode\[\]/ )
assert . match ( adminOceanPage , /listOceanBookingAgents\(carrierCode\)/ )
assert . match ( adminOceanPage , /bookingAgentId\s*=\s*null/ )
assert . match ( adminOceanPage , /shouldClearBookingAgent\(form\.data\.bookingAgentId, options\)/ )
assert . match ( adminBookingAgents , /!options\.some\(\(option\) => option\.supplierId === String\(currentSupplierId\)\)/ )
})
test ( 'bypassed unlinked agent is A0400 with trace and both DRAFT save and RELEASE revalidate the matrix' , () => {
assert . match ( oceanBookingService , /resolveDraft\(request\.data\(\)\)/ )
assert . match ( oceanBookingService , /resolveBookingAgent\(d\.bookingAgentId\(\), d\.bookingAgentName\(\), carrier == null \? null : carrier\.carrierCode\(\)\)/ )
assert . match ( oceanBookingService , /validateMasterData\(before\)/ )
assert . match ( oceanBookingService , /resolveBookingAgent\(booking\.getBookingAgentId\(\), booking\.getBookingAgentName\(\), booking\.getCarrierCode\(\)\)/ )
assert . match ( oceanBookingService , /throw badRequest\("所选订舱代理未关联当前船公司"\)/ )
const bypass = { http : 200 , body : { code : 'A0400' , message : '所选订舱代理未关联当前船公司' , requestId : 'request-matrix' , traceId : 'trace-matrix' } }
assert . equal ( bypass . http , 200 )
assert . equal ( bypass . body . code , 'A0400' )
assert . ok ( bypass . body . requestId && bypass . body . traceId )
})
test ( 'CARRIER master data keeps the fixed enum while shipping-node tasks consume carrierCode only' , () => {
assert . deepEqual ( CARRIER_CODES , [ 'MATSON' , 'SML' , 'ZIM' , 'COSCO' , 'EMC' , 'HEDE' , 'OTHER' ])
assert . match ( oceanBookingService , /CARRIER_CODES = Set\.of\("MATSON", "SML", "ZIM", "COSCO", "EMC", "HEDE", "OTHER"\)/ )
assert . match ( carrierScheduleService , /setCarrierCode\(strategy\.carrierCode\(\)\)/ )
assert . doesNotMatch ( carrierScheduleService , /carrierCodes\(\)|bookingAgentId|bookingAgentName|wh_supplier_carrier/ )
})
test ( 'logistics channel contract remains independent from carrier-agent relationships' , () => {
const channelMethod = oceanBookingService . slice ( oceanBookingService . indexOf ( 'public List<ChannelOption> channels' ), oceanBookingService . indexOf ( 'public Detail detail' ))
const channelDto = oceanBookingDtos . slice ( oceanBookingDtos . indexOf ( 'record ChannelOption' ), oceanBookingDtos . indexOf ( 'record BookingAgentOption' ))
assert . match ( channelMethod , /priceBooks\.channels/ )
assert . match ( channelMethod , /channelOption/ )
assert . doesNotMatch ( channelMethod , /carrierCodes|bookingAgent|shippingCompany|supplier/ )
assert . doesNotMatch ( channelDto , /carrierCodes|bookingAgent|shippingCompany|supplier/ )
assert . match ( oceanBookingOpenapi , /channelCode:/ )
})