Introduce service/repository dependency protocols and tests
This commit is contained in:
189
backend/tests/test_routers/test_dependency_injection.py
Normal file
189
backend/tests/test_routers/test_dependency_injection.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""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
|
||||
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.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(jails=[], 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()
|
||||
|
||||
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/auth/login",
|
||||
json={"password": "ignored"},
|
||||
)
|
||||
|
||||
await db.close()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["token"].startswith("fake-token")
|
||||
assert response.cookies.get("bangui_session") 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()
|
||||
|
||||
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/jails",
|
||||
headers={"Cookie": "bangui_session=fake-token"},
|
||||
)
|
||||
|
||||
await db.close()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"jails": [], "total": 0}
|
||||
Reference in New Issue
Block a user