102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
|
|
"""Conftest for the integration test layer.
|
||
|
|
|
||
|
|
The Unit-Suite in ``tests/conftest.py`` aggressively stubs ``fitz``,
|
||
|
|
``bs4``, ``openai`` and ``pydantic_settings`` so the 77 fast tests can
|
||
|
|
run without the full prod-requirements installed. That's the right
|
||
|
|
trade-off for unit tests but blocks every E2E case in this directory:
|
||
|
|
- ``fitz`` (PyMuPDF) is needed to read Wahlprogramm-PDF pages for
|
||
|
|
citation verification (Sub-Issue D) and content plausibility (Sub-C)
|
||
|
|
- ``bs4`` (BeautifulSoup) is needed by the live NRWAdapter for HTML
|
||
|
|
parsing of OPAL responses (Sub-Issue A)
|
||
|
|
- ``openai`` is needed by ``embeddings.create_embedding`` if a test
|
||
|
|
ever wants to compute a query vector against the live DashScope API
|
||
|
|
- ``pydantic_settings`` provides the real ``Settings`` class with
|
||
|
|
paths to the prod-DB and the embeddings-DB
|
||
|
|
|
||
|
|
This conftest does NOT replace those modules. It only sets up:
|
||
|
|
|
||
|
|
- The ``app`` package import path so ``from app.parlamente import ...``
|
||
|
|
works when pytest is invoked from the webapp/ root
|
||
|
|
- A skip-on-import-error guard for tests that need a particular
|
||
|
|
optional dep but don't want to crash the whole collection if it
|
||
|
|
isn't installed locally
|
||
|
|
|
||
|
|
A test that runs in this directory must therefore have a real
|
||
|
|
``pip install -r requirements.txt -r requirements-dev.txt`` setup. The
|
||
|
|
``@pytest.mark.integration`` marker on every test in this directory
|
||
|
|
ensures the default ``pytest`` invocation skips them.
|
||
|
|
"""
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
# Make the `app` package importable when pytest is run from the webapp/ root.
|
||
|
|
ROOT = Path(__file__).resolve().parent.parent.parent
|
||
|
|
sys.path.insert(0, str(ROOT))
|
||
|
|
|
||
|
|
|
||
|
|
# The parent ``tests/conftest.py`` aggressively stubs ``fitz``, ``bs4``,
|
||
|
|
# ``openai`` and ``pydantic_settings`` in sys.modules so the unit suite
|
||
|
|
# can run without prod-requirements. Pytest loads parent conftests
|
||
|
|
# *first*, so by the time control reaches this file the stubs are
|
||
|
|
# already in place.
|
||
|
|
#
|
||
|
|
# For integration tests we want to use the *real* modules where they're
|
||
|
|
# installed. Strategy: per stubbed module, try to import the real one
|
||
|
|
# (after temporarily removing the stub from sys.modules). If the real
|
||
|
|
# module is available, keep it; if not, restore the stub so collection
|
||
|
|
# doesn't crash on import — individual tests that need the real module
|
||
|
|
# will skip via ``pytest.require_module(...)``.
|
||
|
|
_OPTIONAL_REAL_MODULES = ("fitz", "bs4", "openai", "pydantic_settings")
|
||
|
|
import importlib
|
||
|
|
|
||
|
|
_STUB_MODULES: dict[str, object] = {}
|
||
|
|
for _name in _OPTIONAL_REAL_MODULES:
|
||
|
|
_stub = sys.modules.pop(_name, None)
|
||
|
|
try:
|
||
|
|
importlib.import_module(_name)
|
||
|
|
# Real module found and now lives in sys.modules — drop the stub.
|
||
|
|
except ImportError:
|
||
|
|
# No real module available; restore the stub so unrelated
|
||
|
|
# imports of e.g. ``app.embeddings`` (which does ``from openai
|
||
|
|
# import OpenAI`` at module level) don't crash collection.
|
||
|
|
if _stub is not None:
|
||
|
|
sys.modules[_name] = _stub
|
||
|
|
_STUB_MODULES[_name] = _stub
|
||
|
|
del _name, _stub, importlib
|
||
|
|
|
||
|
|
|
||
|
|
def _require(module_name: str) -> None:
|
||
|
|
"""Skip the calling test if an optional dependency isn't installed
|
||
|
|
or is currently still represented by the parent-conftest stub.
|
||
|
|
|
||
|
|
Use as ``pytest.require_module("fitz")`` at the top of a test that
|
||
|
|
needs PyMuPDF.
|
||
|
|
"""
|
||
|
|
if module_name in _STUB_MODULES:
|
||
|
|
pytest.skip(
|
||
|
|
f"integration test skipped: real {module_name!r} not installed "
|
||
|
|
"in this environment (parent conftest stub still active)"
|
||
|
|
)
|
||
|
|
try:
|
||
|
|
__import__(module_name)
|
||
|
|
except ImportError as e:
|
||
|
|
pytest.skip(f"integration test skipped: {module_name} not installed ({e})")
|
||
|
|
|
||
|
|
|
||
|
|
# Make the helper available on the pytest module namespace
|
||
|
|
pytest.require_module = _require # type: ignore[attr-defined]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="session")
|
||
|
|
def webapp_root() -> Path:
|
||
|
|
"""The webapp/ directory root, useful for resolving fixture paths."""
|
||
|
|
return ROOT
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="session")
|
||
|
|
def referenzen_dir(webapp_root: Path) -> Path:
|
||
|
|
"""The static/referenzen directory containing all Wahlprogramm-PDFs."""
|
||
|
|
return webapp_root / "app" / "static" / "referenzen"
|