"""Nexus — Test Configuration (pytest + async fixtures) Fixed: wired up dependency_overrides properly, added test Redis mock, added admin fixture for JWT auth tests. Test layers (see docs/testing/test-case-matrix.md): unit / integration / chain / slow — register via pytest_configure below. """ import asyncio import uuid import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from server.config import settings from server.domain.models import Admin, Base, Server def pytest_configure(config): config.addinivalue_line("markers", "unit: L1 isolated logic (docs/testing/test-case-matrix.md)") config.addinivalue_line("markers", "integration: L2 API + DB/Redis") config.addinivalue_line("markers", "chain: L3 multi-step business flows") config.addinivalue_line("markers", "slow: >5s or needs live stack; skip in fast CI") # Use in-memory SQLite for unit/integration tests TEST_DATABASE_URL = "sqlite+aiosqlite://" class _RequestSessionCtx: """New SQLite session per HTTP request (avoids MissingGreenlet).""" def __init__(self, session_factory): self._session_factory = session_factory async def __aenter__(self) -> AsyncSession: self._session = self._session_factory() return self._session async def __aexit__(self, exc_type, exc, tb) -> None: if exc: await self._session.rollback() await self._session.close() @pytest.fixture(scope="session") def event_loop(): loop = asyncio.new_event_loop() yield loop loop.close() @pytest_asyncio.fixture(scope="function") async def test_engine(): engine = create_async_engine(TEST_DATABASE_URL, echo=False) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await engine.dispose() @pytest.fixture def auth_headers(test_admin): return {"Authorization": f"Bearer {test_admin['token']}"} @pytest.fixture def integration_env(test_engine, test_admin, monkeypatch): """Wire middleware DB + JWT to in-memory fixtures (IT/CH tests).""" session_factory = async_sessionmaker( test_engine, class_=AsyncSession, expire_on_commit=False, ) monkeypatch.setattr( "server.main.AsyncSessionLocal", lambda: _RequestSessionCtx(session_factory), ) admin = test_admin["admin"] token = test_admin["token"] async def fake_verify(jwt_token, request): return admin if jwt_token == token else None monkeypatch.setattr("server.api.auth_jwt._verify_token", fake_verify) @pytest_asyncio.fixture(scope="function") async def db_session(test_engine): session_factory = async_sessionmaker( test_engine, class_=AsyncSession, expire_on_commit=False, ) session = session_factory() try: yield session finally: await session.close() @pytest_asyncio.fixture(scope="function") async def api_client(db_session): """Create an async HTTP client for API integration tests""" from server.main import app from server.api.dependencies import get_db async def override_get_session(): yield db_session app.dependency_overrides[get_db] = override_get_session transport = ASGITransport(app=app) client = AsyncClient(transport=transport, base_url="http://test") yield client await client.aclose() app.dependency_overrides.clear() @pytest_asyncio.fixture async def test_admin(db_session): """Create a test admin user and return with JWT token""" import bcrypt from server.application.services.auth_service import AuthService from server.infrastructure.database.admin_repo import AdminRepositoryImpl from server.infrastructure.database.audit_log_repo import AuditLogRepositoryImpl # LoginAttemptRepositoryImpl requires login_attempt_repo which may not exist in test DB # Use a simple mock approach from unittest.mock import MagicMock password_hash = bcrypt.hashpw("test123".encode(), bcrypt.gensalt()).decode() admin = Admin( username=f"testadmin_{uuid.uuid4().hex[:8]}", password_hash=password_hash, is_active=True, totp_enabled=False, ) db_session.add(admin) await db_session.commit() await db_session.refresh(admin) auth_service = AuthService( admin_repo=AdminRepositoryImpl(db_session), attempt_repo=MagicMock(), audit_repo=AuditLogRepositoryImpl(db_session), ) token = auth_service._create_access_token(admin) return {"admin": admin, "token": token} @pytest.fixture def mock_request(db_session): """Minimal FastAPI Request for direct route-handler tests.""" from unittest.mock import MagicMock request = MagicMock() request.state.db = db_session request.client = MagicMock() request.client.host = "127.0.0.1" request.cookies = {} return request @pytest_asyncio.fixture async def test_server(db_session): """Single server with target_path for push/sync integration tests.""" server = Server( name=f"test-push-srv-{uuid.uuid4().hex[:8]}", domain="10.0.0.99", target_path="/www/wwwroot", is_online=True, ) db_session.add(server) await db_session.commit() await db_session.refresh(server) return server