"""Tests du gestionnaire de téléchargements (serveur HTTP local avec Range).""" import asyncio import pytest from aiohttp import web from app.config import get_settings from app.services.downloads import download_manager as dm PAYLOAD = b"x" * 500_000 async def _start_file_server() -> tuple[web.AppRunner, int]: """Mini serveur HTTP supportant les requêtes Range.""" async def handle(request: web.Request) -> web.StreamResponse: range_header = request.headers.get("Range") if range_header: start = int(range_header.removeprefix("bytes=").split("-")[0]) return web.Response( body=PAYLOAD[start:], status=206, headers={"Content-Range": f"bytes {start}-{len(PAYLOAD) - 1}/{len(PAYLOAD)}"}, ) return web.Response(body=PAYLOAD, headers={"Content-Length": str(len(PAYLOAD))}) app = web.Application() app.router.add_get("/video.mp4", handle) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "127.0.0.1", 0) await site.start() port = site._server.sockets[0].getsockname()[1] return runner, port @pytest.fixture async def file_server(): runner, port = await _start_file_server() yield f"http://127.0.0.1:{port}" await runner.cleanup() @pytest.fixture async def manager(): await dm.start() yield dm await dm.stop() async def _wait_status(download_id: int, wanted: set[str], timeout: float = 10) -> dict: for _ in range(int(timeout * 10)): data = await dm.get(download_id) if data["status"] in wanted: return data await asyncio.sleep(0.1) raise AssertionError(f"statut {wanted} non atteint (actuel: {data['status']})") async def test_full_download(file_server, manager): d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Episode Test 1") data = await _wait_status(d["id"], {"done", "failed"}) assert data["status"] == "done", data.get("error") assert data["percent"] == 100.0 path = get_settings().download_dir / data["file_path"] assert path.read_bytes() == PAYLOAD async def test_anti_duplicate(file_server, manager): d1 = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Ep") d2 = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Ep") assert d1["id"] == d2["id"] assert d2["duplicate"] is True await _wait_status(d1["id"], {"done", "failed"}) async def test_pause_resume(file_server, manager): d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Ep Pause") await asyncio.sleep(0.2) await dm.pause(d["id"]) data = await _wait_status(d["id"], {"paused", "done"}) if data["status"] == "paused": # assez rapide pour être pausé assert await dm.resume(d["id"]) is True data = await _wait_status(d["id"], {"done", "failed"}) assert data["status"] == "done", data.get("error") path = get_settings().download_dir / data["file_path"] assert path.read_bytes() == PAYLOAD async def test_failed_download_and_retry(manager): d = await dm.enqueue("http://127.0.0.1:1/nope.mp4", "http://page", "Ep KO") data = await _wait_status(d["id"], {"failed"}) assert data["error"] assert await dm.retry(d["id"]) is True data = await _wait_status(d["id"], {"failed"}) assert await dm.cancel(d["id"]) is True async def test_cancel(file_server, manager): d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Ep Cancel") await dm.cancel(d["id"]) data = await dm.get(d["id"]) assert data["status"] in ("cancelled", "done") # course possible si déjà fini