package notifyclient import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) // TestNotify_SendsEventToRealHTTPServer ist ein echter Ende-zu-Ende-Test // gegen einen echten, laufenden HTTP-Server (kein Mock der // Standardbibliothek umgangen) — Core CFG-02/CFG-05 haben im aktuellen // Repository-Stand keinen abrufbaren Endpunkt (siehe Paketkommentar), // dieser Server implementiert den in CFG-05 dokumentierten Vertrag // (service-token-authentifiziertes POST /notify) real. func TestNotify_SendsEventToRealHTTPServer(t *testing.T) { var gotToken string var gotEvent Event srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.URL.Path != "/notify" { http.NotFound(w, r) return } gotToken = r.Header.Get("Authorization") _ = json.NewDecoder(r.Body).Decode(&gotEvent) w.WriteHeader(http.StatusAccepted) })) defer srv.Close() client := NewClient(srv.URL, "test-service-token") err := client.Notify(context.Background(), Event{TenantSlug: "mandant-a", EventType: "mail.new", Summary: "3 neue Mails", Count: 3}) if err != nil { t.Fatalf("Notify: %v", err) } if gotToken != "Bearer test-service-token" { t.Fatalf("erwartete service-token-header, habe: %q", gotToken) } if gotEvent.TenantSlug != "mandant-a" || gotEvent.Count != 3 { t.Fatalf("unerwartetes ereignis beim server angekommen: %+v", gotEvent) } } // TestNotify_TreatsNoContentAsSuppressedNotAsError bestätigt: ein // 204-Status (laut CFG-05-Vertrag "durch Benutzerpräferenz unterdrückt") // wird NICHT als Fehler behandelt. func TestNotify_TreatsNoContentAsSuppressedNotAsError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) })) defer srv.Close() client := NewClient(srv.URL, "test-service-token") if err := client.Notify(context.Background(), Event{TenantSlug: "mandant-a", EventType: "mail.new"}); err != nil { t.Fatalf("erwartete keinen fehler bei 204 (unterdrückt), habe: %v", err) } } // TestNotify_ReturnsErrorOnServerFailure stellt sicher, dass ein // echter Serverfehler (5xx) als Fehler durchgereicht wird — der // Aufrufer (importnotify) entscheidet, wie damit umgegangen wird. func TestNotify_ReturnsErrorOnServerFailure(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("boom")) })) defer srv.Close() client := NewClient(srv.URL, "test-service-token") if err := client.Notify(context.Background(), Event{TenantSlug: "mandant-a", EventType: "mail.new"}); err == nil { t.Fatal("erwartete fehler bei 500") } } // TestNotify_UnreachableEndpointReturnsErrorWithoutHanging bestätigt, // dass ein nicht erreichbarer Endpunkt zeitnah einen Fehler liefert // (Timeout im Client konfiguriert) statt unbegrenzt zu blockieren — // Grundlage für INT-09/INT-10s "fail open"-Prinzip, hier für INT-05 // mitgeprüft. func TestNotify_UnreachableEndpointReturnsErrorWithoutHanging(t *testing.T) { client := NewClient("http://127.0.0.1:1", "test-service-token") // Port 1: garantiert nichts lauscht dort err := client.Notify(context.Background(), Event{TenantSlug: "mandant-a", EventType: "mail.new"}) if err == nil { t.Fatal("erwartete fehler bei nicht erreichbarem endpunkt") } }