From e5af1ba7581052fe552f4ce92c978b166e8ba68c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 3 Oct 2026 19:27:03 +0800 Subject: [PATCH] test(fleet): handle PDFs from direct print requests --- scripts/fleet-test-env-acceptance.mjs | 12 +++++---- scripts/fleet-test-env-acceptance.test.mjs | 29 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/scripts/fleet-test-env-acceptance.mjs b/scripts/fleet-test-env-acceptance.mjs index 30ed00d3a..ba63348d4 100644 --- a/scripts/fleet-test-env-acceptance.mjs +++ b/scripts/fleet-test-env-acceptance.mjs @@ -30,10 +30,11 @@ export function guardFleetWrites(context, checkpoint, { guardFetch = false } = { const scope = new AsyncLocalStorage(), originalFetch = globalThis.fetch const direct = { async requestWithBody(method, path, body, token, headers, request) { const nativeResponse = await originalFetch(request), copy = nativeResponse.clone() - const content = await copy.text() + const bytes = new Uint8Array(await copy.arrayBuffer()), contentType = nativeResponse.headers.get('content-type') || '' + const content = /json|text|xml|html/i.test(contentType) ? new TextDecoder().decode(bytes) : '' let payload = null try { payload = content ? JSON.parse(content) : null } catch { /* Keep bounded diagnostic only. */ } - return { nativeResponse, http: nativeResponse.status, body: payload, raw: payload ? null : content.slice(0, 500), + return { nativeResponse, http: nativeResponse.status, body: payload, bytes, contentType, raw: payload || !content ? null : content.slice(0, 500), requestId: payload?.requestId || nativeResponse.headers.get('x-request-id'), traceId: payload?.traceId || nativeResponse.headers.get('x-trace-id') } } } @@ -65,13 +66,14 @@ export function guardFleetWrites(context, checkpoint, { guardFetch = false } = { try { const response = await scope.run(true, () => original.apply(this, arguments)) const http = response?.http, payload = response?.body, code = id(payload?.code) - const binary = name === 'requestBinary' && response?.bytes instanceof Uint8Array - const pdf = binary && http === 200 && /^application\/pdf(?:\s*;|$)/i.test(response.contentType || '') + const expectsPdf = name === 'requestBinary' || owner === direct && /(?:^|,)\s*application\/pdf(?:\s*[;,]|$)/i.test(headers?.accept || '') + const binary = (name === 'requestBinary' || owner === direct) && response?.bytes instanceof Uint8Array + const pdf = expectsPdf && binary && http === 200 && /^application\/pdf(?:\s*;|$)/i.test(response.contentType || '') && response.bytes.length > 4 && Buffer.from(response.bytes.subarray(0, 5)).toString('ascii') === '%PDF-' const definite = Number.isInteger(http) && http >= 200 && http < 500 && response.requestId && response.traceId && (pdf || payload && typeof payload === 'object' && !Array.isArray(payload) - && (name !== 'requestBinary' && http === 200 && code === '200' || /^[A-Z]\d{4}$/.test(code) && (http === 200 || http >= 400))) + && (!expectsPdf && http === 200 && code === '200' || /^[A-Z]\d{4}$/.test(code) && (http === 200 || http >= 400))) Object.assign(entry, { status: definite ? 'RESPONDED' : 'UNKNOWN_RESULT', http, code, requestId: response?.requestId, traceId: response?.traceId, raw: boundedFleetDiagnostic(response?.raw), responseBody: redactFleetWrite(response?.body), finishedAt: new Date().toISOString() }) diff --git a/scripts/fleet-test-env-acceptance.test.mjs b/scripts/fleet-test-env-acceptance.test.mjs index 0e28c93ba..e7cd4baf8 100644 --- a/scripts/fleet-test-env-acceptance.test.mjs +++ b/scripts/fleet-test-env-acceptance.test.mjs @@ -5,6 +5,7 @@ import { tmpdir } from 'node:os' import { join, dirname, resolve } from 'node:path' import { AcceptanceRun } from './admin-test-env-acceptance.mjs' import { run as runInboundMeasurement } from './admin-test-env-modules/inbound-measurement.mjs' +import { verifyPalletLabels } from './admin-test-env-modules/phase3-loading-finalize.mjs' import { parseFleetArguments, fleetExecutionPlan, runFleetAcceptance, reviewedFullCheckpoint, verifyFleetTerminalState, runFleetHandoverStage, fleetCostShares, prepareFleetCostActors, guardFleetWrites, redactFleetWrite } from './fleet-test-env-acceptance.mjs' for (const response of [ @@ -112,6 +113,34 @@ test('raw JSON prerequisite writes retain headers, original version and bounded } finally { restore(); t.mock.restoreAll() } }) +for (const invalidPdf of [false, true, 'zip']) test(`real loading finalize prints both PDFs through guarded direct fetch; invalid=${invalidPdf}`, async t => { + const saved = [], calls = [], fixture = { destinationType: 'FBA', warehouseId: '1', containerNo: 'CONTAINER', cabinetNo: 'CABINET' } + const manifest = { containerNo: fixture.containerNo, totalCount: 1, groups: [{ destinationWarehouseCode: 'FBA', labels: [{ + containerNo: fixture.containerNo, destinationWarehouseInternalCode: 'F01', palletIndex: 1, palletCode: 'CABINET-F01-001' }] }] } + t.mock.method(globalThis, 'fetch', async (input, init) => { + const req = new Request(input, init); calls.push(req.url) + assert.equal(saved.at(-1).status, 'SENDING') + const headers = { 'x-request-id': 'r', 'x-trace-id': 't' } + return req.url.includes('/prepare?') + ? new Response(JSON.stringify({ code: '200', data: manifest }), { headers: { ...headers, 'content-type': 'application/json' } }) + : new Response(invalidPdf === true ? 'proxy failure' : '%PDF-1.6\nfixture', + { headers: { ...headers, 'content-type': invalidPdf === 'zip' ? 'application/zip' : 'application/pdf' } }) + }) + const ctx = new AcceptanceRun({ gateway: 'https://test.invalid' }) + const restore = guardFleetWrites(ctx, entry => saved.push(redactFleetWrite(structuredClone(entry))), { guardFetch: true }) + try { + await verifyPalletLabels(ctx, fixture, '2') + const prints = saved.filter(row => row.path.includes('/print?')) + assert.equal(calls.filter(url => url.includes('/print?')).length, invalidPdf ? 1 : 2) + assert.equal(prints.at(-1).status, invalidPdf ? 'UNKNOWN_RESULT' : 'RESPONDED') + assert.equal(prints.at(-1).binary.validPdf, !invalidPdf) + assert.equal(prints.at(-1).raw, null, 'binary response bytes must not enter text evidence') + assert.equal(prints[0].body.destinationWarehouseCode, null) + if (!invalidPdf) { assert.equal(prints[2].body.destinationWarehouseCode, 'FBA'); assert.ok(ctx.steps.every(row => row.passed)) } + else await assert.rejects(ctx.request('POST', '/no-retry'), /Unknown write outcome/) + } finally { restore(); t.mock.restoreAll() } +}) + for (const override of [{ http: 0 }, { http: 503 }, { bytes: Buffer.from('proxy') }, { bytes: new Uint8Array() }, { contentType: 'text/html' }, { traceId: null }, { contentType: 'application/json', bytes: Buffer.from('{"code":"200"}'), body: { code: '200' } }])