"""Test StashDB connectora — paginate, delta-by-updated, error handling.""" from __future__ import annotations import json from datetime import UTC, datetime from pathlib import Path import httpx import pytest import respx from app.connectors.stashdb import StashDBConnector @pytest.fixture def stashdb_raw() -> dict: return json.loads( (Path(__file__).parent / "fixtures" / "stashdb_scene.json").read_text() ) def _gql_response(scenes: list[dict]) -> dict: return {"data": {"queryScenes": {"count": len(scenes), "scenes": scenes}}} def test_paginates_until_short_page(stashdb_raw: dict) -> None: s2 = {**stashdb_raw, "id": "scene-2", "title": "Two", "updated": "2026-04-29T11:00:00Z"} s3 = {**stashdb_raw, "id": "scene-3", "title": "Three", "updated": "2026-04-29T10:00:00Z"} with respx.mock(assert_all_called=True) as mock: route = mock.post("https://stashdb.org/graphql").mock( side_effect=[ httpx.Response(200, json=_gql_response([stashdb_raw, s2])), httpx.Response(200, json=_gql_response([s3])), ] ) c = StashDBConnector(api_key="k", per_page=2) results = list(c.fetch_scenes()) assert [r.title for r in results] == ["The Great Heist", "Two", "Three"] assert route.call_count == 2 def test_stops_when_updated_before_since(stashdb_raw: dict) -> None: fresh = {**stashdb_raw, "updated": "2026-04-30T12:00:00Z"} stale = {**stashdb_raw, "id": "scene-stale", "updated": "2026-04-01T12:00:00Z"} with respx.mock() as mock: mock.post("https://stashdb.org/graphql").mock( return_value=httpx.Response(200, json=_gql_response([fresh, stale])) ) c = StashDBConnector(api_key="k") since = datetime(2026, 4, 15, tzinfo=UTC) results = list(c.fetch_scenes(since=since)) assert len(results) == 1 assert results[0].external_id == fresh["id"] def test_respects_limit(stashdb_raw: dict) -> None: s2 = {**stashdb_raw, "id": "scene-2", "title": "Two"} with respx.mock() as mock: mock.post("https://stashdb.org/graphql").mock( return_value=httpx.Response(200, json=_gql_response([stashdb_raw, s2])) ) c = StashDBConnector(api_key="k", per_page=10) results = list(c.fetch_scenes(limit=1)) assert len(results) == 1 def test_raises_on_graphql_errors() -> None: with respx.mock() as mock: mock.post("https://stashdb.org/graphql").mock( return_value=httpx.Response( 200, json={"errors": [{"message": "unauthorized"}]} ) ) c = StashDBConnector(api_key="k") with pytest.raises(RuntimeError, match="stashdb graphql errors"): list(c.fetch_scenes()) def test_api_key_required(monkeypatch: pytest.MonkeyPatch) -> None: from app.config import get_settings monkeypatch.setenv("STASHDB_API_KEY", "") get_settings.cache_clear() with pytest.raises(RuntimeError, match="STASHDB_API_KEY"): StashDBConnector(api_key=None) get_settings.cache_clear()