test: integrate reliable event recovery and receiving evidence

This commit is contained in:
2026-10-02 20:14:17 +08:00
parent d0df738bba
commit 4b531c7e22
17 changed files with 1527 additions and 37 deletions
+42 -3
View File
@@ -1,5 +1,5 @@
import assert from 'node:assert/strict'
import { spawn } from 'node:child_process'
import { spawn, spawnSync } from 'node:child_process'
import { createServer } from 'node:http'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
@@ -19,7 +19,7 @@ const event = (id = targetId, planId = '2100000000000000001') => ({ eventId: id,
payload: { loadingPlanId: planId, syntheticPrivateField: secret } })
const close = server => new Promise(resolve => server.close(resolve))
async function fixture(selector = ['--loading-plan-id', '2100000000000000001']) {
async function fixture(selector = ['--loading-plan-id', '2100000000000000001'], eventType = 'LoadingCompleted') {
const calls = []
const applied = new Set()
let invalidAck = false
@@ -40,7 +40,7 @@ async function fixture(selector = ['--loading-plan-id', '2100000000000000001'])
const directory = await mkdtemp(join(tmpdir(), 'event-fault-offline-'))
const log = join(directory, 'evidence.jsonl')
const child = spawn(process.env.PYTHON || 'python', [script, '--upstream',
`http://127.0.0.1:${consumer.address().port}/internal/events`, '--event-type', 'LoadingCompleted',
`http://127.0.0.1:${consumer.address().port}/internal/events`, '--event-type', eventType,
...selector, '--log', log], { env: { ...process.env, EVENT_FAULT_CONTROL_TOKEN: controlToken },
stdio: ['ignore', 'pipe', 'pipe'] })
let output = ''; let errors = ''
@@ -179,3 +179,42 @@ test('explicit event-ID selector requires exact event type and forwards unrelate
assert.equal(status.attempt, 1)
assert.equal(status.eventId, targetId)
})
test('unloading selector pins only the exact source booking and preserves reverse-event ACK recovery', async t => {
const bookingId = '2100000000000000066'
const proxy = await fixture(['--container-booking-id', bookingId], 'OverseasContainerUnloaded')
t.after(() => proxy.stop())
await proxy.control('/mode', { mode: 'FAULT' })
const unloaded = { ...event(), eventType: 'OverseasContainerUnloaded',
payload: { sourceContainerBookingId: bookingId, containerNo: 'TEST-CONTAINER' } }
for (const unrelated of [
{ ...unloaded, eventId: otherId, payload: { sourceContainerBookingId: '2100000000000000067' } },
{ ...unloaded, eventId: otherId, payload: { loadingPlanId: bookingId } },
{ ...unloaded, eventId: otherId, eventType: 'OverseasDeliveryCompleted' },
]) assert.equal((await proxy.send(unrelated)).status, 200)
assert.equal((await proxy.send(unloaded)).status, 503)
assert.equal((await proxy.send(unloaded)).status, 503)
assert.equal((await proxy.send({ ...unloaded, eventId: samePlanOtherId })).status, 200)
const before = proxy.calls.length
assert.equal((await proxy.send({ ...unloaded, payload: { sourceContainerBookingId: '2100000000000000067' } })).status, 409)
assert.equal(proxy.calls.length, before)
await proxy.control('/mode', { mode: 'PASS' })
const recovered = await proxy.send(unloaded)
assert.equal(recovered.status, 200)
assert.equal(JSON.parse(recovered.raw).data.outcome, 'DUPLICATE')
const status = await (await proxy.control('/status')).json()
assert.equal(status.eventId, targetId)
assert.deepEqual(status.records.filter(row => row.errorCode !== 'TARGET_EVENT_HASH_CONFLICT').map(row => row.outcome),
['APPLIED', 'DUPLICATE', 'DUPLICATE'])
})
test('booking selector rejects malformed identities and mixed selection before starting listeners', () => {
for (const args of [['--container-booking-id', '0'], ['--container-booking-id', '../66'],
['--container-booking-id', '66', '--loading-plan-id', '77'], ['--container-booking-id', '66', '--event-id', targetId]]) {
const result = spawnSync(process.env.PYTHON || 'python', [script, '--upstream', 'http://127.0.0.1:1/internal/events',
'--event-type', 'OverseasContainerUnloaded', ...args], { encoding: 'utf8', timeout: 5000,
env: { ...process.env, EVENT_FAULT_CONTROL_TOKEN: controlToken } })
assert.equal(result.status, 2)
assert.ok(!result.stdout.includes('"ready"'))
}
})