23e11c16c6
Commit e2f0384 (vor dieser Session) liess EXPORTCSV/EXPORTSIVAS auch die in
VF_n-Blockdefinitionen verpackten Separator_SP-Sensoren als eigene Zeile
exportieren (TeileArt "ILS 2.0 Separator"). tests/test_foerderer.py wurde
seitdem nicht angepasst und nahm weiter an, JEDE Zeile sei eine Vario-
Strecke - drei Tests schlugen daher fehl, sobald die CSV tatsaechlich
vorlag (39 statt 13 Zeilen: 13 Vario- + 26 Separator-Zeilen).
Neu NEBEN_TEILEARTEN = {"ILS 2.0 Separator"} plus zwei Filter-Helfer
(_vario_rows/_separator_rows). test_vario_teileart, test_merkmale_felder
und test_anzahl_csv_zeilen_entspricht_gebaut pruefen jetzt nur noch die
Vario-Zeilen (verifiziert: 13 Vario- + 26 Separator-Zeilen in der aktuellen
CSV, Summe stimmt).
Statt die Separator-Zeilen nur zu ignorieren, zwei neue, eigenstaendige
Tests:
- test_teileart_bekannt: jede Zeile ist Vario-Strecke ODER eine bekannte
Neben-TeileArt - faengt eine dritte, unerwartete Art, die die beiden
Filter sonst stillschweigend durchliessen.
- test_separator_merkmale_felder: die Separator-Merkmale haben ihr eigenes,
kleineres Pflichtfeld-Schema (Zuordnung, ARTINR) statt der Vario-Felder.
- test_separator_zuordnung_konsistent: der eigentliche Beweis, dass
csv:sep-proxies-erzeugen die Sensoren dem RICHTIGEN Wrapper zuordnet -
jede Separator-Zuordnung zeigt auf eine Vario-TeileId derselben Datei,
und je Vario-Zeile stimmt die Anzahl ihrer Separatoren mit dem eigenen
Merkmal Anzahl_Separator ueberein (test_export_ids.py prueft nur, dass
die Zuordnung ueberhaupt auf eine bekannte ID zeigt, nicht auf die
richtige). Gegengeprobt: eine verfaelschte Zuordnung wird erkannt.
Verifiziert: alle 27 Tests in test_foerderer.py gruen (vorher 3 FAILED),
Gesamtsuite 188 passed / 0 failed / 19 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
449 lines
19 KiB
Python
449 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
test_foerderer.py - Validiert VarioFoerderer Testergebnisse.
|
|
|
|
Prueft:
|
|
1. Status-Werte und Vollstaendigkeit (foerderer_results.json)
|
|
2. Mathematische Invarianten (Laengen, Winkel, Richtung)
|
|
3. Geometrisch unmoegliche Faelle
|
|
4. CSV-Export-Struktur (foerderer_tests_export.csv, aus TEST_EXPORT_ALL) -
|
|
seit csv:sep-proxies-erzeugen (Commit e2f0384) enthaelt die CSV neben
|
|
den Vario-Zeilen auch eine Zeile je verpacktem Separator-Sensor; siehe
|
|
NEBEN_TEILEARTEN und TestFoerdererExportCSV._vario_rows/_separator_rows.
|
|
5. Sivas-Export-Struktur (foerderer_tests_sivas.csv, aus TEST_EXPORT_ALL)
|
|
"""
|
|
|
|
import csv
|
|
import json
|
|
import os
|
|
import pytest
|
|
|
|
|
|
# TeileArt-Werte, die einen SENSOR beschreiben statt einer Vario-Strecke -
|
|
# csv:sep-proxies-erzeugen (export.lsp, Commit e2f0384) erfasst seither auch
|
|
# Separator_SP-Symbole, die als Sub-INSERT in der VF_n-Blockdefinition
|
|
# verpackt sind, und exportiert je Fund eine eigene Zeile. Foerderer kennt
|
|
# nur diese eine Sensorart (kein Scanner); die vollstaendige Liste steht in
|
|
# test_export_ids.py:SENSOR_TEILEARTEN.
|
|
NEBEN_TEILEARTEN = {"ILS 2.0 Separator"}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Hilfsfunktionen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _testdata_dir():
|
|
return os.getenv("DXFM_TESTDATA",
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata"))
|
|
|
|
|
|
def _output_dir():
|
|
return os.getenv("DXFM_TESTOUT",
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "output"))
|
|
|
|
|
|
def _load_json(path):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def _load_foerderer_results():
|
|
path = os.path.join(_output_dir(), "foerderer_results.json")
|
|
if not os.path.exists(path):
|
|
pytest.skip(
|
|
"foerderer_results.json nicht vorhanden - "
|
|
"TEST_FOERDERER in BricsCAD ausfuehren"
|
|
)
|
|
return _load_json(path)
|
|
|
|
|
|
def _load_foerderer_testdata():
|
|
path = os.path.join(_testdata_dir(), "foerderer_tests.json")
|
|
return _load_json(path)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Klasse 1: Status und Vollstaendigkeit
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFoerdererStatus:
|
|
"""Prueft Status-Werte und Vollstaendigkeit der Ergebnisse."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
self.results = _load_foerderer_results()
|
|
testdata = _load_foerderer_testdata()
|
|
self.tests = [t for t in testdata if "test_id" in t]
|
|
self.results_by_id = {r["test_id"]: r for r in self.results}
|
|
|
|
def test_alle_testfaelle_vorhanden(self):
|
|
"""Alle Testfaelle muessen in den Ergebnissen vorhanden sein."""
|
|
expected_ids = {t["test_id"] for t in self.tests}
|
|
result_ids = {r["test_id"] for r in self.results}
|
|
missing = expected_ids - result_ids
|
|
assert not missing, f"Fehlende Testfaelle in Ergebnissen: {missing}"
|
|
|
|
def test_status_werte_gueltig(self):
|
|
"""Status muss GEBAUT, NICHT_GEBAUT oder GEOMETRISCH_UNMOEGLICH sein."""
|
|
gueltige_status = {"GEBAUT", "NICHT_GEBAUT", "GEOMETRISCH_UNMOEGLICH"}
|
|
for r in self.results:
|
|
assert r["status"] in gueltige_status, (
|
|
f'{r["test_id"]}: Unbekannter Status "{r["status"]}"'
|
|
)
|
|
|
|
def test_geometrisch_unmoeglich_ab_ohne_hoehe(self):
|
|
"""VF_Ab_0 (Abwaerts ohne Hoehenunterschied) muss GEOMETRISCH_UNMOEGLICH sein."""
|
|
r = self.results_by_id.get("VF_Ab_0")
|
|
assert r is not None, "VF_Ab_0 fehlt in den Ergebnissen"
|
|
assert r["status"] == "GEOMETRISCH_UNMOEGLICH", (
|
|
f'VF_Ab_0: erwartet GEOMETRISCH_UNMOEGLICH, ist "{r["status"]}"'
|
|
)
|
|
|
|
def test_aufwaerts_flach_gebaut(self):
|
|
"""VF_Auf_0 (Aufwaerts, kein Hoehenunterschied) muss GEBAUT sein."""
|
|
r = self.results_by_id.get("VF_Auf_0")
|
|
assert r is not None, "VF_Auf_0 fehlt in den Ergebnissen"
|
|
assert r["status"] == "GEBAUT", (
|
|
f'VF_Auf_0: erwartet GEBAUT, ist "{r["status"]}"'
|
|
)
|
|
|
|
def test_mindestens_ein_gebaut(self):
|
|
"""Mindestens ein Testfall muss GEBAUT sein."""
|
|
gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
|
assert len(gebaut) > 0, "Kein einziger Foerderer konnte gebaut werden"
|
|
|
|
def test_richtung_vorhanden(self):
|
|
"""Jedes Ergebnis muss das Feld 'richtung' enthalten."""
|
|
for r in self.results:
|
|
assert "richtung" in r, f'{r["test_id"]}: Feld "richtung" fehlt'
|
|
assert r["richtung"] in ("Auf", "Ab"), (
|
|
f'{r["test_id"]}: Richtung "{r["richtung"]}" ist nicht "Auf" oder "Ab"'
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Klasse 2: Mathematische Invarianten
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFoerdererMathematik:
|
|
"""Prueft mathematische Korrektheit der berechneten Werte."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
self.results = _load_foerderer_results()
|
|
self.gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
|
|
|
def test_gebaut_hat_winkel_und_laengen(self):
|
|
"""GEBAUT-Ergebnisse muessen winkel, L_GF und L_VF enthalten."""
|
|
for r in self.gebaut:
|
|
assert "winkel" in r, f'{r["test_id"]}: Feld "winkel" fehlt'
|
|
assert "L_GF" in r, f'{r["test_id"]}: Feld "L_GF" fehlt'
|
|
assert "L_VF" in r, f'{r["test_id"]}: Feld "L_VF" fehlt'
|
|
|
|
def test_laengen_positiv(self):
|
|
"""L_GF und L_VF muessen beide groesser als 0 sein."""
|
|
for r in self.gebaut:
|
|
assert r["L_GF"] > 0, (
|
|
f'{r["test_id"]}: L_GF={r["L_GF"]} muss > 0 sein'
|
|
)
|
|
assert r["L_VF"] > 0, (
|
|
f'{r["test_id"]}: L_VF={r["L_VF"]} muss > 0 sein'
|
|
)
|
|
|
|
def test_winkel_gueltig(self):
|
|
"""Winkel muss aus dem festen Satz gueltiger Foerderer-Winkel stammen."""
|
|
gueltige_winkel = {9, 15, 21, 27, 33, 39, 45, 51}
|
|
for r in self.gebaut:
|
|
assert r["winkel"] in gueltige_winkel, (
|
|
f'{r["test_id"]}: Winkel={r["winkel"]} nicht in {sorted(gueltige_winkel)}'
|
|
)
|
|
|
|
def test_aufwaerts_groesserer_deltaH_groesserer_winkel(self):
|
|
"""Aufwaerts: groesserer Hoehenunterschied erfordert groesseren oder gleichen Winkel."""
|
|
aufwaerts_gebaut = sorted(
|
|
[r for r in self.results if r["richtung"] == "Auf" and r["status"] == "GEBAUT"],
|
|
key=lambda r: r["deltaH"]
|
|
)
|
|
for i in range(1, len(aufwaerts_gebaut)):
|
|
prev = aufwaerts_gebaut[i - 1]
|
|
curr = aufwaerts_gebaut[i]
|
|
assert curr["winkel"] >= prev["winkel"], (
|
|
f'{curr["test_id"]}: Winkel={curr["winkel"]} < '
|
|
f'vorheriger Winkel={prev["winkel"]} bei groesserem deltaH'
|
|
)
|
|
|
|
def test_kein_winkel_bei_nicht_gebaut(self):
|
|
"""NICHT_GEBAUT und GEOMETRISCH_UNMOEGLICH duerfen keinen Winkel haben."""
|
|
for r in self.results:
|
|
if r["status"] in ("NICHT_GEBAUT", "GEOMETRISCH_UNMOEGLICH"):
|
|
assert "winkel" not in r, (
|
|
f'{r["test_id"]}: Status={r["status"]} hat unerwartet Winkel'
|
|
)
|
|
|
|
def test_deltaH_und_deltaL_vorhanden(self):
|
|
"""Alle Ergebnisse muessen deltaH und deltaL enthalten."""
|
|
for r in self.results:
|
|
assert "deltaH" in r, f'{r["test_id"]}: Feld "deltaH" fehlt'
|
|
assert "deltaL" in r, f'{r["test_id"]}: Feld "deltaL" fehlt'
|
|
assert r["deltaL"] > 0, f'{r["test_id"]}: deltaL muss > 0 sein'
|
|
assert r["deltaH"] >= 0, f'{r["test_id"]}: deltaH muss >= 0 sein'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Klasse 3: CSV-Export-Pruefung (foerderer_tests_export.csv)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFoerdererExportCSV:
|
|
"""Prueft den CSV-Export aus BricsCAD (foerderer_tests_export.csv, aus TEST_EXPORT_ALL)."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
csv_path = os.path.join(_output_dir(), "foerderer_tests_export.csv")
|
|
if not os.path.exists(csv_path):
|
|
pytest.skip(
|
|
"foerderer_tests_export.csv nicht vorhanden - "
|
|
"TEST_FOERDERER und anschliessend TEST_EXPORT_ALL in BricsCAD ausfuehren"
|
|
)
|
|
self.rows = []
|
|
with open(csv_path, "r", encoding="utf-8") as f:
|
|
reader = csv.reader(f, delimiter=";")
|
|
self.header = next(reader)
|
|
for row in reader:
|
|
self.rows.append(row)
|
|
# Spalten header-basiert ansprechen (die CSV-Struktur waechst - Insertpoint,
|
|
# K1-K4, Warnungen kamen spaeter dazu, siehe lib/export_csv.py) statt ueber
|
|
# feste Indizes. So bleiben die Tests robust gegen Spalten-Erweiterungen.
|
|
self.col = {name: i for i, name in enumerate(self.header)}
|
|
|
|
def _cell(self, row, spalte):
|
|
"""Zellwert einer Zeile ueber den Spaltennamen (oder None, falls fehlt)."""
|
|
i = self.col.get(spalte)
|
|
if i is None or i >= len(row):
|
|
return None
|
|
return row[i]
|
|
|
|
def _vario_rows(self):
|
|
"""Nur die eigentlichen Foerderer-Zeilen, ohne die mitexportierten
|
|
Separator-Sensoren (siehe NEBEN_TEILEARTEN)."""
|
|
return [row for row in self.rows
|
|
if self._cell(row, "TeileArt") not in NEBEN_TEILEARTEN]
|
|
|
|
def _separator_rows(self):
|
|
return [row for row in self.rows
|
|
if self._cell(row, "TeileArt") == "ILS 2.0 Separator"]
|
|
|
|
def test_header_felder(self):
|
|
"""CSV-Header muss die erwarteten Spalten enthalten (Reihenfolge/Zusatz-
|
|
spalten wie Insertpoint/K1-K4/Warnungen egal - per Name geprueft)."""
|
|
expected = ["Elementnummer", "TeileArt", "TeileId", "Bezeichnung",
|
|
"Planquadrat", "Anzahl", "Position", "Boundingbox", "Nachbarn",
|
|
"Fehler", "Merkmale"]
|
|
fehlend = [name for name in expected if name not in self.col]
|
|
assert not fehlend, (
|
|
f"Fehlende Spalten im CSV-Header: {fehlend}\nHeader ist: {self.header}"
|
|
)
|
|
|
|
def test_hat_eintraege(self):
|
|
"""CSV muss mindestens einen Dateneintrag enthalten."""
|
|
assert len(self.rows) > 0, "foerderer_tests_export.csv enthaelt keine Daten"
|
|
|
|
def test_vario_teileart(self):
|
|
"""Jede Vario-Zeile (ohne die mitexportierten Separator-Sensoren, siehe
|
|
NEBEN_TEILEARTEN) muss TeileArt 'ILS 2.0 Strecke' haben (VarioFoerderer
|
|
ist ein Streckentyp, siehe export_csv.py:process_blocks)."""
|
|
for i, row in enumerate(self._vario_rows()):
|
|
teileart = self._cell(row, "TeileArt")
|
|
if teileart is None:
|
|
continue
|
|
assert teileart.strip('"') == "ILS 2.0 Strecke", (
|
|
f"Zeile {i + 2}: TeileArt='{teileart}', erwartet 'ILS 2.0 Strecke'"
|
|
)
|
|
|
|
def test_teileart_bekannt(self):
|
|
"""Jede Zeile ist entweder eine Vario-Strecke oder eine der bekannten
|
|
Neben-TeileArten - keine unerwartete dritte Art (die die beiden obigen
|
|
Filter sonst stillschweigend durchliesse)."""
|
|
bekannt = {"ILS 2.0 Strecke"} | NEBEN_TEILEARTEN
|
|
for i, row in enumerate(self.rows):
|
|
teileart = self._cell(row, "TeileArt")
|
|
if teileart is None:
|
|
continue
|
|
assert teileart.strip('"') in bekannt, (
|
|
f"Zeile {i + 2}: unbekannte TeileArt '{teileart}'"
|
|
)
|
|
|
|
def test_anzahl_spalte_ist_eins(self):
|
|
"""Anzahl-Spalte muss fuer alle Zeilen '1' sein."""
|
|
for i, row in enumerate(self.rows):
|
|
anzahl = self._cell(row, "Anzahl")
|
|
if anzahl is None:
|
|
continue
|
|
assert anzahl == "1", (
|
|
f"Zeile {i + 2}: Anzahl={anzahl}, erwartet 1"
|
|
)
|
|
|
|
def test_merkmale_sind_valides_json(self):
|
|
"""Merkmale-Spalte muss valides JSON enthalten."""
|
|
for i, row in enumerate(self.rows):
|
|
merkmale = self._cell(row, "Merkmale")
|
|
if merkmale is None:
|
|
continue
|
|
try:
|
|
json.loads(merkmale)
|
|
except json.JSONDecodeError as e:
|
|
pytest.fail(f"Zeile {i + 2}: Merkmale ist kein valides JSON: {e}")
|
|
|
|
def test_merkmale_felder(self):
|
|
"""Merkmale der Vario-Zeilen muessen alle Pflichtfelder enthalten. Die
|
|
mitexportierten Separator-Sensoren haben ein eigenes, kleineres Schema
|
|
(siehe test_separator_merkmale_felder) und sind hier ausgenommen."""
|
|
required = {"Typ", "Winkel", "DeltaH_mm", "DeltaL_mm",
|
|
"L_VF_m", "Hoehe_Von_mm", "Hoehe_Bis_mm"}
|
|
for i, row in enumerate(self._vario_rows()):
|
|
zelle = self._cell(row, "Merkmale")
|
|
if zelle is None:
|
|
continue
|
|
merkmale = json.loads(zelle)
|
|
missing = required - set(merkmale.keys())
|
|
assert not missing, (
|
|
f"Zeile {i + 2}: Fehlende Merkmale-Felder: {missing}"
|
|
)
|
|
|
|
def test_separator_merkmale_felder(self):
|
|
"""Merkmale der mitexportierten Separator-Sensoren muessen die
|
|
Zuordnung zu ihrem Vario-Wrapper tragen (siehe
|
|
test_separator_zuordnung_konsistent) sowie ARTINR."""
|
|
required = {"Zuordnung", "ARTINR"}
|
|
for i, row in enumerate(self._separator_rows()):
|
|
zelle = self._cell(row, "Merkmale")
|
|
if zelle is None:
|
|
continue
|
|
merkmale = json.loads(zelle)
|
|
missing = required - set(merkmale.keys())
|
|
assert not missing, (
|
|
f"Zeile {i + 2}: Fehlende Merkmale-Felder: {missing}"
|
|
)
|
|
|
|
def test_anzahl_csv_zeilen_entspricht_gebaut(self):
|
|
"""Anzahl der Vario-Zeilen (ohne die mitexportierten Separatoren) muss
|
|
der Anzahl GEBAUT-Testfaellen entsprechen."""
|
|
results = _load_foerderer_results()
|
|
anzahl_gebaut = sum(1 for r in results if r["status"] == "GEBAUT")
|
|
vario_rows = self._vario_rows()
|
|
assert len(vario_rows) == anzahl_gebaut, (
|
|
f"CSV hat {len(vario_rows)} Vario-Zeilen, "
|
|
f"erwartet {anzahl_gebaut} (Anzahl GEBAUT-Faelle)"
|
|
)
|
|
|
|
def test_separator_zuordnung_konsistent(self):
|
|
"""Jeder Separator zeigt (ueber Merkmale.Zuordnung) auf die TeileId
|
|
eines Vario-Eintrags DERSELBEN Datei, und je Vario-Zeile stimmt die
|
|
Anzahl seiner Separatoren mit ihrem eigenen Merkmal Anzahl_Separator
|
|
ueberein. Das ist der eigentliche Beweis, dass
|
|
csv:sep-proxies-erzeugen die verpackten Sensoren dem richtigen
|
|
Wrapper zuordnet - test_export_ids.py::test_sensor_zuordnung_existiert
|
|
prueft nur, dass die Zuordnung ueberhaupt auf eine bekannte ID zeigt."""
|
|
vario_rows = self._vario_rows()
|
|
vario_ids = {self._cell(row, "TeileId") for row in vario_rows}
|
|
|
|
separator_je_id = {}
|
|
for row in self._separator_rows():
|
|
zelle = self._cell(row, "Merkmale")
|
|
if zelle is None:
|
|
continue
|
|
zuordnung = json.loads(zelle).get("Zuordnung")
|
|
assert zuordnung in vario_ids, (
|
|
f'Separator {self._cell(row, "TeileId")}: Zuordnung '
|
|
f"'{zuordnung}' zeigt auf keine Vario-Zeile dieser Datei"
|
|
)
|
|
separator_je_id[zuordnung] = separator_je_id.get(zuordnung, 0) + 1
|
|
|
|
for row in vario_rows:
|
|
tid = self._cell(row, "TeileId")
|
|
zelle = self._cell(row, "Merkmale")
|
|
if zelle is None:
|
|
continue
|
|
soll = int(json.loads(zelle).get("Anzahl_Separator", 0))
|
|
ist = separator_je_id.get(tid, 0)
|
|
assert soll == ist, (
|
|
f"TeileId {tid}: Anzahl_Separator={soll}, aber {ist} "
|
|
f"Separator-Zeile(n) mit Zuordnung darauf gefunden"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Klasse 4: Sivas-Export-Pruefung (foerderer_tests_sivas.csv)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFoerdererExportSivas:
|
|
"""Prueft den Sivas-Export aus BricsCAD (foerderer_tests_sivas.csv, aus TEST_EXPORT_ALL)."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
csv_path = os.path.join(_output_dir(), "foerderer_tests_sivas.csv")
|
|
if not os.path.exists(csv_path):
|
|
pytest.skip(
|
|
"foerderer_tests_sivas.csv nicht vorhanden - "
|
|
"TEST_FOERDERER und anschliessend TEST_EXPORT_ALL in BricsCAD ausfuehren"
|
|
)
|
|
self.rows = []
|
|
with open(csv_path, "r", encoding="utf-8") as f:
|
|
reader = csv.reader(f, delimiter=";")
|
|
self.header = next(reader)
|
|
for row in reader:
|
|
self.rows.append(row)
|
|
|
|
def test_header_felder(self):
|
|
"""Sivas-CSV-Header muss korrekte Spalten haben."""
|
|
expected = ["Nr", "TeileArt", "SivasNummer", "Bezeichnung",
|
|
"Dispogruppe", "Anzahl", "Laenge[mm]", "IDs", "details"]
|
|
for i, name in enumerate(expected):
|
|
assert self.header[i] == name, (
|
|
f"Header[{i}]: erwartet '{name}', ist '{self.header[i]}'"
|
|
)
|
|
|
|
def test_hat_eintraege(self):
|
|
"""Sivas-CSV muss mindestens einen Dateneintrag enthalten."""
|
|
assert len(self.rows) > 0, "foerderer_tests_sivas.csv enthaelt keine Daten"
|
|
|
|
def test_strecke_zeile_vorhanden(self):
|
|
"""Eine Zeile mit TeileArt 'ILS 2.0 Strecke' muss vorhanden sein."""
|
|
teilearten = [row[1].strip('"') for row in self.rows if len(row) >= 2]
|
|
assert "ILS 2.0 Strecke" in teilearten, (
|
|
f"Keine Strecke-Zeile gefunden. Vorhandene: {teilearten}"
|
|
)
|
|
|
|
def test_automation_zeile_vorhanden(self):
|
|
"""Eine ILS Automation Summierungszeile muss vorhanden sein."""
|
|
teilearten = [row[1].strip('"') for row in self.rows if len(row) >= 2]
|
|
assert "ILS 2.0 Automation und Pneumatik - Gesamtanlage" in teilearten, (
|
|
f"Keine Automation-Zeile gefunden. Vorhandene: {teilearten}"
|
|
)
|
|
|
|
def test_details_sind_valides_json(self):
|
|
"""details-Spalte muss valides JSON enthalten."""
|
|
for i, row in enumerate(self.rows):
|
|
if len(row) < 9:
|
|
continue
|
|
try:
|
|
json.loads(row[8])
|
|
except json.JSONDecodeError as e:
|
|
pytest.fail(f"Zeile {i + 2}: details ist kein valides JSON: {e}")
|
|
|
|
def test_automation_details_felder(self):
|
|
"""Automation-Zeile muss Schluessel-Felder in details haben."""
|
|
required = {"Anzahl der einzelnen Variofoerderer",
|
|
"Laengen aller Variofoerderer",
|
|
"Anzahl aller Separatoren"}
|
|
for row in self.rows:
|
|
if len(row) < 9:
|
|
continue
|
|
if row[1].strip('"') == "ILS 2.0 Automation und Pneumatik - Gesamtanlage":
|
|
details = json.loads(row[8])
|
|
missing = required - set(details.keys())
|
|
assert not missing, (
|
|
f"Automation-details: fehlende Felder: {missing}"
|
|
)
|
|
break
|