import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { controlledPhotoDownload } from './phase3-container-overview.mjs' const read = (path) => readFileSync(new URL(path, import.meta.url), 'utf8') const overviewOpenApi = read('../../firstleg/src/main/resources/openapi/phase3-container-overview-v1.yaml') const oceanOpenApi = read('../../firstleg/src/main/resources/openapi/ocean-bookings-v1.yaml') const overviewAcceptance = read('phase3-container-overview.mjs') const oceanAcceptance = read('ocean-bookings.mjs') const adminRoot = '../../../apps/admin-web/src/modules/' const releaseView = read(`${adminRoot}ocean-bookings/pages/OceanBookingDetailView.vue`) const releaseTypes = read(`${adminRoot}ocean-bookings/types.ts`) const truckApi = read(`${adminRoot}trucking/api/platformTrucking.ts`) const truckAttachmentDialog = read(`${adminRoot}trucking/components/TruckAttachmentDialog.vue`) const truckList = read(`${adminRoot}trucking/pages/TruckAppointmentsView.vue`) const truckForm = read(`${adminRoot}trucking/components/TruckAppointmentForm.vue`) const containerList = read(`${adminRoot}container-loading/pages/ContainersView.vue`) const containerOverviewPanel = read(`${adminRoot}container-loading/components/ContainerOverviewPanel.vue`) const containerLoadingAcceptance = read('phase3-container-loading.mjs') const truckAcceptance = read('truck-appointments.mjs') const matrix = read('../../../docs/三期/货柜管理全链验收矩阵-2026-08-13.md') test('release request freezes all five transport fields and UI blocks incomplete submission', () => { assert.match(oceanOpenApi, /required: \[expectedVersion, billOfLadingNo, vesselName, voyageNo, estimatedSailingAt, estimatedArrivalAt\]/) for (const field of ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt', 'estimatedArrivalAt']) { assert.match(releaseTypes, new RegExp(`${field}: string`), field) assert.match(releaseView, new RegExp(`releaseForm\\.${field}`), field) assert.match(oceanAcceptance, new RegExp(`${field}:`), field) } assert.match(releaseView, /if \(!billOfLadingNo \|\| !vesselName \|\| !voyageNo \|\| !releaseForm\.estimatedSailingAt \|\| !releaseForm\.estimatedArrivalAt\)/) }) test('overview regression requires workflowStatus and distinguishes 100 percent awaiting review', () => { assert.match(overviewAcceptance, /'workflowStatus'/) assert.match(overviewAcceptance, /workflowStatus === 'AWAITING_REVIEW'/) assert.match(overviewOpenApi, /workflowStatus:/) assert.doesNotMatch(matrix, /PDA overview `workflowStatus` 缺失[\s\S]*BLOCKED_CONTRACT/) }) test('every overview photo uses its server-provided controlled downloadPath', () => { assert.match(overviewOpenApi, /\/api\/platform\/v1\/containers\/\{id\}\/pda\/photos\/\{fileId\}\/download/) assert.match(overviewAcceptance, /context\.requestBinary\('GET', downloadPath\)/) assert.equal(controlledPhotoDownload('/api/platform/v1/containers/7/pda/photos/23/download', { http: 200, bytes: new Uint8Array([1]), contentType: 'image/png', }), true) for (const [path, response] of [ ['https://example.test/file', { http: 200, bytes: new Uint8Array([1]), contentType: 'image/png' }], ['/api/platform/v1/files/loading-evidence/21/22/23', { http: 200, bytes: new Uint8Array([1]), contentType: 'image/png' }], ['/api/platform/v1/containers/7/pda/photos/23/download', { http: 200, bytes: new Uint8Array([1]), contentType: 'application/json' }], ['/api/platform/v1/containers/7/pda/photos/23/download', { http: 404, bytes: new Uint8Array(), contentType: 'image/png' }], ]) assert.equal(controlledPhotoDownload(path, response), false) }) test('truck attachment API and Admin upload-reference-delete flow are frozen', () => { assert.match(truckApi, /referenceTruckAppointmentAttachment/) assert.match(truckApi, /uploadTruckAppointmentAttachment/) assert.match(truckApi, /deleteTruckAppointmentAttachment/) assert.match(truckAttachmentDialog, /uploadTruckAppointmentAttachment/) assert.match(truckAttachmentDialog, /上传并关联/) assert.match(truckAttachmentDialog, /confirmButtonText: '确认', cancelButtonText: '取消'/) assert.match(truckAcceptance, /删除后回查拖车预约附件列表/) assert.match(truckAcceptance, /!remainingAttachments\.some/) assert.match(matrix, /拖车附件闭环[\s\S]*`LANDED`/) }) test('existing container number replacement keeps container plan and PDA identities stable', () => { assert.match(containerLoadingAcceptance, /replacementContainerNo/) assert.match(containerLoadingAcceptance, /containerBookingId: containerId/) assert.match(containerLoadingAcceptance, /loadingPlanId: planId/) assert.match(containerLoadingAcceptance, /pdaTaskId: taskId/) assert.match(containerLoadingAcceptance, /pdaIdentityStable: true/) }) test('PDA task opens only after preload push, real container number and ocean release converge', () => { assert.match(containerLoadingAcceptance, /无真实柜号时计划可排柜但 PDA 任务不开放/) assert.match(containerLoadingAcceptance, /订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务/) assert.match(containerLoadingAcceptance, /ocean-bookings\/\$\{bookingId\}\/release/) assert.match(containerLoadingAcceptance, /订舱放舱并开放现场 PDA 任务/) assert.match(containerLoadingAcceptance, /releasedBooking\?\.oceanBooking\?\.status === 'RELEASED'/) }) test('customer public display uses tenantName without a legalName fallback', () => { for (const source of [truckList, containerList]) { assert.match(source, /label: customer\.tenantName/) } assert.match(truckForm, /return customer\.tenantName/) for (const source of [truckList, containerList, truckForm]) assert.doesNotMatch(source, /customer\.legalName\?\.trim\(\) \|\| customer\.tenantName/) assert.match(matrix, /客户公开名称一致性[\s\S]*已统一 `tenantName`[\s\S]*`LANDED`/) }) test('Chinese status dialogs and 390px layout are landed', () => { assert.match(releaseView, /确认放舱/) assert.match(containerOverviewPanel, /AWAITING_REVIEW: '待人工复核'/) assert.match(containerOverviewPanel, /PHOTO_STEP_EMPTY_CONTAINER: '空柜照片'/) assert.match(matrix, /中文状态、弹框与 390px 布局[\s\S]*`LANDED`/) assert.doesNotMatch(matrix, /拖车附件新增入口缺失|中文状态、弹框与 390px 布局[^\n]*BLOCKED_CONTRACT/) })