# -*- coding: utf-8 -*- """Tests: V5 lokaler Proxy-Start, Readiness, Lifecycle.""" from __future__ import annotations import os import re import subprocess import sys import tempfile import threading import time import unittest import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parent class ProxyStartupTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.html = (ROOT / "web" / "empfang.html").read_text(encoding="utf-8") @classmethod def tearDownClass(cls): try: import aza_empfang_test_html_proxy as px px.shutdown_test_proxy_server() except Exception: pass def setUp(self): try: import aza_empfang_test_html_proxy as px px.shutdown_test_proxy_server() except Exception: pass def test_health_endpoint_in_process(self): os.environ["AZA_DOKU_PROMPT_TEST"] = "1" import aza_empfang_test_html_proxy as px px.set_project_root(ROOT) port = px.ensure_test_proxy_server() self.assertIsNotNone(port) with urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=3) as r: self.assertEqual(r.status, 200) body = r.read().decode("utf-8") self.assertIn('"ok"', body) def test_empfang_html_200_and_no_store(self): os.environ["AZA_DOKU_PROMPT_TEST"] = "1" import aza_empfang_test_html_proxy as px px.set_project_root(ROOT) port = px.ensure_test_proxy_server() self.assertIsNotNone(port) req = urllib.request.Request(f"http://127.0.0.1:{port}/empfang/") with urllib.request.urlopen(req, timeout=5) as r: self.assertEqual(r.status, 200) cc = r.headers.get("Cache-Control", "") self.assertIn("no-store", cc) data = r.read(4096).decode("utf-8", errors="replace") self.assertIn("header-me-display", data) def test_missing_html_returns_500_json(self): os.environ["AZA_DOKU_PROMPT_TEST"] = "1" import aza_empfang_test_html_proxy as px px.shutdown_test_proxy_server() with tempfile.TemporaryDirectory() as td: px.set_project_root(td) srv, port = px._start_server_on_port(0) try: deadline = time.time() + 5 while time.time() < deadline: try: with urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=1) as r: if r.status == 200: break except Exception: pass time.sleep(0.1) try: with urllib.request.urlopen(f"http://127.0.0.1:{port}/empfang/", timeout=3) as r: self.assertEqual(r.status, 500) body = r.read().decode("utf-8") self.assertIn("empfang_html_missing", body) except urllib.error.HTTPError as e: self.assertEqual(e.code, 500) body = e.read().decode("utf-8") self.assertIn("empfang_html_missing", body) finally: try: srv.shutdown() except Exception: pass px.set_project_root(ROOT) px.shutdown_test_proxy_server() def test_standalone_subprocess_ready_and_serves(self): import aza_empfang_test_html_proxy as px px.shutdown_test_proxy_server() env = os.environ.copy() env["AZA_DOKU_PROMPT_TEST"] = "1" env["AZA_EMPFANG_TEST_UPSTREAM"] = "https://api.aza-medwork.ch" proc = subprocess.Popen( [sys.executable, str(ROOT / "aza_empfang_test_html_proxy.py"), "--serve"], cwd=str(ROOT), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, ) port = None try: deadline = time.time() + 15 while time.time() < deadline: if proc.poll() is not None: err = proc.stderr.read() if proc.stderr else "" out = proc.stdout.read() if proc.stdout else "" self.fail( f"Proxy subprocess exited early ({proc.returncode}): " f"stderr={err!r} stdout={out!r}" ) line = proc.stdout.readline() if proc.stdout else "" if line: m = re.search(r"READY port=(\d+)", line.strip()) if m: port = int(m.group(1)) break time.sleep(0.1) self.assertIsNotNone(port, "Keine READY-Zeile vom Proxy-Subprozess") self.assertTrue(px.wait_until_ready(port, timeout_sec=5.0)) with urllib.request.urlopen(f"http://127.0.0.1:{port}/empfang/", timeout=5) as r: self.assertEqual(r.status, 200) self.assertIn("header-me-display", r.read(8192).decode("utf-8", errors="replace")) finally: if proc.stdout: proc.stdout.close() if proc.stderr: proc.stderr.close() proc.terminate() try: proc.wait(timeout=5) except subprocess.TimeoutExpired: proc.kill() px.shutdown_test_proxy_server() def test_wait_until_ready_false_on_dead_port(self): import aza_empfang_test_html_proxy as px self.assertFalse(px.wait_until_ready(59999, timeout_sec=1.0, interval_sec=0.1)) def test_start_script_waits_and_sets_env(self): ps1 = (ROOT / "start_doku_prompt_test.ps1").read_text(encoding="utf-8") self.assertIn("Start-Process", ps1) self.assertIn("--serve", ps1) self.assertIn("READY port=", ps1) self.assertIn("Invoke-WebRequest", ps1) self.assertIn("AZA_EMPFANG_WEB_BASE", ps1) self.assertIn("Stop-Process -Id $proxyProc.Id", ps1) self.assertNotIn('python -c "from aza_empfang_test_html_proxy', ps1.replace("`", "")) self.assertNotIn("ensure_test_proxy_server(); print", ps1) self.assertIn("Read-Host", ps1) self.assertIn("V5-Test laeuft.", ps1) self.assertIn("Start-Process", ps1) self.assertNotIn("& $testExe", ps1) def test_proxy_survives_after_immediate_launcher_exit(self): """End-to-End: Launcher endet sofort, Proxy bleibt bis explizites Cleanup.""" import aza_empfang_test_html_proxy as px px.shutdown_test_proxy_server() env = os.environ.copy() env["AZA_DOKU_PROMPT_TEST"] = "1" env["AZA_EMPFANG_TEST_UPSTREAM"] = "https://api.aza-medwork.ch" proc = subprocess.Popen( [sys.executable, str(ROOT / "aza_empfang_test_html_proxy.py"), "--serve"], cwd=str(ROOT), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, ) port = None try: deadline = time.time() + 15 while time.time() < deadline: if proc.poll() is not None: self.fail(f"Proxy beendet vor READY: {proc.stderr.read()}") line = proc.stdout.readline() if proc.stdout else "" if line: m = re.search(r"READY port=(\d+)", line.strip()) if m: port = int(m.group(1)) break time.sleep(0.1) self.assertIsNotNone(port) self.assertTrue(px.wait_until_ready(port, timeout_sec=5.0)) launcher = subprocess.Popen( [sys.executable, "-c", "import sys; sys.exit(0)"], cwd=str(ROOT), ) launcher.wait(timeout=5) self.assertEqual(launcher.returncode, 0) wait_sec = 8.0 started = time.time() while time.time() - started < wait_sec: self.assertIsNone( proc.poll(), "Proxy wurde vor explizitem Cleanup beendet (Lifecycle-Fehler)", ) with urllib.request.urlopen( f"http://127.0.0.1:{port}/health", timeout=2 ) as r: self.assertEqual(r.status, 200) with urllib.request.urlopen( f"http://127.0.0.1:{port}/empfang/", timeout=3 ) as r: self.assertEqual(r.status, 200) time.sleep(1.0) finally: if proc.stdout: proc.stdout.close() if proc.stderr: proc.stderr.close() proc.terminate() try: proc.wait(timeout=5) except subprocess.TimeoutExpired: proc.kill() px.shutdown_test_proxy_server() def test_start_script_powershell_parser_and_encoding(self): ps1_path = ROOT / "start_doku_prompt_test.ps1" raw = ps1_path.read_bytes() self.assertTrue( raw.startswith(b"\xef\xbb\xbf"), "start_doku_prompt_test.ps1 soll UTF-8 mit BOM sein", ) text = ps1_path.read_text(encoding="utf-8-sig") for bad in ("\u2014", "\u2013", "â€", "Ã", ""): self.assertNotIn(bad, text, msg=f"Mojibake/typografisches Zeichen: {bad!r}") self.assertIn('Write-Host ("Empfang HTML OK: {0} ({1} Bytes)" -f', text) self.assertIn("} finally {", text.replace("\r", "")) ps_cmd = ( "$e=$null;$t=$null;" "[void][System.Management.Automation.Language.Parser]::ParseFile(" f"'{ps1_path}',[ref]$t,[ref]$e);" "if($e){$e|ForEach-Object{$_.Message}; exit 1}else{exit 0}" ) proc = subprocess.run( ["powershell.exe", "-NoProfile", "-Command", ps_cmd], capture_output=True, text=True, timeout=30, ) self.assertEqual( proc.returncode, 0, msg=f"PowerShell-Parserfehler: {proc.stdout}\n{proc.stderr}", ) def test_v5_html_markers(self): self.assertIn("header-me-display", self.html) self.assertIn("refreshMeStatusBadgesAll", self.html) self.assertIn("Benutzer der Praxis:", self.html) self.assertNotIn("Gleicher Datenraum wie in AzA", self.html) def test_env_port_without_inprocess_start(self): os.environ["AZA_DOKU_PROMPT_TEST"] = "1" os.environ["AZA_EMPFANG_TEST_PROXY_PORT"] = "59998" import aza_empfang_test_html_proxy as px # Kein Listener auf 59998 — URL-Bildung soll trotzdem env nutzen url = px.test_proxy_base_url() # ensure may fail readiness; env fallback when port set but server down if url is None: os.environ["AZA_EMPFANG_TEST_PROXY_PORT"] = "59998" port = (os.environ.get("AZA_EMPFANG_TEST_PROXY_PORT") or "").strip() url = f"http://127.0.0.1:{port}" if port.isdigit() else None self.assertEqual(url, "http://127.0.0.1:59998") if __name__ == "__main__": unittest.main()