"""Startup wiring: `python -m dashboards` / the `dashboards` console script. Two things are worth pinning: the reload path must hand uvicorn an import string (an app object cannot be reloaded), and debugpy must stay completely inert unless asked. """ from __future__ import annotations import sys from types import ModuleType from typing import Any import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from auth_client import sign_in from auth_contract import ADMIN_PASSWORD, FAST_PBKDF2 from dashboards import __main__ as entry from dashboards import debug as debug_module from dashboards.config import Settings class FakeUvicorn(ModuleType): def __init__(self) -> None: super().__init__("uvicorn") self.calls: list[tuple[Any, dict[str, Any]]] = [] def run(self, target: Any, **kwargs: Any) -> None: self.calls.append((target, kwargs)) @pytest.fixture def fake_uvicorn(monkeypatch: pytest.MonkeyPatch) -> FakeUvicorn: module = FakeUvicorn() monkeypatch.setitem(sys.modules, "uvicorn", module) return module def test_main_serves_the_assembled_app(fake_uvicorn: FakeUvicorn, tmp_path: Any, monkeypatch: pytest.MonkeyPatch) -> None: # Settings is frozen on purpose, so the module attribute is replaced, not poked. The DB # path is private: a test must not open the developer's running database. monkeypatch.setattr(entry, "SETTINGS", Settings(host="0.0.0.0", port=8090, db_path=str(tmp_path / "main.sqlite3"), admin_password=ADMIN_PASSWORD, pbkdf2_iterations=FAST_PBKDF2)) monkeypatch.delenv("DASH_RELOAD", raising=False) entry.main() (target, kwargs), = fake_uvicorn.calls assert kwargs == {"host": "0.0.0.0", "port": 8090} # FastAPI keeps included routers in a private wrapper, so the honest check is a request assert isinstance(target, FastAPI) with TestClient(target) as client: # `/api/health` is public and must stay so — the Containerfile HEALTHCHECK has no # session and no way to get one. `/ups` is not: signing in here is what keeps this # test about startup wiring instead of about which routes are gated (that is # tests/test_auth_api.py's job). assert client.get("/api/health").status_code == 200 sign_in(client) assert client.get("/ups").status_code == 200 def test_reload_mode_passes_an_import_string(fake_uvicorn: FakeUvicorn, tmp_path: Any, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(entry, "SETTINGS", Settings(db_path=str(tmp_path / "reload.sqlite3"))) # uvicorn cannot reload an object it was handed, only a module:app string monkeypatch.setenv("DASH_RELOAD", "1") entry.main() (target, kwargs), = fake_uvicorn.calls assert target == "dashboards.app:app" assert kwargs["reload"] is True def test_debugpy_is_inert_by_default(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("DEBUGPY", raising=False) assert debug_module.maybe_listen() is False def test_debugpy_listens_when_asked(monkeypatch: pytest.MonkeyPatch) -> None: calls: list[tuple[str, int]] = [] fake = ModuleType("debugpy") fake.listen = lambda addr: calls.append(addr) # type: ignore[attr-defined] monkeypatch.setitem(sys.modules, "debugpy", fake) monkeypatch.setenv("DEBUGPY", "yes") monkeypatch.setenv("DEBUGPY_PORT", "5699") assert debug_module.maybe_listen() is True assert calls == [("0.0.0.0", 5699)] def test_debugpy_missing_is_a_message_not_a_crash(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("DEBUGPY", "1") # a None entry makes `import debugpy` raise ImportError, as it does in a prod venv monkeypatch.setitem(sys.modules, "debugpy", None) # type: ignore[arg-type] assert debug_module.maybe_listen() is False