# -*- coding: utf-8 -*- """Pruefstand: das Fenster ueber die API bedienen - mit Schluessel. Warum es das gibt (seine Ansage): *"bau dir steuer logik per api rein, dann kannst damit die sachen triggern, brauchst meine maus nicht"* und *"mach pw rein, also schluessel wie bei ssh key, das nur du und das beherbergungssteuer ding rein kann, weil muss ja sprechen"*. Geprueft wird: 1. Ohne Schluessel geht NICHTS - auch nicht "nur nachsehen". 2. Mit falschem Schluessel ebenfalls nicht. 3. Mit dem Schluessel aus dem gemeinsamen Ordner laesst sich das Fenster fuellen, nachlesen und eine Vorschau erzeugen. 4. Was einen BELEG schreibt, geht nur im Testlauf - und schreibt dann tatsaechlich die Rechnung. 5. ping und schau_nach bleiben schluessellos, sonst kann ein aelteres Steuerjournal nicht mehr anklopfen. """ import json import os import shutil import sys import tempfile import threading QUELLE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, QUELLE) tmp = tempfile.mkdtemp(prefix="api_pruef_") ordner = os.path.join(tmp, "rechnungen") gemeinsam_ordner = os.path.join(tmp, "install") os.makedirs(ordner) os.makedirs(gemeinsam_ordner) import config # noqa: E402 shutil.copy(os.path.join(QUELLE, "config.json"), os.path.join(tmp, "config.json")) config.BASE_DIR = tmp config.CONFIG_PATH = os.path.join(tmp, "config.json") roh = json.load(open(config.CONFIG_PATH, encoding="utf-8")) roh["rechnung_jahr"], roh["rechnung_zaehler"] = 2026, 0 # Der Erststart-Zustand gehoert dem Pruefstand, nicht der config des # Entwicklerrechners: sonst laeuft er in einen anderen Zweig, sobald hier # jemand die Nummer einmal eingerichtet hat. roh["nummer_eingerichtet"] = False roh["output_dir"] = ordner roh["gemeinsam_ordner"] = gemeinsam_ordner json.dump(roh, open(config.CONFIG_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=2) os.environ["CAMPINGHOF_TEST"] = "1" import api # noqa: E402 import app as A # noqa: E402 import gemeinsam # noqa: E402 from tkinter import messagebox # noqa: E402 def _reissleine(): print("ABBRUCH | Pruefstand haengt (Zeitgrenze)") os._exit(2) _wecker = threading.Timer(120, _reissleine) _wecker.daemon = True _wecker.start() fehler = [] def pruefe(bedingung, text): zeile = (" OK | " if bedingung else "FEHLER | ") + text k = sys.stdout.encoding or "utf-8" print(zeile.encode(k, "replace").decode(k)) if not bedingung: fehler.append(text) messagebox.showinfo = messagebox.showwarning = messagebox.showerror = lambda *a, **k: None # Der Erprobungshinweis wird von der pauschalen Antwort unten mit erfasst. # Geprueft wird er eigens in pruef_hinweis.py. messagebox.askyesno = lambda *a, **k: True f = A.RechnungsApp() f.cfg["output_dir"] = ordner f.buch = gemeinsam.Nummernbuch(gemeinsam_ordner) f.api.starten(f.buch, ordner=gemeinsam_ordner) f.update() port = f.api.port pruefe(bool(port), f"die API laeuft ({port})") schluessel = api.schluessel_holen(gemeinsam_ordner) pruefe(len(schluessel) == 64, f"ein Schluessel liegt im gemeinsamen Ordner ({len(schluessel)} Zeichen)") pruefe(os.path.exists(os.path.join(gemeinsam_ordner, api.SCHLUESSEL_DATEI)), "und zwar als Datei neben dem Nummernbuch") pruefe(api.schluessel_holen(gemeinsam_ordner) == schluessel, "beim zweiten Lesen kommt derselbe zurueck (kein neuer je Start)") def rufen(**nachricht): """Direkt anrufen - der Pruefstand spielt hier das andere Programm.""" was = nachricht.pop("was", "aktion") return api._reden(port, was, zeitgrenze=20.0, **nachricht) # Das Fenster muss waehrend des Anrufs weiterlaufen: die Aktion wird in seinem # Faden ausgefuehrt. Also nebenher anrufen und die Ereignisschleife drehen. def anrufen_und_drehen(**nachricht): ergebnis = {} def anruf(): ergebnis["antwort"] = rufen(**nachricht) t = threading.Thread(target=anruf, daemon=True) t.start() while t.is_alive(): f.update() return ergebnis.get("antwort") # ---- 1. + 2. Ohne (oder mit falschem) Schluessel: kein Zutritt ------------- pruefe(anrufen_und_drehen(name="zustand") == "kein zutritt", "ohne Schluessel: kein Zutritt") pruefe(anrufen_und_drehen(name="zustand", schluessel="0" * 64) == "kein zutritt", "falscher Schluessel: kein Zutritt") # ---- 5. Die alten zwei Woerter bleiben schluessellos ----------------------- pruefe(anrufen_und_drehen(was="ping") == api.ANTWORT_HALLO, "ping antwortet weiterhin ohne Schluessel") # ---- 3. Mit Schluessel: fuellen, nachlesen, Vorschau ----------------------- antwort = anrufen_und_drehen( name="setze", schluessel=schluessel, daten={"kunde": {"vorname": "Anna", "nachname": "Beispiel", "strasse": "Musterweg 1", "plzort": "12345 Musterstadt"}, "anreise": "01.09.2026", "abreise": "04.09.2026", "positionen": [{"nr": sorted(f.anzahl_vars)[0], "anzahl": 3}]}) pruefe(isinstance(antwort, dict) and antwort.get("kunde", {}).get("nachname") == "Beispiel", f"mit Schluessel laesst sich das Formular fuellen ({str(antwort)[:60]}…)") pruefe(antwort.get("gesamt", 0) > 0, f"und es ist gerechnet ({antwort.get('gesamt')})") antwort = anrufen_und_drehen(name="vorschau", schluessel=schluessel) pfad = (antwort or {}).get("pfad", "") pruefe(bool(pfad) and os.path.exists(pfad), f"die Vorschau entsteht ohne Maus ({os.path.basename(pfad)})") pruefe(not os.listdir(ordner), "und sie landet NICHT im Rechnungsordner") # ---- 4. Beleg schreiben --------------------------------------------------- antwort = anrufen_und_drehen(name="rechnung", schluessel=schluessel) pdfs = [n for n in os.listdir(ordner) if n.lower().endswith(".pdf")] pruefe(len(pdfs) == 1, f"im Testlauf schreibt die API die Rechnung ({pdfs})") # Und im ECHTEN Betrieb nicht: dieselbe Aktion, ohne den Pruefschalter. # Umgeschaltet wird die Umgebungsvariable selbst - das Programm fragt sie bei # jeder Aktion neu ab, genau wie beim echten Start. os.environ.pop("CAMPINGHOF_TEST", None) antwort = anrufen_und_drehen(name="rechnung", schluessel=schluessel) os.environ["CAMPINGHOF_TEST"] = "1" pruefe(isinstance(antwort, dict) and "nur im Testlauf" in str(antwort.get("fehler", "")), f"im echten Betrieb verweigert sie den Beleg ({antwort})") pruefe(len([n for n in os.listdir(ordner) if n.lower().endswith(".pdf")]) == 1, "und es kam keine zweite Rechnung dazu") # Unbekannter Name sagt, was es gibt - statt still nichts zu tun. antwort = anrufen_und_drehen(name="tanz", schluessel=schluessel) pruefe("kenne ich nicht" in str(antwort), f"unbekannte Aktion wird benannt ({str(antwort)[:50]}…)") f.api.stoppen(f.buch) f.destroy() print() if fehler: print(f"FEHLGESCHLAGEN: {len(fehler)}") for x in fehler: print(" -", x) print(f"(Ordner bleibt: {tmp})") sys.exit(1) shutil.rmtree(tmp, ignore_errors=True) print("ALLES GRUEN")