tests/unit/test_collectors.py (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
"""Collector parsing and failure isolation tests."""
from __future__ import annotations
import httpx
import pytest
import respx
from app.collectors.base import parse_and_normalize
from app.collectors.trackerslist import TrackerslistCollector
from app.config import Settings
from app.db.models import Tracker, TrackerSource
from sqlalchemy import select
FIXTURE_ALL = """
udp://tracker.example:6969/announce
http://tracker.example:80/announce
udp://tracker.example:6969/announce
ws://ignored.example/announce
"""
FIXTURE_BEST = """
udp://tracker.example:6969/announce
"""
def test_parse_and_normalize_counts():
parsed = parse_and_normalize(FIXTURE_ALL)
assert parsed.received == 3
assert len(parsed.valid) == 2
assert len(parsed.unsupported) == 1
@pytest.mark.asyncio
@respx.mock
async def test_trackerslist_import_and_empty_does_not_wipe(session, settings: Settings):
settings = Settings(
APP_NAME="RastroTest",
DATABASE_URL=settings.database_url,
TRACKERSLIST_ALL_URL="https://example.test/all.txt",
TRACKERSLIST_BEST_URL="https://example.test/best.txt",
RUN_SCHEDULER=False,
)
respx.get("https://example.test/all.txt").mock(
return_value=httpx.Response(200, text=FIXTURE_ALL)
)
respx.get("https://example.test/best.txt").mock(
return_value=httpx.Response(200, text=FIXTURE_BEST)
)
collector = TrackerslistCollector()
async with httpx.AsyncClient() as client:
stats = await collector.import_once(session, client, settings)
assert stats.valid == 2
assert stats.new == 2
assert stats.error is None
rows = (await session.execute(select(Tracker))).scalars().all()
assert len(rows) == 2
sources = (await session.execute(select(TrackerSource))).scalars().all()
assert any(s.is_best_at_source for s in sources)
# Failed empty import must preserve data
respx.get("https://example.test/all.txt").mock(return_value=httpx.Response(200, text="\n\n"))
async with httpx.AsyncClient() as client:
stats2 = await collector.import_once(session, client, settings)
assert stats2.error is not None
rows2 = (await session.execute(select(Tracker))).scalars().all()
assert len(rows2) == 2
@pytest.mark.asyncio
@respx.mock
async def test_source_failure_preserves_previous(session, settings: Settings):
settings = Settings(
APP_NAME="RastroTest",
DATABASE_URL=settings.database_url,
TRACKERSLIST_ALL_URL="https://example.test/all.txt",
TRACKERSLIST_BEST_URL="https://example.test/best.txt",
RUN_SCHEDULER=False,
)
respx.get("https://example.test/all.txt").mock(
return_value=httpx.Response(200, text="udp://ok.example:80/announce\n")
)
respx.get("https://example.test/best.txt").mock(return_value=httpx.Response(200, text=""))
collector = TrackerslistCollector()
async with httpx.AsyncClient() as client:
await collector.import_once(session, client, settings)
respx.get("https://example.test/all.txt").mock(return_value=httpx.Response(503))
async with httpx.AsyncClient() as client:
stats = await collector.import_once(session, client, settings)
assert stats.error
count = len((await session.execute(select(Tracker))).scalars().all())
assert count == 1
|