feat(downloads): native Patreon verify + uniform backend dispatch (plan #697)
The credential Verify button still ran gallery-dl --simulate for Patreon after the cutover — testing the wrong path (and prone to the vanity "Failed to extract campaign ID" the native resolver fixes). Wire it to the native ingester, behind a DRY dispatch so callers never branch on platform. - services/download_backends.py (new): the ONE place that knows which platforms are native vs gallery-dl. `uses_native_ingester(platform)` is the shared predicate; `verify_source_credential(...)` is the uniform probe (same (ok|None, message) contract for both backends). As a platform migrates, it moves into NATIVE_INGESTER_PLATFORMS here and BOTH download routing and verify switch together. - PatreonClient.verify_auth(campaign_id): one authenticated /api/posts fetch → True (valid) / False (401/403/HTML-login) / None (drift or network — inconclusive, not a credential verdict). - patreon_ingester.verify_patreon_credential(): resolve campaign id, then verify_auth — the verify counterpart to the download path. - patreon_resolver.resolve_campaign_id_for_source(): extracted the override / id:-URL / vanity resolution into ONE helper now shared by the download ingester and verify (download_service no longer carries its own copy + regex; −`import re`). - download_service: routes on uses_native_ingester() instead of inline `== "patreon"` (3 sites); uses the shared resolver. - api/credentials: calls verify_source_credential — no platform branch. Tests: verify_auth mapping, resolve_campaign_id_for_source (override/id:/ vanity/none), the dispatch predicate, verify_patreon_credential glue, credentials endpoint proves Patreon uses the native path (gallery-dl verify asserted not-called); repointed the gallery-dl verify test to subscribestar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -238,3 +238,40 @@ def test_fetch_other_status_raises_api_error_with_code(monkeypatch, status):
|
||||
def test_fetch_ok_returns_payload(monkeypatch):
|
||||
client = _client_returning(monkeypatch, _FakeResp(200, json_data={"data": []}))
|
||||
assert client._fetch("5555", None) == {"data": []}
|
||||
|
||||
|
||||
# -- verify_auth (credential probe; (True/False/None, message) contract) ---
|
||||
|
||||
|
||||
def test_verify_auth_ok_when_page_authenticates(monkeypatch):
|
||||
client = _client_returning(monkeypatch, _FakeResp(200, json_data={"data": []}))
|
||||
ok, msg = client.verify_auth("5555")
|
||||
assert ok is True
|
||||
assert "valid" in msg.lower()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", [401, 403])
|
||||
def test_verify_auth_false_when_rejected(monkeypatch, status):
|
||||
client = _client_returning(monkeypatch, _FakeResp(status))
|
||||
ok, _msg = client.verify_auth("5555")
|
||||
assert ok is False
|
||||
|
||||
|
||||
def test_verify_auth_inconclusive_on_drift(monkeypatch):
|
||||
# 200 + JSON but missing top-level 'data' → drift → inconclusive, NOT a
|
||||
# credential verdict (our parser is stale, the cookie may be fine).
|
||||
client = _client_returning(monkeypatch, _FakeResp(200, json_data={"included": []}))
|
||||
ok, msg = client.verify_auth("5555")
|
||||
assert ok is None
|
||||
assert "api shape changed" in msg.lower()
|
||||
|
||||
|
||||
def test_verify_auth_inconclusive_on_network(monkeypatch):
|
||||
import requests
|
||||
client = PatreonClient(cookies_path=None)
|
||||
|
||||
def _raise(*a, **k):
|
||||
raise requests.ConnectionError("boom")
|
||||
monkeypatch.setattr(client._session, "get", _raise)
|
||||
ok, _msg = client.verify_auth("5555")
|
||||
assert ok is None
|
||||
|
||||
Reference in New Issue
Block a user