from __future__ import annotations import socket import subprocess # noqa: S404 import sys import time from pathlib import Path from urllib.error import URLError from urllib.request import urlopen import pytest from control_plane.host_commands import build_host_command_env from control_plane.runtime_plans import DjangoApplicationLaunchConfig from control_plane.runtime_plans import build_django_server_command def _find_free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe: probe.bind(("127.0.0.1", 0)) probe.listen(1) return int(probe.getsockname()[1]) def _wait_for_http_ready(url: str, *, timeout_seconds: float = 15.0) -> str: deadline = time.monotonic() + timeout_seconds while time.monotonic() < deadline: try: with urlopen(url, timeout=1) as response: # noqa: S310 return response.read().decode("utf-8") except URLError: time.sleep(0.25) msg = f"Timed out waiting for {url}" raise TimeoutError(msg) def _write_sentinel_urls(project_dir: Path, project_name: str) -> None: urls_file = project_dir / project_name / "urls.py" urls_file.write_text( "from django.http import HttpResponse\n" "from django.urls import path\n\n" "def sentinel_view(request):\n" " return HttpResponse('sentinel-ok')\n\n" "urlpatterns = [\n" " path('sentinel/', sentinel_view),\n" "]\n", encoding="utf-8", ) @pytest.mark.host_smoke def test_temp_django_project_serves_content( host_smoke_enabled: bool, tmp_path: Path, ) -> None: """Opt-in smoke test should create a temp Django project and serve a sentinel view.""" if not host_smoke_enabled: pytest.skip("Set TUSSILAGO_RUN_HOST_SMOKE=1 to run host smoke coverage.") workspace_root = Path(__file__).resolve().parents[1] project_name = "guestsite" project_dir = tmp_path / project_name child_process_env = build_host_command_env() startproject_command = [ sys.executable, "-m", "django", "startproject", project_name, str(project_dir), ] subprocess.run( # noqa: S603 startproject_command, check=True, capture_output=True, text=True, cwd=workspace_root, env=child_process_env, timeout=30, ) _write_sentinel_urls(project_dir, project_name) port = _find_free_port() command = build_django_server_command( DjangoApplicationLaunchConfig( wsgi_module=f"{project_name}.wsgi:application", bind_host="127.0.0.1", port=port, workers=1, python_executable=Path(sys.executable), ), ) process = subprocess.Popen( # noqa: S603 command, cwd=project_dir, env=child_process_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) try: response_body = _wait_for_http_ready(f"http://127.0.0.1:{port}/sentinel/") except TimeoutError as error: process.terminate() stdout, stderr = process.communicate(timeout=10) pytest.fail(f"{error}\nstdout:\n{stdout}\nstderr:\n{stderr}") finally: if process.poll() is None: process.terminate() process.wait(timeout=10) assert response_body == "sentinel-ok"