diff --git a/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs b/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs index e2ddb9e05..e5d1dec26 100644 --- a/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-carrier-nodes-contract.test.mjs @@ -279,6 +279,28 @@ test('task page permits explicit platform scope zero and positive customer scope } }) +test('船节点分页接受已规范化的非 ISO 柜号,仍拒绝空值和未规范化输入', async () => { + for (const trackingNo of ['JZXH10231043', 'TEST-GRAY-CAB-NOISO', 'ZCSU7881971']) { + assert.equal(await taskPageStructure({ trackingNo }), true, trackingNo) + } + for (const trackingNo of ['', ' ', '\t', 'jzxh10231043', ' JZXH10231043', 'JZXH 10231043', null, undefined, 123]) { + assert.equal(await taskPageStructure({ trackingNo }), false, String(trackingNo)) + } +}) + +test('非 ISO 柜号沿本轮归属核验完成船节点详情及人工节点链', async () => { + const fixture = markerOwnedContext() + fixture.task.trackingNo = 'TEST-GRAY-CAB-NOISO' + fixture.context.createdResources.phase3CarrierScheduleQuery.trackingNo = fixture.task.trackingNo + + await runUntilPickupReady(fixture.context) + + assert.equal(fixture.task.latestActualNode, 'PICKUP_READY') + assert.ok(fixture.assertions.every((check) => check.passed)) + assert.ok(fixture.context.calls.filter((call) => call.method === 'POST') + .every((call) => call.path === `${BASE}/${QUERY_ID}/manual-nodes`)) +}) + test('task page still rejects negative, missing, numeric and noncanonical customer scopes', async () => { for (const customerId of ['-1', '-9007199254740993002', '', '00', '01', '+0', '0.0', 'customer', null, undefined, 0, 7]) { assert.equal(await taskPageStructure({ customerId }), false, `customerId=${String(customerId)}`) diff --git a/scripts/admin-test-env-modules/phase4-carrier-nodes.mjs b/scripts/admin-test-env-modules/phase4-carrier-nodes.mjs index e1e7ad0d9..5943b4783 100644 --- a/scripts/admin-test-env-modules/phase4-carrier-nodes.mjs +++ b/scripts/admin-test-env-modules/phase4-carrier-nodes.mjs @@ -10,7 +10,6 @@ export const STANDARD_NODES = Object.freeze([ ]) export const EXCEPTION_CODES = Object.freeze(['CUSTOMS_HOLD', 'TERMINAL_HOLD', 'DOCUMENT_HOLD', 'UNKNOWN_HOLD']) export const NODE_SEQUENCES = Object.freeze(Object.fromEntries(STANDARD_NODES.map((node, index) => [node, (index + 1) * 10]))) -const ISO_CONTAINER_PATTERN = /^[A-Z]{3}[UJZ][0-9]{7}$/ function id(value) { return value == null ? '' : String(value) } function number(value) { return Number.isFinite(Number(value)) } @@ -20,16 +19,10 @@ function validLongId(value) { return typeof value === 'string' && /^[1-9]\d{15,} function validNumericId(value) { return typeof value === 'string' && /^\d+$/.test(value) && value !== '0' } function validCustomerScope(value) { return typeof value === 'string' && /^(?:0|[1-9]\d*)$/.test(value) } function validOperatorId(value) { return value === '0' || validNumericId(value) } +// 柜号沿现行合同仅要求非空且已规范化,不能再以旧 ISO 格式或校验位拒绝合法业务事实。 function validContainerNo(value) { - if (typeof value !== 'string' || !ISO_CONTAINER_PATTERN.test(value)) return false - const letterValues = [10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38] - let sum = 0 - for (let index = 0; index < 10; index += 1) { - const code = value.charCodeAt(index) - sum += (code >= 48 && code <= 57 ? code - 48 : letterValues[code - 65]) * (2 ** index) - } - return Number(value[10]) === (sum % 11) % 10 + return typeof value === 'string' && value.length > 0 + && value === value.replace(/\s/g, '').toUpperCase() } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } function rejected(response) { return response?.http === 200 && ['A0409', '409'].includes(codeOf(response)) }