Files
baiyue-firstleg/scripts/admin-test-env-modules/container-simulation-association-contract.test.mjs
T
2026-08-15 09:47:17 +08:00

82 lines
5.7 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const read = (path) => readFileSync(new URL(path, import.meta.url), 'utf8')
const controller = read('../../firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformContainerController.java')
const containerDtos = read('../../firstleg/src/main/java/com/baiyue/firstleg/dto/ContainerDtos.java')
const containerService = read('../../firstleg/src/main/java/com/baiyue/firstleg/service/ContainerService.java')
const oceanService = read('../../firstleg/src/main/java/com/baiyue/firstleg/oceanbooking/service/OceanBookingService.java')
const oceanMapper = read('../../firstleg/src/main/java/com/baiyue/firstleg/oceanbooking/mapper/OceanBookingMapper.java')
const firstlegOpenApi = read('../../firstleg/src/main/resources/openapi/firstleg-v1.yaml')
const oceanOpenApi = read('../../firstleg/src/main/resources/openapi/ocean-bookings-v1.yaml')
const loadingFixture = read('phase3-container-loading.mjs')
const fullEntry = read('../admin-test-env-acceptance.mjs')
const matrix = read('../../../docs/三期/货柜管理全链验收矩阵-2026-08-13.md')
test('operational ocean booking list excludes unconfirmed data', () => {
assert.match(oceanService, /OPERATIONAL_LIST_STATUSES\s*=\s*Set\.of\([\s\S]*CONFIRMED[\s\S]*CHANGE_PENDING[\s\S]*RELEASED[\s\S]*CANCELLED/)
assert.match(oceanMapper, /status IN \('CONFIRMED','CHANGE_PENDING','RELEASED','CANCELLED'\)/)
assert.match(oceanOpenApi, /enum: \[CONFIRMED, CHANGE_PENDING, RELEASED, CANCELLED\]/)
assert.match(oceanOpenApi, /DRAFT is internal to the create transaction and legacy SUBMITTED\/FAILED values are invalid/)
assert.doesNotMatch(oceanOpenApi.match(/name: status[^\n]*/)?.[0] || '', /DRAFT|SUBMITTED/)
})
test('simulation creation uses server-owned identity and formal master data', () => {
assert.match(controller, /@PostMapping[\s\S]*SimulationCreateRequest request[\s\S]*createSimulation/)
assert.match(containerDtos, /SimulationCreateRequest\(@NotBlank[\s\S]*String channelCode[\s\S]*Long warehouseId[\s\S]*String containerType[\s\S]*BigDecimal capacityCbm[\s\S]*BigDecimal maxWeightKg/)
assert.match(containerService, /requireDomesticTransitWarehouse\(request\.warehouseId\(\)\)/)
assert.match(containerService, /activeVersionId\(\)[\s\S]*priceBooks\.channels\(versionId, warehouse\.id\(\)\)/)
assert.match(containerService, /setCabinetNo\("CAB-" \+ value\.getId\(\)\)/)
assert.match(firstlegOpenApi, /服务端生成稳定 containerBookingId\/cabinetNo,不要求真实柜号或订舱/)
})
test('first link and unlink gates are intentionally asymmetric', () => {
assert.match(controller, /@PostMapping\("\/\{id\}\/ocean-booking-link"\)/)
assert.match(controller, /@DeleteMapping\("\/\{id\}\/ocean-booking-link"\)/)
assert.match(containerService, /requireFirstLinkMutable\(container\)/)
assert.match(containerService, /WAIT_LOADING && value != SmartLoadingStatus\.EDITING/)
assert.match(containerService, /hasPickTask[\s\S]*hasEvidence[\s\S]*hasDocuments/)
assert.match(containerService, /requireUnlinkMutable\(container\)/)
assert.match(containerService, /status\(container\) != SmartLoadingStatus\.WAIT_LOADING \|\| repository\.hasActiveLoadingItems/)
assert.match(firstlegOpenApi, /首次关联允许 WAIT_LOADING 或已有预排货的 EDITING 柜/)
assert.match(firstlegOpenApi, /解绑或换绑仅允许完全空的 WAIT_LOADING 模拟柜/)
})
test('real booking association locks scope capacity and stable internal identity', () => {
for (const field of ['warehouseId', 'channelCode', 'containerType', 'capacityCbm', 'maxWeightKg']) {
assert.match(containerService, new RegExp(`container\\.get${field[0].toUpperCase()}${field.slice(1)}\\(\\)`), field)
}
assert.match(containerService, /hasMismatchedCustomerItems/)
assert.match(containerService, /真实订舱柜箱型分配已关联/)
assert.match(containerService, /setOceanBookingId[\s\S]*setOceanBookingRequirementId/)
assert.doesNotMatch(containerService.match(/linkOceanBooking[\s\S]*?return required\(id\);/)?.[0] || '', /container\.setId\(|container\.setCabinetNo\(/)
})
test('HTTP fixture follows simulation arrange link PDA sequence without auto-container fallback', () => {
const ordered = [
'新增内部模拟柜',
'人工分配本轮候选货物',
'首次关联真实已确认订舱柜',
'查询本轮柜次 PDA 拣货任务',
].map((label) => loadingFixture.indexOf(label))
assert.ok(ordered.every((value) => value >= 0), `missing sequence label: ${ordered.join(',')}`)
assert.ok(ordered.every((value, index) => index === 0 || value > ordered[index - 1]), `invalid sequence: ${ordered.join(',')}`)
assert.match(loadingFixture, /ocean-booking-link/)
assert.match(loadingFixture, /关联前后内部柜次、计划和排货明细身份稳定/)
assert.match(loadingFixture, /同一真实订舱箱型容量拒绝重复关联/)
assert.match(loadingFixture, /已有排货后拒绝解绑或换绑/)
assert.match(loadingFixture, /空模拟柜解绑后允许重新关联/)
assert.doesNotMatch(loadingFixture, /确认订舱并自动生成|确认响应包含自动生成/)
})
test('default full acceptance preserves the linked simulation chain before downstream completion', () => {
const loading = fullEntry.indexOf("'phase3-container-loading'")
const pda = fullEntry.indexOf("'phase3-pda-convergence'")
const finalize = fullEntry.indexOf("'phase3-loading-finalize'")
const outbound = fullEntry.indexOf("'phase3-outbound-dispatch'")
assert.ok(loading >= 0 && pda > loading && finalize > pda && outbound > finalize)
assert.match(matrix, /内部模拟柜关联真实订舱增量/)
assert.doesNotMatch(matrix, /CONFIRMED` 自动建柜[\s\S]*既有冻结/)
})