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]*既有冻结/) })