package notify import ( "context" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" ) type received struct { mu sync.Mutex reqs []*http.Request body []string } func (r *received) handler(status int) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { b, _ := io.ReadAll(req.Body) r.mu.Lock() r.reqs = append(r.reqs, req) r.body = append(r.body, string(b)) r.mu.Unlock() w.WriteHeader(status) } } var msg = Message{ Title: "Vertrag kunde läuft ab", Body: "Noch *14 Tage* bis 2026-12-15.", Severity: Critical, } func TestSlack(t *testing.T) { var got received srv := httptest.NewServer(got.handler(200)) defer srv.Close() if err := (Slack{URL: srv.URL}).Send(context.Background(), msg); err != nil { t.Fatal(err) } if ct := got.reqs[0].Header.Get("Content-Type"); ct != "application/json" { t.Errorf("Content-Type %q", ct) } var payload map[string]string if err := json.Unmarshal([]byte(got.body[0]), &payload); err != nil { t.Fatal(err) } text := payload["text"] if !strings.Contains(text, "*Vertrag kunde läuft ab*") || !strings.Contains(text, "Noch *14 Tage*") || !strings.Contains(text, ":rotating_light:") { t.Errorf("text = %q", text) } } func TestNtfy(t *testing.T) { var got received srv := httptest.NewServer(got.handler(200)) defer srv.Close() if err := (Ntfy{URL: srv.URL + "/sultan", Token: "tk_geheim"}).Send(context.Background(), msg); err != nil { t.Fatal(err) } r := got.reqs[0] if r.URL.Path != "/sultan" || got.body[0] != msg.Body { t.Errorf("Pfad %q, Body %q", r.URL.Path, got.body[0]) } for h, want := range map[string]string{ "Title": "Vertrag kunde läuft ab", "Priority": "urgent", "Tags": "rotating_light", "Markdown": "yes", "Authorization": "Bearer tk_geheim", } { if r.Header.Get(h) != want { t.Errorf("%s = %q, erwartet %q", h, r.Header.Get(h), want) } } } func TestSeverityMapping(t *testing.T) { for sev, want := range map[Severity][2]string{ Info: {"default", "information_source"}, Warning: {"high", "warning"}, Critical: {"urgent", "rotating_light"}, } { var got received srv := httptest.NewServer(got.handler(200)) (Ntfy{URL: srv.URL}).Send(context.Background(), Message{Title: "x", Body: "y", Severity: sev}) srv.Close() if got.reqs[0].Header.Get("Priority") != want[0] || got.reqs[0].Header.Get("Tags") != want[1] { t.Errorf("%v: Priority %q Tags %q", sev, got.reqs[0].Header.Get("Priority"), got.reqs[0].Header.Get("Tags")) } if got.reqs[0].Header.Get("Authorization") != "" { t.Error("Authorization ohne Token gesetzt") } } } func TestErrors(t *testing.T) { var got received bad := httptest.NewServer(got.handler(500)) defer bad.Close() for _, target := range []Target{Slack{URL: bad.URL}, Ntfy{URL: bad.URL}, Slack{URL: "http://127.0.0.1:1"}} { if err := target.Send(context.Background(), msg); err == nil { t.Errorf("%T: erwartet Fehler", target) } } } // Multi sendet an alle Ziele; ein Fehler bei einem Ziel hält die anderen nicht auf. func TestMulti(t *testing.T) { var a, b received okA := httptest.NewServer(a.handler(200)) okB := httptest.NewServer(b.handler(200)) bad := httptest.NewServer((&received{}).handler(500)) defer okA.Close() defer okB.Close() defer bad.Close() m := Multi{Slack{URL: okA.URL}, Ntfy{URL: bad.URL}, Ntfy{URL: okB.URL}} err := m.Send(context.Background(), msg) if err == nil { t.Error("Fehler eines Ziels nicht gemeldet") } if len(a.reqs) != 1 || len(b.reqs) != 1 { t.Errorf("nicht alle Ziele erreicht: %d/%d", len(a.reqs), len(b.reqs)) } if !errors.Is(Multi{}.Send(context.Background(), msg), ErrNoTargets) { t.Error("leeres Multi: erwartet ErrNoTargets") } }