Files
nexarch/mail/internal/syncalert/dispatcher_http_test.go
T
sysopsandClaude Sonnet 5 56d31c9176 IMP-08: fehler-benachrichtigung-bei-postfach-sync-ausfall
Benachrichtigung bei wiederholtem Postfach-Sync-Ausfall, mit
Eskalationsschwelle statt Einzel-Alarm pro Fehlversuch. Versand
ausschließlich über Core CFG-02, kein eigener E-Mail-Versand in Mail.

- dispatcher.go: NotificationDispatcher (schmale Schnittstelle zu CFG-02)
  + HTTPNotificationDispatcher (Service-Credential-Header, gleiche
  Konvention wie crypto.HTTPKEKProvider). Core exponiert internal/notify.
  Dispatcher.Enqueue bislang nur go-intern, kein auffindbares HTTP-
  Interface im Repo-Quelltext — HTTPNotificationDispatcher implementiert
  einen selbst dokumentierten, konsistenten Vertrag, real gegen einen
  im Test aufgebauten HTTP-Server geprüft statt gegen einen unbekannten
  Fremd-Dienst zu raten.
- monitor.go: Monitor.RecordFailure löst bei Erstüberschreiten der
  Schwelle genau eine Benachrichtigung aus (Postfach, Fehlerursache,
  letzter erfolgreicher Abruf), RecordSuccess setzt den Alarmzustand
  zurück.

Prüfungen (alle real durchgeführt, siehe mail/docs/IMP-08-PRUEFPROTOKOLL.md):
1. TestRecordFailure_NConsecutiveFailuresTriggerExactlyOneNotification:
   3 Fehlschläge real genau 1 Benachrichtigung, weitere real keine.
2. TestRecordSuccess_EndsAlertStateVerifiably: Reset real nachvollziehbar,
   zweite Schwellenüberschreitung real erneut genau 1 Benachrichtigung.
3. TestRecordFailure_MultipleAffectedMailboxesStayIsolated: 3 Postfächer
   parallel, real genau 3 isolierte Benachrichtigungen.

Kein Umbau: imapimport (IMP-01/IMP-04) unverändert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-09-01 00:11:42 +02:00

68 lines
2.6 KiB
Go

package syncalert
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// TestHTTPNotificationDispatcher_SendsCorrectRequestFormat beweist real
// über echtes HTTP, dass HTTPNotificationDispatcher Channel/Recipient/
// Payload sowie die Service-Credential-Header korrekt sendet — kein
// eigener E-Mail-Versand, nur ein einziger CFG-02-Aufruf
// (Akzeptanzkriterium 1).
func TestHTTPNotificationDispatcher_SendsCorrectRequestFormat(t *testing.T) {
var capturedBody map[string]any
var capturedClientID, capturedClientSecret string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedClientID = r.Header.Get("X-Nexarch-Client-Id")
capturedClientSecret = r.Header.Get("X-Nexarch-Client-Secret")
if err := json.NewDecoder(r.Body).Decode(&capturedBody); err != nil {
t.Fatal(err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"real-notification-id-123"}`))
}))
defer srv.Close()
dispatcher := NewHTTPNotificationDispatcher(srv.URL, "mail", "mail-service-secret", nil)
id, err := dispatcher.Enqueue(context.Background(), NotificationChannel, AdminRecipient, map[string]any{
"mailbox": "INBOX",
"reason": "verbindung abgelehnt",
})
if err != nil {
t.Fatalf("enqueue: %v", err)
}
if id != "real-notification-id-123" {
t.Fatalf("erwartete reale id vom server, habe %q", id)
}
if capturedClientID != "mail" || capturedClientSecret != "mail-service-secret" {
t.Fatalf("service-credential-header fehlen/falsch: id=%q secret=%q", capturedClientID, capturedClientSecret)
}
if capturedBody["channel"] != NotificationChannel || capturedBody["recipient"] != AdminRecipient {
t.Fatalf("channel/recipient falsch übertragen: %+v", capturedBody)
}
payload, ok := capturedBody["payload"].(map[string]any)
if !ok || payload["mailbox"] != "INBOX" {
t.Fatalf("payload nicht korrekt übertragen: %+v", capturedBody)
}
}
// TestHTTPNotificationDispatcher_RejectedByServerReturnsError bestätigt,
// dass eine Ablehnung durch CFG-02 real als Fehler durchgereicht wird,
// statt stillschweigend zu verschwinden.
func TestHTTPNotificationDispatcher_RejectedByServerReturnsError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer srv.Close()
dispatcher := NewHTTPNotificationDispatcher(srv.URL, "mail", "falsch", nil)
if _, err := dispatcher.Enqueue(context.Background(), "c", "r", nil); err == nil {
t.Fatal("erwartete fehler bei abgelehnter anfrage, habe nil")
}
}