package notify import ( "fmt" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" ) func TestSendSetsHeadersAndBody(t *testing.T) { var got *http.Request var body string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got = r b := make([]byte, 1024) n, _ := r.Body.Read(b) body = string(b[:n]) fmt.Fprint(w, `{"id":"x"}`) })) defer srv.Close() c := New(srv.URL, "tok123") err := c.Send("ci-fel", "Build failed", "arm64 test: FAIL", "high", []string{"warning", "ci"}) if err != nil { t.Fatal(err) } if got.URL.Path != "/ci-fel" { t.Errorf("path = %q", got.URL.Path) } if body != "arm64 test: FAIL" { t.Errorf("body = %q", body) } for hdr, want := range map[string]string{ "Title": "Build failed", "Priority": "high", "Tags": "warning,ci", "Authorization": "Bearer tok123", } { if v := got.Header.Get(hdr); v != want { t.Errorf("%s = %q, want %q", hdr, v, want) } } } func TestSendValidation(t *testing.T) { c := New("http://example.invalid", "") if err := c.Send("", "", "hello", "", nil); err == nil { t.Error("empty topic accepted") } if err := c.Send("t", "", "", "", nil); err == nil { t.Error("empty message accepted") } if err := c.Send("t", "", "hello", "banana", nil); err == nil { t.Error("bogus priority accepted") } } func TestReadParsesPollOutput(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("poll") != "1" { t.Errorf("poll param missing: %s", r.URL.RawQuery) } if r.URL.Query().Get("since") != "2h" { t.Errorf("since = %q", r.URL.Query().Get("since")) } fmt.Fprintln(w, `{"id":"a","time":1754500000,"event":"message","topic":"t","message":"first","priority":3}`) fmt.Fprintln(w, `{"id":"b","time":1754500060,"event":"keepalive","topic":"t"}`) fmt.Fprintln(w, `not json at all`) fmt.Fprintln(w, `{"id":"c","time":1754500120,"event":"message","topic":"t","title":"T","message":"second","priority":5,"tags":["x"]}`) })) defer srv.Close() msgs, err := New(srv.URL, "").Read("t", "2h", 0) if err != nil { t.Fatal(err) } if len(msgs) != 2 { t.Fatalf("got %d messages, want 2 (keepalive + junk filtered)", len(msgs)) } if msgs[0].Message != "first" || msgs[1].Title != "T" { t.Errorf("unexpected messages: %+v", msgs) } } func TestReadLimitKeepsNewest(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for i := 1; i <= 5; i++ { fmt.Fprintf(w, "{\"id\":\"%d\",\"time\":%d,\"event\":\"message\",\"topic\":\"t\",\"message\":\"m%d\"}\n", i, 1754500000+i, i) } })) defer srv.Close() msgs, err := New(srv.URL, "").Read("t", "all", 2) if err != nil { t.Fatal(err) } if len(msgs) != 2 || msgs[0].Message != "m4" || msgs[1].Message != "m5" { t.Errorf("limit should keep the newest: %+v", msgs) } } func TestFormatIsOneGreppableLine(t *testing.T) { line := Format(Message{Time: 1754500000, Title: "Backup", Message: "done\nall good", Priority: 4, Tags: []string{"ok"}}) if strings.Contains(line, "\n") { t.Error("format must be a single line") } for _, want := range []string{"[high]", "(Backup)", "done", "#ok"} { if !strings.Contains(line, want) { t.Errorf("line %q missing %q", line, want) } } } func TestConfigRoundtrip(t *testing.T) { path := filepath.Join(t.TempDir(), "config.json") cfg, err := LoadConfig(path) // first run creates defaults if err != nil { t.Fatal(err) } if cfg.Server == "" || cfg.DefaultTopic == "" { t.Error("defaults incomplete") } cfg.DefaultTopic = "elsewhere" if err := SaveConfig(path, cfg); err != nil { t.Fatal(err) } again, err := LoadConfig(path) if err != nil { t.Fatal(err) } if again.DefaultTopic != "elsewhere" { t.Error("saved change did not persist") } }