"""Shared test bootstrap. The repository is not installed as a package, so `src/` and the repository root have to be pushed onto `sys.path` BEFORE anything imports `poolcoach_cv` or `app`. Doing it here rather than relying on `app/main.py` (which also inserts src, but only once imported) keeps pass/fail independent of pytest's collection order. Principle: the tests check CONTRACTS (response shapes, transport behaviour, front-end wiring), never physics. Real pooltool is heavy (~20s to import plus the Numba JIT) and none of these gates need it, so a stub goes into sys.modules before anything else is imported. """ from __future__ import annotations import sys import types from pathlib import Path ROOT = Path(__file__).resolve().parents[1] for _p in (ROOT / "src", ROOT): if str(_p) not in sys.path: sys.path.insert(0, str(_p)) import fakeredis # noqa: E402 import pytest # noqa: E402 def _install_pooltool_stub() -> None: """Stub `pooltool` and `pooltool.constants` as empty modules. `poolcoach_cv.resim` and `poolcoach_sim.table` import pooltool LAZILY inside their functions, so an empty stub is enough for every module to import. The stub deliberately lacks `System`/`simulate`/`Table`: a test that strays into the physics layer raises AttributeError immediately instead of quietly running a 40 second simulation. """ if "pooltool" in sys.modules: return pt = types.ModuleType("pooltool") constants = types.ModuleType("pooltool.constants") constants.pocketed = "pocketed" pt.constants = constants sys.modules["pooltool"] = pt sys.modules["pooltool.constants"] = constants _install_pooltool_stub() class FakeQueue: """A shared FakeServer plus two separate connections (API and worker). The two connections behave like two real processes: the API uses the module client installed by `jobqueue.setup(client=...)` and the fake worker uses the other one. """ def __init__(self): self.server = fakeredis.FakeServer() self.api = fakeredis.FakeStrictRedis(server=self.server, decode_responses=True) self.worker = fakeredis.FakeStrictRedis(server=self.server, decode_responses=True) @pytest.fixture def fake_queue() -> FakeQueue: return FakeQueue()