feat(artist): resolve patreon + subscribestar display names at add-time (#130 step 5)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m41s

Parity with pixiv (operator ask): the extension add now resolves the real
display name for our other native platforms too, not just the URL handle.
patreon_resolver.resolve_display_name reads the campaigns API's
attributes.name; SubscribeStarClient.resolve_display_name pulls the creator
name off the profile page (og:title, else the <title> stripped of the
SubscribeStar suffix). extension_service._resolve_artist_name dispatches per
platform (pixiv=token, patreon/subscribestar=cookies via get_cookies_path),
best-effort in an executor, falling back to the readable URL handle on any
failure. Still all curator core — the extension is unchanged (sends only the
URL). gallery-dl platforms keep the handle (readable, no native client).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-04 22:37:50 -04:00
parent 0c4b8aef8c
commit 0963bf0db3
6 changed files with 187 additions and 30 deletions
+25
View File
@@ -5,9 +5,34 @@ import pytest
from backend.app.services.patreon_resolver import (
resolve_campaign_id,
resolve_campaign_id_for_source,
resolve_display_name,
)
def test_resolve_display_name_from_campaign():
fake = MagicMock()
fake.status_code = 200
fake.json.return_value = {"data": [
{"id": "1", "type": "campaign", "attributes": {"name": "Maewix Studios"}},
]}
with patch("backend.app.services.patreon_resolver.requests.get", return_value=fake):
assert resolve_display_name("maewix", None) == "Maewix Studios"
def test_resolve_display_name_none_on_empty_or_error():
empty = MagicMock()
empty.status_code = 200
empty.json.return_value = {"data": []}
with patch("backend.app.services.patreon_resolver.requests.get", return_value=empty):
assert resolve_display_name("maewix", None) is None
import requests as _rq
with patch(
"backend.app.services.patreon_resolver.requests.get",
side_effect=_rq.ConnectionError("x"),
):
assert resolve_display_name("maewix", None) is None
@pytest.mark.asyncio
async def test_resolves_on_happy_path():
fake_response = MagicMock()