mirror of
https://github.com/darkzoul5/YoutubePlaylistSync.git
synced 2026-07-03 04:23:59 +03:00
Refactor test configurations and enhance testing framework
- Replace TempConfig with DummyConfig across tests for consistency - Introduce unit tests workflow configuration - Add pytest configuration for standardized test discovery - Implement comprehensive tests for config loading and downloader behavior - Clean up unused temp_config.py and related references
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
name: Unit tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, Next ]
|
||||
pull_request:
|
||||
branches: [ main, Next ]
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
name: Run unit tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: "https://gitea.com/actions/checkout@v5"
|
||||
|
||||
- name: Create venv and install project
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
# Install project (editable) and test deps
|
||||
python -m pip install -e .[test] || python -m pip install -e .
|
||||
python -m pip install pytest
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
. .venv/bin/activate
|
||||
pytest -q
|
||||
@@ -4,6 +4,7 @@
|
||||
#Custom for this project
|
||||
config/yt-playlist-config.json
|
||||
/tmp*
|
||||
*.code-workspace
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
# Collect all standardized tests using the conventional pattern
|
||||
python_files = test_*.py
|
||||
addopts = -q
|
||||
@@ -0,0 +1,9 @@
|
||||
import pytest
|
||||
|
||||
from tests.dummy_config import DummyConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_config():
|
||||
"""Return a fresh DummyConfig instance for tests to customize."""
|
||||
return DummyConfig()
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
|
||||
|
||||
class TempConfig:
|
||||
class DummyConfig:
|
||||
"""Small test configuration object used by unit and integration tests.
|
||||
|
||||
Adjust attributes via environment variables where appropriate.
|
||||
@@ -47,7 +47,7 @@ if bin_dir.exists():
|
||||
print(f"Using local aria2c at: {aria2c_path}")
|
||||
|
||||
from ytplaylist.downloader import PlaylistDownloader
|
||||
from tests.temp_config import TempConfig
|
||||
from tests.dummy_config import DummyConfig
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s')
|
||||
|
||||
@@ -60,7 +60,7 @@ if not playlist_url:
|
||||
|
||||
print(f"Using playlist URL: {playlist_url}")
|
||||
|
||||
cfg_base = TempConfig()
|
||||
cfg_base = DummyConfig()
|
||||
|
||||
# ensure yt-dlp exists
|
||||
import shutil as _sh
|
||||
@@ -76,7 +76,7 @@ root_tmp.mkdir(parents=True, exist_ok=True)
|
||||
failed = False
|
||||
for mode in MODES:
|
||||
print(f"\n=== Running mode: {mode} ===")
|
||||
cfg = TempConfig()
|
||||
cfg = DummyConfig()
|
||||
# Allow enabling verbose subprocess output from CI by setting YTPL_DEBUG=1
|
||||
cfg.debug = bool(os.getenv("YTPL_DEBUG", "0") == "1")
|
||||
cfg.download_mode = mode
|
||||
|
||||
+16
-16
@@ -1,23 +1,23 @@
|
||||
import logging
|
||||
from ytplaylist.manager import PlaylistManager
|
||||
from tests.temp_config import TempConfig
|
||||
from tests.dummy_config import DummyConfig
|
||||
|
||||
|
||||
class TestConfig(TempConfig):
|
||||
playlists = [{"url": None, "save_path": "./tmp_test", "archive": "archive.txt"}]
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test_run_with_prune_disabled():
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||
print('--- Running with prune=False ---')
|
||||
cfg=TestConfig()
|
||||
m=PlaylistManager(cfg, debug=False)
|
||||
cfg = DummyConfig()
|
||||
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||
m = PlaylistManager(cfg, debug=False)
|
||||
# should complete without raising
|
||||
m.run()
|
||||
print('Run complete prune=False')
|
||||
|
||||
print('\n--- Running with prune=True, non_interactive=True ---')
|
||||
cfg2=TestConfig()
|
||||
cfg2.prune=True
|
||||
cfg2.non_interactive=True
|
||||
m2=PlaylistManager(cfg2, debug=False)
|
||||
m2.run()
|
||||
print('Run complete prune=True non_interactive=True')
|
||||
|
||||
def test_run_with_prune_enabled_non_interactive():
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||
cfg = DummyConfig()
|
||||
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||
cfg.prune = True
|
||||
cfg.non_interactive = True
|
||||
m = PlaylistManager(cfg, debug=False)
|
||||
# should complete without raising
|
||||
m.run()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from ytplaylist.config import ConfigLoader
|
||||
|
||||
|
||||
def test_config_loader_reads_properties(tmp_path, monkeypatch):
|
||||
# create a minimal config file with known binary names that exist on PATH
|
||||
cfg = {
|
||||
"playlists": [{"url": "https://www.youtube.com/playlist?list=FAKE", "save_path": "./tmp", "archive": "archive.txt"}],
|
||||
"yt_dlp_path": "python",
|
||||
"ffmpeg_path": "python",
|
||||
"aria2c_path": "python",
|
||||
"max_parallel_downloads": 3,
|
||||
"aria2c_connections": 2,
|
||||
}
|
||||
|
||||
p = tmp_path / "yt-playlist-config.json"
|
||||
p.write_text(json.dumps(cfg), encoding="utf-8")
|
||||
|
||||
# Use absolute path so ConfigLoader doesn't try to create ./config
|
||||
loader = ConfigLoader(str(p))
|
||||
|
||||
assert loader.playlists == cfg["playlists"]
|
||||
assert loader.yt_dlp_path == "python"
|
||||
assert loader.ffmpeg_path == "python"
|
||||
assert loader.aria2c_path == "python"
|
||||
assert loader.max_parallel_downloads == 3
|
||||
assert loader.aria2c_connections == 2
|
||||
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
|
||||
from ytplaylist.downloader import PlaylistDownloader
|
||||
from tests.dummy_config import DummyConfig
|
||||
|
||||
|
||||
def test_sanitize_title_and_get_file_path(tmp_path):
|
||||
cfg = DummyConfig()
|
||||
playlist = {"url": None, "save_path": str(tmp_path)}
|
||||
dl = PlaylistDownloader(cfg, playlist, 0)
|
||||
|
||||
# illegal chars should be replaced and trimmed; fallback_id used when title becomes empty
|
||||
title = ' My: <>:"/\\|?*Title '
|
||||
safe = dl.sanitize_title(title, "ABC123")
|
||||
# ensure no illegal characters remain
|
||||
assert all(c not in safe for c in dl.illegal_chars)
|
||||
|
||||
# empty title should return fallback id
|
||||
assert dl.sanitize_title(" ", "FALLBACK") == "FALLBACK"
|
||||
|
||||
# get_file_path uses save_path and zero-padded index
|
||||
path = dl.get_file_path(5, "SongName")
|
||||
assert isinstance(path, Path)
|
||||
assert path.name.startswith("005 - SongName")
|
||||
@@ -0,0 +1,26 @@
|
||||
import logging
|
||||
from tests.dummy_config import DummyConfig
|
||||
from ytplaylist.manager import PlaylistManager
|
||||
|
||||
|
||||
def test_manager_warns_and_sleeps(monkeypatch, caplog):
|
||||
# Avoid actually sleeping during the test
|
||||
slept = {"called": False}
|
||||
|
||||
def fake_sleep(sec):
|
||||
slept["called"] = True
|
||||
|
||||
# monkeypatch the sleep used inside the manager module
|
||||
monkeypatch.setattr("ytplaylist.manager.time.sleep", fake_sleep)
|
||||
|
||||
caplog.set_level(logging.WARNING)
|
||||
cfg = DummyConfig()
|
||||
cfg.max_parallel_downloads = 11
|
||||
cfg.aria2c_connections = 10
|
||||
cfg.playlists = []
|
||||
|
||||
m = PlaylistManager(cfg, debug=False)
|
||||
m.run()
|
||||
|
||||
assert slept["called"] is True
|
||||
assert any("may overload your network" in rec.getMessage() for rec in caplog.records)
|
||||
@@ -0,0 +1,23 @@
|
||||
import logging
|
||||
from ytplaylist.manager import PlaylistManager
|
||||
from tests.dummy_config import DummyConfig
|
||||
|
||||
|
||||
def test_run_with_prune_disabled():
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||
cfg = DummyConfig()
|
||||
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||
m = PlaylistManager(cfg, debug=False)
|
||||
# should complete without raising
|
||||
m.run()
|
||||
|
||||
|
||||
def test_run_with_prune_enabled_non_interactive():
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||
cfg = DummyConfig()
|
||||
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||
cfg.prune = True
|
||||
cfg.non_interactive = True
|
||||
m = PlaylistManager(cfg, debug=False)
|
||||
# should complete without raising
|
||||
m.run()
|
||||
Reference in New Issue
Block a user