"""Shared helpers for source tests: fixture loading and a SyncContext around a real session. Exposed as fixtures rather than importable functions: `tests/sources` is a package, and a module named `sources` on sys.path next to `fintracker.sources` reads like a trap. """ from __future__ import annotations import json from collections.abc import Callable from pathlib import Path from typing import Any import pytest import respx from fintracker.config import Settings from fintracker.db import get_sessionmaker from fintracker.sources.base import SyncContext, SyncResult FIXTURES = Path(__file__).resolve().parent.parent / "fixtures" @pytest.fixture def fixture_json() -> Callable[..., dict[str, Any]]: def _load(*parts: str) -> dict[str, Any]: return json.loads(FIXTURES.joinpath(*parts).read_text(encoding="utf-8")) return _load @pytest.fixture def fixture_bytes() -> Callable[..., bytes]: def _load(*parts: str) -> bytes: return FIXTURES.joinpath(*parts).read_bytes() return _load @pytest.fixture def mock_http(): """respx router without `assert_all_called`: some tests deliberately prove that a route (e.g. the token endpoint) was NOT hit.""" with respx.mock(assert_all_called=False) as router: yield router @pytest.fixture def run_sync(): """Run a source the way the worker does, but without the lock/run-log bookkeeping.""" async def _run(source: Any, *, settings: Settings, cursor: str | None = None) -> SyncResult: async with get_sessionmaker()() as session: ctx = SyncContext( session=session, settings=settings, cursor_before=cursor, triggered_by="test", ) return await source.sync(ctx) return _run