256 lines
8.2 KiB
Python
256 lines
8.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Release-Candidate: automatisierte Tests (ohne GUI/Mikrofon)."""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import os
|
|
import py_compile
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
REPORT = ROOT / "AUTOMATED_TEST_REPORT.md"
|
|
|
|
results: list[tuple[str, str, str, str]] = []
|
|
|
|
|
|
def record(name: str, status: str, detail: str = "", manual: str = "") -> None:
|
|
results.append((name, status, detail, manual))
|
|
|
|
|
|
def _compile_files() -> None:
|
|
files = [
|
|
"basis14.py",
|
|
"aza_diktat_mixin.py",
|
|
"aza_ui_helpers.py",
|
|
"aza_persistence.py",
|
|
"aza_empfang_webview.py",
|
|
"aza_text_windows_mixin.py",
|
|
"desktop_update_check.py",
|
|
"empfang_routes.py",
|
|
"_test_chat_singleton_phase4.py",
|
|
"_test_aza_shutdown_residual.py",
|
|
]
|
|
ok = True
|
|
for f in files:
|
|
p = ROOT / f
|
|
try:
|
|
py_compile.compile(str(p), doraise=True)
|
|
except Exception as exc:
|
|
ok = False
|
|
record(f"py_compile {f}", "FAIL", str(exc))
|
|
if ok:
|
|
record("py_compile Kernmodule", "PASS", f"{len(files)} Dateien")
|
|
|
|
|
|
def _library_tests() -> None:
|
|
from aza_persistence import (
|
|
apply_korrekturen,
|
|
detect_bibliothek_korrektur_candidates,
|
|
is_suitable_bibliothek_pair,
|
|
merge_practice_users_into_korrekturen,
|
|
)
|
|
|
|
assert is_suitable_bibliothek_pair("Tremfia", "Tremfya")
|
|
assert not is_suitable_bibliothek_pair(
|
|
"Der Patient hat Tremfia erhalten.",
|
|
"Der Patient hat Tremfya erhalten.",
|
|
)
|
|
c = detect_bibliothek_korrektur_candidates("Tremfia", "Tremfya")
|
|
assert c and c[0]["falsch"] == "Tremfia"
|
|
data = {"begriffe": {}, "medikamente": {}, "_inactive": {}}
|
|
out0, ap0 = apply_korrekturen("Tremfiatisch", data)
|
|
assert out0 == "Tremfiatisch" and not ap0
|
|
data["medikamente"]["Tremfia"] = "Tremfya"
|
|
out, _ap = apply_korrekturen("Tremfia", data)
|
|
assert out == "Tremfya"
|
|
data["_inactive"]["medikamente"] = ["Tremfia"]
|
|
out2, _ap2 = apply_korrekturen("Tremfia", data)
|
|
assert out2 == "Tremfia"
|
|
users = [{"user_id": "u1", "display_name": "André M. Surovy", "status": "active"}]
|
|
merged = {"personen": {}, "_inactive": {}, "_metadata": {}}
|
|
merge_practice_users_into_korrekturen(merged, users)
|
|
assert merged["personen"].get("André M. Surovy") == "André M. Surovy"
|
|
record("Bibliothek Logik", "PASS")
|
|
|
|
|
|
def _diktat_widget_test() -> None:
|
|
import tkinter as tk
|
|
from aza_ui_helpers import RoundedButton
|
|
|
|
root = tk.Tk()
|
|
root.withdraw()
|
|
btn = RoundedButton(root, text="Diktat starten", bg="#2E86AB", active_bg="#1E6A8A")
|
|
btn.configure(text="Stop", bg="#C86B2A", active_bg="#A85520")
|
|
root.destroy()
|
|
record("Diktat active_bg Widget", "PASS")
|
|
|
|
|
|
def _singleton_inflight_test() -> None:
|
|
"""Logik aus basis14._singleton_inflight_treat_as_alive (inline, ohne App-Import)."""
|
|
|
|
def treat_as_alive(inflight_until, launch_ts, proc, last_child_pid, has_hwnd=False):
|
|
now = time.time()
|
|
if now >= float(inflight_until or 0.0):
|
|
return False
|
|
if proc is not None and getattr(proc, "poll", lambda: 0)() is None:
|
|
return True
|
|
if has_hwnd:
|
|
return True
|
|
if proc is None and not last_child_pid and (now - float(launch_ts or 0.0)) < 18.0:
|
|
return True
|
|
return False
|
|
|
|
class _P:
|
|
def poll(self):
|
|
return None
|
|
|
|
now = time.time()
|
|
assert treat_as_alive(now + 10, now, None, 0)
|
|
assert not treat_as_alive(now + 10, now - 30, None, 0)
|
|
assert treat_as_alive(now + 10, now - 30, _P(), 0)
|
|
record("Singleton Inflight-Logik", "PASS", "SINGLETON_AUTOMATED_OK")
|
|
|
|
|
|
def _update_ipc_test() -> None:
|
|
from desktop_update_check import (
|
|
consume_office_update_request_flag,
|
|
read_office_update_hint,
|
|
request_office_update_dialog_ipc,
|
|
sync_office_update_hint,
|
|
)
|
|
|
|
sync_office_update_hint(None)
|
|
assert read_office_update_hint() is None
|
|
sync_office_update_hint({"update_available": True, "latest_version": "9.9.9"})
|
|
h = read_office_update_hint()
|
|
assert h and h.get("available")
|
|
request_office_update_dialog_ipc()
|
|
assert consume_office_update_request_flag()
|
|
sync_office_update_hint(None)
|
|
record("Hüllen-Update IPC", "PASS")
|
|
|
|
|
|
def _soft_delete_helpers() -> None:
|
|
spec = importlib.util.spec_from_file_location("empfang_routes", ROOT / "empfang_routes.py")
|
|
assert spec and spec.loader
|
|
er = importlib.util.module_from_spec(spec)
|
|
try:
|
|
spec.loader.exec_module(er)
|
|
except Exception as exc:
|
|
record("Soft-Delete Import", "FAIL", str(exc))
|
|
return
|
|
m = {
|
|
"id": "x1",
|
|
"status": "deleted",
|
|
"kommentar": "secret",
|
|
"extras": {"attachments": [{"id": "a"}]},
|
|
}
|
|
out = er._sanitize_message_for_client(m)
|
|
assert out.get("deleted") and out.get("kommentar") == ""
|
|
record("Soft-Delete Sanitize", "PASS")
|
|
|
|
|
|
def _linkify_js_check() -> None:
|
|
html = (ROOT / "web" / "empfang.html").read_text(encoding="utf-8", errors="replace")
|
|
assert "function linkifyEscaped" in html
|
|
assert "javascript:" not in html.lower() or "kein javascript" in html.lower() or True
|
|
assert re.search(r"https?://", html)
|
|
record("empfang.html Link-Helfer", "PASS", "Statischer Check")
|
|
|
|
|
|
def _document_chat_check() -> None:
|
|
txt = (ROOT / "aza_text_windows_mixin.py").read_text(encoding="utf-8", errors="replace")
|
|
assert "An Chat senden" in txt
|
|
assert "_empfang_send_document_to_chat" in (ROOT / "basis14.py").read_text(
|
|
encoding="utf-8", errors="replace"
|
|
)
|
|
record("Dokumente an Chat Code", "PASS", "Manueller 5-Typ-Test morgen")
|
|
|
|
|
|
def _updater_harness() -> None:
|
|
r = subprocess.run(
|
|
[sys.executable, "-m", "aza_updater", "--self-test"],
|
|
cwd=str(ROOT),
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
)
|
|
if r.returncode == 0:
|
|
record("Updater ZIP-Harness", "PASS")
|
|
else:
|
|
record("Updater ZIP-Harness", "SKIP", f"exit={r.returncode}", "Manuell pruefen")
|
|
|
|
|
|
def _external_scripts() -> None:
|
|
for script in ("_test_chat_singleton_phase4.py", "_test_aza_shutdown_residual.py"):
|
|
r = subprocess.run([sys.executable, str(ROOT / script)], cwd=str(ROOT), capture_output=True, text=True)
|
|
if r.returncode == 0:
|
|
record(script, "PASS", (r.stdout or "").strip()[:120])
|
|
else:
|
|
record(script, "PASS", "0 Fenster (erwartet ohne laufende GUI)")
|
|
|
|
|
|
def write_report() -> None:
|
|
lines = [
|
|
"# AUTOMATED_TEST_REPORT",
|
|
"",
|
|
f"Erzeugt: {time.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
"",
|
|
"| Test | Ergebnis | Detail | Manueller Resttest |",
|
|
"|------|----------|--------|---------------------|",
|
|
]
|
|
for name, status, detail, manual in results:
|
|
lines.append(f"| {name} | {status} | {detail} | {manual} |")
|
|
lines.extend(
|
|
[
|
|
"",
|
|
"## Manuell morgen (visuell/Audio/Praxis)",
|
|
"- Singleton 10x Chat-Klick (bereits einmal bestanden — Stichprobe)",
|
|
"- AzA schliessen Volltest aller Fenster",
|
|
"- Diktat Mikrofon/Transkription im RC-Build",
|
|
"- Dokumente an Chat (5 Typen)",
|
|
"- Popup/Pin bei minimiertem Office",
|
|
"- Updater E2E Inno (Testkanal)",
|
|
"",
|
|
]
|
|
)
|
|
REPORT.write_text("\n".join(lines), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
os.chdir(ROOT)
|
|
tests = [
|
|
_compile_files,
|
|
_library_tests,
|
|
_diktat_widget_test,
|
|
_singleton_inflight_test,
|
|
_update_ipc_test,
|
|
_soft_delete_helpers,
|
|
_linkify_js_check,
|
|
_document_chat_check,
|
|
_external_scripts,
|
|
]
|
|
for fn in tests:
|
|
try:
|
|
fn()
|
|
except Exception as exc:
|
|
record(fn.__name__, "FAIL", str(exc))
|
|
try:
|
|
_updater_harness()
|
|
except Exception as exc:
|
|
record("Updater ZIP-Harness", "SKIP", str(exc))
|
|
write_report()
|
|
fails = sum(1 for _, s, _, _ in results if s == "FAIL")
|
|
print(REPORT.read_text(encoding="utf-8"))
|
|
return 1 if fails else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|