/** * Integration test: GET /api/v1/documents/[id]/download/[...slug] * * Verifies the slug truth-check and tenancy isolation on the path-style * download route. The storage backend is swapped for a real FilesystemBackend * rooted in a tempdir so the byte-streaming path is exercised end-to-end. */ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import * as path from 'node:path'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { db } from '@/lib/db'; import { documents, files } from '@/lib/db/schema/documents'; import { user } from '@/lib/db/schema/users'; import { createFolder } from '@/lib/services/document-folders.service'; import { makeMockCtx } from '../helpers/route-tester'; import { makeFullPermissions, makePort } from '../helpers/factories'; let testUserId: string; beforeAll(async () => { const [u] = await db.select({ id: user.id }).from(user).limit(1); testUserId = u!.id; }); describe('GET /api/v1/documents/[id]/download/[...slug]', () => { let storageRoot: string; let backend: import('@/lib/storage/filesystem').FilesystemBackend; beforeEach(async () => { storageRoot = await mkdtemp(path.join(tmpdir(), 'pn-doc-dl-')); const { FilesystemBackend } = await import('@/lib/storage/filesystem'); backend = await FilesystemBackend.create({ root: storageRoot, proxyHmacSecretEncrypted: null, }); vi.doMock('@/lib/storage', async () => { const real = await vi.importActual('@/lib/storage'); return { ...real, getStorageBackend: vi.fn(async () => backend) }; }); }); afterEach(async () => { vi.doUnmock('@/lib/storage'); await rm(storageRoot, { recursive: true, force: true }); }); async function setupDoc(opts: { portId: string; folderId: string | null; filename: string; body: Buffer; storagePath: string; }) { await backend.put(opts.storagePath, opts.body, { contentType: 'application/pdf', sizeBytes: opts.body.length, }); const [fileRow] = await db .insert(files) .values({ portId: opts.portId, filename: opts.filename, originalName: opts.filename, mimeType: 'application/pdf', sizeBytes: String(opts.body.length), storagePath: opts.storagePath, uploadedBy: testUserId, }) .returning(); const [docRow] = await db .insert(documents) .values({ portId: opts.portId, documentType: 'other', title: opts.filename, createdBy: testUserId, folderId: opts.folderId, fileId: fileRow!.id, }) .returning(); return { fileRow: fileRow!, docRow: docRow! }; } it('streams the file when the slug matches the current folder path + filename', async () => { const port = await makePort(); const folder = await createFolder(port.id, testUserId, { name: 'Deals 2026', parentId: null }); const sub = await createFolder(port.id, testUserId, { name: 'Q1', parentId: folder.id }); const body = Buffer.from('hello world'); const { docRow } = await setupDoc({ portId: port.id, folderId: sub.id, filename: 'contract.pdf', body, storagePath: 'test/contract.pdf', }); const { downloadHandler } = await import('@/app/api/v1/documents/[id]/download/[...slug]/handlers'); const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() }); const req = new Request('http://localhost/api/v1/documents/x/download/whatever') as never; const res = await downloadHandler(req, ctx, { id: docRow.id, slug: ['Deals 2026', 'Q1', 'contract.pdf'], }); expect(res.status).toBe(200); const buf = Buffer.from(await res.arrayBuffer()); expect(buf.toString()).toBe('hello world'); expect(res.headers.get('content-type')).toBe('application/pdf'); }); it('404s when the folder-path segments do not match current state', async () => { const port = await makePort(); const folder = await createFolder(port.id, testUserId, { name: 'Real Folder', parentId: null }); const { docRow } = await setupDoc({ portId: port.id, folderId: folder.id, filename: 'spec.pdf', body: Buffer.from('x'), storagePath: 'test/spec.pdf', }); const { downloadHandler } = await import('@/app/api/v1/documents/[id]/download/[...slug]/handlers'); const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() }); const req = new Request('http://localhost/x') as never; const res = await downloadHandler(req, ctx, { id: docRow.id, slug: ['Wrong Folder', 'spec.pdf'], }); expect(res.status).toBe(404); }); it('404s when the filename segment does not match current state', async () => { const port = await makePort(); const folder = await createFolder(port.id, testUserId, { name: 'F', parentId: null }); const { docRow } = await setupDoc({ portId: port.id, folderId: folder.id, filename: 'real.pdf', body: Buffer.from('x'), storagePath: 'test/real.pdf', }); const { downloadHandler } = await import('@/app/api/v1/documents/[id]/download/[...slug]/handlers'); const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() }); const req = new Request('http://localhost/x') as never; const res = await downloadHandler(req, ctx, { id: docRow.id, slug: ['F', 'wrong.pdf'], }); expect(res.status).toBe(404); }); it('404s when the document has no attached file (orphaned doc)', async () => { const port = await makePort(); const [docRow] = await db .insert(documents) .values({ portId: port.id, documentType: 'other', title: 'No file', createdBy: testUserId, folderId: null, }) .returning(); const { downloadHandler } = await import('@/app/api/v1/documents/[id]/download/[...slug]/handlers'); const ctx = makeMockCtx({ portId: port.id, permissions: makeFullPermissions() }); const req = new Request('http://localhost/x') as never; const res = await downloadHandler(req, ctx, { id: docRow!.id, slug: ['anything.pdf'], }); expect(res.status).toBe(404); }); it('404s when the document belongs to another port (tenant isolation)', async () => { const portA = await makePort(); const portB = await makePort(); const folder = await createFolder(portA.id, testUserId, { name: 'Cross', parentId: null, }); const { docRow } = await setupDoc({ portId: portA.id, folderId: folder.id, filename: 'a.pdf', body: Buffer.from('x'), storagePath: 'test/a.pdf', }); const { downloadHandler } = await import('@/app/api/v1/documents/[id]/download/[...slug]/handlers'); const ctx = makeMockCtx({ portId: portB.id, permissions: makeFullPermissions() }); const req = new Request('http://localhost/x') as never; const res = await downloadHandler(req, ctx, { id: docRow.id, slug: ['Cross', 'a.pdf'], }); expect(res.status).toBe(404); }); });