test(lastleg): derive merchant relabel scope from profile receipt

This commit is contained in:
2026-10-18 19:29:35 +08:00
parent 4bef8577fa
commit 2c330bf9df
2 changed files with 20 additions and 3 deletions
@@ -61,7 +61,7 @@ function producerReport() {
event('GET', '/api/merchant/v1/iam/tenant/profile', undefined, { id: 'C' }, 'MERCHANT')
event('POST', '/api/merchant/v1/last-leg/return-orders', { remark: 'CURRENT new return' }, { order: { returnNo: 'RT', overseasWarehouseId: 'W' } }, 'MERCHANT')
event('GET', '/api/platform/v1/last-leg/return-inventory/cartons/BOX', undefined, { cartonMark: 'BOX', sourceReturnNo: 'RT', customerId: 'C', overseasWarehouseId: 'W', status: 'AVAILABLE', items: [{ skuCode: 'SKU-A', quantity: 3 }, { skuCode: 'SKU-B', quantity: 2 }] })
event('POST', '/api/merchant/v1/last-leg/relabel-applications', { operationWarehouseId: 'W' }, { application: { relabelNo: 'RL-N', customerId: 'C', overseasWarehouseId: 'W' } }, 'MERCHANT')
event('POST', '/api/merchant/v1/last-leg/relabel-applications', { operationWarehouseId: 'W' }, { application: { relabelNo: 'RL-N', overseasWarehouseId: 'W' } }, 'MERCHANT')
dispatch('NDP', [candidate(null, '601', null, { sourceStage: 'RELABEL', orderRefs: 'RL-N', returnCartonSource: true })], 'RL-N', 1)
event('POST', '/api/platform/v1/last-leg/dispatches/NDP/transfer-delivery', {}, { outboundNo: 'O', deliveries: [{ deliveryNo: 'D' }] })
event('GET', '/api/platform/v1/last-leg/delivery-orders/D/return-inbound-options?customerId=C', undefined, [{ customerId: 'C', lastMileOrderId: '91', deliveryNo: 'D', outboundNo: 'O', returnSources: [{ returnCartonId: '81', cartonMark: 'BOX', inventoryPositionId: null }] }])
@@ -80,6 +80,20 @@ function context(handler = () => assert.fail('No request expected')) {
}
const execute = (ctx, fixtures) => verifyDispatchRepair(ctx, fixtures, { execute: true })
test('merchant relabel summary derives omitted customer from authenticated profile receipt', async () => {
const ctx = context()
assert.equal((await execute(ctx, {})).status, 'PARTIAL_NOT_ALL_VERIFIED')
assert.equal(ctx.calls.length, 0)
})
test('an explicitly different relabel customer cannot borrow merchant profile scope', async () => {
const ctx = context(), report = producerReport()
report.requests.find(e => e.path === '/api/merchant/v1/last-leg/relabel-applications').result.application.customerId = 'OTHER'
writeProducer(ctx, report)
await assert.rejects(execute(ctx, {}), /Relabel producer scope mismatch/)
assert.equal(ctx.calls.length, 0)
})
test('import/plan is zero-network and missing current fixtures never become a pass', async () => {
const ctx = context(); assert.equal((await verifyDispatchRepair(ctx, {})).status, 'PLAN_ONLY'); assert.equal(ctx.calls.length, 0)
const result = await execute(ctx, {})
@@ -58,8 +58,11 @@ async function loadProducerResources(context) {
}
if (e.method === 'POST' && path === '/api/merchant/v1/last-leg/relabel-applications' && actors.has(e.actor)) {
const a = data.application
must(a && id(a.customerId) === actors.get(e.actor) && id(a.overseasWarehouseId) === id(e.body?.operationWarehouseId), 'Relabel producer scope mismatch')
add('relabel', a.relabelNo, a.customerId, a.overseasWarehouseId, e)
const customerId = actors.get(e.actor)
must(a && (a.customerId === undefined || id(a.customerId) === customerId)
&& id(a.overseasWarehouseId) === id(e.body?.operationWarehouseId), 'Relabel producer scope mismatch')
// Merchant summaries omit customerId; the authenticated profile receipt supplies its scope.
add('relabel', a.relabelNo, customerId, a.overseasWarehouseId, e)
}
if (e.method === 'POST' && path === '/api/merchant/v1/last-leg/return-orders' && actors.has(e.actor)
&& String(e.body?.remark).includes(context.marker)) add('return', data.order?.returnNo, actors.get(e.actor), data.order?.overseasWarehouseId, e)