229 lines
8.5 KiB
Python
229 lines
8.5 KiB
Python
# -*- 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())
|