import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('../src/services/ytDlpRunner.js', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, runYtDlp: vi.fn(() => new Promise(() => {})) }; }); import { createQueueRoutes } from '../src/routes/queue.js'; const validUrl = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; let tmpDir: string; beforeEach(async () => { tmpDir = await mkdtemp(join(tmpdir(), 'ytdl-queue-route-test-')); }); afterEach(async () => { await rm(tmpDir, { recursive: true, force: true }); }); describe('routes de file d’attente', () => { it('POST /api/queue ajoute un élément valide et le retourne', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); const response = await app.inject({ method: 'POST', url: '/api/queue', payload: { url: validUrl, mode: 'audio', audioFormat: 'opus' }, }); expect(response.statusCode).toBe(201); const body = response.json(); expect(body.item).toMatchObject({ url: validUrl, mode: { kind: 'audio', format: 'opus' }, status: expect.stringMatching(/pending|running/), }); }); it('POST /api/queue renvoie 400 pour une URL non-YouTube', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); const response = await app.inject({ method: 'POST', url: '/api/queue', payload: { url: 'https://evil.com/x', mode: 'video' }, }); expect(response.statusCode).toBe(400); }); it('POST /api/queue renvoie 400 pour un mode invalide (validation de schéma)', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); const response = await app.inject({ method: 'POST', url: '/api/queue', payload: { url: validUrl, mode: 'video-4k-ultra' }, }); expect(response.statusCode).toBe(400); }); it('GET /api/queue liste les éléments ajoutés', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); await app.inject({ method: 'POST', url: '/api/queue', payload: { url: validUrl, mode: 'thumbnail' }, }); const response = await app.inject({ method: 'GET', url: '/api/queue' }); expect(response.statusCode).toBe(200); expect(response.json().items).toHaveLength(1); }); it('DELETE /api/queue/:id renvoie 404 pour un identifiant inconnu', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); const response = await app.inject({ method: 'DELETE', url: '/api/queue/inconnu' }); expect(response.statusCode).toBe(404); }); it('GET /api/queue/:id/file renvoie 404 tant que le téléchargement n’est pas terminé', async () => { const app = Fastify(); await app.register(createQueueRoutes({ tmpDir, maxConcurrentDownloads: 2 })); const created = await app.inject({ method: 'POST', url: '/api/queue', payload: { url: validUrl, mode: 'video' }, }); const { id } = created.json().item; const response = await app.inject({ method: 'GET', url: `/api/queue/${id}/file` }); expect(response.statusCode).toBe(404); }); });