|
| 1 | +"""Shared pytest fixtures for the Tofu test suite. |
| 2 | +
|
| 3 | +Provides the ``flask_client`` fixture consumed by ``tests/test_api_integration.py`` |
| 4 | +and ``tests/test_conversation_search.py``. |
| 5 | +
|
| 6 | +Design: |
| 7 | + * Each test session gets a fresh, isolated SQLite database via ``CHATUI_DB_PATH`` |
| 8 | + pointing at a temp file — no PostgreSQL required, no cross-test contamination. |
| 9 | + * The Flask ``app`` is imported lazily AFTER env-vars are set so |
| 10 | + ``lib.database._core`` picks the right backend at import time. |
| 11 | + * ``flask_client`` is function-scoped so each test gets a clean test client |
| 12 | + with its own cookie jar. |
| 13 | +""" |
| 14 | + |
| 15 | +from __future__ import annotations |
| 16 | + |
| 17 | +import os |
| 18 | +import tempfile |
| 19 | + |
| 20 | +import pytest |
| 21 | + |
| 22 | + |
| 23 | +# ─── Session-level: one SQLite DB per pytest run ────────────────────── |
| 24 | +@pytest.fixture(scope="session", autouse=True) |
| 25 | +def _configure_test_env(): |
| 26 | + """Set env vars BEFORE importing the Flask app so the DB layer picks |
| 27 | + SQLite and isolates data to a temp file. Trading features are disabled |
| 28 | + to keep the surface area small. |
| 29 | + """ |
| 30 | + tmpdir = tempfile.mkdtemp(prefix="tofu-test-") |
| 31 | + db_path = os.path.join(tmpdir, "chatui-test.db") |
| 32 | + |
| 33 | + os.environ.setdefault("CHATUI_DB_BACKEND", "sqlite") |
| 34 | + os.environ.setdefault("CHATUI_DB_PATH", db_path) |
| 35 | + os.environ.setdefault("TRADING_ENABLED", "0") |
| 36 | + os.environ.setdefault("PPTX_TRANSLATE_ENABLED", "0") |
| 37 | + # Avoid accidental real LLM calls in CI. |
| 38 | + os.environ.setdefault("LLM_API_KEY", "test-key-placeholder") |
| 39 | + os.environ.setdefault("LLM_API_KEYS", "test-key-placeholder") |
| 40 | + |
| 41 | + yield |
| 42 | + |
| 43 | + # Best-effort cleanup — don't fail the run if files are still locked. |
| 44 | + try: |
| 45 | + import shutil |
| 46 | + shutil.rmtree(tmpdir, ignore_errors=True) |
| 47 | + except Exception: |
| 48 | + pass |
| 49 | + |
| 50 | + |
| 51 | +# ─── Session-level: build the Flask app once ────────────────────────── |
| 52 | +@pytest.fixture(scope="session") |
| 53 | +def flask_app(_configure_test_env): |
| 54 | + """Import and return the Flask app AFTER env-vars are set. |
| 55 | +
|
| 56 | + This runs exactly once per test session so importing 800+ modules |
| 57 | + (server.py's full blueprint stack) doesn't dominate the wall-clock. |
| 58 | + """ |
| 59 | + import server # noqa: F401 — importing triggers app construction |
| 60 | + from server import app |
| 61 | + |
| 62 | + app.config.update( |
| 63 | + TESTING=True, |
| 64 | + # Disable Flask-Compress in tests — it can chunk responses in ways |
| 65 | + # the Werkzeug test client doesn't fully assemble. |
| 66 | + COMPRESS_REGISTER=False, |
| 67 | + ) |
| 68 | + return app |
| 69 | + |
| 70 | + |
| 71 | +# ─── Function-level: fresh test client per test ─────────────────────── |
| 72 | +@pytest.fixture() |
| 73 | +def flask_client(flask_app): |
| 74 | + """Return a Werkzeug test client with its own cookie jar. |
| 75 | +
|
| 76 | + Used by API integration tests and conversation-search tests. |
| 77 | + """ |
| 78 | + with flask_app.test_client() as client: |
| 79 | + yield client |
0 commit comments