fix(dispatch): invalidate resource evidence and bind uploads to review rounds

This commit is contained in:
2026-10-26 19:21:11 +08:00
parent f6d9991955
commit a5befc9bad
15 changed files with 138 additions and 51 deletions
@@ -20,7 +20,7 @@ const receipt = () => ({ environment: 'TEST', marker: 'TEST-NATIVE', dispatchNo:
for (const initialStage of ['LOADING', 'AFTER_LOADING']) test(`loading documents fill missing slots without replacing existing files from ${initialStage}`, async () => {
const proof = { marker: 'TEST-DOCS', dispatchNo: 'DP-A' }, calls = [], replays = new Map()
const state = { dispatchNo: 'DP-A', recordVersion: '9007199254740993', dispatchRecordVersion: '11', stage: initialStage,
const state = { dispatchNo: 'DP-A', recordVersion: '9007199254740993', dispatchRecordVersion: '11', reviewRound: '1', stage: initialStage,
complete: false, editable: true, photos: [{ slot: LOADING_PHOTO_SLOTS[0], fileId: 'existing' }] }
let saved = false
const context = { async checkpoint() { saved = true },
@@ -28,7 +28,7 @@ for (const initialStage of ['LOADING', 'AFTER_LOADING']) test(`loading documents
async requestMultipart(method, path, file, token) {
assert.ok(saved); saved = false; assert.equal(token, 'PDA-TEST'); assert.equal(method, 'POST')
const slot = path.split('/').at(-2)
assert.match(path, /idempotencyKey=[a-f0-9]{64}$/); assert.equal(file.type, 'image/png')
assert.match(path, /idempotencyKey=[a-f0-9]{64}&reviewRound=1$/); assert.equal(file.type, 'image/png')
assert.equal(file.content.subarray(1, 4).toString(), 'PNG'); calls.push(slot)
assert.equal(proof.loadingDocuments.uploads.at(-1).status, 'PREPARED')
return ok({ slot, fileId: `new-${slot}` })
@@ -39,6 +39,7 @@ for (const initialStage of ['LOADING', 'AFTER_LOADING']) test(`loading documents
assert.equal(method, 'PUT')
if (replays.has(body.idempotencyKey)) return ok(replays.get(body.idempotencyKey))
assert.equal(body.recordVersion, state.recordVersion)
assert.equal(body.uploadKey, new URL(proof.loadingDocuments.uploads.at(-1).path, 'https://unused.test').searchParams.get('idempotencyKey'))
state.photos.push({ slot: path.split('/').at(-1), fileId: body.fileId })
state.recordVersion = String(BigInt(state.recordVersion) + 1n)
state.complete = state.photos.length === 5
@@ -54,7 +55,7 @@ for (const initialStage of ['LOADING', 'AFTER_LOADING']) test(`loading documents
for (const fault of ['checkpoint', 'upload-unknown', 'wrong-slot', 'replay-drift', 'early-departure']) test(`loading document ${fault} stops before loading confirmation`, async () => {
const proof = { marker: 'TEST-DOCS', dispatchNo: 'DP-A' }, calls = []
const state = { dispatchNo: 'DP-A', stage: 'LOADING', recordVersion: '1', complete: false, editable: true, photos: [] }
const state = { dispatchNo: 'DP-A', stage: 'LOADING', reviewRound: '1', recordVersion: '1', complete: false, editable: true, photos: [] }
const context = { async checkpoint() { if (fault === 'checkpoint') throw Error('checkpoint failed') },
assert(_m, label, _r, passed) { assert.ok(passed, label) }, apiSuccess(_m, _l, response) { return response?.body?.data },
async requestMultipart(_method, path) {
@@ -127,7 +128,7 @@ function server(fault = '') {
},
async request(method, path, body, token) {
calls.push({ method, path, body: structuredClone(body), actor: token ? 'PDA' : 'PLATFORM' })
if (method === 'GET' && path.endsWith('/loading-documents')) return ok({ dispatchNo: 'DP-A', stage: 'LOADING', complete: true, photos: LOADING_PHOTO_SLOTS.map((slot, index) => ({ slot, fileId: String(index + 1) })) })
if (method === 'GET' && path.endsWith('/loading-documents')) return ok({ dispatchNo: 'DP-A', stage: 'LOADING', reviewRound: '1', complete: true, photos: LOADING_PHOTO_SLOTS.map((slot, index) => ({ slot, fileId: String(index + 1) })) })
if (method === 'GET' && path.endsWith(`/master-data/vehicles/${VID}`)) return ok({ id: VID, status: 'IN_SERVICE', region: 'OVERSEAS', ownership: 'OWNED' })
if (method === 'GET' && path.includes(`/fleet/drivers/${DRIVER}?`)) return ok({ id: DRIVER, active: true, reviewStatus: 'APPROVED', details: { ownership: 'OWNED' }, qualification: { canDispatch: true, reason: 'ELIGIBLE' } })
if (fault === 'unknown-edit' && method === 'PUT' && path === D) throw new Error('transport result unknown')