295 lines
11 KiB
Python
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()
|