Files
FabledCurator/tests/test_download_backends.py
T
bvandeusenandClaude Opus 5.5 da98195ada
CI and images / lint (push) Failing after 2s
CI and images / extension-version (push) Successful in 2s
CI and images / extension-test (push) Successful in 23s
CI and images / frontend-build (push) Successful in 26s
CI and images / backend-lint-and-test (push) Successful in 53s
CI and images / integration (push) Successful in 2m50s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / refresh-dev (push) Skipped
CI and images / unattended-failure (push) Skipped
CI and images / build-agent (push) Skipped
refactor: the native downloaders share one download loop, file record and seen-ledger key; a clean walk marks its credential verified
Patreon, SubscribeStar and Discord each carried their own copy of the same
per-post download loop, file record and seen-ledger key, and every copy was
another place a fix could be missed.

- BaseNativeDownloader runs one post's files: the seen and on-disk skips,
  the mid-post time-box, pacing, validation and the per-file sidecar. Each
  platform overrides only where its layout differs: Discord's channel folder,
  file names and message-id sidecar, and Patreon's Mux video through yt-dlp.
- One MediaItem and one ledger_key in native_ingest_common replace the three
  per-client copies. Keys are unchanged, except that a SubscribeStar file with
  no gallery id is now keyed by its filename instead of an empty id.
- DownloadResult, ErrorType and the run-stats and tier-gated helpers move
  from gallery_dl.py, which now serves only HentaiFoundry, to
  download_result.py. The unused delegator methods on GalleryDLService go.
- The download lock's platform list is the native-ingester list, not a copy.
- A walk that used a credential and finished with no error type stamps that
  credential's last-verified time, so the credentials page shows when the
  account last worked. A tier-limited walk does not count: an expired session
  hides paid posts the same way.
- The extension no longer clears a retired pixiv token on startup.
- Two comments that still described every download as gallery-dl now name
  both backends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 08:03:32 -05:00

138 lines
5.0 KiB
Python

"""download_backends — the single predicate that routes a platform to the
native ingester vs. gallery-dl. Pure, no DB."""
from pathlib import Path
import pytest
from backend.app.services.download_backends import (
NATIVE_INGESTER_PLATFORMS,
_native_ingester_cls,
_unsupported_platform_message,
run_download,
uses_native_ingester,
verify_source_credential,
)
from backend.app.services.download_result import ErrorType
def test_native_platforms():
for platform in ("patreon", "subscribestar", "discord"):
assert uses_native_ingester(platform) is True
assert platform in NATIVE_INGESTER_PLATFORMS
def test_pixiv_is_no_longer_native():
"""Retired at milestone #406 — unregistered in phase 1, deleted in phase 2.
The refusal below is what stops it falling through to gallery-dl now that
it is neither native nor registered."""
assert uses_native_ingester("pixiv") is False
assert "pixiv" not in NATIVE_INGESTER_PLATFORMS
# --- the retired-platform guard (#406 phase 1) -----------------------------
class _RecordingGalleryDL:
"""Stands in for GalleryDLService: records whether a download was attempted."""
def __init__(self):
self.calls = []
async def download(self, **kwargs):
self.calls.append(kwargs["platform"])
return "reached gallery-dl"
def _ctx(platform):
return {
"platform": platform, "url": f"https://example.invalid/{platform}",
"artist_slug": "someone", "cookies_path": None, "auth_token": None,
}
@pytest.mark.asyncio
async def test_a_retired_platform_never_reaches_a_downloader():
"""An enabled source on a retired platform is data that survives a deploy.
Unguarded, pixiv — no longer native — would fall straight through to the
gallery-dl branch, which still has a pixiv extractor."""
gdl = _RecordingGalleryDL()
result, campaign_id = await run_download(
ctx=_ctx("pixiv"), source_config=None, skip_value=False, mode=None,
gdl=gdl, sync_session_factory=None,
)
assert gdl.calls == []
assert result.success is False
assert result.error_type == ErrorType.UNSUPPORTED_URL
assert "pixiv" in result.error_message
assert campaign_id is None
@pytest.mark.asyncio
async def test_a_supported_gallery_dl_platform_still_reaches_gallery_dl():
"""The positive control. Without it, a guard that refused EVERY platform
would pass the test above just as well (rule #167)."""
gdl = _RecordingGalleryDL()
result, _ = await run_download(
ctx=_ctx("hentaifoundry"), source_config=None, skip_value=False, mode=None,
gdl=gdl, sync_session_factory=None,
)
assert gdl.calls == ["hentaifoundry"]
assert result == "reached gallery-dl"
def test_the_guard_discriminates_by_registration():
assert _unsupported_platform_message("hentaifoundry") is None
assert _unsupported_platform_message("patreon") is None
assert _unsupported_platform_message("pixiv") is not None
assert _unsupported_platform_message("deviantart") is not None
@pytest.mark.asyncio
async def test_verifying_a_retired_platform_is_inconclusive_not_rejected():
"""Nothing is probed, so nothing is rejected — returning False would tell the
operator their credential is bad when the platform is simply gone."""
ok, message = await verify_source_credential(
platform="pixiv", url="https://www.pixiv.net/users/1", artist_slug="someone",
config_overrides=None, cookies_path=None, auth_token=None,
images_root=Path("/nonexistent"),
)
assert ok is None
assert "pixiv" in message
def test_gallery_dl_platforms_are_not_native():
# The platforms still served by gallery-dl must NOT route to the native
# ingester — guards an accidental over-broad migration.
assert uses_native_ingester("hentaifoundry") is False
@pytest.mark.asyncio
async def test_discord_verify_without_a_token_is_a_rejection_not_a_request():
"""Discord authenticates by token (milestone 428); with none saved there is
nothing to send, and saying so beats an HTTP 401 from Discord."""
ok, message = await verify_source_credential(
platform="discord", url="https://discord.com/channels/1/2",
artist_slug="someone", config_overrides=None, cookies_path=None,
auth_token=None, images_root=Path("/nonexistent"),
)
assert ok is False
assert "token" in message.lower()
def test_unknown_platform_is_not_native():
assert uses_native_ingester("nonsense") is False
def test_every_native_platform_dispatches_to_an_ingester():
"""The dispatch table and NATIVE_INGESTER_PLATFORMS have to agree, or a
platform that routes native raises KeyError mid-download instead of being
refused up front. Written over the set rather than per-platform so adding
one to NATIVE_INGESTER_PLATFORMS and forgetting the class fails here.
(This replaces the per-platform dispatch tests, one of which was pixiv's;
it was deleted with pixiv at milestone #406 phase 2.)"""
for platform in NATIVE_INGESTER_PLATFORMS:
assert _native_ingester_cls(platform) is not None