Skip to content

Commit 5fcb1dc

Browse files
committed
CI: fix healthcheck schema path, update stale thinking-hot-tail test, add flask_client fixture
1 parent 2dceacf commit 5fcb1dc

3 files changed

Lines changed: 107 additions & 13 deletions

File tree

healthcheck.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,31 @@ def warn(msg):
205205
'trading_intel_cache', 'trading_strategies',
206206
]
207207

208-
# Schema definitions moved from lib/database.py → lib/database/_schema.py
209-
_schema_file = 'lib/database/_schema.py'
210-
try:
211-
with open(_schema_file) as f:
212-
db_source = f.read()
208+
# Schema definitions moved from lib/database.py → lib/database/_schema_{pg,sqlite}.py.
209+
# We check BOTH backends: every required table must be defined in both files.
210+
_schema_files = ['lib/database/_schema_pg.py', 'lib/database/_schema_sqlite.py']
211+
_schema_sources = {}
212+
for _sf in _schema_files:
213+
try:
214+
with open(_sf) as f:
215+
_schema_sources[_sf] = f.read()
216+
except Exception as e:
217+
logger.warning('Failed to read %s: %s', _sf, e, exc_info=True)
218+
fail(f"Cannot read {_sf}: {e}")
219+
220+
if _schema_sources:
213221
for table in required_tables:
214-
if f"CREATE TABLE IF NOT EXISTS {table}" in db_source:
215-
ok(f"Table '{table}' defined in schema")
222+
# PG uses CREATE TABLE IF NOT EXISTS; SQLite also uses that form.
223+
# The trading_* tables may be gated by TRADING_ENABLED at runtime but
224+
# their DDL should still appear in the schema files.
225+
missing_in = [
226+
sf for sf, src in _schema_sources.items()
227+
if f"CREATE TABLE IF NOT EXISTS {table}" not in src
228+
]
229+
if not missing_in:
230+
ok(f"Table '{table}' defined in both backends")
216231
else:
217-
fail(f"Table '{table}' NOT found in schema")
218-
except Exception as e:
219-
logger.warning('Failed to read %s: %s', _schema_file, e, exc_info=True)
220-
fail(f"Cannot read {_schema_file}: {e}")
232+
fail(f"Table '{table}' NOT found in: {missing_in}")
221233

222234

223235
# ═══════════════════════════════════════════════════════════════════════

tests/conftest.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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

tests/test_compaction_improvements.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,17 @@ def _make_messages(self, n_assistants=10, thinking_size=5000):
150150

151151
def test_strips_old_thinking_blocks(self):
152152
from lib.tasks_pkg.compaction import _THINKING_HOT_TAIL, micro_compact
153-
messages = self._make_messages(n_assistants=10, thinking_size=5000)
153+
# Need more than _THINKING_HOT_TAIL assistants for stripping to kick in.
154+
# (Stripping only applies to the cold tail beyond the hot tail.)
155+
n_total = _THINKING_HOT_TAIL + 5
156+
messages = self._make_messages(n_assistants=n_total, thinking_size=5000)
154157

155158
# Count reasoning_content before
156159
rc_before = sum(
157160
1 for m in messages
158161
if m.get('role') == 'assistant' and m.get('reasoning_content')
159162
)
160-
assert rc_before == 10
163+
assert rc_before == n_total
161164

162165
saved = micro_compact(messages, conv_id='test123')
163166
assert saved > 0

0 commit comments

Comments
 (0)