"""Unit tests for GalleryDLService._categorize_error. Focus: making sure errors gallery-dl logs but recovers from (e.g., Patreon's "Failed to extract campaign ID" that falls back to /cw/) don't get misclassified as real failures when the overall run was just archive skips. """ from unittest.mock import patch from app.services.gallery_dl import ErrorType, GalleryDLService def _make_service(): """Build a service without touching the filesystem.""" with patch.object(GalleryDLService, "_load_base_config", return_value={}): return GalleryDLService(rate_limit=1.0) def test_patreon_campaign_id_error_with_skips_is_no_new_content(): """gallery-dl logged the extractor error but recovered + skipped all → no_new_content.""" service = _make_service() stderr = ( "[patreon][error] Failed to extract campaign ID\n" "[patreon][info] campaign_id: 548223\n" "[patreon][api] HEAD https://c10.patreonusercontent.com/4/.../img.jpg\n" ) stdout = ( "# /data/downloads/ElArteDeVero/patreon/2024-01-01_1_title/01_img.jpg\n" "# /data/downloads/ElArteDeVero/patreon/2024-01-02_2_title/01_img.jpg\n" ) error_type, _message = service._categorize_error(1, stdout, stderr) assert error_type == ErrorType.NO_NEW_CONTENT def test_patreon_campaign_id_error_without_skips_still_fails(): """Same error line but no skip activity → genuine failure, not masked.""" service = _make_service() stderr = "[patreon][error] Failed to extract campaign ID\n" stdout = "" error_type, _message = service._categorize_error(1, stdout, stderr) assert error_type != ErrorType.NO_NEW_CONTENT def test_patreon_campaign_id_error_alongside_real_error_still_fails(): """Campaign-ID error + a genuine auth error → auth error wins, not masked.""" service = _make_service() stderr = ( "[patreon][error] Failed to extract campaign ID\n" '[patreon][error] "GET /api/posts HTTP/1.1" 401 None\n' ) stdout = "# /data/downloads/Foo/patreon/post1/img.jpg\n" error_type, _message = service._categorize_error(1, stdout, stderr) assert error_type == ErrorType.AUTH_ERROR