test: complete phase4 cross-region handoff

This commit is contained in:
2026-08-24 23:35:26 +08:00
parent 615ecb4ba1
commit 058bf9f4e6
4 changed files with 28 additions and 4 deletions
@@ -128,7 +128,7 @@ function markerOwnedContext() {
}]
const histories = [{
id: String(nextId++), action: 'AUTO_CREATE', fromVersion: null, toVersion: 0,
reason: '国内确认出仓自动创建', operatorId: CUSTOMER_ID, operatorName: 'fixture',
reason: '国内确认出仓自动创建', operatorId: '1000001', operatorName: 'fixture',
createdAt: '2026-08-24T08:00:01',
}]
const detail = () => ({ query: { query: clone(task), expired: true },
@@ -178,7 +178,7 @@ function markerOwnedContext() {
} else task.status = 'READY'
}
histories.push({ id: String(nextId++), action: 'MANUAL_NODE', fromVersion: task.version - 1,
toVersion: task.version, reason: body.reason, operatorId: CUSTOMER_ID, operatorName: 'fixture',
toVersion: task.version, reason: body.reason, operatorId: '1000001', operatorName: 'fixture',
createdAt: body.observedAt })
const saved = detail(); idempotency.set(key, { signature, detail: clone(saved) })
return ok(saved, `req-${key}`)
@@ -71,7 +71,7 @@ function validHistory(item) {
return validLongId(item?.id) && Boolean(item?.action)
&& (item?.fromVersion == null || number(item.fromVersion))
&& (item?.toVersion == null || number(item.toVersion))
&& nullableText(item?.reason) && nullableId(item?.operatorId)
&& nullableText(item?.reason) && (item?.operatorId == null || validNumericId(item.operatorId))
&& nullableText(item?.operatorName) && nullableIso(item?.createdAt)
}
@@ -45,6 +45,9 @@ test('PDA module covers the container-number receiving contract and excludes out
assert.match(source, /retained as acceptance evidence/)
assert.match(source, /preferredProducer\?\.cartonMarks\?\.length/)
assert.match(source, /for \(const \[index, cartonMark\] of cartonMarks\.entries\(\)\)/)
assert.match(source, /last-leg\/orders\?page=1&size=200&containerNo=/)
assert.match(source, /phase4OverseasPda\.orderId = id\(order\.id\)/)
assert.match(source, /phase4OverseasPda\.customerId = id\(order\.customerId\)/)
})
test('missing WAREHOUSE_PICKER overseas authority is a deployment blocker', async () => {
@@ -17,6 +17,7 @@ function forbidden(response) { return [401, 403].includes(response?.http) || (re
function trace(response) { return `HTTP=${response?.http ?? 'missing'}, code=${response?.body?.code ?? 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` }
function key(context, action, suffix = '') { return `${context.marker}-${context.pdaContainerSuffix || ''}-${action}-${suffix}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 120) }
function markerSuffix(context) { return `${context.marker}${context.pdaContainerSuffix || ''}`.replace(/[^A-Za-z0-9]/g, '').slice(-16).toUpperCase() }
function records(data) { return Array.isArray(data?.records) ? data.records : Array.isArray(data) ? data : [] }
function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) {
context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true })
@@ -35,6 +36,23 @@ async function expected(context, token, method, name, path, body, predicate) {
return response
}
async function captureCompletedOrder(context, containerNo, expectedCustomerId) {
const response = await context.requestOverseas('GET',
`/api/platform/v1/last-leg/orders?page=1&size=200&containerNo=${encodeURIComponent(containerNo)}`)
const candidates = records(response?.body?.data)
.filter((item) => id(item?.containerNo) === id(containerNo))
const exactCustomer = expectedCustomerId == null ? candidates
: candidates.filter((item) => id(item?.customerId) === id(expectedCustomerId))
const order = exactCustomer[0] || candidates[0]
const mapped = success(response) && longId(order?.id) && nonBlank(id(order?.customerId))
context.assert(MODULE, '完成柜回查平台尾程订单并冻结跨区身份', response, mapped,
`containerNo=${containerNo}, orderId=${id(order?.id) || 'missing'}, customerId=${id(order?.customerId) || 'missing'}; ${trace(response)}`, OPTIONS)
if (mapped) {
context.createdResources.phase4OverseasPda.orderId = id(order.id)
context.createdResources.phase4OverseasPda.customerId = id(order.customerId)
}
}
async function loginMerchantForNegativeProbe(context) {
const fixture = context.context?.merchantQualificationFixture
if (!fixture?.username || !fixture?.password) return null
@@ -362,11 +380,14 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
const after = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/current', undefined, {}, token)
context.addStep(MODULE, '完成后current返回404正常空态', after, notFound(after), trace(after), OPTIONS)
context.createdResources.phase4OverseasPda.finalStatus = completed.data?.status || null
if (context.createdResources.phase4OverseasPda.finalStatus === 'COMPLETED') {
await captureCompletedOrder(context, containerNo, preferredProducer?.tenantId)
}
}
export async function run(context) {
context.createdResources.phase4OverseasPda = {
marker: context.marker, containerNo: null, customerId: null,
marker: context.marker, containerNo: null, customerId: null, orderId: null,
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], completedContainers: [],
overseasWarehouseId: null, overseasWarehouseName: null,
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',