Files
aza/AzA march 2026/_test_desktop_live_e2e.py

229 lines
8.5 KiB
Python
Raw Normal View History

2026-06-13 22:47:31 +02:00
# -*- coding: utf-8 -*-
"""Desktop-Client E2E gegen live Backend (gleiche Sync-Funktionen wie aza_desktop.exe)."""
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from typing import Any
ROOT = os.path.dirname(os.path.abspath(__file__))
BASE = "https://api.aza-medwork.ch"
TOKEN_PATH = os.path.join(ROOT, "backend_token.txt")
class MockApp:
def __init__(
self,
*,
token: str,
practice_id: str,
user_id: str,
display_name: str = "AzA E2E Test",
) -> None:
self._token = token
self._practice_id = practice_id
self._user_id = user_id
self._user_profile = {
"display_name": display_name,
"specialty": "Dermatologie",
"city": "Winterthur",
}
def get_backend_url(self) -> str:
return BASE
def get_backend_token(self) -> str:
return self._token
def get_practice_id(self) -> str:
return self._practice_id
def _empfang_self_user_id(self) -> str:
return self._user_id
def after(self, _ms: int, fn): # noqa: ANN001
fn()
def set_status(self, _msg: str) -> None:
pass
def _read_token() -> str:
with open(TOKEN_PATH, encoding="utf-8") as fh:
return fh.read().strip()
def _api_get(path: str, headers: dict) -> tuple[int, dict]:
req = urllib.request.Request(BASE + path, headers=headers, method="GET")
try:
with urllib.request.urlopen(req, timeout=20) as resp:
return resp.status, json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
raw = exc.read().decode("utf-8", errors="replace")
try:
return exc.code, json.loads(raw)
except json.JSONDecodeError:
return exc.code, {}
def _fetch_practice_context(token: str) -> tuple[str, str]:
"""Practice + User aus /license/debug + DB — ohne Secrets loggen."""
hdrs = {"X-API-Token": token}
code, data = _api_get("/license/debug", hdrs)
if code != 200:
return os.environ.get("E2E_PRACTICE_ID", "prac_883ddc21fb6a"), os.environ.get(
"E2E_USER_ID", "smoke_test_user"
)
practice_id = os.environ.get("E2E_PRACTICE_ID", "prac_883ddc21fb6a")
user_id = os.environ.get("E2E_USER_ID", "smoke_test_user")
return practice_id, user_id
def _print_result(label: str, ok: bool, detail: str = "") -> bool:
status = "OK" if ok else "FAIL"
line = f"{label}: {status}"
if detail:
line += f" ({detail})"
print(line)
return ok
def main() -> int:
results: list[bool] = []
tok = _read_token()
pid, uid = _fetch_practice_context(tok)
app = MockApp(token=tok, practice_id=pid, user_id=uid, display_name="AzA Desktop E2E Test")
from aza_doku_vorlagen import (
fetch_public_prompt_library_result,
is_own_public_item,
publish_template_to_server,
)
from aza_doku_prompt_sync import unpublish_doku_prompt, normalize_public_doku_item
from aza_bibliothek import sync_public_library_from_server
from aza_bibliothek_sync import fetch_public_library_from_server_result
from aza_doku_prompt_sync import _sync_headers, _http_get
# A. Public list
items, err = fetch_public_prompt_library_result(app)
results.append(_print_result("doku_public_list", err is None and isinstance(items, list), f"err={err} count={len(items)}"))
if err is None and items:
sample = items[0]
meta_ok = all(k in sample for k in ("doc_type", "title", "author_display", "city", "specialty"))
results.append(_print_result("doku_public_metadata", meta_ok))
# Auth: ohne User-Id → kein 403 als Route-Missing
hdrs_no_user = _sync_headers(app)
if hdrs_no_user:
hdrs_no_user = dict(hdrs_no_user)
hdrs_no_user.pop("X-User-Id", None)
hdrs_no_user.pop("X-Empfang-User-Id", None)
code, _ = _http_get(f"{BASE.rstrip('/')}/v1/doku-prompts/public", hdrs_no_user)
results.append(_print_result("doku_403_not_route_missing", code == 403, f"http={code}"))
# B. Publish
tpl = {
"content": "Dies ist eine technische Desktop-Testvorlage ohne Patientendaten.",
"city": "Winterthur",
"specialty": "Dermatologie",
}
ok_pub, msg_pub = publish_template_to_server(
app,
"verlauf",
tpl,
"AZA Desktop E2E Test",
"Desktop-End-to-End-Test, danach löschen.",
)
pub_id = ""
if ok_pub:
items2, err2 = fetch_public_prompt_library_result(app)
found = [x for x in items2 if x.get("title") == "AZA Desktop E2E Test"]
pub_id = found[0].get("id", "") if found else ""
own = found[0] if found else {}
results.append(_print_result("doku_publish", True, f"id={pub_id[:20]}..." if pub_id else "no id"))
results.append(_print_result("doku_own_after_publish", is_own_public_item(app, own) if found else False))
else:
results.append(_print_result("doku_publish", False, msg_pub[:80]))
# C. Update (republish same server_id)
if ok_pub and pub_id:
tpl2 = dict(tpl)
tpl2["server_id"] = pub_id
ok_upd, msg_upd = publish_template_to_server(
app, "verlauf", tpl2, "AZA Desktop E2E Test", "Desktop-E2E aktualisiert, danach löschen.",
)
results.append(_print_result("doku_republish_update", ok_upd, msg_upd[:60] if not ok_upd else "revision+1"))
# D. Unpublish
if pub_id:
ok_un, msg_un = unpublish_doku_prompt(app, pub_id)
results.append(_print_result("doku_unpublish", ok_un, msg_un[:60] if not ok_un else ""))
items3, _ = fetch_public_prompt_library_result(app)
still = any(x.get("id") == pub_id for x in items3)
results.append(_print_result("doku_gone_after_unpublish", not still))
# G. Bibliothek public
lib_items, lib_err = fetch_public_library_from_server_result(app)
results.append(_print_result("library_public_list", lib_err is None, f"err={lib_err} count={len(lib_items)}"))
sync_err = sync_public_library_from_server(app)
results.append(_print_result("library_sync_cache", sync_err is None, f"err={sync_err}"))
# H. Bibliothek publish — direkt API (Desktop-UI hat keinen Publish-Button)
hdrs = _sync_headers(app)
lib_pub_id = ""
if hdrs:
from aza_doku_prompt_sync import _http_post
body = {
"category": "general",
"term": "AZA-Testbegriff",
"preferred_spelling": "AZA-Testbegriff",
"variants": "AZA-Testvariante",
"description": "Technischer E2E-Test, danach löschen.",
"language": "de",
"market_region": "de-CH",
}
code, data = _http_post(f"{BASE.rstrip('/')}/v1/library/publish", hdrs, body)
lib_ok = code == 200 and isinstance(data, dict) and data.get("ok")
lib_pub_id = str(data.get("id") or "") if isinstance(data, dict) else ""
results.append(_print_result("library_publish_api", lib_ok, f"http={code}"))
if lib_pub_id:
code_u, data_u = _http_post(
f"{BASE.rstrip('/')}/v1/library/unpublish/{lib_pub_id}", hdrs, {}
)
results.append(_print_result("library_unpublish_api", code_u == 200 and data_u.get("ok")))
# J. License status + chat fields
lic_q = urllib.parse.urlencode({"email": os.environ.get("E2E_LICENSE_EMAIL", "")})
lic_path = "/license/status" + (("?" + lic_q) if lic_q.strip("email=") else "")
if hdrs:
code, lic = _api_get(lic_path, hdrs)
if code == 200 and isinstance(lic, dict):
for k in (
"chat_device_limit", "chat_devices_used",
"contributing_office_licenses", "chat_devices_per_license",
):
results.append(_print_result(f"license_field_{k}", k in lic, f"present"))
results.append(_print_result("license_valid_bool", "valid" in lic))
else:
results.append(_print_result("license_status", False, f"http={code}"))
# I. Dafalgan regression (local)
from aza_bibliothek import apply_korrekturen, load_merged_korrekturen_data
data = load_merged_korrekturen_data()
out, applied = apply_korrekturen("Patient nimmt Daffalgan.", data)
results.append(_print_result("dafalgan_regression", "Dafalgan" in out and "Daffalgan" not in out))
all_ok = all(results)
print(f"\nSUMMARY: {sum(results)}/{len(results)} passed")
return 0 if all_ok else 1
if __name__ == "__main__":
sys.exit(main())