test: complete phase4 cross-region handoff
This commit is contained in:
@@ -128,7 +128,7 @@ function markerOwnedContext() {
|
|||||||
}]
|
}]
|
||||||
const histories = [{
|
const histories = [{
|
||||||
id: String(nextId++), action: 'AUTO_CREATE', fromVersion: null, toVersion: 0,
|
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',
|
createdAt: '2026-08-24T08:00:01',
|
||||||
}]
|
}]
|
||||||
const detail = () => ({ query: { query: clone(task), expired: true },
|
const detail = () => ({ query: { query: clone(task), expired: true },
|
||||||
@@ -178,7 +178,7 @@ function markerOwnedContext() {
|
|||||||
} else task.status = 'READY'
|
} else task.status = 'READY'
|
||||||
}
|
}
|
||||||
histories.push({ id: String(nextId++), action: 'MANUAL_NODE', fromVersion: task.version - 1,
|
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 })
|
createdAt: body.observedAt })
|
||||||
const saved = detail(); idempotency.set(key, { signature, detail: clone(saved) })
|
const saved = detail(); idempotency.set(key, { signature, detail: clone(saved) })
|
||||||
return ok(saved, `req-${key}`)
|
return ok(saved, `req-${key}`)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function validHistory(item) {
|
|||||||
return validLongId(item?.id) && Boolean(item?.action)
|
return validLongId(item?.id) && Boolean(item?.action)
|
||||||
&& (item?.fromVersion == null || number(item.fromVersion))
|
&& (item?.fromVersion == null || number(item.fromVersion))
|
||||||
&& (item?.toVersion == null || number(item.toVersion))
|
&& (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)
|
&& 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, /retained as acceptance evidence/)
|
||||||
assert.match(source, /preferredProducer\?\.cartonMarks\?\.length/)
|
assert.match(source, /preferredProducer\?\.cartonMarks\?\.length/)
|
||||||
assert.match(source, /for \(const \[index, cartonMark\] of cartonMarks\.entries\(\)\)/)
|
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 () => {
|
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 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 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 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) {
|
function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) {
|
||||||
context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true })
|
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
|
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) {
|
async function loginMerchantForNegativeProbe(context) {
|
||||||
const fixture = context.context?.merchantQualificationFixture
|
const fixture = context.context?.merchantQualificationFixture
|
||||||
if (!fixture?.username || !fixture?.password) return null
|
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)
|
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.addStep(MODULE, '完成后current返回404正常空态', after, notFound(after), trace(after), OPTIONS)
|
||||||
context.createdResources.phase4OverseasPda.finalStatus = completed.data?.status || null
|
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) {
|
export async function run(context) {
|
||||||
context.createdResources.phase4OverseasPda = {
|
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: [],
|
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], completedContainers: [],
|
||||||
overseasWarehouseId: null, overseasWarehouseName: null,
|
overseasWarehouseId: null, overseasWarehouseName: null,
|
||||||
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',
|
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',
|
||||||
|
|||||||
Reference in New Issue
Block a user