"""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