From d2fb3c8431047816e20354e61e6e937420c6ddba Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Sat, 16 May 2026 14:18:21 +0200 Subject: [PATCH] fix: Google Drive picks of native Docs fail with "appears to be empty" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs in handleGoogleDrive vs. the working handleDropbox pattern: 1. responseType: 'blob' is not a valid axios responseType in Node. For text/html responses (the new native-Doc export path), axios coerced the body to a string and the downstream parser saw garbage. 2. buffer: contents.data forwarded whatever axios returned (string, raw ArrayBuffer, etc). Multer's downstream file shape needs a Node Buffer. 3. size: file.sizeBytes — the picker reports sizeBytes: 0 for native Google Docs because Docs have no fixed byte size. getUploadValidationError rejects size === 0 as "appears to be empty" before the parser ever reads the body. Fix mirrors handleDropbox.ts exactly: responseType: 'arraybuffer', buffer = Buffer.from(contents.data), size = buffer.length. Two regression tests assert (a) zero-sizeBytes native Docs produce a non-zero size on the downstream file, and (b) responseType: 'arraybuffer' is in the request config so HTML/CSV bodies aren't string-coerced. No changelog entry — the existing "Google Docs, Sheets, and Slides from your Drive turn straight into decks" line from #2309 becomes accurate once this lands. A duplicate fix entry on the same day would read as confused noise. Co-Authored-By: Claude Opus 4.7 --- .../Upload/helpers/handleGoogleDrive.test.ts | 29 +++++++++++++++++++ .../Upload/helpers/handleGoogleDrive.ts | 9 +++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/controllers/Upload/helpers/handleGoogleDrive.test.ts b/src/controllers/Upload/helpers/handleGoogleDrive.test.ts index 1388ae281..f92902cd3 100644 --- a/src/controllers/Upload/helpers/handleGoogleDrive.test.ts +++ b/src/controllers/Upload/helpers/handleGoogleDrive.test.ts @@ -181,6 +181,35 @@ describe('handleGoogleDrive — native Google Apps mime types', () => { expect(reqFiles[0].originalname).toMatch(/\.pdf$/); }); + it('derives size from the downloaded buffer, not the picker-reported sizeBytes', async () => { + const zeroSizeDoc = { ...baseDocFile, sizeBytes: 0 }; + const req = makeReq([zeroSizeDoc]); + const res = makeRes(); + const handleUpload = jest.fn(); + mockedAxios.get.mockResolvedValue({ + data: Buffer.from('

hello

'), + } as never); + await handleGoogleDrive(req, res as unknown as express.Response, handleUpload); + const reqFiles = (req as unknown as { + files: { size: number; buffer: Buffer }[]; + }).files; + expect(reqFiles[0].size).toBeGreaterThan(0); + expect(Buffer.isBuffer(reqFiles[0].buffer)).toBe(true); + expect(handleUpload).toHaveBeenCalled(); + }); + + it('requests arraybuffer responseType so bodies are not coerced to strings', async () => { + const req = makeReq([baseDocFile]); + const res = makeRes(); + const handleUpload = jest.fn(); + await handleGoogleDrive(req, res as unknown as express.Response, handleUpload); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'google_drive', + expect.any(String), + expect.objectContaining({ responseType: 'arraybuffer' }) + ); + }); + it('returns 400 and does not call handleUpload when googleDriveAuth is missing', async () => { const req = makeReq([basePdfFile], undefined); (req.body as Record).googleDriveAuth = undefined; diff --git a/src/controllers/Upload/helpers/handleGoogleDrive.ts b/src/controllers/Upload/helpers/handleGoogleDrive.ts index c49fb5229..30b72697d 100644 --- a/src/controllers/Upload/helpers/handleGoogleDrive.ts +++ b/src/controllers/Upload/helpers/handleGoogleDrive.ts @@ -75,20 +75,21 @@ export async function handleGoogleDrive( req.files = await Promise.all( files.map(async (file) => { const { url, originalname } = resolveUrlAndName(file); - const contents = await instrumentedAxios.get( + const contents = await instrumentedAxios.get( 'google_drive', url, { headers: { Authorization: `Bearer ${googleDriveAuth}`, }, - responseType: 'blob', + responseType: 'arraybuffer', } ); + const buffer = Buffer.from(contents.data); return { originalname, - size: file.sizeBytes, - buffer: contents.data, + size: buffer.length, + buffer, }; }) );