"""Test fixtures and configuration.""" from datetime import datetime import pytest from sqlalchemy import Integer, String, create_engine from sqlalchemy.engine import Connection from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from abacus_models.core.contexts import set_user_context from abacus_models.core.mixins.base_mixin import BaseMixin from abacus_models.core.mixins.create_mixin import CreateMixin from abacus_models.core.mixins.soft_delete_mixin import SoftDeleteMixin from abacus_models.core.mixins.update_mixin import UpdateMixin from abacus_models.core.types.normalized_date_time import NormalizedDateTime class Base(DeclarativeBase): """Base class for test models.""" pass class TestModelFull(Base, BaseMixin, CreateMixin, UpdateMixin, SoftDeleteMixin): """Test model with all mixins and tracking fields.""" __tablename__ = 'test_model_full' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) created_at: Mapped[datetime | None] = mapped_column(NormalizedDateTime) created_by: Mapped[str | None] = mapped_column(String(100)) last_modified: Mapped[datetime | None] = mapped_column(NormalizedDateTime) last_modified_by: Mapped[str | None] = mapped_column(String(100)) deleted_at: Mapped[datetime | None] = mapped_column(NormalizedDateTime) deleted_by: Mapped[str | None] = mapped_column(String(100)) class TestModelMinimal(Base, BaseMixin): """Test model with only base mixin (no tracking fields).""" __tablename__ = 'test_model_minimal' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) class TestModelCompositeKey(Base, BaseMixin): """Test model with composite primary key.""" __tablename__ = 'test_model_composite' id1: Mapped[int] = mapped_column(Integer, primary_key=True) id2: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(100)) class TestModelCreateOnly(Base, BaseMixin, CreateMixin): """Test model with only create tracking.""" __tablename__ = 'test_model_create' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) created_at: Mapped[datetime | None] = mapped_column(NormalizedDateTime) created_by: Mapped[str | None] = mapped_column(String(100)) class TestModelUpdateOnly(Base, BaseMixin, UpdateMixin): """Test model with only update tracking.""" __tablename__ = 'test_model_update' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) last_modified: Mapped[datetime | None] = mapped_column(NormalizedDateTime) last_modified_by: Mapped[str | None] = mapped_column(String(100)) class TestModelSoftDeleteOnly(Base, BaseMixin, SoftDeleteMixin): """Test model with only soft delete tracking.""" __tablename__ = 'test_model_soft_delete' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) deleted_at: Mapped[datetime | None] = mapped_column(NormalizedDateTime) deleted_by: Mapped[str | None] = mapped_column(String(100)) class TestModelDeletedAtOnly(Base, BaseMixin, SoftDeleteMixin): """Test model with only deleted_at (no deleted_by).""" __tablename__ = 'test_model_deleted_at' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) deleted_at: Mapped[datetime | None] = mapped_column(NormalizedDateTime) class TestModelWithDefaultFilter(Base, BaseMixin): """Test model with default filter implementation.""" __tablename__ = 'test_model_with_filter' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) is_active: Mapped[bool | None] = mapped_column(Integer, default=1) @classmethod def _get_default_filter(cls): """Filter for active records only.""" return cls.is_active == 1 class TestModelWithDefaultOrder(Base, BaseMixin): """Test model with default ordering implementation.""" __tablename__ = 'test_model_with_order' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) priority: Mapped[int | None] = mapped_column(Integer, default=0) @classmethod def _get_default_order(cls): """Order by priority descending, then name ascending.""" return [cls.priority.desc(), cls.name.asc()] class TestModelDeletedByOnly(Base, BaseMixin, SoftDeleteMixin): """Test model with only deleted_by (no deleted_at).""" __tablename__ = 'test_model_deleted_by' id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100)) deleted_by: Mapped[str | None] = mapped_column(String(100)) @pytest.fixture(scope='function') def engine(): """Create in-memory SQLite database engine.""" engine = create_engine('sqlite:///:memory:', echo=False) try: yield engine finally: engine.dispose() @pytest.fixture(scope='function') def session(engine): """Create database session with tables.""" Base.metadata.create_all(engine) with Session(engine) as session: yield session session.rollback() @pytest.fixture(scope='function') def connection(engine) -> Connection: """Create database connection with tables.""" Base.metadata.create_all(engine) with engine.connect() as conn: yield conn conn.rollback() @pytest.fixture def user_context(): """Set user context for tests.""" user_id = 'test_user_123' with set_user_context(user_id): yield user_id @pytest.fixture def sample_full_model(session, user_context): """Create a sample TestModelFull instance.""" model = TestModelFull(name='Test Model') session.add(model) session.commit() session.refresh(model) return model @pytest.fixture def sample_minimal_model(session): """Create a sample TestModelMinimal instance.""" model = TestModelMinimal(name='Minimal Model') session.add(model) session.commit() session.refresh(model) return model