From a99864c649f3fba7d6c3698cb98da550f8f06b35 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Mon, 4 May 2026 19:38:16 +0100 Subject: [PATCH] =?UTF-8?q?notification:=20B3=20=E2=80=94=20Content-Type?= =?UTF-8?q?=20header=20+=20URL=20trim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes flagged in spec review of f0a323e: ntfy POSTs need explicit Content-Type: text/plain (the spec calls for it; ntfy works without but explicit beats inferred); trim trailing slashes from server URL to avoid double-slash when operators paste 'https://ntfy.sh/'. --- internal/notification/ntfy.go | 5 ++++- internal/notification/ntfy_test.go | 17 +++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/notification/ntfy.go b/internal/notification/ntfy.go index b01d20f..a8315cf 100644 --- a/internal/notification/ntfy.go +++ b/internal/notification/ntfy.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "strings" "time" ) @@ -49,13 +50,15 @@ func (c *NtfyChannel) Kind() string { return "ntfy" } // with ntfy headers. Returns (statusCode, latency, err). 4xx/5xx // responses are returned as errors with the status code set. func (c *NtfyChannel) Send(ctx context.Context, p Payload) (int, time.Duration, error) { - url := c.cfg.ServerURL + "/" + c.cfg.Topic + server := strings.TrimRight(c.cfg.ServerURL, "/") + url := server + "/" + c.cfg.Topic req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBufferString(p.Message)) if err != nil { return 0, 0, fmt.Errorf("ntfy: build request: %w", err) } + req.Header.Set("Content-Type", "text/plain") req.Header.Set("Title", fmt.Sprintf("[%s] %s %s", p.Severity, p.HostName, p.Kind)) req.Header.Set("Tags", p.Severity+","+p.Kind) req.Header.Set("Priority", priorityForSeverity(p.Severity, c.defaultPriority)) diff --git a/internal/notification/ntfy_test.go b/internal/notification/ntfy_test.go index b50de85..7aa2a0b 100644 --- a/internal/notification/ntfy_test.go +++ b/internal/notification/ntfy_test.go @@ -12,12 +12,13 @@ func TestNtfySendsHeadersAndBody(t *testing.T) { t.Parallel() var ( - gotTitle string - gotPri string - gotTags string - gotClick string - gotAuth string - gotBody string + gotTitle string + gotPri string + gotTags string + gotClick string + gotAuth string + gotContentType string + gotBody string ) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -26,6 +27,7 @@ func TestNtfySendsHeadersAndBody(t *testing.T) { gotTags = r.Header.Get("Tags") gotClick = r.Header.Get("Click") gotAuth = r.Header.Get("Authorization") + gotContentType = r.Header.Get("Content-Type") b, _ := io.ReadAll(r.Body) gotBody = string(b) w.WriteHeader(http.StatusOK) @@ -73,6 +75,9 @@ func TestNtfySendsHeadersAndBody(t *testing.T) { if want := "Bearer tk1"; gotAuth != want { t.Errorf("Authorization: got %q want %q", gotAuth, want) } + if gotContentType != "text/plain" { + t.Errorf("Content-Type: got %q want %q", gotContentType, "text/plain") + } if gotBody != "errors found" { t.Errorf("body: got %q want %q", gotBody, "errors found") }