read.markets/tests/test_localization_integration.py
Giorgio Gilestro e4982cdc04 ai-log-job: translate strategic log for active non-en languages
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:57:06 +02:00

178 lines
6.1 KiB
Python

"""Integration tests: model surface, ai_log_job translation fan-out,
route-level localized fetch, settings PATCH validation."""
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
from app.models import User
cols = {c.name: c for c in inspect(User).columns}
assert "lang" in cols
assert cols["lang"].nullable is False
# SQLAlchemy default may be a callable or a literal — check both.
default = cols["lang"].default
assert default is not None
if hasattr(default, "arg"):
assert default.arg == "en"
def test_strategic_log_translation_model_columns():
from sqlalchemy import inspect
from app.models import StrategicLogTranslation
cols = {c.name: c for c in inspect(StrategicLogTranslation).columns}
assert "log_id" in cols
assert "lang" in cols
assert "content_md" in cols
assert "generated_at" in cols
assert "llm_model" in cols
assert "llm_cost_usd" in cols
assert cols["log_id"].nullable is False
assert cols["lang"].nullable is False
assert cols["content_md"].nullable is False
@pytest.mark.asyncio
async def test_log_translation_fanout_no_active_non_en_users(tmp_path, 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
from sqlalchemy import select
from app.db import utcnow
from app.models import StrategicLog, StrategicLogTranslation, User
from app.jobs import ai_log_job
_, factory, setup = _build_session_factory(tmp_path)
await setup()
fake_translate = AsyncMock()
monkeypatch.setattr(ai_log_job, "translate", fake_translate)
# Seed an English user (no non-en users).
async with factory() as session:
session.add(User(id=1, email="en@x", tier="paid", lang="en"))
slog = StrategicLog(
generated_at=utcnow(), content="# Open\n\nDown 0.4%.",
model="test-model",
tone="INTERMEDIATE", analysis="NORMAL",
)
session.add(slog)
await session.commit()
log_id = slog.id
async with factory() as session:
await ai_log_job.translate_log_for_active_languages(session, log_id)
fake_translate.assert_not_awaited()
async with factory() as session:
rows = (await session.execute(select(StrategicLogTranslation))).scalars().all()
assert rows == []
@pytest.mark.asyncio
async def test_log_translation_fanout_italian_user(tmp_path, monkeypatch):
"""One user at lang=it triggers one translation; the row lands with
the right lang and log_id."""
from sqlalchemy import select
from app.db import utcnow
from app.models import StrategicLog, StrategicLogTranslation, User
from app.services.openrouter import LogResult
from app.jobs import ai_log_job
_, factory, setup = _build_session_factory(tmp_path)
await setup()
async def _fake_translate(client, text, target_lang):
assert target_lang == "it"
return "# Apertura\n\nIn calo 0,4%.", LogResult(
content="# Apertura\n\nIn calo 0,4%.",
model="deepseek/deepseek-v4-flash",
prompt_tokens=300, completion_tokens=80, cost_usd=0.00002,
)
monkeypatch.setattr(ai_log_job, "translate", _fake_translate)
async with factory() as session:
session.add(User(id=2, email="it@x", tier="paid", lang="it"))
slog = StrategicLog(
generated_at=utcnow(), content="# Open\n\nDown 0.4%.",
model="test-model",
tone="INTERMEDIATE", analysis="NORMAL",
)
session.add(slog)
await session.commit()
log_id = slog.id
async with factory() as session:
await ai_log_job.translate_log_for_active_languages(session, log_id)
async with factory() as session:
rows = (await session.execute(select(StrategicLogTranslation))).scalars().all()
assert len(rows) == 1
row = rows[0]
assert row.log_id == log_id
assert row.lang == "it"
assert row.content_md.startswith("# Apertura")
assert row.llm_model == "deepseek/deepseek-v4-flash"
assert row.llm_cost_usd == pytest.approx(0.00002)
@pytest.mark.asyncio
async def test_log_translation_fanout_per_language_failure_isolated(tmp_path, monkeypatch):
"""If one language's translation fails, the others (if any) still land
and the job does not raise."""
from sqlalchemy import select
from app.db import utcnow
from app.models import StrategicLog, StrategicLogTranslation, User
from app.jobs import ai_log_job
_, factory, setup = _build_session_factory(tmp_path)
await setup()
async def _fake_translate(client, text, target_lang):
raise RuntimeError("upstream down")
monkeypatch.setattr(ai_log_job, "translate", _fake_translate)
async with factory() as session:
session.add(User(id=3, email="it@x", tier="paid", lang="it"))
slog = StrategicLog(
generated_at=utcnow(), content="# Open",
model="test-model",
tone="INTERMEDIATE", analysis="NORMAL",
)
session.add(slog)
await session.commit()
log_id = slog.id
# Must NOT raise.
async with factory() as session:
await ai_log_job.translate_log_for_active_languages(session, log_id)
async with factory() as session:
rows = (await session.execute(select(StrategicLogTranslation))).scalars().all()
assert rows == []