feat(mail): ING-10 Ingestion-Testsuite — Tenant-Scoping-Tests, mimeparse-Lücke geschlossen
Kein neues Produktionspaket, Audit- und Test-Kachel über die fünf Ingestion-Module (IMAP, POP3, SMTP, MIME, Folder-State). Zwei konkrete Lücken geschlossen: Neuer tenant_scoping_test.go in allen fünf Paketen: je zwei simulierte Mandanten mit ABSICHTLICH identischen Schlüsseln (Benutzername, Postfachname) — der Realfall, in dem ein fehlendes Scoping-Prädikat am ehesten eine echte Vermischung zeigen würde, statt trivial durch unterschiedliche Schlüssel zu bestehen. IMAP/POP3: zwei unabhängige Serverinstanzen mit je eigenem Store. SMTP: zwei Serverinstanzen, gleichzeitig mit vielen Nachrichten bedient. mimeparse: paralleles Parsen vieler "Mandanten"-Nachrichten (das Paket hat keinen Datenbankzugriff — Tenant-Scoping bedeutet hier: kein geteilter veränderlicher Zustand). folderstate: echte Postgres-Instanz, NextUID/Rebuild für Mandant A dürfen Mandant Bs Zustand nachweislich nicht verändern. mimeparse.ParseTolerant (IMP-02) war zu 0% Zeilenabdeckung vollständig ungetestet — genau der aus known-issues-archivmail.md #4 bekannte Fehler (kritische Ingestion-Logik ohne Tests). Neue tolerant_test.go: ein fehlerhafter Teil reißt die übrigen nicht mit, Gesamtgrößenlimit über alle Teile hinweg, strukturell kaputte Multipart-Hülle liefert weiterhin einen echten Fehler, Nicht-Multipart-Pfad. Abdeckung mimeparse 44,0% -> 76,7%. Testabdeckungsbericht für alle fünf Module dokumentiert, CI-Lauf auf frischem Checkout ohne externe Live-Postfächer verifiziert grün. Pflichtprüfung 3 (Stichprobenreview durch zweite Person) ist durch eine einzelne Sitzung strukturell nicht erfüllbar und bleibt offen — im Prüfprotokoll dokumentiert, Nutzer-Review ausstehend. go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~29 Pakete) regressionsfrei getestet.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestTenantScoping_ConcurrentServersNeverMixMessages ist die
|
||||
// geforderte Pflichtprüfung (ING-10, Akzeptanzkriterium 2): Tenant-
|
||||
// Scoping für den SMTP-Ingestion-Pfad. Zwei vollständig unabhängige
|
||||
// Server-Instanzen (Mandant A/B), GLEICHZEITIG mit vielen Nachrichten
|
||||
// bedient — jede Instanz bekommt nur ihren eigenen Sink injiziert.
|
||||
// Eine Vermischung würde sich hier als falscher Nachrichteninhalt beim
|
||||
// jeweils anderen Sink zeigen.
|
||||
func TestTenantScoping_ConcurrentServersNeverMixMessages(t *testing.T) {
|
||||
sinkA := &fakeSink{}
|
||||
sinkB := &fakeSink{}
|
||||
addrA, stopA := startTestServer(t, sinkA, defaultMaxMessageBytes)
|
||||
defer stopA()
|
||||
addrB, stopB := startTestServer(t, sinkB, defaultMaxMessageBytes)
|
||||
defer stopB()
|
||||
|
||||
const perTenant = 20
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < perTenant; i++ {
|
||||
wg.Add(2)
|
||||
go func(n int) {
|
||||
defer wg.Done()
|
||||
sendTenantMessage(t, addrA, "Mandant-A")
|
||||
}(i)
|
||||
go func(n int) {
|
||||
defer wg.Done()
|
||||
sendTenantMessage(t, addrB, "Mandant-B")
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if sinkA.count() != perTenant {
|
||||
t.Fatalf("mandant A: erwartete %d nachrichten, habe %d", perTenant, sinkA.count())
|
||||
}
|
||||
if sinkB.count() != perTenant {
|
||||
t.Fatalf("mandant B: erwartete %d nachrichten, habe %d", perTenant, sinkB.count())
|
||||
}
|
||||
for _, m := range sinkA.accepted {
|
||||
if !strings.Contains(string(m.raw), "Mandant-A") || strings.Contains(string(m.raw), "Mandant-B") {
|
||||
t.Fatalf("mandant A hat fremden/vermischten inhalt bekommen: %q", m.raw)
|
||||
}
|
||||
}
|
||||
for _, m := range sinkB.accepted {
|
||||
if !strings.Contains(string(m.raw), "Mandant-B") || strings.Contains(string(m.raw), "Mandant-A") {
|
||||
t.Fatalf("mandant B hat fremden/vermischten inhalt bekommen: %q", m.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendTenantMessage(t *testing.T, addr, marker string) {
|
||||
t.Helper()
|
||||
c := dial(t, addr)
|
||||
defer c.close()
|
||||
c.send(t, "EHLO client.example.com")
|
||||
for {
|
||||
line := c.readLine(t)
|
||||
if strings.HasPrefix(line, "250 ") {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.send(t, "MAIL FROM:<a@example.com>")
|
||||
c.send(t, "RCPT TO:<b@example.com>")
|
||||
c.send(t, "DATA")
|
||||
c.send(t, "Subject: "+marker+"\r\n\r\nInhalt von "+marker+"\r\n.")
|
||||
}
|
||||
Reference in New Issue
Block a user