import asyncio import os from collections.abc import Iterator from datetime import UTC, datetime, timedelta from pathlib import Path from uuid import uuid4 import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from motor.motor_asyncio import AsyncIOMotorClient os.environ.setdefault( "MONGODB_URI", "mongodb://theplug:pass@localhost:27017/theplug?authSource=admin", ) os.environ.setdefault("MONGODB_DB_NAME", "theplug") os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key") os.environ.setdefault("JWT_ALGORITHM", "HS256") os.environ.setdefault("JWT_EXPIRE_MINUTES", "720") os.environ.setdefault("AUTH_USERNAME", "admin") os.environ.setdefault("AUTH_PASSWORD", "admin") @pytest.fixture def fake_user(): from app.models.auth import AuthUserDocument return AuthUserDocument( username="admin", password_hash="hashed-password", ) @pytest.fixture def auth_context(fake_user): from app.core.security import AuthContext from app.models.auth import AccessTokenPayload exp = int((datetime.now(UTC) + timedelta(minutes=720)).timestamp()) payload = AccessTokenPayload(sub=fake_user.username, jti="test-jti", exp=exp) return AuthContext(token="test-token", user=fake_user, payload=payload) @pytest.fixture def app(fake_user, auth_context) -> FastAPI: from app.api.auth.router import router as auth_router from app.api.editor.router import router as editor_router from app.api.proxy.router import router as proxy_router from app.core.security import ( AuthContext, get_auth_context, require_authenticated_user, ) from app.models.auth import AuthUserDocument app_instance = FastAPI() app_instance.include_router(auth_router) app_instance.include_router(editor_router) app_instance.include_router(proxy_router) async def _override_current_user() -> AuthUserDocument: return fake_user async def _override_auth_context() -> AuthContext: return auth_context app_instance.dependency_overrides[require_authenticated_user] = ( _override_current_user ) app_instance.dependency_overrides[get_auth_context] = _override_auth_context return app_instance @pytest.fixture def client(app: FastAPI): with TestClient(app) as test_client: yield test_client async def _mongo_ping(uri: str) -> str | None: client = AsyncIOMotorClient(uri, serverSelectionTimeoutMS=500) try: await client.admin.command("ping") except Exception as error: # pragma: no cover - exercised via skip path return str(error) finally: client.close() return None async def _drop_database(uri: str, database_name: str) -> None: client = AsyncIOMotorClient(uri, serverSelectionTimeoutMS=500) try: await client.drop_database(database_name) finally: client.close() @pytest.fixture def integration_runtime(tmp_path: Path) -> Iterator[dict[str, str]]: from app.core.config import settings from app.services.mitmproxy.manager import manager mongodb_uri = settings.MONGODB_URI ping_error = asyncio.run(_mongo_ping(mongodb_uri)) if ping_error is not None: pytest.skip(f"MongoDB is not available for integration tests: {ping_error}") db_name = f"theplug_test_{uuid4().hex}" scan_root = tmp_path / "scan-root" runtime_root = tmp_path / "runtime" scan_root.mkdir(parents=True, exist_ok=True) runtime_root.mkdir(parents=True, exist_ok=True) original_db_name = settings.MONGODB_DB_NAME original_scan_root = settings.SERVICE_SCAN_ROOT original_auth_username = settings.AUTH_USERNAME original_auth_password = settings.AUTH_PASSWORD original_manager_work_dir = manager.work_dir settings.MONGODB_DB_NAME = db_name settings.SERVICE_SCAN_ROOT = str(scan_root) settings.AUTH_USERNAME = "integration-admin" settings.AUTH_PASSWORD = "integration-admin" manager.work_dir = runtime_root try: yield { "db_name": db_name, "scan_root": str(scan_root), "runtime_root": str(runtime_root), } finally: manager.work_dir = original_manager_work_dir manager.processes.clear() manager.attached_pids.clear() settings.MONGODB_DB_NAME = original_db_name settings.SERVICE_SCAN_ROOT = original_scan_root settings.AUTH_USERNAME = original_auth_username settings.AUTH_PASSWORD = original_auth_password asyncio.run(_drop_database(mongodb_uri, db_name)) @pytest.fixture def real_app(integration_runtime) -> FastAPI: from app.main import app as real_app_instance real_app_instance.dependency_overrides.clear() return real_app_instance @pytest.fixture def real_client(real_app: FastAPI): with TestClient(real_app) as test_client: yield test_client @pytest.fixture def auth_token(real_client: TestClient) -> str: response = real_client.post( "/auth/login", json={"username": "integration-admin", "password": "integration-admin"}, ) assert response.status_code == 200 return response.json()["access_token"] @pytest.fixture def auth_headers(auth_token: str) -> dict[str, str]: return {"Authorization": f"Bearer {auth_token}"}