From 37218a436ac3ae017a5cb0809dade67540b9e6e5 Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Tue, 3 Mar 2026 17:47:39 +0300 Subject: [PATCH] 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. --- duty_teller/config.py | 3 +++ duty_teller/run.py | 4 ++-- tests/test_app.py | 11 ++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/duty_teller/config.py b/duty_teller/config.py index 489455e..49fb88e 100644 --- a/duty_teller/config.py +++ b/duty_teller/config.py @@ -22,6 +22,9 @@ logger = logging.getLogger(__name__) # Valid port range for HTTP_PORT. 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 = Path(__file__).resolve().parent.parent diff --git a/duty_teller/run.py b/duty_teller/run.py index 0c416ad..702f8c7 100644 --- a/duty_teller/run.py +++ b/duty_teller/run.py @@ -77,7 +77,7 @@ def _run_uvicorn(web_app, port: int) -> None: def _wait_for_http_ready(port: int) -> bool: """Return True if /health responds successfully within _HTTP_STARTUP_WAIT_SEC.""" host = config.HTTP_HOST - if host == "0.0.0.0": + if host not in config.LOOPBACK_HTTP_HOSTS: host = "127.0.0.1" url = f"http://{host}:{port}/health" deadline = time.monotonic() + _HTTP_STARTUP_WAIT_SEC @@ -113,7 +113,7 @@ def main() -> None: logger.warning( "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( "ERROR: MINI_APP_SKIP_AUTH must not be used in production (non-localhost).", file=sys.stderr, diff --git a/tests/test_app.py b/tests/test_app.py index 6424d94..e786fdf 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,6 +1,7 @@ """Tests for FastAPI app /api/duties.""" import time +from pathlib import Path from unittest.mock import ANY, patch import pytest @@ -59,7 +60,15 @@ def test_app_static_has_no_store_and_vary(client): def test_app_js_has_no_store(client): """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/.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.headers.get("cache-control") == "no-store"