Files
BanGUI/backend/tests/test_routers/test_dependency_injection.py
Lukas cc6dbcf3f0 feat: implement API versioning /api/v1/
- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 21:29:30 +02:00

196 lines
6.0 KiB
Python

"""Router dependency injection tests.
These tests verify that routers can consume service abstractions via FastAPI
dependencies and that those dependencies can be overridden cleanly for unit
testing without touching concrete implementations.
"""
from __future__ import annotations
from pathlib import Path
import aiosqlite
from httpx import ASGITransport, AsyncClient
from app.config import Settings
from app.db import init_db
# Note: Service dependency injection at router level is not yet implemented.
# These tests are placeholders for future refactoring.
# from app.dependencies import get_auth_service, get_jail_service
from app.main import create_app
from app.models.auth import Session
from app.models.jail import JailListResponse
from app.utils.constants import SESSION_COOKIE_NAME
from app.utils.setup_state import set_setup_complete_cache
class FakeAuthService:
async def login(
self,
_db: aiosqlite.Connection,
password: str,
session_duration_minutes: int,
session_repo: object | None = None,
) -> Session:
return Session(
id=1,
token="fake-token",
created_at="2025-01-01T00:00:00Z",
expires_at="2099-01-01T00:00:00Z",
)
async def validate_session(
self,
_db: aiosqlite.Connection,
token: str,
session_secret: str | None = None,
session_repo: object | None = None,
) -> Session:
return Session(
id=1,
token=token,
created_at="2025-01-01T00:00:00Z",
expires_at="2099-01-01T00:00:00Z",
)
async def logout(
self,
_db: aiosqlite.Connection,
token: str,
session_secret: str | None = None,
session_repo: object | None = None,
) -> str | None:
return token
class FakeJailService:
async def list_jails(self, _socket_path: str) -> JailListResponse:
return JailListResponse(items=[], total=0)
async def get_jail(self, _socket_path: str, _name: str) -> JailListResponse:
raise NotImplementedError
async def reload_all(self, _socket_path: str) -> None:
raise NotImplementedError
async def start_jail(self, _socket_path: str, _name: str) -> None:
raise NotImplementedError
async def stop_jail(self, _socket_path: str, _name: str) -> None:
raise NotImplementedError
async def set_idle(self, socket_path: str, name: str, *, on: bool) -> None:
raise NotImplementedError
async def reload_jail(self, socket_path: str, name: str) -> None:
raise NotImplementedError
async def get_ignore_list(self, socket_path: str, name: str) -> list[str]:
raise NotImplementedError
async def add_ignore_ip(self, socket_path: str, name: str, ip: str) -> None:
raise NotImplementedError
async def del_ignore_ip(self, socket_path: str, name: str, ip: str) -> None:
raise NotImplementedError
async def set_ignore_self(self, socket_path: str, name: str, *, on: bool) -> None:
raise NotImplementedError
async def get_jail_banned_ips(
self,
socket_path: str,
jail_name: str,
page: int,
page_size: int,
search: str | None = None,
*,
geo_batch_lookup: object,
http_session: object,
app_db: aiosqlite.Connection,
) -> JailListResponse:
raise NotImplementedError
async def _build_app(settings: Settings):
app = create_app(settings=settings)
set_setup_complete_cache(app, True)
db = await aiosqlite.connect(settings.database_path)
db.row_factory = aiosqlite.Row
await init_db(db)
return app, db
async def test_auth_login_uses_injected_auth_service(tmp_path: Path) -> None:
settings = Settings(
database_path=str(tmp_path / "test_bangui.db"),
fail2ban_socket="/tmp/fake_fail2ban.sock",
fail2ban_config_dir=str(tmp_path / "fail2ban"),
session_secret="test-secret-key-do-not-use-in-production",
session_duration_minutes=60,
timezone="UTC",
log_level="debug",
)
app, db = await _build_app(settings)
def _fake_auth_service() -> FakeAuthService:
return FakeAuthService()
# Service dependency injection not yet implemented
# app.dependency_overrides[get_auth_service] = _fake_auth_service
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport,
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/auth/login",
json={"password": "ignored"},
)
await db.close()
assert response.status_code == 200
assert response.json()["token"].startswith("fake-token")
assert response.cookies.get(SESSION_COOKIE_NAME) is not None
async def test_jail_list_uses_injected_jail_service_and_auth(tmp_path: Path) -> None:
settings = Settings(
database_path=str(tmp_path / "test_bangui.db"),
fail2ban_socket="/tmp/fake_fail2ban.sock",
fail2ban_config_dir=str(tmp_path / "fail2ban"),
session_secret="test-secret-key-do-not-use-in-production",
session_duration_minutes=60,
timezone="UTC",
log_level="debug",
)
app, db = await _build_app(settings)
def _fake_auth_service() -> FakeAuthService:
return FakeAuthService()
def _fake_jail_service() -> FakeJailService:
return FakeJailService()
# Service dependency injection not yet implemented
# app.dependency_overrides[get_auth_service] = _fake_auth_service
# app.dependency_overrides[get_jail_service] = _fake_jail_service
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport,
base_url="http://test",
) as client:
response = await client.get(
"/api/v1/jails",
headers={"Cookie": f"{SESSION_COOKIE_NAME}=fake-token"},
)
await db.close()
assert response.status_code == 200
assert response.json() == {"jails": [], "total": 0}