"""Unit tests for GalleryDLService._compute_run_stats. Covers the structured counts the Downloads modal surfaces in its header row: exit code, downloaded count, skipped count, per-item failures, warning count. """ from unittest.mock import patch from app.services.gallery_dl import GalleryDLService def _make_service(): with patch.object(GalleryDLService, "_load_base_config", return_value={}): return GalleryDLService(rate_limit=1.0) def test_run_stats_all_zeros_on_empty_output(): service = _make_service() stats = service._compute_run_stats(0, "", "") assert stats == { "exit_code": 0, "downloaded_count": 0, "skipped_count": 0, "per_item_failures": 0, "warning_count": 0, "tier_gated_count": 0, } def test_run_stats_counts_downloads_from_stdout_paths(): service = _make_service() stdout = ( "/data/downloads/Foo/bar/1.jpg\n" "/data/downloads/Foo/bar/2.jpg\n" "[some log line that should not count]\n" ) stats = service._compute_run_stats(0, stdout, "") assert stats["downloaded_count"] == 2 def test_run_stats_skips_from_both_streams(): """stdout '#' lines AND stderr 'skipping ' lines both count.""" service = _make_service() stdout = ( "# /data/downloads/Foo/already_archived_1.jpg\n" "# /data/downloads/Foo/already_archived_2.jpg\n" ) stderr = ( "[patreon][debug] skipping https://example.com/a.jpg (abc image_large)\n" "[patreon][debug] skipping https://example.com/b.jpg (def image_large)\n" "[patreon][debug] skipping https://example.com/c.jpg (ghi image_large)\n" ) stats = service._compute_run_stats(0, stdout, stderr) assert stats["skipped_count"] == 5 def test_run_stats_per_item_failures_counted(): service = _make_service() stderr = ( "[download][error] Failed to download 02_46004845.part\n" "[download][error] Failed to download 03_46004845.part\n" "[patreon][debug] something unrelated\n" ) stats = service._compute_run_stats(1, "", stderr) assert stats["per_item_failures"] == 2 def test_run_stats_warnings_counted(): service = _make_service() stderr = ( "[downloader.http][warning] '404 OK' for 'https://example.com/x'\n" "[patreon][warning] something else\n" "[patreon][error] not a warning\n" ) stats = service._compute_run_stats(1, "", stderr) assert stats["warning_count"] == 2 def test_run_stats_exit_code_passed_through(): service = _make_service() assert service._compute_run_stats(0, "", "")["exit_code"] == 0 assert service._compute_run_stats(4, "", "")["exit_code"] == 4 assert service._compute_run_stats(-9, "", "")["exit_code"] == -9 def test_run_stats_tier_gated_count_from_patreon_warnings(): """'Not allowed to view post N' warnings tallied separately from generic warnings.""" service = _make_service() stderr = ( "[patreon][warning] Not allowed to view post 100\n" "[patreon][warning] Not allowed to view post 101\n" "[patreon][warning] Not allowed to view post 102\n" "[downloader.http][warning] '404 OK' for 'https://example.com/x'\n" ) stats = service._compute_run_stats(4, "", stderr) assert stats["tier_gated_count"] == 3 # Still tallied in warning_count — tier_gated is a subset, not separate assert stats["warning_count"] == 4