- 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
214 lines
6.5 KiB
Python
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
|