Files
baiyue-firstleg/scripts/admin-test-env-modules/phase3-container-regression-contract.test.mjs
T

153 lines
11 KiB
JavaScript

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 oceanFixture = read('ocean-booking-fixture.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 phase3LoadingAcceptance = read('phase3-loading.mjs')
const truckAcceptance = read('truck-appointments.mjs')
const matrix = read('../../../docs/国内货柜履约/归档/货柜管理全链验收矩阵-2026-08-13.md')
test('container acceptance reads the canonical domestic workbench route', () => {
assert.match(containerLoadingAcceptance, /const DOMESTIC_CONTAINERS = `\$\{CONTAINERS\}\/domestic`/)
assert.match(containerLoadingAcceptance, /function containerRecord\(value\) \{ return value\?\.container \}/)
assert.doesNotMatch(containerLoadingAcceptance, /value\?\.container \|\| value/)
assert.match(containerLoadingAcceptance, /DOMESTIC_CONTAINERS\}\?page=1&size=50&scope=ACTIVE&keyword=/)
assert.doesNotMatch(containerLoadingAcceptance, /request\('GET', `\$\{CONTAINERS\}\/\$\{containerId\}`\)/)
assert.match(phase3LoadingAcceptance, /\/api\/platform\/v1\/containers\/domestic\/\$\{containerBookingId\}/)
assert.match(phase3LoadingAcceptance, /containerRecord\(context\.apiSuccess/)
})
test('DRAFT stores all five transport fields and release validates the persisted snapshot', () => {
assert.match(oceanOpenApi, /ReleaseRequest: \{type: object, required: \[expectedVersion\]/)
for (const field of ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt', 'estimatedArrivalAt']) {
assert.match(releaseTypes, new RegExp(`${field}: string`), field)
assert.match(releaseView, new RegExp(`form\\.data\\.${field}`), field)
assert.match(oceanFixture, new RegExp(`${field}:`), field)
}
assert.match(oceanAcceptance, /完整 DRAFT 放舱为 RELEASED 且冻结五项运输事实/)
assert.match(oceanAcceptance, /RELEASED 编辑拒绝且零写入/)
})
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('pre-departure overview rejects destination workbench documents explicitly', () => {
assert.match(overviewAcceptance, /国内未出仓时 \$\{documentType\} 不生成国外工作台数据/)
assert.match(overviewAcceptance, /国内未出仓时目的港清关不生成国外工作台数据/)
assert.match(overviewAcceptance, /国外工作台货柜不存在/)
})
test('active overview photos download through controlled paths while void history has no download entry', () => {
assert.match(overviewOpenApi, /\/api\/platform\/v1\/containers\/\{id\}\/pda\/photos\/\{fileId\}\/download/)
assert.match(overviewAcceptance, /\['evidenceId', 'fileId', 'status'\]\.every/)
assert.doesNotMatch(overviewAcceptance, /\['evidenceId', 'fileId', 'status', 'downloadPath'\]\.every/)
assert.match(overviewAcceptance, /context\.requestBinary\('GET', downloadPath\)/)
assert.match(overviewAcceptance, /photo\.status === 'VOID'/)
assert.match(overviewAcceptance, /overview PDA 作废照片 \$\{index \+ 1\} 无下载入口/)
assert.match(overviewAcceptance, /photo\.status !== 'ACTIVE' \|\| !controlledPath/)
assert.match(overviewOpenApi, /status: \{type: string, enum: \[ACTIVE, VOID\]\}/)
assert.match(overviewOpenApi, /downloadPath: \{type: string, nullable: true, description: '仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空'\}/)
assert.equal(overviewAcceptance.indexOf("if (photo.status === 'VOID')")
< overviewAcceptance.indexOf("context.requestBinary('GET', downloadPath)"), true)
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 reference flow and Admin upload-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: '取消'/)
for (const type of ['DISPATCH_ORDER', 'DRIVER_DOCUMENT', 'VEHICLE_DOCUMENT', 'OTHER'])
assert.match(truckAttachmentDialog, new RegExp(type))
assert.doesNotMatch(truckAttachmentDialog, /BILL_OF_LADING/)
for (const label of ['上传并关联DISPATCH_ORDER拖车附件', '删除首次上传的拖车附件引用',
'重新引用平台归属拖车附件', '附件列表包含重新引用的DISPATCH_ORDER', '删除重新引用的拖车附件'])
assert.match(truckAcceptance, new RegExp(label))
assert.doesNotMatch(truckAcceptance, /attachmentType=BILL_OF_LADING|派车前保留有效 BILL_OF_LADING 提单附件/)
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, /PDA任务领取后真实柜号纠错被拒绝/)
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 no-number push, release, four-field link and plan-level confirmation', () => {
assert.match(containerLoadingAcceptance, /分配后自动建立预装关联且计划可读/)
assert.match(containerLoadingAcceptance, /无号 push 后预装清单可见/)
assert.match(containerLoadingAcceptance, /无号 push 成功后 PDA 查询 A0404/)
assert.match(containerLoadingAcceptance, /oceanBookingId: bookingId, requirementId: id\(requirement\.id\), containerNo: realContainerNo/)
assert.match(containerLoadingAcceptance, /关联后列表仅保留模拟柜原地补全的一条 ACTIVE 操作身份/)
assert.match(containerLoadingAcceptance, /ocean-bookings\/\$\{bookingId\}\/release/)
assert.match(containerLoadingAcceptance, /ocean-bookings\/\$\{bookingId\}\/documents\/upload/)
assert.match(containerLoadingAcceptance, /documentType', 'BOOKING_CONFIRMATION'/)
assert.match(containerLoadingAcceptance, /订舱放舱为 RELEASED 且不预生成真实货柜/)
assert.match(containerLoadingAcceptance, /订舱 RELEASED 但预装未 CONFIRMED 不生成 PDA 任务/)
assert.match(containerLoadingAcceptance, /loading-plans\/\$\{planId\}\/confirm/)
assert.match(containerLoadingAcceptance, /PDA}\/tasks\/\$\{taskId\}\/evidence/)
assert.match(containerLoadingAcceptance, /evidence\/\$\{encodeURIComponent\(evidenceId\)\}\/content/)
assert.match(containerLoadingAcceptance, /计划级原子确认整份预装并触发唯一 PDA 任务/)
assert.match(containerLoadingAcceptance, /waitForCompletedTask/)
assert.match(containerLoadingAcceptance, /taskStatus === 'COMPLETED' && task\?\.workflowStatus === 'COMPLETED'/)
assert.match(containerLoadingAcceptance, /confirmationStatus === 'CONFIRMED'/)
assert.match(containerLoadingAcceptance, /readiness\?\.readyForPda === true/)
assert.match(containerLoadingAcceptance, /taskId === confirmedTaskId/)
assert.match(containerLoadingAcceptance, /计划级确认同 key 异参返回冲突/)
assert.match(containerLoadingAcceptance, /已确认计划使用新 key 重复确认返回冲突/)
assert.doesNotMatch(containerLoadingAcceptance, /\/preload\/confirming|\/preload\/confirm\?tenantId=/)
assert.match(containerLoadingAcceptance, /confirmedBooking\?\.oceanBooking\?\.status === 'RELEASED'/)
assert.doesNotMatch(containerLoadingAcceptance, /realContainerBookingId|MERGED/)
})
test('customer public display uses tenantName without a legalName fallback', () => {
assert.match(containerList, /label: customer\.tenantName/)
for (const source of [truckList, truckForm])
assert.doesNotMatch(source, /customer\.(?:tenantName|legalName)/)
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/)
})