Files
business-card-scanner/tests/test_vcard.py
Lucas Orth ab993e98e1 Visitenkarten-Scanner: Stapelscan, Extraktion, Übersicht, vCard-Export
Selbst gehostete PWA, die Visitenkarten von einem Foto freistellt, ausliest
und als Kontakt bereitstellt.

- Stapelscan: OpenCV findet die Kartenrechtecke über mehrere Binärmasken,
  entzerrt sie perspektivisch und schneidet sie einzeln aus. Ohne Fund gilt
  das ganze Foto als eine Karte.
- Extraktion: ein Aufruf je Zuschnitt an das Vision-Modell mit
  JSON-Schema. Kein vorgeschaltetes OCR - das würde Layout und
  Schriftgrößen wegwerfen, aus denen die Feldzuordnung entsteht.
- Metadaten: Aufnahmezeit und GPS aus den EXIF-Daten des Fotos, Ortsname
  über Nominatim, Browserstandort nur als Rückfallebene.
- Übersicht mit Volltextsuche und Filtern, Detailansicht mit Korrekturmaske.
- Notizfeld je Karte, Erinnerungen per Mail inklusive Nachholen verpasster
  Termine nach einem Neustart.
- vCard 3.0 einzeln und als Sammeldatei, Karte gilt danach als exportiert.
- Anmeldung über ein Passwort, Sitzung als signiertes Cookie.
- Deployment per Tag-Push nach den Konventionen in DEPLOY.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:45:56 +02:00

72 lines
2.0 KiB
Python

from app import vcard
BASE = {
"full_name": "Dr. Anna Berg",
"first_name": "Anna",
"last_name": "Berg",
"job_title": "Geschäftsführerin",
"company": "Berg & Partner GmbH",
"email": "a.berg@berg-partner.de",
"phone": "+49 511 1234560",
"mobile": "+49 170 1234567",
"website": "berg-partner.de",
"street": "Musterweg 3",
"postal_code": "30159",
"city": "Hannover",
"country": "Deutschland",
"notes": "Sucht ein neues CRM.",
"captured_at": "2026-09-01T10:15:00+00:00",
"place": "Hannover, Messegelände",
"lat": 52.3216,
"lon": 9.8078,
}
def test_enthaelt_die_pflichtzeilen():
output = vcard.build(BASE)
assert output.startswith("BEGIN:VCARD\r\nVERSION:3.0\r\n")
assert output.endswith("END:VCARD\r\n")
assert "FN:Dr. Anna Berg" in output
assert "N:Berg;Anna;;;" in output
assert "TEL;TYPE=CELL:+49 170 1234567" in output
def test_kommas_und_semikolons_werden_maskiert():
output = vcard.build({**BASE, "company": "Berg, Partner; Co"})
assert "ORG:Berg\\, Partner\\; Co" in output
def test_notiz_traegt_zeit_und_ort():
note = [line for line in vcard.build(BASE).splitlines() if line.startswith("NOTE:")][0]
assert "Sucht ein neues CRM." in note
assert "01.09.2026" in note
assert "Messegelände" in note
def test_leere_karte_bekommt_trotzdem_einen_namen():
output = vcard.build({"notes": ""})
assert "FN:Unbenannt" in output
def test_firma_ersetzt_den_namen_wenn_keiner_erkannt_wurde():
output = vcard.build({"company": "Berg & Partner GmbH"})
assert "FN:Berg & Partner GmbH" in output
def test_mehrere_karten_in_einer_datei():
output = vcard.build_many([BASE, {"full_name": "Bea Kurz"}])
assert output.count("BEGIN:VCARD") == 2
assert output.count("END:VCARD") == 2
def test_dateiname_ist_dateisystemtauglich():
assert vcard.filename(BASE) == "Dr-Anna-Berg.vcf"
assert vcard.filename({}) == "Unbenannt.vcf"
assert vcard.filename({"full_name": "?!"}) == "kontakt.vcf"