feat(ntfy): add the HTTP client, stream manager, and account discovery
Speak the ntfy JSON API directly: subscribe, publish, group topics that share a server and credentials, reconnect with backoff and a since cursor, and list account subscriptions, reservations, and ACL grants.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package ntfy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"lazyntfy/internal/config"
|
||||
)
|
||||
|
||||
func TestOpenStreamAndPublish(t *testing.T) {
|
||||
var published string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer tk_test" {
|
||||
t.Errorf("auth header missing")
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
if !strings.Contains(r.URL.Path, "/alerts/json") {
|
||||
t.Errorf("path %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Messages-Truncated", "1")
|
||||
_, _ = io.WriteString(w, "{\"id\":\"m1\",\"time\":10,\"event\":\"message\",\"topic\":\"alerts\",\"message\":\"hi\"}\n")
|
||||
case http.MethodPost:
|
||||
if r.URL.Path != "/alerts" {
|
||||
t.Errorf("post path %s", r.URL.Path)
|
||||
}
|
||||
if r.Header.Get("Title") != "Disk" || r.Header.Get("Priority") != "high" || r.Header.Get("Tags") != "a,b" {
|
||||
t.Errorf("headers %v", r.Header)
|
||||
}
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
published = string(b)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
t.Errorf("method %s", r.Method)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient()
|
||||
auth := config.Auth{Token: "tk_test"}
|
||||
resp, meta, err := c.OpenStream(context.Background(), srv.URL+"/alerts/json?since=all", auth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if !meta.Truncated {
|
||||
t.Fatal("expected truncation header")
|
||||
}
|
||||
var n int
|
||||
if err := ReadLines(resp.Body, func(ev Event) error {
|
||||
n++
|
||||
if ev.Message != "hi" {
|
||||
t.Fatalf("%+v", ev)
|
||||
}
|
||||
return nil
|
||||
}, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("events %d", n)
|
||||
}
|
||||
|
||||
err = c.Publish(context.Background(), srv.URL, "alerts", auth, PublishRequest{
|
||||
Title: "Disk", Message: "full", Priority: 4, Tags: []string{"a", "b"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if published != "full" {
|
||||
t.Fatalf("body %q", published)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnauthorizedHidesBody(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = io.WriteString(w, "token tk_supersecret rejected")
|
||||
}))
|
||||
defer srv.Close()
|
||||
_, _, err := NewClient().OpenStream(context.Background(), srv.URL+"/alerts/json", config.Auth{Token: "tk_test"})
|
||||
if err == nil || strings.Contains(err.Error(), "tk_supersecret") || !strings.Contains(err.Error(), "not authorized") {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePriority(t *testing.T) {
|
||||
p, err := ParsePriority("high")
|
||||
if err != nil || p != 4 {
|
||||
t.Fatal(p, err)
|
||||
}
|
||||
if _, err := ParsePriority("nope"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
tags := ParseTags(" a, b ,,c ")
|
||||
if strings.Join(tags, "|") != "a|b|c" {
|
||||
t.Fatal(tags)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user