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
68 lines
2.6 KiB
Go
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")
|
|
}
|
|
}
|