import test from 'node:test' import assert from 'node:assert/strict' import { prepareFleetTrip, confirmFleetLoading, controlledPalletMeasurements, finishFleetTrip } from './fleet-trip-fulfillment.mjs' const D = '/api/platform/v1/last-leg/dispatches/DP-A' const P = '/api/pda/v1/last-leg/dispatches/DP-A' const F = '/api/platform/v1/last-leg/fleet/trips/TR-A' const stops = [{ name: '真实起运仓' }, { name: 'ONT8', destinationKey: 'FBA:ONT8' }] const VID = '9007199254740993', DRIVER = '9007199254740995', PALLET = '9007199254740997' const ok = data => ({ http: 200, body: { code: '200', data: structuredClone(data) } }) const conflict = message => ({ http: 200, body: { code: 'A0409', message } }) const evidence = (field, value) => ({ field, value, actual: true, sourceType: 'TEST_PRODUCER_MEASUREMENT', sourceBusinessNo: 'RECEIPT-A', observedAt: '2026-10-18T10:00:00Z' }) const pallet = () => ({ warehousePalletId: PALLET, palletNo: 'PLT-A', scheduledPackages: 3, pickedPackages: 3, palletSpaces: null, weightKg: '187.25', volumeCbm: '1.375', deliveryIndex: 1, missingFields: ['palletSpaces'], evidence: [evidence('weightKg', '187.25'), evidence('volumeCbm', '1.375')] }) const receipt = () => ({ environment: 'TEST', marker: 'TEST-NATIVE', dispatchNo: 'DP-A', pallets: [ { warehousePalletId: PALLET, palletSpaces: 2, deliveryIndex: 1, evidence: [evidence('palletSpaces', 2)] }, ] }) test('real Jackson long counts accept safe decimal strings without changing pallet IDs', () => { const expected = controlledPalletMeasurements([pallet()], stops, receipt().pallets) for (const counts of [{ scheduledPackages: '3', pickedPackages: '3' }, { scheduledPackages: '3', pickedPackages: 3 }]) { const actual = controlledPalletMeasurements([{ ...pallet(), ...counts }], stops, receipt().pallets) assert.deepEqual(actual, expected) assert.equal(actual[0].warehousePalletId, PALLET) } for (const value of [null, undefined, '', '03', '3.0', '3e0', true, -1, '9007199254740993', 9007199254740992]) { assert.throws(() => controlledPalletMeasurements([{ ...pallet(), scheduledPackages: value }], stops, receipt().pallets), /箱数/) assert.throws(() => controlledPalletMeasurements([{ ...pallet(), pickedPackages: value }], stops, receipt().pallets), /箱数/) } assert.throws(() => controlledPalletMeasurements([{ ...pallet(), pickedPackages: '2' }], stops, receipt().pallets), /核平/) }) function server(fault = '') { const calls = [], assertions = [], saved = new Map(), loadingActors = new Map() const detail = { dispatchNo: 'DP-A', status: 'ACTIVE', stage: 'BEFORE_LOADING', reviewState: 'DRAFT', recordVersion: '9007199254740998', counts: { packageCount: 3, pickedPackageCount: 0, shippedPackageCount: 0 }, reviews: {}, dispatchInfo: { estimatedArrivalAt: '2026-10-21T10:00:00Z', fbaAppointments: [{ appointmentNo: 'FBA-ORIGINAL' }] }, vehicleInfo: {} } const trip = { tripNo: 'TR-A', status: 'PLANNED', warehouseId: '92', recordVersion: 4, tripPurpose: 'DELIVERY', stops } let driverVersion = 0, uploadComplete = false, driverReviewed = false, nextPlate = '', checkpointError = false, nextTrip, nextDetail let mutatedItems = false, mutatedLoading = false const claims = new Map([['VEHICLE', 'TR-A'], ['DRIVER', 'TR-A']]) const bump = () => detail.recordVersion = String(BigInt(detail.recordVersion) + 1n) const result = (action, body, extra = {}) => ({ dispatchNo: 'DP-A', status: detail.status, stage: detail.stage, recordVersion: detail.recordVersion, action, idempotencyKey: body.idempotencyKey, result: extra, replayed: false, counts: structuredClone(detail.counts) }) const remember = (action, body, extra) => { const r = result(action, body, extra); saved.set(body.idempotencyKey, { body: structuredClone(body), result: structuredClone(r) }); return ok(r) } const context = { marker: 'TEST-NATIVE', createdResources: {}, checkpoint: async () => { if (checkpointError) throw new Error('checkpoint failed') }, assert(_module, label, _response, passed) { assertions.push({ label, passed }) }, apiSuccess(_module, _label, response) { return response?.body?.code === '200' ? response.body.data : null }, async requestMultipart(method, path, file) { calls.push({ method, path, file: { name: file.name, size: file.content.byteLength } }) assert.equal(file.type, 'image/png'); assert.match(file.name, /TEST-LICENSE/) uploadComplete = true; driverVersion++ return ok({ id: DRIVER, licenseFileId: '9007199254740999', recordVersion: driverVersion }) }, async request(method, path, body, token) { calls.push({ method, path, body: structuredClone(body), actor: token ? 'PDA' : 'PLATFORM' }) if (fault === 'unknown-edit' && method === 'PUT' && path === D) throw new Error('transport result unknown') if (path.endsWith('/master-data/vehicles')) { assert.equal(body.status, 'IN_SERVICE'); assert.match(body.remark, /TEST/); nextPlate = body.plateNumber.toUpperCase() return ok({ id: VID }) } if (path.endsWith('/fleet/drivers')) return ok({ id: DRIVER, recordVersion: 0 }) if (path.endsWith(`/drivers/${DRIVER}/review`)) { assert.ok(uploadComplete); assert.equal(body.recordVersion, driverVersion); driverReviewed = true return ok({ id: DRIVER, reviewStatus: 'APPROVED', recordVersion: ++driverVersion }) } if (path.startsWith('/api/platform/v1/last-leg/fleet/trips?')) { const rows = fault === 'missing-trip' ? [] : [trip, ...(nextTrip ? [nextTrip] : [])] if (fault === 'duplicate-trip') rows.push({ ...trip, tripNo: 'TR-B' }) const wrong = { 'wrong-plate': { vehiclePlate: `${trip.vehiclePlate}-OTHER` }, 'wrong-vehicle': { vehicleId: 'OTHER-VEHICLE' }, 'wrong-driver': { driverId: 'OTHER-DRIVER' }, 'wrong-warehouse': { warehouseId: 'OTHER-WAREHOUSE' } }[fault] if (wrong) rows[0] = { ...rows[0], ...wrong } return ok({ records: rows, page: 1, size: 200, total: rows.length, pages: 1 }) } if (method === 'GET' && path === D) return ok(detail) if (method === 'GET' && path === `${D.replace('DP-A', 'DP-NEXT')}`) return ok(nextDetail) if (method === 'GET' && path === `${D.replace('DP-A', 'DP-NEXT')}/items`) return ok({ dispatchNo: 'DP-NEXT', recordVersion: nextDetail.recordVersion, items: [] }) if (method === 'POST' && path === D.replace('/DP-A', '')) { assert.deepEqual(body.cargo, []); assert.deepEqual(body.batchAllocations, []) assert.equal(body.vehicle.vehicleId, VID); assert.equal(body.driver.driverId, DRIVER) assert.equal(claims.size, 0) if (fault === 'unknown-next-create') throw new Error('next create unknown') nextTrip = { ...trip, tripNo: 'TR-NEXT', status: 'PLANNED', recordVersion: 0, vehicleRelease: null, driverRelease: null } nextDetail = { dispatchNo: 'DP-NEXT', status: 'ACTIVE', stage: 'BEFORE_LOADING', recordVersion: 0, counts: { itemCount: 0, palletCount: 0, packageCount: 0, pickedPackageCount: 0, shippedPackageCount: 0 } } if (fault === 'new-empty-has-cargo') nextDetail.counts.itemCount = 1 claims.set('VEHICLE', 'TR-NEXT'); claims.set('DRIVER', 'TR-NEXT') return ok({ ...nextDetail, result: { dispatchNo: 'DP-NEXT' } }) } if (method === 'POST' && path === `${D.replace('DP-A', 'DP-NEXT')}/cancel`) { assert.equal(body.recordVersion, nextDetail.recordVersion) nextDetail.status = 'CANCELLED'; nextDetail.recordVersion++; claims.clear() return ok(nextDetail) } if (method === 'GET' && path === `${D}/items`) return ok({ dispatchNo: 'DP-A', recordVersion: detail.recordVersion, items: [{ warehousePalletId: PALLET, pickedPackages: detail.counts.pickedPackageCount, customerId: mutatedItems ? 'OTHER-CUSTOMER' : 'CUSTOMER-A' }] }) if (method === 'GET' && [D, P].some(prefix => path === `${prefix}/loading`)) { return ok({ dispatchNo: 'DP-A', status: detail.status, stage: detail.stage, recordVersion: detail.recordVersion, pickingReconciled: detail.counts.pickedPackageCount === 3, manifestHash: mutatedLoading ? 'CHANGED-MANIFEST' : 'REAL-MANIFEST', loadingComplete: { allowed: detail.counts.pickedPackageCount === 3, blockReason: '拣货未核平' }, pallets: [pallet()], missingFields: ['PLT-A:palletSpaces'] }) } if (method === 'PUT' && path === D) { if (detail.stage !== 'BEFORE_LOADING') { if (fault === 'edit-mutates-on-rejection') detail.counts.pickedPackageCount-- return conflict('仅装车前可编辑,请显式退回;已经扫码不得改派') } assert.ok(driverReviewed); assert.equal(body.recordVersion, detail.recordVersion) assert.equal(body.vehicle.mode, 'MASTER'); assert.equal(body.vehicle.vehicleId, VID) assert.equal(body.driver.driverId, DRIVER); assert.ok(body.driver.evidence.expectedTaskEndAt) detail.vehicleInfo = { vehicle: { mode: 'MASTER', vehicleId: VID, plate: nextPlate, carrierName: 'TEST自有车', capacity: { palletSpaces: 30, maxLoadKg: 18000, maxVolumeCbm: 90 } }, driver: { mode: 'MASTER', driverId: DRIVER, name: 'TEST司机', phone: '19900000000' } } Object.assign(trip, { vehicleId: VID, vehiclePlate: nextPlate, driverId: DRIVER }) bump(); return remember('EDIT', body) } if (method === 'POST' && path === `${D}/return-to-review`) { assert.equal(detail.counts.pickedPackageCount, 0); assert.equal(body.recordVersion, detail.recordVersion) detail.stage = 'BEFORE_LOADING'; detail.reviewState = 'DRAFT'; bump(); return remember('RETURN_TO_REVIEW', body) } if (method === 'POST' && ['submit-review', 'dispatch-review', 'loading-review'].some(action => path === `${D}/${action}`)) { assert.equal(body.recordVersion, detail.recordVersion); assert.match(body.idempotencyKey, /^[a-f0-9]{64}$/) if (path.endsWith('/submit-review')) { detail.reviewState = 'DISPATCH_PENDING'; detail.reviews.dispatchReview = { status: 'PENDING' } } else if (path.endsWith('/dispatch-review')) { assert.equal(detail.reviewState, 'DISPATCH_PENDING'); detail.reviewState = 'LOADING_PENDING'; detail.reviews.dispatchReview.status = 'APPROVED' } else { assert.equal(detail.reviewState, 'LOADING_PENDING'); detail.reviewState = 'APPROVED'; detail.stage = 'LOADING'; detail.reviews.loadingReview = { status: 'APPROVED' } } bump(); return remember('REVIEW', body) } if (method === 'POST' && [D, P].some(prefix => path === `${prefix}/loading-complete`)) { if (saved.has(body.idempotencyKey)) { assert.deepEqual(body, saved.get(body.idempotencyKey).body) if (loadingActors.get(body.idempotencyKey) !== (token ? 'PDA' : 'PLATFORM')) { if (fault === 'cross-actor-unknown') throw new Error('cross actor result unknown') if (fault === 'cross-actor-mutates-main') bump() if (fault === 'cross-actor-mutates-items') mutatedItems = true if (fault === 'cross-actor-mutates-loading') mutatedLoading = true if (fault !== 'cross-actor-accepted') return { http: 200, body: { code: 'A0403', message: fault === 'cross-actor-generic-forbidden' ? '没有装车权限' : '该请求标识不属于当前操作员' } } } const replay = { ...saved.get(body.idempotencyKey).result, replayed: true } if (fault === 'replay-new-version') replay.recordVersion = String(BigInt(replay.recordVersion) + 1n) return ok(replay) } assert.equal(detail.stage, 'LOADING'); assert.equal(detail.counts.pickedPackageCount, 3) assert.equal(body.recordVersion, detail.recordVersion); assert.equal(body.manifestHash, 'REAL-MANIFEST') assert.deepEqual(body.measurements, [{ warehousePalletId: PALLET, palletSpaces: 2, weightKg: '187.25', volumeCbm: '1.375', deliveryIndex: 1 }]) loadingActors.set(body.idempotencyKey, token ? 'PDA' : 'PLATFORM') detail.stage = 'DELIVERING'; bump(); return remember('LOADING_COMPLETE', body, { confirmedLoad: { manifestHash: body.manifestHash, pallets: body.measurements } }) } if (method === 'GET' && path.startsWith(`${F}/resource-occupancy?`)) { const query = new URL(`https://unused.test${path}`).searchParams, kind = query.get('kind') return ok([{ resourceId: kind === 'VEHICLE' ? VID : DRIVER, occupiedTripNo: claims.get(kind) ?? null, available: true }]) } if (method === 'GET' && path === `${F}/completion`) return ok({ tripNo: 'TR-A', recordVersion: trip.recordVersion, canComplete: fault !== 'incomplete', outcomes: [{ deliveryNo: 'DL-A', packages: 3 }] }) if (method === 'GET' && path === F) return ok({ trip, dispatches: fault === 'wrong-binding' ? [{ dispatchNo: 'DP-OTHER' }] : [{ dispatchNo: 'DP-A' }] }) if (method === 'GET' && path === F.replace('TR-A', 'TR-B')) return ok({ trip: { ...trip, tripNo: 'TR-B' }, dispatches: [{ dispatchNo: 'DP-A' }] }) if (method === 'GET' && path === F.replace('TR-A', 'TR-NEXT')) return ok({ trip: nextTrip, dispatches: [{ dispatchNo: 'DP-NEXT' }] }) if (method === 'POST' && [ `${F}/complete`, `${F}/resources/release` ].includes(path)) { if (saved.has(body.idempotencyKey)) { assert.deepEqual(body, saved.get(body.idempotencyKey)) if (fault === 'old-release-clears-new-claim' && nextTrip && path.endsWith('/resources/release')) claims.delete(body.resourceKind) return ok({ trip }) } assert.equal(body.recordVersion, trip.recordVersion); saved.set(body.idempotencyKey, structuredClone(body)); trip.recordVersion++ if (path.endsWith('/complete')) { trip.status = 'COMPLETED'; trip.completedAt = '2026-10-19T00:00:00Z' if (fault === 'completion-auto-releases') { claims.clear(); trip.vehicleRelease = { actualEndAt: trip.completedAt } } } else { claims.delete(body.resourceKind) trip[body.resourceKind === 'VEHICLE' ? 'vehicleRelease' : 'driverRelease'] = { actualEndAt: body.actualEndAt, endLocation: body.endLocation } if (fault === 'vehicle-frees-driver' && body.resourceKind === 'VEHICLE') claims.delete('DRIVER') } return ok({ trip }) } assert.fail(`retired or unexpected request ${method} ${path}`) }, } return { context, detail, trip, calls, assertions, claims, pick() { detail.counts.pickedPackageCount = 3; bump() }, failCheckpoint() { checkpointError = true } } } const prepare = s => prepareFleetTrip(s.context, 'native', 'DP-A', '92') function jacksonCountWire(s) { const names = new Set(['itemCount', 'palletCount', 'packageCount', 'pickedPackageCount', 'shippedPackageCount', 'scheduledPackages', 'pickedPackages']) const serialize = value => Array.isArray(value) ? value.map(serialize) : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).map(([name, nested]) => [name, names.has(name) && typeof nested === 'number' ? String(nested) : serialize(nested)])) : value const original = s.context.request s.context.request = async (...args) => serialize(await original(...args)) return s } test('real string-zero counts permit preparation, PDA loading replay and safe empty-dispatch closure', async () => { const s = jacksonCountWire(server()), proof = await prepare(s) assert.equal(proof.preparation, 'VERIFIED') s.pick() await confirmFleetLoading(s.context, 'native', proof, 'PDA-IN-MEMORY-ONLY', receipt()) s.trip.status = 'IN_TRANSIT' const closure = await finishFleetTrip(s.context, 'native', proof) assert.equal(closure.status, 'CARGO_COMPLETED_AND_RESOURCES_RELEASED') assert.equal(closure.nextDispatch.finalStatus, 'CANCELLED') assert.ok(s.assertions.every(row => row.passed)) }) test('missing or malformed zero counts cannot create resources before preparation', async () => { for (const value of [undefined, null, false, '', '00', '0.0', '0e0', '9007199254740993']) { const s = server(); s.detail.counts.pickedPackageCount = value await assert.rejects(prepare(s), /箱数/) assert.deepEqual(s.calls.map(row => row.method), ['GET']) } }) test('native preparation edits existing master then submits both reviews, discovers the real sole automatic Fleet record', async () => { const s = server(), proof = await prepare(s) assert.equal(proof.tripNo, 'TR-A'); assert.equal(proof.preparation, 'VERIFIED'); assert.equal(proof.vehicleId, VID) assert.equal(proof.vehicle.plate, proof.marker.toUpperCase()); assert.equal(proof.driver.phone, '19900000000') assert.equal(proof.recordVersion, '9007199254741002'); assert.equal(proof.fleetRecordVersion, 4) const writes = s.calls.filter(c => ['POST', 'PUT'].includes(c.method)) assert.deepEqual(writes.slice(-4).map(c => c.path), [D, `${D}/submit-review`, `${D}/dispatch-review`, `${D}/loading-review`]) assert.ok(writes.every(c => !/\/trips(?:$|\/.*\/(?:assignment|plan|bind|review))/.test(c.path))) assert.equal(s.detail.dispatchInfo.fbaAppointments[0].appointmentNo, 'FBA-ORIGINAL') assert.ok(s.assertions.every(a => a.passed)) }) test('zero-scan LOADING must explicitly return to review before editing and pass a new review round', async () => { const s = server(); s.detail.stage = 'LOADING'; s.detail.reviewState = 'APPROVED' const proof = await prepare(s) const returnIndex = s.calls.findIndex(c => c.path.endsWith('/return-to-review')) assert.ok(returnIndex >= 0 && returnIndex < s.calls.findIndex(c => c.method === 'PUT')) assert.equal(proof.returnRequest.recordVersion, '9007199254740998'); assert.equal(proof.preparation, 'VERIFIED') }) test('authoritative uppercase vehicle plate discovers both the prepared and subsequent native dispatch without changing marker', async () => { const s = server(), request = s.context.request, marker = 'FT-1d529b3e-d15' s.context.request = async (...args) => { // 固定本轮随机标识以精确复现真实HTTP大小写回执;首次GET仍在任何车辆写入之前。 if (args[0] === 'GET' && args[1] === D && !s.context.createdResources.fleetTrips[0].vehicleId) s.context.createdResources.fleetTrips[0].marker = marker return request(...args) } const proof = await prepare(s) assert.equal(proof.marker, marker); assert.equal(proof.vehicle.plate, 'FT-1D529B3E-D15') assert.equal(proof.tripNo, 'TR-A') s.trip.status = 'IN_TRANSIT' const closure = await finishFleetTrip(s.context, 'native', proof) assert.equal(closure.nextDispatch.marker, marker); assert.equal(closure.nextDispatch.vehicle, undefined) assert.equal(closure.nextDispatch.tripNo, 'TR-NEXT'); assert.equal(closure.nextDispatch.finalStatus, 'CANCELLED') const queries = s.calls.filter(c => c.path.startsWith('/api/platform/v1/last-leg/fleet/trips?')) assert.equal(queries.length, 2) for (const call of queries) assert.equal(new URL(`https://unused.test${call.path}`).searchParams.get('keyword'), 'FT-1D529B3E-D15') }) for (const fault of ['missing-trip', 'duplicate-trip', 'wrong-binding', 'wrong-plate', 'wrong-vehicle', 'wrong-driver', 'wrong-warehouse']) test(`automatic Fleet identity fails closed: ${fault}`, async () => { const s = server(fault); await assert.rejects(prepare(s), /唯一|绑定/) assert.equal(s.context.createdResources.fleetTrips[0].preparation, 'CHECKING') }) test('started picking cannot be silently prepared, reassigned or returned', async () => { const s = server(); s.detail.stage = 'LOADING'; s.detail.counts.pickedPackageCount = 1 await assert.rejects(prepare(s), /零扫码/) assert.equal(s.calls.length, 1) }) test('unknown write result retains the exact command, does not retry or generate another Fleet record', async () => { const s = server('unknown-edit'); await assert.rejects(prepare(s), /unknown/) const last = s.context.createdResources.fleetTripCommands.at(-1) assert.equal(last.status, 'UNKNOWN'); assert.equal(last.path, D) assert.equal(s.calls.filter(c => c.method === 'PUT').length, 1) }) test('checkpoint failure sends no business write', async () => { const s = server(); s.failCheckpoint(); await assert.rejects(prepare(s), /checkpoint/) assert.deepEqual(s.calls.map(c => c.method), ['GET']) }) test('actual loading uses authoritative decimal measurements and a proven producer pallet-space measurement', () => { assert.deepEqual(controlledPalletMeasurements([pallet()], stops, receipt().pallets), [ { warehousePalletId: PALLET, palletSpaces: 2, weightKg: '187.25', volumeCbm: '1.375', deliveryIndex: 1 }, ]) }) for (const field of ['palletSpaces', 'weightKg', 'volumeCbm']) test(`missing actual ${field} blocks; billing values cannot substitute`, () => { const row = pallet(), r = receipt(); row[field] = null; delete r.pallets[0][field] row.grossWeightKg = 999; row.billingWeight = 999 assert.throws(() => controlledPalletMeasurements([row], stops, r.pallets), /实际测量缺少/) }) test('suggested measurements, duplicate physical pallets and unpicked cargo are rejected', () => { const row = pallet(); row.evidence[0].actual = false assert.throws(() => controlledPalletMeasurements([row], stops, receipt().pallets), /真实来源/) assert.throws(() => controlledPalletMeasurements([pallet(), pallet()], stops, receipt().pallets), /重复/) assert.throws(() => controlledPalletMeasurements([{ ...pallet(), pickedPackages: 2 }], stops, receipt().pallets), /核平/) assert.throws(() => controlledPalletMeasurements([pallet()], [{ name: 'Origin' }], receipt().pallets), /卸货站/) }) test('extra or inconsistent producer measurements cannot alter another pallet or authoritative actual weight', () => { const r = receipt(); r.pallets[0].weightKg = 100 assert.throws(() => controlledPalletMeasurements([pallet()], stops, r.pallets), /不一致/) assert.throws(() => controlledPalletMeasurements([pallet()], stops, [...receipt().pallets, { warehousePalletId: 'OTHER' }]), /本车以外/) }) test('PDA original-actor replay restores loading while another platform actor is rejected without mutation', async () => { const s = server(), proof = await prepare(s); s.pick() const beforeCount = s.detail.counts.pickedPackageCount const v = await confirmFleetLoading(s.context, 'native', proof, 'PDA-IN-MEMORY-ONLY', receipt()) assert.equal(v, '9007199254741004'); assert.equal(proof.reassignment.status, 'REJECTED_WITHOUT_MUTATION') assert.equal(s.detail.counts.pickedPackageCount, beforeCount); assert.equal(s.detail.stage, 'DELIVERING') const writes = s.calls.filter(c => c.path.endsWith('/loading-complete')) assert.equal(writes.length, 3) assert.deepEqual(writes.map(c => c.body), [writes[0].body, writes[0].body, writes[0].body]) assert.deepEqual(writes.map(c => [c.path, c.actor]), [[`${P}/loading-complete`, 'PDA'], [`${P}/loading-complete`, 'PDA'], [`${D}/loading-complete`, 'PLATFORM']]) assert.equal(proof.loadingCommand.replay.replayed, true); assert.equal(proof.loadingCommand.platformRejection.code, 'A0403') assert.equal(proof.loadingCommand.platformPositiveLoading, 'PENDING_SEPARATE_SCENARIO') assert.deepEqual(proof.loadingCommand.beforeReplay, proof.loadingCommand.afterReplay) assert.equal(proof.loadingCommand.platformReplay, undefined) assert.equal(writes[0].body.measurements[0].weightKg, '187.25') assert.ok(s.calls.every(c => !/\/fleet\/trips\/.*\/(?:assignment|loading-review|loading)$/.test(c.path))) const count = s.calls.length await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', receipt()), /已开始/) assert.equal(s.calls.length, count) }) test('platform positive loading remains executable in its own scenario with original-actor replay', async () => { const s = server(), proof = await prepare(s); s.pick() await confirmFleetLoading(s.context, 'native', proof, undefined, receipt()) const writes = s.calls.filter(c => c.path.endsWith('/loading-complete')) assert.deepEqual(writes.map(c => [c.path, c.actor]), [[`${D}/loading-complete`, 'PLATFORM'], [`${D}/loading-complete`, 'PLATFORM']]) assert.equal(proof.loadingCommand.replay.replayed, true) assert.equal(proof.loadingCommand.platformPositiveLoading, 'VERIFIED'); assert.equal(proof.loadingCommand.platformRejection, undefined) assert.equal(s.detail.stage, 'DELIVERING') }) for (const fault of ['cross-actor-accepted', 'cross-actor-generic-forbidden', 'cross-actor-mutates-main', 'cross-actor-mutates-items', 'cross-actor-mutates-loading', 'cross-actor-unknown']) test(`cross-actor loading cannot claim isolation: ${fault}`, async () => { const s = server(fault), proof = await prepare(s); s.pick() await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', receipt()), /失败|unknown/) assert.equal(proof.loadingCommand.platformPositiveLoading, undefined) assert.equal(s.calls.filter(c => c.path.endsWith('/loading-complete')).length, 3) if (fault === 'cross-actor-unknown') assert.equal(s.context.createdResources.fleetTripCommands.at(-1).status, 'UNKNOWN') const count = s.calls.length await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', receipt()), /已开始/) assert.equal(s.calls.length, count) }) for (const fault of ['edit-mutates-on-rejection', 'replay-new-version']) test(`loading verification rejects native drift: ${fault}`, async () => { const s = server(fault), proof = await prepare(s); s.pick() await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', receipt()), /失败/) if (fault === 'edit-mutates-on-rejection') assert.equal(s.calls.some(c => c.path.endsWith('/loading-complete')), false) }) test('missing actual producer measurement blocks before any edit rejection or loading write', async () => { const s = server(), proof = await prepare(s); s.pick(); const count = s.calls.length await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA'), /palletSpaces/) assert.equal(s.calls.length, count + 1); assert.equal(s.calls.at(-1).method, 'GET') }) test('wrong-run producer receipt and unreconciled picking block loading without mutation', async () => { const s = server(), proof = await prepare(s) await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', receipt()), /未就绪/) s.pick(); await assert.rejects(confirmFleetLoading(s.context, 'native', proof, 'PDA', { ...receipt(), marker: 'OTHER-RUN' }), /不属于本轮/) assert.equal(s.calls.some(c => c.path.endsWith('/loading-complete')), false) }) test('closure keeps cargo completion, vehicle ending and driver ending distinct using real occupancy owners', async () => { const s = server(), proof = await prepare(s); s.trip.status = 'IN_TRANSIT' const state = await finishFleetTrip(s.context, 'native', proof) assert.equal(state.status, 'CARGO_COMPLETED_AND_RESOURCES_RELEASED'); assert.equal(s.claims.size, 0) assert.equal(state.subsequentTripClaimProtection, 'VERIFIED_NATIVE_EMPTY_DISPATCH') assert.equal(state.nextDispatch.tripNo, 'TR-NEXT'); assert.equal(state.nextDispatch.finalStatus, 'CANCELLED') assert.deepEqual(state.occupancy.map(c => [c.kind, c.rows[0].occupiedTripNo]), [ ['VEHICLE', 'TR-A'], ['DRIVER', 'TR-A'], ['VEHICLE', null], ['DRIVER', 'TR-A'], ['VEHICLE', null], ['DRIVER', null], ['VEHICLE', 'TR-NEXT'], ['DRIVER', 'TR-NEXT'], ['VEHICLE', 'TR-NEXT'], ['DRIVER', 'TR-NEXT'], ['VEHICLE', null], ['DRIVER', null], ]) assert.equal(s.calls.filter(c => c.path.endsWith('/resources/release')).length, 4) assert.ok(s.calls.every(c => !(c.method === 'POST' && /\/fleet\/trips$|\/assignment$/.test(c.path)))) await assert.rejects(finishFleetTrip(s.context, 'native', proof), /已开始/) }) for (const fault of ['incomplete', 'completion-auto-releases', 'vehicle-frees-driver', 'old-release-clears-new-claim', 'new-empty-has-cargo', 'unknown-next-create']) test(`closure does not claim success on independent-resource failure: ${fault}`, async () => { const s = server(fault), proof = await prepare(s); s.trip.status = 'IN_TRANSIT' await assert.rejects(finishFleetTrip(s.context, 'native', proof), /失败|unknown/) assert.equal(proof.closure.status, 'CHECKING') if (['new-empty-has-cargo', 'unknown-next-create'].includes(fault)) assert.equal(s.calls.some(c => c.path.endsWith('/DP-NEXT/cancel')), false) })