package imap import ( "context" "net" "strings" "testing" ) // TestTenantScoping_IsolatedStoresNeverLeakAcrossServers ist die // geforderte Pflichtprüfung (ING-10, Akzeptanzkriterium 2): Tenant- // Scoping für den IMAP-Ingestion-Pfad. Zwei vollständig unabhängige // Server-Instanzen (Mandant A/B) mit identischem Benutzernamen/Passwort // und identischem Postfachnamen "INBOX", aber unterschiedlichem Inhalt // (als Flag codiert, damit ein FETCH ihn sichtbar macht) — Bug würde // sich hier als Vermischung der Flags zeigen. func TestTenantScoping_IsolatedStoresNeverLeakAcrossServers(t *testing.T) { auth := fakeAuthenticator{users: map[string]string{"alice": "geheim123"}} storeA := fakeMailboxStore{mailboxes: map[string][]Message{ "INBOX": {{SequenceNumber: 1, UID: 1, Flags: []string{"Mandant-A-Marker"}}}, }} storeB := fakeMailboxStore{mailboxes: map[string][]Message{ "INBOX": {{SequenceNumber: 1, UID: 1, Flags: []string{"Mandant-B-Marker"}}}, }} addrA, stopA := startIMAPServer(t, NewServer(auth, storeA)) defer stopA() addrB, stopB := startIMAPServer(t, NewServer(auth, storeB)) defer stopB() fetchA := fetchInboxFlags(t, addrA) fetchB := fetchInboxFlags(t, addrB) if !strings.Contains(fetchA, "Mandant-A-Marker") { t.Fatalf("mandant A hat nicht seine eigenen daten bekommen: %q", fetchA) } if !strings.Contains(fetchB, "Mandant-B-Marker") { t.Fatalf("mandant B hat nicht seine eigenen daten bekommen: %q", fetchB) } if strings.Contains(fetchA, "Mandant-B-Marker") || strings.Contains(fetchB, "Mandant-A-Marker") { t.Fatalf("mandantenvermischung: A=%q B=%q", fetchA, fetchB) } } func startIMAPServer(t *testing.T, srv *Server) (addr string, stop func()) { t.Helper() listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listener: %v", err) } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { _ = srv.Serve(ctx, listener) close(done) }() return listener.Addr().String(), func() { cancel() <-done } } func fetchInboxFlags(t *testing.T, addr string) string { t.Helper() c := dial(t, addr) defer c.close() c.sendTagged(t, "LOGIN alice geheim123") c.sendTagged(t, "SELECT INBOX") _, lines := c.sendTagged(t, "FETCH 1 (FLAGS)") return strings.Join(lines, "\n") }