Files
siftlode/backend/tests/test_download_errors.py
T
peter b7a6451dba fix(downloads): iPad audio playback + honest non-retryable failure for auth-walled hosts
5a — the public watch page rendered every asset in a <video>, but WebKit (all
iOS browsers) refuses to play an audio-only mp3 in <video>, so shared audio
links were broken on iPad while working on desktop Chromium. public_meta now
flags audio-only assets (codec present, no video stream) and WatchPage renders
a native <audio> in a padded surface card (no artwork exists for these).

5c — a login-walled Facebook post failed as a bare 'unknown', which offered a
misleading Retry. Classify closed-group / cookie-suggestion auth walls as
login_required and genuinely unsupported sites as a new unsupported_host class
(both non-retryable), and drop unknown from the modal's retryable set — an
unclassified failure is most often an unparsed auth wall. The queue row keeps
its own force-retry for real transients.
2026-08-28 02:22:28 +02:00

57 lines
3.2 KiB
Python

"""Category classification of raw yt-dlp / ffmpeg download-failure strings (S2)."""
from app.downloads.errors import classify
def test_real_ytdlp_messages_map_to_categories():
# Verbatim-shaped yt-dlp / ffmpeg output → the category the UI explains.
cases = {
"ERROR: [youtube] xxxxxxxxxxx: Video unavailable": "unavailable",
"ERROR: [youtube] abc: Private video. Sign in if you've been granted access": "unavailable",
"ERROR: [youtube] abc: This video is no longer available because the uploader has closed their "
"account": "unavailable",
"ERROR: [youtube] abc: Sign in to confirm your age. This video may be inappropriate": "login_required",
"ERROR: [youtube] abc: Sign in to confirm you're not a bot": "login_required",
"ERROR: [youtube] abc: Join this channel to get access to members-only content": "login_required",
"ERROR: [Reddit] 1vi1x56: Account authentication is required.": "login_required",
# Closed-group / private-post auth walls: yt-dlp either says to sign in or suggests cookies.
"ERROR: [facebook] 123: You must be logged in to see this post": "login_required",
"ERROR: [facebook] 123: Login required. Use --cookies for the authentication.": "login_required",
"ERROR: [instagram] abc: This content is only available for registered users": "login_required",
"ERROR: [youtube] abc: Sign in to confirm... Use --cookies-from-browser or --cookies": "login_required",
# A site yt-dlp has no extractor for at all — distinct from an auth wall, still non-retryable.
"ERROR: Unsupported URL: https://example.com/whatever": "unsupported_host",
"ERROR: [youtube] abc: The uploader has not made this video available in your country": "geo_blocked",
"ERROR: [youtube] abc: Requested format is not available": "format_unavailable",
"ERROR: [youtube] abc: Only images are available for download": "format_unavailable",
"ERROR: [generic] This video is DRM protected": "drm",
"ffmpeg failed (1): Conversion failed!": "postprocess",
"The source download is no longer available.": "source_missing",
"The source file is missing.": "source_missing",
"ERROR: unable to download video data: HTTP Error 403: Forbidden": "network",
"ERROR: Unable to download webpage: <urlopen error timed out>": "network",
}
for msg, expected in cases.items():
assert classify(msg) == expected, f"{msg!r} → {classify(msg)!r}, expected {expected!r}"
def test_unmatched_and_empty_are_unknown():
assert classify("some totally novel failure text") == "unknown"
assert classify("") == "unknown"
assert classify(None) == "unknown"
def test_matching_is_case_insensitive():
assert classify("VIDEO UNAVAILABLE") == "unavailable"
assert classify("drm protected") == "drm"
def test_login_beats_generic_unavailable_when_both_present():
# A members-only video also reads as "unavailable" to a signed-out client; the sign-in cause is
# the more useful, more specific one, so it must win (rule order guards this).
msg = (
"ERROR: [youtube] abc: Join this channel to get access to members-only content. "
"This video is unavailable"
)
assert classify(msg) == "login_required"