package importnotify import ( "context" "net/http" "net/http/httptest" "sync" "testing" "gitea.perlbach24.de/scripte/nexarch/mail/internal/imapimport" "gitea.perlbach24.de/scripte/nexarch/mail/internal/notifyclient" ) type recordingNotifier struct { mu sync.Mutex events []notifyclient.Event } func (r *recordingNotifier) Notify(_ context.Context, ev notifyclient.Event) error { r.mu.Lock() defer r.mu.Unlock() r.events = append(r.events, ev) return nil } func (r *recordingNotifier) count() int { r.mu.Lock() defer r.mu.Unlock() return len(r.events) } // TestNotifyBatch_SingleNewMessageTriggersExactlyOneNotification ist // die geforderte Pflichtprüfung 1 (INT-05): Import einer Mail löst // genau eine Benachrichtigung aus. func TestNotifyBatch_SingleNewMessageTriggersExactlyOneNotification(t *testing.T) { n := &recordingNotifier{} err := NotifyBatch(context.Background(), n, "mandant-a", "INBOX", imapimport.SyncResult{NewMessages: 1}) if err != nil { t.Fatalf("NotifyBatch: %v", err) } if n.count() != 1 { t.Fatalf("erwartete genau 1 benachrichtigung, habe %d", n.count()) } if n.events[0].Count != 1 || n.events[0].TenantSlug != "mandant-a" { t.Fatalf("unerwartetes ereignis: %+v", n.events[0]) } } // TestNotifyBatch_MassImportProducesOneBundledNotification ist die // geforderte Pflichtprüfung 2 (INT-05): Massenimport erzeugt eine // gebündelte Zusammenfassung statt Dutzende Einzelbenachrichtigungen. func TestNotifyBatch_MassImportProducesOneBundledNotification(t *testing.T) { n := &recordingNotifier{} err := NotifyBatch(context.Background(), n, "mandant-a", "INBOX", imapimport.SyncResult{NewMessages: 50}) if err != nil { t.Fatalf("NotifyBatch: %v", err) } if n.count() != 1 { t.Fatalf("erwartete genau 1 GEBÜNDELTE benachrichtigung für 50 neue nachrichten, habe %d einzelne", n.count()) } if n.events[0].Count != 50 { t.Fatalf("erwartete gebündelte anzahl 50, habe %d", n.events[0].Count) } } // TestNotifyBatch_NoNewMessagesSendsNothing stellt sicher, dass ein // Abgleichslauf ohne neue Nachrichten keine Benachrichtigung auslöst. func TestNotifyBatch_NoNewMessagesSendsNothing(t *testing.T) { n := &recordingNotifier{} if err := NotifyBatch(context.Background(), n, "mandant-a", "INBOX", imapimport.SyncResult{NewMessages: 0}); err != nil { t.Fatalf("NotifyBatch: %v", err) } if n.count() != 0 { t.Fatalf("erwartete keine benachrichtigung ohne neue nachrichten, habe %d", n.count()) } } // TestNotifyBatch_DisabledNotificationDeliversNothing ist die // geforderte Pflichtprüfung 3 (INT-05): deaktivierte Benachrichtigung // erzeugt keine Zustellung — real gegen einen echten HTTP-Server // geprüft, der den CFG-05-Vertrag nachbildet: 204 bedeutet "durch // Benutzerpräferenz unterdrückt". NotifyBatch ruft trotzdem exakt // einmal auf (die Unterdrückungsentscheidung liegt bei Core, nicht bei // Mail), der Aufruf selbst liefert keinen Fehler. func TestNotifyBatch_DisabledNotificationDeliversNothing(t *testing.T) { var callCount int srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { callCount++ w.WriteHeader(http.StatusNoContent) // "durch benutzerpräferenz unterdrückt" })) defer srv.Close() client := notifyclient.NewClient(srv.URL, "test-service-token") err := NotifyBatch(context.Background(), client, "mandant-a", "INBOX", imapimport.SyncResult{NewMessages: 1}) if err != nil { t.Fatalf("NotifyBatch: %v", err) } if callCount != 1 { t.Fatalf("erwartete genau 1 aufruf an den (unterdrückenden) server, habe %d", callCount) } }