mirror of
https://github.com/darkzoul5/YoutubePlaylistSync.git
synced 2026-11-04 07:38:14 +02: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:
1 parent
21ba87c501
commit
b5ee8dbf3b
13 files changed
+170
-20
No files matched your search
@@ -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
|
#Custom for this project
|
||||||
config/yt-playlist-config.json
|
config/yt-playlist-config.json
|
||||||
/tmp*
|
/tmp*
|
||||||
|
*.code-workspace
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.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
|
import os
|
||||||
|
|
||||||
|
|
||||||
class TempConfig:
|
class DummyConfig:
|
||||||
"""Small test configuration object used by unit and integration tests.
|
"""Small test configuration object used by unit and integration tests.
|
||||||
|
|
||||||
Adjust attributes via environment variables where appropriate.
|
Adjust attributes via environment variables where appropriate.
|
||||||
@@ -47,7 +47,7 @@ if bin_dir.exists():
|
|||||||
print(f"Using local aria2c at: {aria2c_path}")
|
print(f"Using local aria2c at: {aria2c_path}")
|
||||||
|
|
||||||
from ytplaylist.downloader import PlaylistDownloader
|
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')
|
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}")
|
print(f"Using playlist URL: {playlist_url}")
|
||||||
|
|
||||||
cfg_base = TempConfig()
|
cfg_base = DummyConfig()
|
||||||
|
|
||||||
# ensure yt-dlp exists
|
# ensure yt-dlp exists
|
||||||
import shutil as _sh
|
import shutil as _sh
|
||||||
@@ -76,7 +76,7 @@ root_tmp.mkdir(parents=True, exist_ok=True)
|
|||||||
failed = False
|
failed = False
|
||||||
for mode in MODES:
|
for mode in MODES:
|
||||||
print(f"\n=== Running mode: {mode} ===")
|
print(f"\n=== Running mode: {mode} ===")
|
||||||
cfg = TempConfig()
|
cfg = DummyConfig()
|
||||||
# Allow enabling verbose subprocess output from CI by setting YTPL_DEBUG=1
|
# Allow enabling verbose subprocess output from CI by setting YTPL_DEBUG=1
|
||||||
cfg.debug = bool(os.getenv("YTPL_DEBUG", "0") == "1")
|
cfg.debug = bool(os.getenv("YTPL_DEBUG", "0") == "1")
|
||||||
cfg.download_mode = mode
|
cfg.download_mode = mode
|
||||||
|
|||||||
+16
-16
@@ -1,23 +1,23 @@
|
|||||||
import logging
|
import logging
|
||||||
from ytplaylist.manager import PlaylistManager
|
from ytplaylist.manager import PlaylistManager
|
||||||
from tests.temp_config import TempConfig
|
from tests.dummy_config import DummyConfig
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(TempConfig):
|
def test_run_with_prune_disabled():
|
||||||
playlists = [{"url": None, "save_path": "./tmp_test", "archive": "archive.txt"}]
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||||
print('--- Running with prune=False ---')
|
cfg = DummyConfig()
|
||||||
cfg=TestConfig()
|
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||||
m=PlaylistManager(cfg, debug=False)
|
m = PlaylistManager(cfg, debug=False)
|
||||||
|
# should complete without raising
|
||||||
m.run()
|
m.run()
|
||||||
print('Run complete prune=False')
|
|
||||||
|
|
||||||
print('\n--- Running with prune=True, non_interactive=True ---')
|
|
||||||
cfg2=TestConfig()
|
def test_run_with_prune_enabled_non_interactive():
|
||||||
cfg2.prune=True
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s")
|
||||||
cfg2.non_interactive=True
|
cfg = DummyConfig()
|
||||||
m2=PlaylistManager(cfg2, debug=False)
|
cfg.playlists = [{"url": None, "save_path": "tests/tmp_test", "archive": "archive.txt"}]
|
||||||
m2.run()
|
cfg.prune = True
|
||||||
print('Run complete prune=True non_interactive=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()
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Reference in new issue
Block a user