package alert import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "gitea.lerkolabs.com/lerko/uptop/internal/models" ) func TestHTTPProviderDiscord(t *testing.T) { var received map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := GetProvider(models.AlertConfig{Type: "discord", Settings: map[string]string{"url": srv.URL}}) if err := p.Send(context.Background(), "Test Title", "Test Body"); err != nil { t.Fatalf("Send: %v", err) } if received["content"] != "**Test Title**\nTest Body" { t.Errorf("unexpected payload: %s", received["content"]) } } func TestHTTPProviderSlack(t *testing.T) { var received map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := GetProvider(models.AlertConfig{Type: "slack", Settings: map[string]string{"url": srv.URL}}) if err := p.Send(context.Background(), "Alert", "Message"); err != nil { t.Fatalf("Send: %v", err) } if received["text"] != "*Alert*\nMessage" { t.Errorf("unexpected payload: %s", received["text"]) } } func TestHTTPProviderWebhook(t *testing.T) { var received map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := GetProvider(models.AlertConfig{Type: "webhook", Settings: map[string]string{"url": srv.URL}}) if err := p.Send(context.Background(), "Title", "Body"); err != nil { t.Fatalf("Send: %v", err) } if received["title"] != "Title" || received["message"] != "Body" || received["status"] != "alert" { t.Errorf("unexpected webhook payload: %v", received) } } func TestHTTPProviderErrorOnHTTP4xx(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(403) })) defer srv.Close() p := GetProvider(models.AlertConfig{Type: "discord", Settings: map[string]string{"url": srv.URL}}) if err := p.Send(context.Background(), "Test", "Test"); err == nil { t.Fatal("expected error on 403 response") } } func TestNtfyProvider(t *testing.T) { var title, body string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { title = r.Header.Get("Title") buf := make([]byte, 1024) n, _ := r.Body.Read(buf) body = string(buf[:n]) w.WriteHeader(200) })) defer srv.Close() p := GetProvider(models.AlertConfig{Type: "ntfy", Settings: map[string]string{ "url": srv.URL, "topic": "test", }}) if err := p.Send(context.Background(), "Alert Title", "Alert Body"); err != nil { t.Fatalf("Send: %v", err) } if title != "Alert Title" { t.Errorf("expected title 'Alert Title', got '%s'", title) } if body != "Alert Body" { t.Errorf("expected body 'Alert Body', got '%s'", body) } } func TestHTTPProviderTelegram(t *testing.T) { var received map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := &HTTPProvider{URL: srv.URL, Payload: telegramPayload("12345")} if err := p.Send(context.Background(), "Alert", "Down"); err != nil { t.Fatalf("Send: %v", err) } if received["chat_id"] != "12345" { t.Errorf("expected chat_id '12345', got '%s'", received["chat_id"]) } if received["text"] != "*Alert*\nDown" { t.Errorf("unexpected text: %s", received["text"]) } if received["parse_mode"] != "Markdown" { t.Errorf("expected parse_mode 'Markdown', got '%s'", received["parse_mode"]) } } func TestHTTPProviderPagerDuty(t *testing.T) { var received map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := &HTTPProvider{URL: srv.URL, Payload: pagerdutyPayload("test-key", "critical")} if err := p.Send(context.Background(), "Alert", "Down"); err != nil { t.Fatalf("Send: %v", err) } if received["routing_key"] != "test-key" { t.Errorf("expected routing_key 'test-key', got '%v'", received["routing_key"]) } if received["event_action"] != "trigger" { t.Errorf("expected event_action 'trigger', got '%v'", received["event_action"]) } payload := received["payload"].(map[string]any) if payload["summary"] != "Alert: Down" { t.Errorf("unexpected summary: %v", payload["summary"]) } if payload["severity"] != "critical" { t.Errorf("expected severity 'critical', got '%v'", payload["severity"]) } } func TestHTTPProviderPushover(t *testing.T) { var received map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := &HTTPProvider{URL: srv.URL, Payload: pushoverPayload("app-tok", "user-key")} if err := p.Send(context.Background(), "Alert", "Down"); err != nil { t.Fatalf("Send: %v", err) } if received["token"] != "app-tok" { t.Errorf("expected token 'app-tok', got '%s'", received["token"]) } if received["user"] != "user-key" { t.Errorf("expected user 'user-key', got '%s'", received["user"]) } if received["title"] != "Alert" || received["message"] != "Down" { t.Errorf("unexpected payload: %v", received) } } func TestHTTPProviderGotify(t *testing.T) { var received map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewDecoder(r.Body).Decode(&received) w.WriteHeader(200) })) defer srv.Close() p := &HTTPProvider{URL: srv.URL, Payload: gotifyPayload("8")} if err := p.Send(context.Background(), "Alert", "Down"); err != nil { t.Fatalf("Send: %v", err) } if received["title"] != "Alert" || received["message"] != "Down" { t.Errorf("unexpected payload: %v", received) } if pri, ok := received["priority"].(float64); !ok || pri != 8 { t.Errorf("expected priority 8, got %v", received["priority"]) } } func TestGetProviderNewTypes(t *testing.T) { for _, typ := range []string{"telegram", "pagerduty", "pushover", "gotify"} { p := GetProvider(models.AlertConfig{Type: typ, Settings: map[string]string{ "token": "x", "chat_id": "1", "routing_key": "k", "user": "u", "url": "http://localhost", }}) if p == nil { t.Errorf("GetProvider(%q) returned nil", typ) } } } func TestGetProviderUnknown(t *testing.T) { p := GetProvider(models.AlertConfig{Type: "unknown"}) if p != nil { t.Error("expected nil for unknown provider type") } } func TestSanitizeHeader(t *testing.T) { tests := []struct { input, want string }{ {"normal subject", "normal subject"}, {"inject\r\nBcc: evil@bad.com", "injectBcc: evil@bad.com"}, {"has\nnewline", "hasnewline"}, {"has\rcarriage", "hascarriage"}, } for _, tt := range tests { got := sanitizeHeader(tt.input) if got != tt.want { t.Errorf("sanitizeHeader(%q) = %q, want %q", tt.input, got, tt.want) } } }