import { beforeEach, describe, expect, it, vi } from 'vitest'; // Documenso 2.13's v1-compat `GET /api/v1/documents/{id}/download` returns // JSON `{ downloadUrl }` (a presigned S3 URL), NOT raw PDF bytes. The CRM was // saving that ~500-byte JSON as the "signed PDF" → corrupt file emailed to every // signer + filed in the CRM. These tests pin the two-step follow behaviour. vi.mock('@/lib/fetch-with-timeout', () => ({ fetchWithTimeout: vi.fn(), FetchTimeoutError: class FetchTimeoutError extends Error { timeoutMs = 0; }, })); vi.mock('@/lib/services/port-config', () => ({ getPortDocumensoConfig: vi.fn().mockResolvedValue({ apiUrl: 'https://sig.example.com', apiKey: 'k', apiVersion: 'v1', }), })); vi.mock('@/lib/logger', () => ({ logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() }, })); import { downloadSignedPdf } from '@/lib/services/documenso-client'; import { fetchWithTimeout } from '@/lib/fetch-with-timeout'; const mockFetch = vi.mocked(fetchWithTimeout); function jsonRes(obj: unknown) { const bytes = Buffer.from(JSON.stringify(obj)); return { ok: true, status: 200, arrayBuffer: async () => bytes, text: async () => JSON.stringify(obj), headers: { get: () => 'application/json' }, } as unknown as Response; } function pdfRes(text: string) { const bytes = Buffer.from(text); return { ok: true, status: 200, arrayBuffer: async () => bytes, headers: { get: () => 'application/pdf' }, } as unknown as Response; } describe('downloadSignedPdf (v1) — Documenso 2.13 JSON downloadUrl', () => { beforeEach(() => mockFetch.mockReset()); it('follows the JSON { downloadUrl } and returns the real signed PDF bytes', async () => { mockFetch .mockResolvedValueOnce(jsonRes({ downloadUrl: 'https://s3.example/signed.pdf?sig=1' })) .mockResolvedValueOnce(pdfRes('%PDF-1.7\nreal signed content')); const buf = await downloadSignedPdf('117', 'port-1'); expect(buf.subarray(0, 5).toString('latin1')).toBe('%PDF-'); expect(buf.toString()).toContain('real signed content'); expect(mockFetch).toHaveBeenCalledTimes(2); expect(mockFetch.mock.calls[1]![0]).toBe('https://s3.example/signed.pdf?sig=1'); }); it('returns raw PDF directly when the endpoint already serves PDF bytes (older v1)', async () => { mockFetch.mockResolvedValueOnce(pdfRes('%PDF-1.7 direct bytes')); const buf = await downloadSignedPdf('118', 'port-1'); expect(buf.toString()).toContain('direct bytes'); expect(mockFetch).toHaveBeenCalledTimes(1); }); it('throws when the body is neither a PDF nor a downloadUrl JSON', async () => { mockFetch.mockResolvedValueOnce(jsonRes({ nope: true })); await expect(downloadSignedPdf('119', 'port-1')).rejects.toThrow(); }); });