Files
aza/AzA march 2026/_test_v5_local_proxy_startup.py
2026-06-13 22:47:31 +02:00

295 lines
11 KiB
Python

# -*- 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()