from unittest.mock import MagicMock, patch import pytest from backend.app.services.patreon_resolver import ( resolve_campaign_id, resolve_campaign_id_for_source, ) @pytest.mark.asyncio async def test_resolves_on_happy_path(): fake_response = MagicMock() fake_response.status_code = 200 fake_response.json.return_value = {"data": [{"id": "12345678", "type": "campaign"}]} with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake_response): result = await resolve_campaign_id("alice", cookies_path=None) assert result == "12345678" @pytest.mark.asyncio async def test_returns_none_on_401(): fake_response = MagicMock() fake_response.status_code = 401 fake_response.json.return_value = {} with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake_response): result = await resolve_campaign_id("alice", cookies_path=None) assert result is None @pytest.mark.asyncio async def test_returns_none_on_network_error(): import requests with patch( "backend.app.services.patreon_resolver.requests.get", side_effect=requests.ConnectionError("nope"), ): result = await resolve_campaign_id("alice", cookies_path=None) assert result is None @pytest.mark.asyncio async def test_returns_none_on_empty_data(): fake_response = MagicMock() fake_response.status_code = 200 fake_response.json.return_value = {"data": []} with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake_response): result = await resolve_campaign_id("alice", cookies_path=None) assert result is None @pytest.mark.asyncio async def test_returns_none_on_malformed_json(): fake_response = MagicMock() fake_response.status_code = 200 fake_response.json.side_effect = ValueError("not json") with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake_response): result = await resolve_campaign_id("alice", cookies_path=None) assert result is None @pytest.mark.asyncio async def test_missing_cookies_file_does_not_raise(tmp_path): fake_response = MagicMock() fake_response.status_code = 200 fake_response.json.return_value = {"data": [{"id": "1", "type": "campaign"}]} with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake_response): result = await resolve_campaign_id( "alice", cookies_path=str(tmp_path / "does-not-exist.txt") ) assert result == "1" @pytest.mark.asyncio async def test_loads_cookies_file_when_present(tmp_path): cookies = tmp_path / "cookies.txt" cookies.write_text( "# Netscape HTTP Cookie File\n" ".patreon.com\tTRUE\t/\tTRUE\t1700000000\tsession_id\tabc\n" ) fake_response = MagicMock() fake_response.status_code = 200 fake_response.json.return_value = {"data": [{"id": "9", "type": "campaign"}]} with patch( "backend.app.services.patreon_resolver.requests.get", return_value=fake_response, ) as mock_get: result = await resolve_campaign_id("alice", cookies_path=str(cookies)) assert result == "9" _, kwargs = mock_get.call_args assert kwargs.get("cookies") is not None # -- resolve_campaign_id_for_source (shared by download + verify) ---------- @pytest.mark.asyncio async def test_for_source_uses_cached_override_without_lookup(): with patch("backend.app.services.patreon_resolver.requests.get") as mock_get: cid, resolved = await resolve_campaign_id_for_source( "https://patreon.com/alice", None, {"patreon_campaign_id": "999"}, ) assert cid == "999" assert resolved is None # cached → no newly-resolved id to cache mock_get.assert_not_called() @pytest.mark.asyncio async def test_for_source_extracts_id_url_without_lookup(): with patch("backend.app.services.patreon_resolver.requests.get") as mock_get: cid, resolved = await resolve_campaign_id_for_source( "https://www.patreon.com/id:4242", None, {}, ) assert cid == "4242" assert resolved is None mock_get.assert_not_called() @pytest.mark.asyncio async def test_for_source_resolves_vanity_and_reports_it(): fake = MagicMock() fake.status_code = 200 fake.json.return_value = {"data": [{"id": "777", "type": "campaign"}]} with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake): cid, resolved = await resolve_campaign_id_for_source( "https://patreon.com/alice", None, {}, ) assert cid == "777" assert resolved == "777" # newly resolved → caller caches it @pytest.mark.asyncio async def test_for_source_unresolvable_returns_none(): cid, resolved = await resolve_campaign_id_for_source("not-a-patreon-url", None, {}) assert cid is None assert resolved is None