test(shipping): validate platform customer scope separately from business IDs
This commit is contained in:
@@ -251,3 +251,63 @@ test('marker-owned pre-PDA chain covers idempotency conflicts through pickup-rea
|
||||
const firstPageQuery = fixture.context.calls.find((item) => item.path === `${BASE}?page=1&size=20`)
|
||||
assert.ok(firstPageQuery)
|
||||
})
|
||||
|
||||
async function taskPageStructure(overrides) {
|
||||
const fixture = markerOwnedContext()
|
||||
const task = { ...fixture.task, ...overrides }
|
||||
const checks = []
|
||||
const originalRequest = fixture.context.request
|
||||
fixture.context.createdResources = {}
|
||||
fixture.context.request = async (method, requestPath, ...args) => {
|
||||
if (method === 'GET' && requestPath === `${BASE}?page=1&size=20`) {
|
||||
fixture.context.calls.push({ method, path: requestPath })
|
||||
return ok({ records: [{ query: task, expired: false }], page: 1, size: 20, total: 1, pages: 1 })
|
||||
}
|
||||
return originalRequest(method, requestPath, ...args)
|
||||
}
|
||||
fixture.context.assert = (_module, name, _response, condition) => checks.push({ name, passed: Boolean(condition) })
|
||||
fixture.context.addStep = (_module, name, _response, passed) => checks.push({ name, passed })
|
||||
await runUntilPickupReady(fixture.context)
|
||||
assert.ok(fixture.context.calls.every((call) => call.method === 'GET'))
|
||||
assert.ok(checks.some((check) => check.name.includes('缺少本轮phase3出仓CarrierScheduleQuery') && !check.passed))
|
||||
return checks.find((check) => check.name === '船节点分页与长ID结构稳定')?.passed
|
||||
}
|
||||
|
||||
test('task page permits explicit platform scope zero and positive customer scopes as serialized longs', async () => {
|
||||
for (const customerId of ['0', '7', CUSTOMER_ID]) {
|
||||
assert.equal(await taskPageStructure({ customerId }), true, `customerId=${customerId}`)
|
||||
}
|
||||
})
|
||||
|
||||
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)}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('allowing platform scope zero never relaxes positive long task, booking or container identifiers', async () => {
|
||||
for (const field of ['id', 'oceanBookingId', 'containerBookingId']) {
|
||||
for (const invalid of ['0', '0000000000000000', '09007199254740993', '-9007199254740993001', '7',
|
||||
'9007199254740993.001', null, 9007199254740993]) {
|
||||
assert.equal(await taskPageStructure({ customerId: '0', [field]: invalid }), false,
|
||||
`${field}=${String(invalid)}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('platform scope zero completes the same detail and manual-node lifecycle assertions without changing business identifiers', async () => {
|
||||
const fixture = markerOwnedContext()
|
||||
fixture.task.customerId = '0'
|
||||
fixture.context.createdResources.phase3CarrierScheduleQuery.customerId = '0'
|
||||
|
||||
await runUntilPickupReady(fixture.context)
|
||||
|
||||
assert.equal(fixture.task.customerId, '0')
|
||||
assert.equal(fixture.task.id, QUERY_ID)
|
||||
assert.equal(fixture.task.oceanBookingId, OCEAN_ID)
|
||||
assert.equal(fixture.task.containerBookingId, CONTAINER_ID)
|
||||
assert.equal(fixture.task.latestActualNode, 'PICKUP_READY')
|
||||
assert.equal(fixture.context.calls.filter((call) => call.method === 'POST').length, 15)
|
||||
assert.ok(fixture.assertions.every((check) => check.passed))
|
||||
assert.ok(fixture.steps.every((check) => check.passed))
|
||||
})
|
||||
|
||||
@@ -16,8 +16,9 @@ function id(value) { return value == null ? '' : String(value) }
|
||||
function number(value) { return Number.isFinite(Number(value)) }
|
||||
function nullableText(value) { return value == null || typeof value === 'string' }
|
||||
function nullableIso(value) { return value == null || (typeof value === 'string' && !Number.isNaN(Date.parse(value))) }
|
||||
function validLongId(value) { return typeof value === 'string' && /^\d{16,}$/.test(value) }
|
||||
function validLongId(value) { return typeof value === 'string' && /^[1-9]\d{15,}$/.test(value) }
|
||||
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) }
|
||||
function validContainerNo(value) {
|
||||
if (typeof value !== 'string' || !ISO_CONTAINER_PATTERN.test(value)) return false
|
||||
@@ -69,7 +70,7 @@ export function manualNodePayload({ expectedVersion, standardNode, observedAt, e
|
||||
}
|
||||
|
||||
function validTask(task) {
|
||||
return validLongId(task?.id) && Boolean(task?.queryNo) && validNumericId(task?.customerId)
|
||||
return validLongId(task?.id) && Boolean(task?.queryNo) && validCustomerScope(task?.customerId)
|
||||
&& validLongId(task?.oceanBookingId) && QUERY_STATUSES.includes(task?.status)
|
||||
&& validLongId(task?.containerBookingId) && task?.trackingType === 'CONTAINER_NO'
|
||||
&& validContainerNo(task?.trackingNo) && nullableText(task?.latestActualNode)
|
||||
|
||||
Reference in New Issue
Block a user