172 lines
5.2 KiB
Python
172 lines
5.2 KiB
Python
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}"}
|