package mimeparse import ( "errors" "strings" "testing" ) // TestParseTolerant_SingleBrokenPartDoesNotAbortWholeMessage ist die // geforderte Pflichtprüfung/Lücke (ING-10): ParseTolerant war bislang // vollständig ungetestet (0% Abdeckung) — genau der aus // known-issues-archivmail.md #4 bekannte Fehler (kritische // Ingestion-Logik ohne Tests). Ein Anhang, der die Größenbegrenzung // überschreitet, darf die übrigen Teile NICHT mit sich reißen // (Akzeptanzkriterium 3 des ursprünglichen Tickets IMP-02). func TestParseTolerant_SingleBrokenPartDoesNotAbortWholeMessage(t *testing.T) { raw := "From: a@example.com\r\n" + "Content-Type: multipart/mixed; boundary=\"b\"\r\n\r\n" + "--b\r\n" + "Content-Type: text/plain; charset=utf-8\r\n\r\n" + "Guter Teil\r\n" + "--b\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Disposition: attachment; filename=\"zu-gross.bin\"\r\n\r\n" + strings.Repeat("x", 1000) + "\r\n" + "--b\r\n" + "Content-Type: text/plain; charset=utf-8\r\n\r\n" + "Zweiter guter Teil\r\n" + "--b--\r\n" msg, partErrors, err := ParseTolerant(strings.NewReader(raw), 100, defaultMaxSize) if err != nil { t.Fatalf("ParseTolerant: unerwarteter gesamtfehler: %v", err) } if len(partErrors) != 1 { t.Fatalf("erwartete genau 1 teilfehler (überdimensionierter anhang), habe %d: %+v", len(partErrors), partErrors) } if len(msg.Parts) != 2 { t.Fatalf("erwartete 2 verarbeitete teile trotz des fehlerhaften anhangs, habe %d", len(msg.Parts)) } if string(msg.Parts[0].Content) != "Guter Teil" || string(msg.Parts[1].Content) != "Zweiter guter Teil" { t.Fatalf("unerwarteter inhalt der verbleibenden teile: %+v", msg.Parts) } } // TestParseTolerant_TotalSizeBudgetEnforcedAcrossParts ist // Akzeptanzkriterium 2 des ursprünglichen Tickets IMP-02: ein // Gesamtgrößenlimit über ALLE Teile hinweg, zusätzlich zum // Je-Anhang-Limit. func TestParseTolerant_TotalSizeBudgetEnforcedAcrossParts(t *testing.T) { raw := "From: a@example.com\r\n" + "Content-Type: multipart/mixed; boundary=\"b\"\r\n\r\n" + "--b\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Disposition: attachment; filename=\"a.bin\"\r\n\r\n" + strings.Repeat("x", 60) + "\r\n" + "--b\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Disposition: attachment; filename=\"b.bin\"\r\n\r\n" + strings.Repeat("y", 60) + "\r\n" + "--b--\r\n" // Je-Anhang-Limit großzügig (100), Gesamtlimit knapp (80) — der // zweite Anhang muss am GESAMTLIMIT scheitern, nicht am // Je-Anhang-Limit. msg, partErrors, err := ParseTolerant(strings.NewReader(raw), 100, 80) if err != nil { t.Fatalf("ParseTolerant: unerwarteter gesamtfehler: %v", err) } if len(msg.Parts) != 1 { t.Fatalf("erwartete genau 1 teil innerhalb des gesamtbudgets, habe %d", len(msg.Parts)) } if len(partErrors) != 1 || !errors.Is(partErrors[0].Err, ErrMessageTooLarge) { t.Fatalf("erwartete genau 1 ErrMessageTooLarge-teilfehler, habe: %+v", partErrors) } } // TestParseTolerant_StructurallyBrokenMultipartStillFails belegt: nur // eine strukturell unlesbare Hülle (fehlende Boundary) liefert // weiterhin einen echten Gesamtfehler — kein Teil-für-Teil-Fallback // möglich, wie im Code dokumentiert. func TestParseTolerant_StructurallyBrokenMultipartStillFails(t *testing.T) { raw := "From: a@example.com\r\n" + "Content-Type: multipart/mixed\r\n\r\n" + // keine boundary=... angegeben "irgendwas" _, _, err := ParseTolerant(strings.NewReader(raw), 100, defaultMaxSize) if err == nil { t.Fatalf("erwartete fehler bei multipart ohne boundary") } } // TestParseTolerant_NonMultipartSinglePart deckt den Nicht-Multipart- // Pfad von ParseTolerant ab (bislang ebenfalls ungetestet). func TestParseTolerant_NonMultipartSinglePart(t *testing.T) { raw := "From: a@example.com\r\n" + "Content-Type: text/plain; charset=utf-8\r\n\r\n" + "Einfache Nachricht ohne Multipart" msg, partErrors, err := ParseTolerant(strings.NewReader(raw), defaultMaxSize, defaultMaxSize) if err != nil { t.Fatalf("ParseTolerant: %v", err) } if len(partErrors) != 0 { t.Fatalf("unerwartete teilfehler: %+v", partErrors) } if len(msg.Parts) != 1 || string(msg.Parts[0].Content) != "Einfache Nachricht ohne Multipart" { t.Fatalf("unerwartetes ergebnis: %+v", msg.Parts) } }