tests: extract _build_session_factory to a shared conftest fixture
The same per-test sqlite-engine setup was duplicated across 14 test files (~30 lines each). Consolidated into a single async fixture `db_factory` in tests/conftest.py; tests now take db_factory as a parameter and use `async with db_factory() as session` directly. No behaviour change — same function-scope, same in-memory schema created via Base.metadata.create_all, same app.db._engine / _session_factory rebinding so module-level helpers see the test engine. Just ~420 lines of boilerplate removed.
This commit is contained in:
parent
b13caa4c51
commit
dcc2c07111
5 changed files with 167 additions and 250 deletions
|
|
@ -5,25 +5,6 @@ from __future__ import annotations
|
|||
import pytest
|
||||
|
||||
|
||||
def _build_session_factory(tmp_path):
|
||||
"""Per-test sqlite engine + factory. Mirrors test_referral_conversion.py."""
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from app import db as db_mod
|
||||
from app.db import Base
|
||||
import app.models # noqa: F401 — registers models on Base.metadata
|
||||
|
||||
engine = create_async_engine(f"sqlite+aiosqlite:///{tmp_path}/loc.db")
|
||||
factory = async_sessionmaker(engine, expire_on_commit=False)
|
||||
db_mod._engine = engine
|
||||
db_mod._session_factory = factory
|
||||
|
||||
async def _setup():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
return engine, factory, _setup
|
||||
|
||||
|
||||
def test_user_has_lang_column_with_default_en():
|
||||
from sqlalchemy import inspect
|
||||
|
|
@ -55,7 +36,7 @@ def test_strategic_log_translation_model_columns():
|
|||
assert cols["content_md"].nullable is False
|
||||
|
||||
|
||||
async def test_log_translation_fanout_no_active_non_en_users(tmp_path, monkeypatch):
|
||||
async def test_log_translation_fanout_no_active_non_en_users(db_factory, monkeypatch):
|
||||
"""When no users have an active non-en lang, the fan-out makes no
|
||||
translation calls and no rows are inserted."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
|
@ -65,8 +46,7 @@ async def test_log_translation_fanout_no_active_non_en_users(tmp_path, monkeypat
|
|||
from app.models import StrategicLog, StrategicLogTranslation, User
|
||||
from app.jobs import ai_log_job
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
fake_translate = AsyncMock()
|
||||
monkeypatch.setattr(ai_log_job, "translate", fake_translate)
|
||||
|
|
@ -92,7 +72,7 @@ async def test_log_translation_fanout_no_active_non_en_users(tmp_path, monkeypat
|
|||
assert rows == []
|
||||
|
||||
|
||||
async def test_log_translation_fanout_italian_user(tmp_path, monkeypatch):
|
||||
async def test_log_translation_fanout_italian_user(db_factory, monkeypatch):
|
||||
"""One user at lang=it triggers one translation; the row lands with
|
||||
the right lang and log_id."""
|
||||
from sqlalchemy import select
|
||||
|
|
@ -102,8 +82,7 @@ async def test_log_translation_fanout_italian_user(tmp_path, monkeypatch):
|
|||
from app.services.openrouter import LogResult
|
||||
from app.jobs import ai_log_job
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
async def _fake_translate(client, text, target_lang):
|
||||
assert target_lang == "it"
|
||||
|
|
@ -139,7 +118,7 @@ async def test_log_translation_fanout_italian_user(tmp_path, monkeypatch):
|
|||
assert row.llm_cost_usd == pytest.approx(0.00002)
|
||||
|
||||
|
||||
async def test_log_translation_fanout_per_language_failure_isolated(tmp_path, monkeypatch):
|
||||
async def test_log_translation_fanout_per_language_failure_isolated(db_factory, monkeypatch):
|
||||
"""If one language's translation fails, the others (if any) still land
|
||||
and the job does not raise."""
|
||||
from sqlalchemy import select
|
||||
|
|
@ -148,8 +127,7 @@ async def test_log_translation_fanout_per_language_failure_isolated(tmp_path, mo
|
|||
from app.models import StrategicLog, StrategicLogTranslation, User
|
||||
from app.jobs import ai_log_job
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
async def _fake_translate(client, text, target_lang):
|
||||
raise RuntimeError("upstream down")
|
||||
|
|
@ -175,7 +153,7 @@ async def test_log_translation_fanout_per_language_failure_isolated(tmp_path, mo
|
|||
assert rows == []
|
||||
|
||||
|
||||
async def test_analyse_threads_lang_into_system_prompt(tmp_path, monkeypatch):
|
||||
async def test_analyse_threads_lang_into_system_prompt(db_factory, monkeypatch):
|
||||
"""When lang='it', the system prompt sent to call_llm contains
|
||||
'Respond in Italian.' — the LLM does the rest."""
|
||||
from app.services import portfolio_analysis as pa
|
||||
|
|
@ -191,8 +169,7 @@ async def test_analyse_threads_lang_into_system_prompt(tmp_path, monkeypatch):
|
|||
)
|
||||
monkeypatch.setattr(pa, "call_llm", _fake_call_llm)
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
payload = {
|
||||
"positions": [{"yahoo_ticker": "AAPL", "qty": 10, "avg_cost": 150.0,
|
||||
|
|
@ -213,7 +190,7 @@ async def test_analyse_threads_lang_into_system_prompt(tmp_path, monkeypatch):
|
|||
assert "Respond in Italian" in system
|
||||
|
||||
|
||||
async def test_analyse_no_clause_when_lang_is_en(tmp_path, monkeypatch):
|
||||
async def test_analyse_no_clause_when_lang_is_en(db_factory, monkeypatch):
|
||||
from app.services import portfolio_analysis as pa
|
||||
from app.services.openrouter import LogResult
|
||||
|
||||
|
|
@ -227,8 +204,7 @@ async def test_analyse_no_clause_when_lang_is_en(tmp_path, monkeypatch):
|
|||
)
|
||||
monkeypatch.setattr(pa, "call_llm", _fake_call_llm)
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
payload = {
|
||||
"positions": [{"yahoo_ticker": "AAPL", "qty": 10, "avg_cost": 150.0,
|
||||
|
|
@ -328,13 +304,12 @@ def test_digest_pick_variant_uses_user_lang():
|
|||
|
||||
|
||||
|
||||
async def test_patch_language_accepts_active(tmp_path):
|
||||
async def test_patch_language_accepts_active(db_factory):
|
||||
"""PATCH /api/settings/language accepts 'en' and 'it' and persists."""
|
||||
from app.models import User
|
||||
from app.routers.api import patch_language_prefs, LanguagePrefsIn
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
async with factory() as session:
|
||||
session.add(User(id=20, email="u@x", tier="paid", lang="en"))
|
||||
|
|
@ -358,14 +333,13 @@ async def test_patch_language_accepts_active(tmp_path):
|
|||
assert user.lang == "it"
|
||||
|
||||
|
||||
async def test_patch_language_rejects_wip(tmp_path):
|
||||
async def test_patch_language_rejects_wip(db_factory):
|
||||
"""PATCH rejects 'es'/'fr'/'de'/'xx' with 400 — ACTIVE_LANGUAGES gate."""
|
||||
from fastapi import HTTPException
|
||||
from app.models import User
|
||||
from app.routers.api import patch_language_prefs, LanguagePrefsIn
|
||||
|
||||
_, factory, setup = _build_session_factory(tmp_path)
|
||||
await setup()
|
||||
factory = db_factory
|
||||
|
||||
async with factory() as session:
|
||||
session.add(User(id=21, email="u2@x", tier="paid", lang="en"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue