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