"""Tests for extract_contracts.py — run: python test_extract_contracts.py Zero third-party deps (mirrors test_extract_docx.py). Builds tiny in-memory .docx files (a zip with word/document.xml) so the tests run without the network share. Covers: filename parsing, run-splitting de-mangling, both body layouts (side-by-side / stacked), price/date normalisation, and skip rules. """ import io import os import sys import zipfile try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") except Exception: pass sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import extract_contracts as ec failed = 0 def check(name, cond): global failed print(("ok: " if cond else "FAIL: ") + name) if not cond: failed += 1 def make_docx(paragraphs): """paragraphs: list of lists of run-strings → bytes of a .docx zip.""" body = [] for runs in paragraphs: rs = "".join(f"{r}" for r in runs) body.append(f"{rs}") xml = ('' + "".join(body) + "") buf = io.BytesIO() with zipfile.ZipFile(buf, "w") as z: z.writestr("word/document.xml", xml) return buf.getvalue() def write_tmp(name, data): d = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_test_tmp") os.makedirs(d, exist_ok=True) p = os.path.join(d, name) with open(p, "wb") as f: f.write(data) return p # --- _norm_date ----------------------------------------------------------- check("date 4-digit year", ec._norm_date("18.02.2014") == "2014-02-18") check("date 2-digit year", ec._norm_date("09.12.14") == "2014-12-09") check("date embedded", ec._norm_date("Bonn, 20.09.2014") == "2014-09-20") check("date invalid", ec._norm_date("99.99.9999") == "") check("date empty", ec._norm_date("") == "") # --- _clean_money --------------------------------------------------------- check("money comma", ec._clean_money("27,50 Euro (Überweisung)") == "27,50") check("money dot→comma", ec._clean_money("25.00 €") == "25,00") check("money plain", ec._clean_money("28 Euro") == "28") check("money none", ec._clean_money("kostenlos") == "") # --- parse_filename ------------------------------------------------------- fn = ec.parse_filename( "Zucht der kleinen Chaoten _ Agouti (Kathlin.Baxter) - Stephan Füchsle_.docx") check("fn color", fn["color"] == "Agouti") check("fn animals", fn["animals"] == ["Kathlin", "Baxter"]) check("fn buyer", fn["buyer"] == "Stephan Füchsle") fn2 = ec.parse_filename( "Zucht der kleinen Chaoten _ dd Polar Sp (Velvet.Vance Jr.)- Alexandra Wendler_.docx") check("fn multiword color", fn2["color"] == "dd Polar Sp") check("fn multiword animal", fn2["animals"] == ["Velvet", "Vance Jr"]) check("fn buyer 2", fn2["buyer"] == "Alexandra Wendler") fn3 = ec.parse_filename("Zucht der kleinen Chaoten _ Ethan - Stefanie Stoica_.docx") check("fn no-paren animal", fn3["animals"] == ["Ethan"]) check("fn no-paren buyer", fn3["buyer"] == "Stefanie Stoica") fn4 = ec.parse_filename("Zucht der kleinen Chaoten _Azrael_.docx") check("fn name-only animal", fn4["animals"] == ["Azrael"]) check("fn name-only no buyer", fn4["buyer"] == "") # --- run-splitting de-mangling (the core text-extraction fix) ------------- data = make_docx([["Name:", "F", "r", "au Josephin Kiefer"]]) p = write_tmp("split.docx", data) check("run-split joins to 'Frau Josephin Kiefer'", ec._full_text(p) == "Name:Frau Josephin Kiefer") money = make_docx([["Schutzgebühr: ", "3", "0,00 €"]]) p = write_tmp("money.docx", money) check("run-split price intact", ec._clean_money(ec._label_value(ec._full_text(p), r"Schutzgeb\w+")) == "30,00") # --- full contract: stacked layout (B) ------------------------------------ stacked = make_docx([ ["Vermittlungsvertrag"], ["Abgebender/Züchter:"], ["Name:", "Frau Drazena Rimac"], ["Straße:", "New-York-Str. 30"], ["Empfänger/Abnehmer:"], ["Name:", "Familie Tanja und Thorsten Kurz"], ["Straße:", "Ebelstraße 2"], ["Tierdaten:"], ["Name:", "Einstein"], ["Geburtsdatum:", "10.08.2019"], ["Geschlecht:", "männlich"], ["Farbschlag: ", "Schwarz"], ["Abgabedatum:", "31.12.2019"], ["Schutzgebühr: ", "25,00 €"], ["Butzbach, den 31.12.2019"], ]) p = write_tmp("stacked.docx", make_docx_b := stacked) rec = ec.parse_contract(p) check("stacked buyer", rec["buyer"] == "Familie Tanja und Thorsten Kurz") check("stacked not seller", rec["buyer"] != "Frau Drazena Rimac") check("stacked animal name", rec["animalNameBody"] == "Einstein") check("stacked dob", rec["dob"] == "2019-08-10") check("stacked gender", rec["gender"] == "Male") check("stacked color", rec["color"] == "Schwarz") check("stacked handover", rec["handoverDate"] == "2019-12-31") check("stacked price", rec["price"] == "25,00") check("stacked contract date", rec["contractDate"] == "2019-12-31") # --- full contract: side-by-side columns (A) ------------------------------ # Two columns render as separate paragraphs (table cells). sidebyside = make_docx([ ["Vermittlungsvertrag"], ["Züchter:"], ["Abnehmer:"], ["Name:", "Drazena Rimac"], ["Name:", "Andrea Thesing"], ["Straße:", "Alten-Busecker-Str. 57"], ["Straße:", "Veilchenweg 32"], ["Tierdaten:"], ["Name:", "Sally"], ["Geburtsdatum:", "03.02.2014"], ["Geschlecht:", "weiblich"], ["Farbschlag:", "Kohlfuchs-hell"], ["Abgabedatum:", "20.09.2014"], ["Gesamtpreis: ", "27,00 Euro (Überweisung)"], ["Bonn, 20.09.2014"], ]) p = write_tmp("sidebyside.docx", sidebyside) rec = ec.parse_contract(p) check("sbs buyer is Andrea Thesing", rec["buyer"] == "Andrea Thesing") check("sbs buyer not seller", rec["buyer"] != "Drazena Rimac") check("sbs animal", rec["animalNameBody"] == "Sally") check("sbs gender female", rec["gender"] == "Female") check("sbs price", rec["price"] == "27,00") # --- skip rules ----------------------------------------------------------- abstammung = make_docx([ ["Abstammungsnachweis"], ["Name:"], ["Grace"], ["Geburtsdatum:", "04.10.2015"], ]) p = write_tmp("abst.docx", abstammung) check("Abstammungsnachweis skipped", ec.parse_contract(p) is None) # bad zip badp = write_tmp("bad.docx", b"not a zip") check("bad zip → None", ec.parse_contract(badp) is None) # --- filename color fallback when body has no Farbschlag ------------------ nocolor = make_docx([ ["Vermittlungsvertrag"], ["Abnehmer:"], ["Name:", "Max Mustermann"], ["Tierdaten:"], ["Name:", "Bello"], ["Geburtsdatum:", "01.01.2020"], ["Geschlecht:", "männlich"], ["Gesamtpreis: ", "20,00 €"], ]) p = write_tmp("Zucht der kleinen Chaoten _ Agouti (Bello)- Max Mustermann_.docx", nocolor) rec = ec.parse_contract(p) check("color falls back to filename", rec["color"] == "Agouti") check("animals from filename", rec["animals"] == ["Bello"]) # cleanup tmp import shutil shutil.rmtree(os.path.join(os.path.dirname(os.path.abspath(__file__)), "_test_tmp"), ignore_errors=True) print() if failed: print(f"{failed} test(s) FAILED") sys.exit(1) print("All extract_contracts tests passed.")