# #region IntegrationTestConftest [C:3] [TYPE Module] [SEMANTICS test, conftest, testcontainers, postgres, integration] # @BRIEF Shared pytest fixtures for integration tests using Testcontainers PostgreSQL. # @RELATION BINDS_TO -> [TranslationJob] # @RELATION BINDS_TO -> [TranslationRun] # @RELATION BINDS_TO -> [TerminologyDictionary] # @PRE Docker daemon is running and accessible. # @POST PostgreSQL container is started, tables created, session available per test. # @RATIONALE # Architecture: # - Testcontainers spins up a real PostgreSQL 16 container per session. # - Each test gets an isolated session with transaction rollback. # - FK constraints, JSON columns, indexes — all behave like production. # # Why Testcontainers over SQLite: # - SQLite silently ignores FK violations in some edge cases. # - JSON column behavior differs (PostgreSQL JSONB vs SQLite JSON text). # - Index behavior, constraint names, and error messages differ. # - Production parity — catches bugs that SQLite tests miss. # # @REJECTED # Always-on PostgreSQL — requires manual setup, not portable. # Docker Compose for tests — slower startup, harder isolation. # Shared container across tests — state leakage between tests. import os import sys from pathlib import Path import pytest from sqlalchemy import create_engine, event, text from sqlalchemy.orm import Session, sessionmaker sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) from src.core.database import Base # Import all models to ensure tables are created from src.models.translate import ( # noqa: F401 DictionaryEntry, MetricSnapshot, TerminologyDictionary, TranslationBatch, TranslationEvent, TranslationJob, TranslationJobDictionary, TranslationLanguage, TranslationPreviewLanguage, TranslationPreviewRecord, TranslationPreviewSession, TranslationRecord, TranslationRun, TranslationRunLanguageStats, TranslationSchedule, ) from src.models.llm import ( # noqa: F401 LLMProvider, ValidationPolicy, ValidationRun, ValidationRecord, ValidationSource, ) from src.models.maintenance import ( # noqa: F401 MaintenanceEvent, MaintenanceDashboardBanner, MaintenanceDashboardState, MaintenanceSettings, MaintenanceEventStatus, MaintenanceDashboardBannerStatus, MaintenanceDashboardStateStatus, DashboardScope, ) from src.models.task import ( # noqa: F401 TaskRecord, TaskLogRecord, ) from src.models.mapping import Environment # noqa: F401 # #region postgres_container [C:2] [TYPE Fixture] # @BRIEF Session-scoped Testcontainers PostgreSQL container. @pytest.fixture(scope="session") def postgres_container(): """Start a PostgreSQL 16 container for the test session.""" from testcontainers.postgres import PostgresContainer with PostgresContainer( image="postgres:16-alpine", username="test", password="test", dbname="test_translate", ) as pg: yield pg # #endregion postgres_container # #region pg_engine [C:2] [TYPE Fixture] # @BRIEF SQLAlchemy engine connected to the Testcontainers PostgreSQL. @pytest.fixture(scope="session") def pg_engine(postgres_container): """Create engine and tables once per session.""" url = postgres_container.get_connection_url() engine = create_engine(url, echo=False) # Create all tables Base.metadata.create_all(bind=engine) yield engine # Cleanup Base.metadata.drop_all(bind=engine) engine.dispose() # #endregion pg_engine # #region db_session [C:2] [TYPE Fixture] # @BRIEF Per-test database session with transaction rollback for isolation. @pytest.fixture def db_session(pg_engine): """Provide a transactional database session that rolls back after each test. This ensures test isolation — each test starts with a clean database state. Uses connection-level transaction to wrap all operations. """ connection = pg_engine.connect() transaction = connection.begin() session = sessionmaker(bind=connection)() # Join the outer transaction session.begin_nested() @event.listens_for(session, "after_transaction_end") def restart_savepoint(session, transaction): if transaction.nested and not transaction._parent.nested: session.begin_nested() try: yield session finally: session.close() transaction.rollback() connection.close() # #endregion db_session # #region mock_config_manager [C:2] [TYPE Fixture] # @BRIEF Mock ConfigManager for integration tests. @pytest.fixture def mock_config_manager(): """Provide a mock ConfigManager that returns test environment.""" from unittest.mock import MagicMock from src.core.config_models import Environment config_manager = MagicMock() config_manager.get_environments.return_value = [ Environment( id="test_env", name="Test Environment", url="http://superset:8088", username="admin", password="admin", ) ] config_manager.get_environment.return_value = Environment( id="test_env", name="Test Environment", url="http://superset:8088", username="admin", password="admin", ) config_manager.get_config.return_value = MagicMock() return config_manager # #endregion mock_config_manager # #region verify_postgres_features [C:2] [TYPE Function] # @BRIEF Helper to verify PostgreSQL-specific features are working. def verify_postgres_features(session: Session) -> dict: """Verify that PostgreSQL-specific features are available. Returns a dict with feature availability status. """ features = {} # Check JSON support try: result = session.execute(text("SELECT '{}'::jsonb")).scalar() features["jsonb"] = result is not None except Exception: features["jsonb"] = False # Check FK constraints try: result = session.execute(text( "SELECT COUNT(*) FROM information_schema.table_constraints " "WHERE constraint_type = 'FOREIGN KEY'" )).scalar() features["foreign_keys"] = result > 0 except Exception: features["foreign_keys"] = False # Check indexes try: result = session.execute(text( "SELECT COUNT(*) FROM pg_indexes WHERE schemaname = 'public'" )).scalar() features["indexes"] = result > 0 except Exception: features["indexes"] = False return features # #endregion verify_postgres_features # #endregion IntegrationTestConftest