Files
ss-tools/backend/tests/integration/conftest.py
busya 143f14d516 chore: remainder — backend test infra, agent config, docker, i18n, frontend ui
- Backend: alembic env, config manager/models, dependencies, translate plugin
- Backend tests: async_sync_regression, integration tests, git services, test_agent
- Docker: docker-compose.yml updates
- Agent: qa-tester.md update, semantics-testing SKILL.md update
- Frontend: TopNavbar, sidebarNavigation, FeaturesSettings, FeatureGate
- i18n: assistant.json en/ru locale updates
- New: frontend/src/lib/components/agent/ directory
2026-06-10 15:06:36 +03:00

214 lines
6.5 KiB
Python

# #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