"""SQLite in-memory fixtures for API tests.""" import os # Must be set before app.database imports create_engine os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") os.environ.setdefault("SAC_BOOTSTRAP_API_KEY", "sac_test_key_for_pytest_only") os.environ.setdefault("SAC_SECURITY_ENFORCE", "false") os.environ.setdefault("JWT_SECRET", "pytest-jwt-secret-not-for-production-use") os.environ.setdefault("SAC_SSH_AUTO_ADD_HOST_KEY", "true") os.environ.setdefault("SAC_HOST_SILENCE_SCAN_ENABLED", "false") import pytest from fastapi.testclient import TestClient from sqlalchemy import JSON, create_engine, event from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import StaticPool from app.auth.api_key import hash_api_key from app.auth.jwt_auth import create_access_token from app.database import Base, get_db from app.main import app as fastapi_app import app.models # noqa: F401 — register all tables on Base.metadata from app.models import ApiKey, User from app.models.user import USER_ROLE_ADMIN, USER_ROLE_MONITOR from app.services.user_auth import hash_password TEST_API_KEY = os.environ["SAC_BOOTSTRAP_API_KEY"] @event.listens_for(Base.metadata, "before_create") def _sqlite_jsonb_as_json(metadata, connection, **_kwargs) -> None: if connection.dialect.name != "sqlite": return for table in metadata.tables.values(): for column in table.columns: if isinstance(column.type, JSONB): column.type = JSON() @pytest.fixture def db_engine(): engine = create_engine( "sqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) yield engine engine.dispose() @pytest.fixture def db_session(db_engine): Session = sessionmaker(bind=db_engine, autocommit=False, autoflush=False) session = Session() session.add( ApiKey( name="test", key_prefix=TEST_API_KEY[:12], key_hash=hash_api_key(TEST_API_KEY), is_active=True, ) ) session.add( User( username="test-admin", password_hash=hash_password("test-admin-password"), role=USER_ROLE_ADMIN, is_active=True, ) ) session.add( User( username="test-monitor", password_hash=hash_password("test-monitor-password"), role=USER_ROLE_MONITOR, is_active=True, ) ) session.commit() yield session session.close() @pytest.fixture(autouse=True) def mock_agent_git_release(monkeypatch): """API tests must not clone real git repos.""" from app.services.agent_git_release import GitReleaseVersions empty = GitReleaseVersions(versions={}, fetched_at=None, from_cache=True) monkeypatch.setattr( "app.services.agent_git_release.get_git_release_versions", lambda cfg, **kwargs: empty, ) monkeypatch.setattr( "app.api.v1.hosts.get_git_release_versions", lambda cfg, **kwargs: empty, ) monkeypatch.setattr( "app.api.v1.settings.get_git_release_versions", lambda cfg, **kwargs: empty, ) monkeypatch.setattr( "app.services.agent_update.get_git_release_versions", lambda cfg, **kwargs: empty, ) @pytest.fixture(autouse=True) def reset_remote_action_state(): from app.services import host_remote_actions with host_remote_actions._lock: host_remote_actions._running.clear() yield with host_remote_actions._lock: host_remote_actions._running.clear() @pytest.fixture def client(db_session, db_engine, monkeypatch): monkeypatch.setenv("SAC_REMOTE_ACTION_INLINE", "1") monkeypatch.setattr("app.main.bootstrap_api_key", lambda: None) monkeypatch.setattr("app.main.bootstrap_users", lambda: None) monkeypatch.setattr("app.main.bootstrap_stale_remote_actions", lambda: None) test_session_local = sessionmaker(bind=db_engine, autocommit=False, autoflush=False) monkeypatch.setattr("app.services.host_remote_actions.SessionLocal", test_session_local) def override_get_db(): session = test_session_local() try: yield session finally: session.close() fastapi_app.dependency_overrides[get_db] = override_get_db with TestClient(fastapi_app) as c: yield c fastapi_app.dependency_overrides.clear() @pytest.fixture def auth_headers(): return {"Authorization": f"Bearer {TEST_API_KEY}"} @pytest.fixture def jwt_headers(): token = create_access_token("test-admin", USER_ROLE_ADMIN) return {"Authorization": f"Bearer {token}"} @pytest.fixture def jwt_monitor_headers(): token = create_access_token("test-monitor", USER_ROLE_MONITOR) return {"Authorization": f"Bearer {token}"}