""" Integration tests for locale-aware template API endpoints. Tests: - GET /api/portal/templates (no locale) returns English fields - GET /api/portal/templates?locale=es returns Spanish-translated fields - GET /api/portal/templates?locale=pt returns Portuguese-translated fields - GET /api/portal/templates?locale=fr falls back to English - Translated fields overlay English base, English values still in DB Uses the same pattern as existing integration tests: - Session override via app.dependency_overrides - X-Portal-User-Id / X-Portal-User-Role header injection - db_session fixture from tests/conftest.py (Alembic migrations applied) """ from __future__ import annotations import uuid import pytest import pytest_asyncio from fastapi import FastAPI from httpx import ASGITransport, AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from shared.api.portal import portal_router from shared.api.templates import templates_router from shared.db import get_session from shared.models.tenant import AgentTemplate # --------------------------------------------------------------------------- # App factory # --------------------------------------------------------------------------- def make_app(session: AsyncSession) -> FastAPI: """Build a minimal FastAPI test app with portal + templates routers.""" app = FastAPI() app.include_router(portal_router) app.include_router(templates_router) async def override_get_session(): # type: ignore[return] yield session app.dependency_overrides[get_session] = override_get_session return app # --------------------------------------------------------------------------- # Header helpers # --------------------------------------------------------------------------- def platform_admin_headers(user_id: uuid.UUID) -> dict[str, str]: return { "X-Portal-User-Id": str(user_id), "X-Portal-User-Role": "platform_admin", } # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest_asyncio.fixture async def i18n_client(db_session: AsyncSession) -> AsyncClient: """HTTP client with portal + templates router mounted.""" app = make_app(db_session) async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c @pytest_asyncio.fixture def admin_user_id() -> uuid.UUID: """Fixed UUID for a fake platform_admin — no DB row needed for header-based auth.""" return uuid.UUID("00000000-0000-0000-0000-000000000099") # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_list_templates_default_locale( i18n_client: AsyncClient, admin_user_id: uuid.UUID, ) -> None: """GET /api/portal/templates (no locale param) returns English fields.""" headers = platform_admin_headers(admin_user_id) response = await i18n_client.get("/api/portal/templates", headers=headers) assert response.status_code == 200 templates = response.json() assert len(templates) >= 7 # All returned names should be English (Customer Support Rep is the first by sort_order) names = {t["name"] for t in templates} assert "Customer Support Rep" in names, f"Expected English template name, got: {names}" @pytest.mark.asyncio async def test_list_templates_spanish( i18n_client: AsyncClient, admin_user_id: uuid.UUID, ) -> None: """GET /api/portal/templates?locale=es returns Spanish-translated name/description/persona.""" headers = platform_admin_headers(admin_user_id) response = await i18n_client.get("/api/portal/templates?locale=es", headers=headers) assert response.status_code == 200 templates = response.json() assert len(templates) >= 7 # Find the Customer Support Rep template (ID: 000...001) support_tmpl = next( (t for t in templates if "soporte" in t["name"].lower() or "support" in t["name"].lower()), None, ) assert support_tmpl is not None, f"Customer support template not found in: {[t['name'] for t in templates]}" # Spanish name should differ from English assert support_tmpl["name"] != "Customer Support Rep", ( f"Expected Spanish translation, got English name: {support_tmpl['name']!r}" ) # Spanish description should be present and non-empty assert len(support_tmpl["description"]) > 10 @pytest.mark.asyncio async def test_list_templates_portuguese( i18n_client: AsyncClient, admin_user_id: uuid.UUID, ) -> None: """GET /api/portal/templates?locale=pt returns Portuguese-translated fields.""" headers = platform_admin_headers(admin_user_id) response = await i18n_client.get("/api/portal/templates?locale=pt", headers=headers) assert response.status_code == 200 templates = response.json() assert len(templates) >= 7 # Find the Customer Support Rep template support_tmpl = next( (t for t in templates if "suporte" in t["name"].lower() or "support" in t["name"].lower()), None, ) assert support_tmpl is not None, f"Customer support template not found in: {[t['name'] for t in templates]}" # Portuguese name should differ from English assert support_tmpl["name"] != "Customer Support Rep", ( f"Expected Portuguese translation, got English name: {support_tmpl['name']!r}" ) # Portuguese description should be present and non-empty assert len(support_tmpl["description"]) > 10 @pytest.mark.asyncio async def test_list_templates_unsupported_locale( i18n_client: AsyncClient, admin_user_id: uuid.UUID, ) -> None: """GET /api/portal/templates?locale=fr falls back to English.""" headers = platform_admin_headers(admin_user_id) response = await i18n_client.get("/api/portal/templates?locale=fr", headers=headers) assert response.status_code == 200 templates = response.json() assert len(templates) >= 7 # Names should be English (fallback) names = {t["name"] for t in templates} assert "Customer Support Rep" in names, ( f"Expected English fallback for unsupported locale 'fr', got names: {names}" ) @pytest.mark.asyncio async def test_template_translations_overlay( i18n_client: AsyncClient, admin_user_id: uuid.UUID, db_session: AsyncSession, ) -> None: """Translated fields overlay English, English base fields still in DB.""" headers = platform_admin_headers(admin_user_id) # Get Spanish-translated templates es_response = await i18n_client.get("/api/portal/templates?locale=es", headers=headers) assert es_response.status_code == 200 es_templates = es_response.json() # Get English templates (default) en_response = await i18n_client.get("/api/portal/templates", headers=headers) assert en_response.status_code == 200 en_templates = en_response.json() # Find the support template in both es_support = next((t for t in es_templates if "soporte" in t["name"].lower()), None) en_support = next((t for t in en_templates if t["name"] == "Customer Support Rep"), None) assert es_support is not None, "Spanish support template not found" assert en_support is not None, "English support template not found" # They should share the same template ID assert es_support["id"] == en_support["id"], "Template IDs should match across locales" # Names should differ between locales assert es_support["name"] != en_support["name"], ( "Spanish and English names should differ for Customer Support Rep template" ) # English base values must still be present in DB (not overwritten) result = await db_session.execute( select(AgentTemplate).where( AgentTemplate.id == uuid.UUID(en_support["id"]) ) ) tmpl_orm = result.scalar_one_or_none() assert tmpl_orm is not None assert tmpl_orm.name == "Customer Support Rep", ( f"DB English name should be unchanged, got: {tmpl_orm.name!r}" )