from pathlib import Path import pytest from alembic import command from alembic.config import Config from sqlalchemy import create_engine, func, select from sqlalchemy.orm import Session, sessionmaker from app.core.config import get_settings from app.core.config import Settings from app.core.database import Base from app.core.security import OperationsDisabledError from app.modules.audit.models import AuditLog from app.modules.audit.schemas import AuditLogCreate from app.modules.audit.service import AuditService from app.modules.events.models import DomainEvent from app.modules.events.services import EventService from app.modules.risk.services import RiskService def test_audit_and_outbox_rollback_with_business_transaction() -> None: engine = create_engine("sqlite://") Base.metadata.create_all(engine) session_factory = sessionmaker(bind=engine, expire_on_commit=False) with session_factory() as db: AuditService(db).record(AuditLogCreate(action="transaction.rollback")) EventService(db).enqueue( event_type="test.rollback", source="pytest", aggregate_type="test", aggregate_id="rollback", idempotency_key="test-transaction-rollback", ) db.rollback() with session_factory() as db: assert db.scalar(select(func.count()).select_from(AuditLog)) == 0 assert db.scalar(select(func.count()).select_from(DomainEvent)) == 0 def test_business_service_enforces_read_only_policy(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("READ_ONLY_MODE", "true") get_settings.cache_clear() try: with pytest.raises(OperationsDisabledError): RiskService(Session()).generate_events(actor="pytest") finally: get_settings.cache_clear() def test_production_settings_fail_closed() -> None: with pytest.raises(ValueError, match="PostgreSQL"): Settings( app_env="production", database_url="sqlite:///unsafe.db", api_key="api", audit_api_key="audit", cors_origins=["https://internal.example.com"], ) def test_migrations_match_sqlalchemy_metadata( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: database_path = tmp_path / "migration-check.db" monkeypatch.setenv("DATABASE_URL", f"sqlite:///{database_path}") get_settings.cache_clear() config = Config("alembic.ini") try: command.upgrade(config, "head") command.check(config) finally: get_settings.cache_clear()