test(events): add cross-region HTTP acceptance gates
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs'
|
||||
import { run } from './phase4-cross-region-events.mjs'
|
||||
|
||||
const source = readFileSync(new URL('./phase4-cross-region-events.mjs', import.meta.url), 'utf8')
|
||||
const system = readFileSync(new URL('./system.mjs', import.meta.url), 'utf8')
|
||||
|
||||
function response(path, data, http = 200, code = '200') {
|
||||
return { method: 'GET', path, http, body: { code, data }, requestId: 'request-1', traceId: 'trace-1' }
|
||||
}
|
||||
|
||||
function context(resources = {}) {
|
||||
const steps = []
|
||||
const gaps = []
|
||||
const calls = []
|
||||
return {
|
||||
marker: 'MARKER-1', token: 'domestic-token', overseasToken: 'overseas-token',
|
||||
createdResources: resources, steps, gaps, calls,
|
||||
addCoverageGap(module, scenario, name, reason, category, metadata) {
|
||||
gaps.push({ module, scenario, name, reason, category, ...metadata })
|
||||
},
|
||||
addStep(module, name, value, passed, evidence, options) {
|
||||
steps.push({ module, name, passed: Boolean(passed), evidence, options })
|
||||
return Boolean(passed)
|
||||
},
|
||||
assert(module, name, value, passed, evidence, options) {
|
||||
return this.addStep(module, name, value, passed, evidence, options)
|
||||
},
|
||||
async requestOverseas(method, path, body, headers, token) {
|
||||
calls.push({ region: 'OVERSEAS', method, path, token: token || this.overseasToken })
|
||||
if (token === this.token) return response(path, null, 403, 'A0403')
|
||||
if (path.includes('/last-leg/orders/9001')) {
|
||||
return response(path, { order: { id: '9001', containerNo: 'MSCU1234567' } })
|
||||
}
|
||||
return response(path, { records: [{ id: '7001', containerNo: 'MSCU1234567' }] })
|
||||
},
|
||||
async request(method, path, body, token) {
|
||||
calls.push({ region: 'CN', method, path, token: token || this.token })
|
||||
if (token === this.overseasToken) return response(path, null, 403, 'A0403')
|
||||
if (path.includes('/overview')) return response(path, { container: {
|
||||
id: '3001', containerNo: 'MSCU1234567', unloadingCompletedAt: '2026-08-24T10:00:00Z',
|
||||
unloadingPhotoUploadedAt: '2026-08-24T09:00:00Z', estimatedEmptyReportedAt: '2026-08-26T09:00:00Z',
|
||||
actualEmptyReportedAt: null, emptyReportSourceVersion: 7,
|
||||
} })
|
||||
return response(path, { records: [{ id: '3001', containerNo: 'MSCU1234567' }] })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('entry orders marker-owned prerequisites before cross-region acceptance', () => {
|
||||
const modules = normalizeModuleOrder(['phase4-cross-region-events'])
|
||||
const cross = modules.indexOf('phase4-cross-region-events')
|
||||
for (const prerequisite of ['system', 'phase3-loading-finalize', 'phase4-overseas-pda']) {
|
||||
assert.ok(modules.indexOf(prerequisite) >= 0 && modules.indexOf(prerequisite) < cross, prerequisite)
|
||||
}
|
||||
})
|
||||
|
||||
test('contract covers dual audience, canonical 403, both event directions and diagnostics', () => {
|
||||
for (const token of [
|
||||
'context.token)', 'context.overseasToken)', 'cn-overseas-event-not-delivered',
|
||||
'overseas-cn-event-not-delivered', 'requestId=', 'traceId=', 'pollRequests=',
|
||||
'装柜完成事件重试保持建单幂等', '海外柜完成事件重试保持回写幂等',
|
||||
]) assert.ok(source.includes(token), token)
|
||||
assert.match(system, /缺少canonical海外订单权限必须403/)
|
||||
assert.match(system, /restrictedOverseasToken/)
|
||||
})
|
||||
|
||||
test('full observable cross-region flow performs requests and passes', async () => {
|
||||
const ctx = context({
|
||||
phase3Loading: { marker: 'MARKER-1', finalizeSucceeded: true, containerBookingId: '3001',
|
||||
containerNo: 'MSCU1234567', loadingPlanId: '4001' },
|
||||
phase4OverseasPda: { marker: 'MARKER-1', finalStatus: 'COMPLETED', customerId: '100', orderId: '9001' },
|
||||
})
|
||||
|
||||
await run(ctx)
|
||||
|
||||
assert.ok(ctx.calls.length >= 8)
|
||||
assert.equal(ctx.steps.every((step) => step.passed), true)
|
||||
assert.equal(ctx.gaps.length, 0)
|
||||
assert.ok(ctx.calls.some((call) => call.region === 'CN' && call.token === 'overseas-token'))
|
||||
assert.ok(ctx.calls.some((call) => call.region === 'OVERSEAS' && call.token === 'domestic-token'))
|
||||
})
|
||||
|
||||
test('missing marker fixture is a blocking coverage gap after real security request', async () => {
|
||||
const ctx = context()
|
||||
|
||||
await assert.rejects(() => run(ctx), /blocking coverage gap/)
|
||||
|
||||
assert.ok(ctx.calls.length > 0)
|
||||
assert.equal(ctx.gaps.length, 1)
|
||||
assert.equal(ctx.gaps[0].deploymentBlocker, true)
|
||||
assert.equal(ctx.steps.some((step) => !step.passed && step.options?.deploymentBlocker), true)
|
||||
})
|
||||
Reference in New Issue
Block a user