feat(patreon): add campaign-ID resolver service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 13:22:36 -04:00
parent d819a184a1
commit ce8ba538b7
3 changed files with 205 additions and 0 deletions
View File
@@ -0,0 +1,86 @@
"""Unit tests for the Patreon campaign-ID resolver."""
import aiohttp
import pytest
from aioresponses import aioresponses
from app.services.patreon_resolver import resolve_campaign_id
CAMPAIGNS_URL = "https://www.patreon.com/api/campaigns?filter%5Bvanity%5D=mstsu&fields%5Bcampaign%5D=name"
async def test_happy_path_returns_campaign_id():
with aioresponses() as mock:
mock.get(
CAMPAIGNS_URL,
payload={"data": [{"id": "12345", "type": "campaign", "attributes": {"name": "MSTSU"}}]},
)
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result == "12345"
async def test_empty_data_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, payload={"data": []})
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_missing_data_key_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, payload={"errors": [{"code": 1}]})
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_missing_id_field_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, payload={"data": [{"type": "campaign"}]})
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_http_401_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, status=401, body="Unauthorized")
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_network_error_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, exception=aiohttp.ClientConnectionError("boom"))
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_timeout_returns_none():
import asyncio
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, exception=asyncio.TimeoutError())
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_invalid_json_returns_none():
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, body="<html>not json</html>", content_type="text/html")
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result is None
async def test_missing_cookies_path_proceeds_without_jar(tmp_path):
"""When cookies_path is None, the request still goes out (cookies are optional)."""
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, payload={"data": [{"id": "99", "type": "campaign"}]})
result = await resolve_campaign_id("mstsu", cookies_path=None)
assert result == "99"
async def test_nonexistent_cookies_path_proceeds_without_jar(tmp_path):
"""When cookies_path points to a missing file, don't crash — just skip the jar."""
ghost = tmp_path / "does-not-exist.txt"
with aioresponses() as mock:
mock.get(CAMPAIGNS_URL, payload={"data": [{"id": "99"}]})
result = await resolve_campaign_id("mstsu", cookies_path=str(ghost))
assert result == "99"