import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import type { APIRoute } from 'astro'; let workDir: string; let GET: APIRoute; // PNG 1x1 transparent minimal const PNG_1X1 = Buffer.from( '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000a49444154789c63000100000005000100' + '0d0a2db40000000049454e44ae426082', 'hex', ); async function setupTmpRoot() { workDir = await mkdtemp(join(tmpdir(), 'portfolio-files-')); await mkdir(join(workDir, 'data', 'uploads', 'img'), { recursive: true }); await writeFile(join(workDir, 'data', 'uploads', 'img', 'test.png'), PNG_1X1); vi.spyOn(process, 'cwd').mockReturnValue(workDir); vi.resetModules(); const mod = await import('../../src/pages/api/files/[...path]'); GET = mod.GET as APIRoute; } async function cleanup() { vi.restoreAllMocks(); if (workDir) await rm(workDir, { recursive: true, force: true }); } beforeEach(setupTmpRoot); afterEach(cleanup); function call(pathParam: string) { return GET({ params: { path: pathParam } } as unknown as Parameters[0]) as Promise; } describe('GET /api/files/[...path]', () => { it('retourne 404 si path vide', async () => { const res = await call(''); expect(res.status).toBe(404); }); it('bloque le path traversal classique (../../etc/passwd)', async () => { const res = await call('../../etc/passwd'); expect(res.status).toBe(403); }); it('bloque le path traversal imbriqué (img/../../../etc/passwd)', async () => { const res = await call('img/../../../etc/passwd'); expect(res.status).toBe(403); }); it('chemin absolu (/etc/passwd) est aplati dans UPLOADS_ROOT → 404 sans accès au vrai /etc/passwd', async () => { const res = await call('/etc/passwd'); expect(res.status).toBe(404); }); it('retourne 404 sur un fichier inexistant dans uploads', async () => { const res = await call('img/missing.png'); expect(res.status).toBe(404); }); it('sert un fichier valide avec le bon Content-Type', async () => { const res = await call('img/test.png'); expect(res.status).toBe(200); expect(res.headers.get('Content-Type')).toBe('image/png'); const body = Buffer.from(await res.arrayBuffer()); expect(body.equals(PNG_1X1)).toBe(true); }); });