Files
Tha.Les 4a3ba0f92a fix(download): retry the metadata probe; set socket timeouts everywhere (#300)
The pre-download metadata probe (duration check) ran outside the retry
loop: a transient network blip on that single request failed the whole
job immediately, even though the actual download had a 3-attempt
backoff. The probe and the download now share one retry policy
(_with_retries), with the same retriable/non-retriable classification,
cancel translation, and user-visible "retrying" stage message.

Every YoutubeDL instance (probe, audio download, video track) now sets
an explicit 30 s socket_timeout so a stalled TCP connection can never
hang a job indefinitely.

Closes #279

Co-authored-by: Thales <>
2026-07-17 01:42:02 +01:00

87 lines
2.3 KiB
Python

"""Tests for the shared download retry policy (#279)."""
from __future__ import annotations
import pytest
from app.core.models import Job, JobCancelled
from app.pipeline import download as dl_mod
@pytest.fixture(autouse=True)
def _no_sleep(monkeypatch):
"""Backoff sleeps are pointless in tests."""
monkeypatch.setattr(dl_mod.time, "sleep", lambda _s: None)
@pytest.fixture()
def job():
return Job(id="abcdefabc279")
def test_retries_transient_error_then_succeeds(job):
calls: list[int] = []
def flaky():
calls.append(1)
if len(calls) < 3:
raise OSError("Connection reset by peer")
return {"title": "ok"}
result = dl_mod._with_retries(job, flaky, what="metadata probe")
assert result == {"title": "ok"}
assert len(calls) == 3
# The retry stage message reached the job (user-visible feedback).
assert "retrying" in job.stage_message
def test_non_retriable_raises_immediately(job):
calls: list[int] = []
def private_video():
calls.append(1)
raise RuntimeError("ERROR: Private video. Sign in if you have access")
with pytest.raises(RuntimeError, match="Private video"):
dl_mod._with_retries(job, private_video, what="metadata probe")
assert len(calls) == 1 # never retried
def test_exhausted_retries_reraise_last_error(job):
calls: list[int] = []
def always_down():
calls.append(1)
raise OSError("Read timed out")
with pytest.raises(OSError, match="timed out"):
dl_mod._with_retries(job, always_down, what="download")
assert len(calls) == dl_mod._MAX_RETRIES + 1
def test_cancel_mid_attempt_becomes_jobcancelled(job):
def fails():
job.cancel_requested = True # POST /cancel raced the attempt
raise OSError("connection reset")
with pytest.raises(JobCancelled):
dl_mod._with_retries(job, fails, what="download")
def test_unrecognized_error_is_not_retried(job):
"""Errors matching neither list are treated as permanent -- retrying an
unknown failure mode would just triple the wait for the same outcome."""
calls: list[int] = []
def weird():
calls.append(1)
raise ValueError("some novel explosion")
with pytest.raises(ValueError):
dl_mod._with_retries(job, weird, what="download")
assert len(calls) == 1