diff --git a/Makefile b/Makefile index 1bba6562..5a88647e 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ coverage-upload: # Lint/formatting targets fmt: - $(GO) fmt ./... + gofmt -s -w . fmt-check: test -z $(shell gofmt -l .) diff --git a/README.md b/README.md index 93eed30f..2357185f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ # ntfy.sh | Send push notifications to your phone or desktop via PUT/POST [![Release](https://img.shields.io/github/release/binwiederhier/ntfy.svg?color=success&style=flat-square)](https://github.com/binwiederhier/ntfy/releases/latest) +[![Go Reference](https://pkg.go.dev/badge/heckel.io/ntfy.svg)](https://pkg.go.dev/heckel.io/ntfy) +[![Tests](https://github.com/binwiederhier/ntfy/workflows/test/badge.svg)](https://github.com/binwiederhier/ntfy/actions) +[![Go Report Card](https://goreportcard.com/badge/github.com/binwiederhier/ntfy)](https://goreportcard.com/report/github.com/binwiederhier/ntfy) +[![codecov](https://codecov.io/gh/binwiederhier/ntfy/branch/main/graph/badge.svg?token=A597KQ463G)](https://codecov.io/gh/binwiederhier/ntfy) [![Slack channel](https://img.shields.io/badge/slack-@gophers/binwiederhier-success.svg?logo=slack)](https://gophers.slack.com/archives/C01JMTPGF2Q) **ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service. diff --git a/docs/publish.md b/docs/publish.md index 22329cdf..375840ec 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -279,7 +279,7 @@ Here's an **excerpt of emojis** I've found very useful in alert messages: -You can set tags with the `X-Tags` header (or any of its aliases: `Tags`, or `ta`). Specify multiple tags by separating +You can set tags with the `X-Tags` header (or any of its aliases: `Tags`, `tag`, or `ta`). Specify multiple tags by separating them with a comma, e.g. `tag1,tag2,tag3`. === "Command line (curl)" diff --git a/server/server.go b/server/server.go index f16b866a..bfc767c8 100644 --- a/server/server.go +++ b/server/server.go @@ -306,17 +306,22 @@ func parseHeaders(header http.Header) (title string, priority int, tags []string priority = 1 case "2", "low": priority = 2 + case "3", "default": + priority = 3 case "4", "high": priority = 4 case "5", "max", "urgent": priority = 5 default: - priority = 3 + priority = 0 } } - tagsStr := readHeader(header, "x-tags", "tags", "ta") + tagsStr := readHeader(header, "x-tags", "tag", "tags", "ta") if tagsStr != "" { - tags = strings.Split(tagsStr, ",") + tags = make([]string, 0) + for _, s := range strings.Split(tagsStr, ",") { + tags = append(tags, strings.TrimSpace(s)) + } } return title, priority, tags } @@ -325,7 +330,7 @@ func readHeader(header http.Header, names ...string) string { for _, name := range names { value := header.Get(name) if value != "" { - return value + return strings.TrimSpace(value) } } return "" diff --git a/server/server_test.go b/server/server_test.go index c7b29011..904e4b4a 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -17,18 +17,18 @@ import ( func TestServer_PublishAndPoll(t *testing.T) { s := newTestServer(t, newTestConfig(t)) - response1 := request(t, s, "PUT", "/mytopic", "my first message") + response1 := request(t, s, "PUT", "/mytopic", "my first message", nil) msg1 := toMessage(t, response1.Body.String()) assert.NotEmpty(t, msg1.ID) assert.Equal(t, "my first message", msg1.Message) - response2 := request(t, s, "PUT", "/mytopic", "my second message") + response2 := request(t, s, "PUT", "/mytopic", "my second message", nil) msg2 := toMessage(t, response2.Body.String()) assert.NotEqual(t, msg1.ID, msg2.ID) assert.NotEmpty(t, msg2.ID) assert.Equal(t, "my second message", msg2.Message) - response := request(t, s, "GET", "/mytopic/json?poll=1", "") + response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil) messages := toMessages(t, response.Body.String()) assert.Equal(t, 2, len(messages)) assert.Equal(t, "my first message", messages[0].Message) @@ -73,6 +73,42 @@ func TestServer_SubscribeOpenAndKeepalive(t *testing.T) { assert.Nil(t, messages[1].Tags) } +func TestServer_PublishAndSubscribe(t *testing.T) { + s := newTestServer(t, newTestConfig(t)) + + subscribeRR := httptest.NewRecorder() + subscribeCancel := subscribe(t, s, "/mytopic/json", subscribeRR) + + publishFirstRR := request(t, s, "PUT", "/mytopic", "my first message", nil) + assert.Equal(t, 200, publishFirstRR.Code) + + publishSecondRR := request(t, s, "PUT", "/mytopic", "my other message", map[string]string{ + "Title": " This is a title ", + "X-Tags": "tag1,tag 2, tag3", + "p": "1", + }) + assert.Equal(t, 200, publishSecondRR.Code) + + subscribeCancel() + messages := toMessages(t, subscribeRR.Body.String()) + assert.Equal(t, 3, len(messages)) + assert.Equal(t, openEvent, messages[0].Event) + + assert.Equal(t, messageEvent, messages[1].Event) + assert.Equal(t, "mytopic", messages[1].Topic) + assert.Equal(t, "my first message", messages[1].Message) + assert.Equal(t, "", messages[1].Title) + assert.Equal(t, 0, messages[1].Priority) + assert.Nil(t, messages[1].Tags) + + assert.Equal(t, messageEvent, messages[2].Event) + assert.Equal(t, "mytopic", messages[2].Topic) + assert.Equal(t, "my other message", messages[2].Message) + assert.Equal(t, "This is a title", messages[2].Title) + assert.Equal(t, 1, messages[2].Priority) + assert.Equal(t, []string{"tag1", "tag 2", "tag3"}, messages[2].Tags) +} + func newTestConfig(t *testing.T) *config.Config { conf := config.New(":80") conf.CacheFile = filepath.Join(t.TempDir(), "cache.db") @@ -87,16 +123,41 @@ func newTestServer(t *testing.T, config *config.Config) *Server { return server } -func request(t *testing.T, s *Server, method, url, body string) *httptest.ResponseRecorder { +func request(t *testing.T, s *Server, method, url, body string, headers map[string]string) *httptest.ResponseRecorder { rr := httptest.NewRecorder() req, err := http.NewRequest(method, url, strings.NewReader(body)) if err != nil { t.Fatal(err) } + if headers != nil { + for k, v := range headers { + req.Header.Set(k, v) + } + } s.handle(rr, req) return rr } +func subscribe(t *testing.T, s *Server, url string, rr *httptest.ResponseRecorder) context.CancelFunc { + ctx, cancel := context.WithCancel(context.Background()) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + t.Fatal(err) + } + done := make(chan bool) + go func() { + s.handle(rr, req) + done <- true + }() + cancelAndWaitForDone := func() { + time.Sleep(100 * time.Millisecond) + cancel() + <-done + } + time.Sleep(100 * time.Millisecond) + return cancelAndWaitForDone +} + func toMessages(t *testing.T, s string) []*message { messages := make([]*message, 0) scanner := bufio.NewScanner(strings.NewReader(s))