import { createHash } from 'node:crypto'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { NextRequest } from 'next/server'; const hoisted = vi.hoisted(() => ({ requireAuthContext: vi.fn(), registerUploadedDocument: vi.fn(), convertDocxBufferToPdfBuffer: vi.fn(), headTempDocumentBlob: vi.fn(), getTempDocumentBlob: vi.fn(), getTempDocumentFinalizeReceipt: vi.fn(), putTempDocumentFinalizeReceipt: vi.fn(), deleteTempDocumentUpload: vi.fn(), headDocumentBlob: vi.fn(), copyTempDocumentBlobToDocument: vi.fn(), putDocumentBlob: vi.fn(), })); vi.mock('@/lib/server/auth/auth', () => ({ requireAuthContext: hoisted.requireAuthContext, })); vi.mock('@/lib/server/documents/register-upload', () => ({ registerUploadedDocument: hoisted.registerUploadedDocument, })); vi.mock('@/lib/server/documents/blob-lease', () => ({ withDocumentBlobLease: vi.fn(async (_documentId: string, fn: () => Promise) => fn()), })); vi.mock('@/lib/server/documents/docx-convert', () => ({ convertDocxBufferToPdfBuffer: hoisted.convertDocxBufferToPdfBuffer, })); vi.mock('@/lib/server/documents/blobstore', () => ({ TEMP_DOCUMENT_UPLOAD_TTL_MS: 24 * 60 * 60 * 1000, copyTempDocumentBlobToDocument: hoisted.copyTempDocumentBlobToDocument, deleteTempDocumentUpload: hoisted.deleteTempDocumentUpload, getTempDocumentBlob: hoisted.getTempDocumentBlob, getTempDocumentFinalizeReceipt: hoisted.getTempDocumentFinalizeReceipt, headDocumentBlob: hoisted.headDocumentBlob, headTempDocumentBlob: hoisted.headTempDocumentBlob, isMissingBlobError: vi.fn((error: unknown) => { const maybe = error as { code?: string } | undefined; return maybe?.code === 'NoSuchKey'; }), isPreconditionFailed: vi.fn(() => false), isValidTempUploadToken: vi.fn(() => true), putDocumentBlob: hoisted.putDocumentBlob, putTempDocumentFinalizeReceipt: hoisted.putTempDocumentFinalizeReceipt, })); vi.mock('@/lib/server/testing/test-namespace', () => ({ getOpenReaderTestNamespace: vi.fn(() => null), })); vi.mock('@/lib/server/storage/s3', () => ({ isS3Configured: vi.fn(() => true), })); vi.mock('@/lib/server/logger', () => ({ errorToLog: vi.fn((error: unknown) => error), serverLogger: { warn: vi.fn(), error: vi.fn(), }, })); describe('POST /api/documents/blob/upload/finalize DOCX flow', () => { beforeEach(() => { hoisted.requireAuthContext.mockReset(); hoisted.registerUploadedDocument.mockReset(); hoisted.convertDocxBufferToPdfBuffer.mockReset(); hoisted.headTempDocumentBlob.mockReset(); hoisted.getTempDocumentBlob.mockReset(); hoisted.getTempDocumentFinalizeReceipt.mockReset(); hoisted.putTempDocumentFinalizeReceipt.mockReset(); hoisted.deleteTempDocumentUpload.mockReset(); hoisted.headDocumentBlob.mockReset(); hoisted.copyTempDocumentBlobToDocument.mockReset(); hoisted.putDocumentBlob.mockReset(); hoisted.requireAuthContext.mockResolvedValue({ userId: 'user-1' }); hoisted.getTempDocumentFinalizeReceipt.mockResolvedValue(null); hoisted.headTempDocumentBlob.mockResolvedValue({ contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', contentLength: 12, lastModified: Date.now(), }); hoisted.getTempDocumentBlob.mockResolvedValue(Buffer.from('docx-bytes')); hoisted.convertDocxBufferToPdfBuffer.mockResolvedValue(Buffer.from('pdf-bytes')); hoisted.headDocumentBlob .mockRejectedValueOnce({ code: 'NoSuchKey' }) .mockResolvedValue({ contentLength: Buffer.byteLength('pdf-bytes'), contentType: 'application/pdf', eTag: 'etag-1', }); hoisted.registerUploadedDocument.mockImplementation(async (input: { documentId: string; name: string; type: string; size: number; lastModified: number }) => ({ id: input.documentId, name: input.name, type: input.type, size: input.size, lastModified: input.lastModified, scope: 'user', })); hoisted.putTempDocumentFinalizeReceipt.mockResolvedValue(undefined); hoisted.deleteTempDocumentUpload.mockResolvedValue(undefined); }); test('converts raw DOCX during finalize and registers a PDF', async () => { const { POST } = await import('../../src/app/api/documents/blob/upload/finalize/route'); const request = new NextRequest('http://localhost/api/documents/blob/upload/finalize', { method: 'POST', body: JSON.stringify({ uploads: [{ token: '123e4567-e89b-12d3-a456-426614174000', name: 'Report.docx', type: 'docx', lastModified: 1700000000000, }], }), headers: { 'Content-Type': 'application/json', }, }); const response = await POST(request); const expectedId = createHash('sha256').update(Buffer.from('pdf-bytes')).digest('hex'); expect(response.status).toBe(200); await expect(response.json()).resolves.toMatchObject({ stored: [{ id: expectedId, name: 'Report.pdf', type: 'pdf', }], }); expect(hoisted.convertDocxBufferToPdfBuffer).toHaveBeenCalledTimes(1); expect(hoisted.putDocumentBlob).toHaveBeenCalledWith( expectedId, Buffer.from('pdf-bytes'), 'application/pdf', null, { ifNoneMatch: true }, ); expect(hoisted.copyTempDocumentBlobToDocument).not.toHaveBeenCalled(); expect(hoisted.registerUploadedDocument).toHaveBeenCalledWith(expect.objectContaining({ documentId: expectedId, name: 'Report.pdf', type: 'pdf', })); }); });