feat: add loopback host configuration for health checks

- Introduced LOOPBACK_HTTP_HOSTS in config.py to define valid loopback addresses for health-check URL and MINI_APP_SKIP_AUTH safety.
- Updated run.py to utilize LOOPBACK_HTTP_HOSTS for determining the host in health check and authentication logic.
- Enhanced test_app.py to skip tests if the required webapp output directory is not built, improving test reliability.
This commit is contained in:
2026-03-03 17:47:39 +03:00
parent 50d734e192
commit 37218a436a
3 changed files with 15 additions and 3 deletions

View File

@@ -22,6 +22,9 @@ logger = logging.getLogger(__name__)
# Valid port range for HTTP_PORT. # Valid port range for HTTP_PORT.
HTTP_PORT_MIN, HTTP_PORT_MAX = 1, 65535 HTTP_PORT_MIN, HTTP_PORT_MAX = 1, 65535
# Host values treated as loopback (for health-check URL and MINI_APP_SKIP_AUTH safety).
LOOPBACK_HTTP_HOSTS = ("127.0.0.1", "localhost", "::1", "")
# Project root (parent of duty_teller package). Used for webapp path, etc. # Project root (parent of duty_teller package). Used for webapp path, etc.
PROJECT_ROOT = Path(__file__).resolve().parent.parent PROJECT_ROOT = Path(__file__).resolve().parent.parent

View File

@@ -77,7 +77,7 @@ def _run_uvicorn(web_app, port: int) -> None:
def _wait_for_http_ready(port: int) -> bool: def _wait_for_http_ready(port: int) -> bool:
"""Return True if /health responds successfully within _HTTP_STARTUP_WAIT_SEC.""" """Return True if /health responds successfully within _HTTP_STARTUP_WAIT_SEC."""
host = config.HTTP_HOST host = config.HTTP_HOST
if host == "0.0.0.0": if host not in config.LOOPBACK_HTTP_HOSTS:
host = "127.0.0.1" host = "127.0.0.1"
url = f"http://{host}:{port}/health" url = f"http://{host}:{port}/health"
deadline = time.monotonic() + _HTTP_STARTUP_WAIT_SEC deadline = time.monotonic() + _HTTP_STARTUP_WAIT_SEC
@@ -113,7 +113,7 @@ def main() -> None:
logger.warning( logger.warning(
"MINI_APP_SKIP_AUTH is set — API auth disabled (insecure); use only for dev" "MINI_APP_SKIP_AUTH is set — API auth disabled (insecure); use only for dev"
) )
if config.HTTP_HOST not in ("127.0.0.1", "localhost", ""): if config.HTTP_HOST not in config.LOOPBACK_HTTP_HOSTS:
print( print(
"ERROR: MINI_APP_SKIP_AUTH must not be used in production (non-localhost).", "ERROR: MINI_APP_SKIP_AUTH must not be used in production (non-localhost).",
file=sys.stderr, file=sys.stderr,

View File

@@ -1,6 +1,7 @@
"""Tests for FastAPI app /api/duties.""" """Tests for FastAPI app /api/duties."""
import time import time
from pathlib import Path
from unittest.mock import ANY, patch from unittest.mock import ANY, patch
import pytest import pytest
@@ -59,7 +60,15 @@ def test_app_static_has_no_store_and_vary(client):
def test_app_js_has_no_store(client): def test_app_js_has_no_store(client):
"""JS and all static under /app get Cache-Control: no-store.""" """JS and all static under /app get Cache-Control: no-store."""
r = client.get("/app/js/main.js") webapp_out = config.PROJECT_ROOT / "webapp-next" / "out"
if not webapp_out.is_dir():
pytest.skip("webapp-next/out not built")
# Next.js static export serves JS under _next/static/chunks/<hash>.js
js_files = list(webapp_out.glob("_next/static/chunks/*.js"))
if not js_files:
pytest.skip("no JS chunks in webapp-next/out")
rel = js_files[0].relative_to(webapp_out)
r = client.get(f"/app/{rel.as_posix()}")
assert r.status_code == 200 assert r.status_code == 200
assert r.headers.get("cache-control") == "no-store" assert r.headers.get("cache-control") == "no-store"