# Proteus-Testdaten-Pipeline Ergänzender Ansatz zur [Codeanalyse](UPDATEANALYSE.md): Statt den `__register__`-Code zu lesen, wird ein deklarativ definierter Testdatensatz über Proteus in zwei frisch installierten Tryton-Instanzen (5.0 und 8.0) tatsächlich angelegt. Ziel ist, dieselbe Datei gegen beide Versionen laufen zu lassen, damit sowohl die Skript-Ausgabe (`compare_snapshots.py`) als auch anschließend die SQL-Dumps beider Datenbanken vergleichbar sind - dafür müssen inhaltlich identische Daten in derselben Reihenfolge eingespielt werden. ## Workflow ``` tests/scenario_*.rst (Tryton-Repo, pro Modul) │ rst_to_dataset.py ▼ rsts/e/*.yaml (ein YAML-Record pro angelegtem Proteus-Objekt) │ rsts_merge_dedup.py ▼ reports/_testdata_merged.yaml (dedupliziert, abhängigkeitskorrekt sortiert) │ create_dataset.py --database ... ▼ snapshot_.json │ compare_snapshots.py snapshot_a.json snapshot_b.json ▼ Fund-Liste: status_changed / value_changed / missing_record ``` ### 1. `rst_to_dataset.py` – Testdaten aus den Modul-eigenen Tests extrahieren Tryton-Module bringen eigene `tests/scenario_*.rst`-Doctests mit (ausführbarer Proteus-Code in `>>>`-Zeilen). Das Skript parst diese Dateien per AST und erkennt: ``` >>> Party = Model.get('party.party') >>> party = Party(name='Testfirma GmbH') >>> party.vat_number = '123' >>> party.save() ``` → wird zu einem YAML-Record mit `key`, `model`, `fields`. Referenzen auf zuvor im selben File angelegte Records werden zu `{"$ref": "..."}`, `Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`. Ein weiteres, sehr häufiges Muster wird ebenfalls erkannt: Zugriff auf bereits vorhandene Stammdaten (Länder, Währungen, Einheiten, Standard-Lagerorte, ... - i. d. R. per XML-Fixture beim Modul-Aktivieren angelegt, nicht Teil des eigentlichen Testdatensatzes): ``` >>> ProductUom = Model.get('product.uom') >>> unit, = ProductUom.find([('name', '=', 'Unit')]) ``` → wird zu `{"$search": {"model": "product.uom", "domain": [["name", "=", "Unit"]]}}` - aufgelöst wird das erst live beim Anlegen durch `create_dataset.py` (Schritt 3), nicht schon hier, da diese Records ja nicht selbst Teil der YAML sind. Nur literale Domains werden erkannt (keine Variablen darin); sonst bleibt es wie bisher `$manual`. Alles sonst nicht automatisch Auflösbare (Wizards, `.click(...)`, Helper-Funktionsaufrufe) wird als `{"$manual": ""}` markiert oder ganz übersprungen - bewusst sichtbar unvollständig statt still falsch. Record-Keys werden mit dem Dateinamen präfixiert, damit beim Zusammenführen mehrerer Module keine Kollisionen entstehen. ### 2. `rsts_merge_dedup.py` – pro Version zusammenführen Liest alle `rsts/e/*.yaml` eines Verzeichnisses ein und entfernt Records mit identischem `model`+`fields` (z. B. dasselbe Basis-`product.template`, das in vielen Szenarien wiederverwendet wird). `$ref`-Verweise auf entfernte Duplikate werden auf den behaltenen Record umgeschrieben. Danach wird per Kahn-Algorithmus so sortiert, dass `$ref`-Abhängigkeiten immer VOR dem referenzierenden Record stehen, aber innerhalb der Freiheitsgrade weiterhin nach `model` gruppiert wird. **Achtung:** Dedupliziert bewusst GLOBAL, auch innerhalb derselben Datei. Bei Szenarien, die zwei absichtlich gleich aussehende Records anlegen (z. B. `scenario_party_replace.rst`: zwei Parteien namens "Pam", um eine zu ersetzen), verändert das den Sinn des Szenarios - für einen groben Testdatensatz ist das in Ordnung, für szenario-treue Tests nicht. ### 3. `create_dataset.py` – gegen eine echte Instanz anlegen Legt die Records der Reihe nach per Proteus an, liest jeden Wert danach FRISCH VOM SERVER zurück (nicht aus dem lokalen Proteus-Cache), damit automatische Reformatierung sichtbar wird. Ergebnis: ein JSON-Snapshot (`status`/`error`/`stored` pro Record). **Feld-Umbenennungen zwischen Versionen** (`FIELD_RENAMES`): Da dieselbe YAML gegen mehrere Versionen laufen soll, aber Felder umbenannt werden können (bestätigt: `stock.move.uom` → `stock.move.unit` zwischen 5.0 und 8.0), prüft das Skript pro Modell zuerst per `fields_get()`, ob der YAML-Feldname auf dem jeweiligen Server existiert, und weicht sonst auf einen bekannten, bidirektionalen Alias aus. Ein wirklich unbekanntes Feld bleibt trotzdem ein Fehler - das ist ja gerade das gesuchte Signal für eine echte Schema-Änderung. Weitere Umbenennungen bei Bedarf in `FIELD_RENAMES` ergänzen, sobald ein Lauf sie aufdeckt. **`$search`-Auflösung:** Werte der Form `{"$search": {"model": ..., "domain": ...}}` (von `rst_to_dataset.py` erzeugt, siehe Schritt 1) werden live per `Model.get(model).find(domain)` gegen den Zielserver aufgelöst - für Stammdaten, die nicht selbst angelegt werden, sondern schon per XML-Fixture da sind. Ergebnis wird pro Lauf gecacht (dieselbe Suche, z. B. nach der Einheit "Unit", kommt in vielen Records vor). Kein Treffer -> Fehler mit Klartext statt eines neu angelegten Records - das ist selbst ein Signal, dass sich die Stammdaten zwischen den Versionen geändert haben. ### 4. `compare_snapshots.py` – zwei Versionen gegenüberstellen Vergleicht zwei Snapshots (gleiche YAML, zwei Versionen/Datenbanken) und markiert `status_changed` (ein Record schlägt nur in einer Version fehl - Kandidat für Format-/Constraint-/Pflichtfeld-Änderung), `value_changed` (gespeicherter Wert weicht trotz gleichem Input ab - automatische Reformatierung) und `missing_record`. ## Stand - `rst_to_dataset.py` und `rsts_merge_dedup.py` sind gegen echte `scenario_*.rst`-Dateien aus `/mnt/tryton` (5.0- und 8.0-Branch) getestet, siehe `reports/5_testdata_merged.yaml` / `reports/8_testdata_merged.yaml` (jeweils NUR aus den Szenarien der jeweiligen Version extrahiert, noch NICHT zu einer gemeinsamen Datei zusammengeführt). - `create_dataset.py` und `compare_snapshots.py` sind noch nicht gegen eine echte Tryton-Instanz gelaufen (nur syntaktisch geprüft). - Welche Modelle/Felder überhaupt Testdaten brauchen bzw. wo sich 5.0 und 8.0 tatsächlich unterscheiden, siehe [MODELLVERGLEICH.md](MODELLVERGLEICH.md) - das steuert, wo als Nächstes von Hand YAML-Records ergänzt werden.