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,82 @@
|
||||
package ntfy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"lazyntfy/internal/config"
|
||||
)
|
||||
|
||||
func TestAccountTopics(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer tk_test" {
|
||||
t.Errorf("missing auth")
|
||||
}
|
||||
switch r.URL.Path {
|
||||
case "/v1/account":
|
||||
_, _ = w.Write([]byte(`{
|
||||
"sync_topic":"st_skip",
|
||||
"subscriptions":[
|
||||
{"base_url":"", "topic":"alerts", "display_name":"Alerts"},
|
||||
{"topic":"st_skip"},
|
||||
{"topic":"ops*"}
|
||||
],
|
||||
"reservations":[{"topic":"backups"}]
|
||||
}`))
|
||||
case "/v1/users":
|
||||
_, _ = w.Write([]byte(`[
|
||||
{"username":"ada","grants":[
|
||||
{"topic":"stats","permission":"read-only"},
|
||||
{"topic":"*","permission":"read-write"},
|
||||
{"topic":"secret","permission":"deny-all"}
|
||||
]}
|
||||
]`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
subs, err := NewClient().AccountTopics(context.Background(), srv.URL, config.Auth{Token: "tk_test"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := map[string]string{}
|
||||
for _, sub := range subs {
|
||||
got[sub.Name] = sub.Display
|
||||
if sub.Auth.Token != "tk_test" {
|
||||
t.Fatal("auth dropped")
|
||||
}
|
||||
}
|
||||
_, hasBackups := got["backups"]
|
||||
_, hasStats := got["stats"]
|
||||
if got["alerts"] != "Alerts" || !hasBackups || !hasStats {
|
||||
t.Fatalf("%v", got)
|
||||
}
|
||||
if _, ok := got["st_skip"]; ok {
|
||||
t.Fatal("sync topic included")
|
||||
}
|
||||
if _, ok := got["secret"]; ok {
|
||||
t.Fatal("denied topic included")
|
||||
}
|
||||
if _, ok := got["*"]; ok || len(got) != 3 {
|
||||
t.Fatalf("%v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountTopicsIgnoresUsersForbidden(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/users" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"reservations":[{"topic":"alerts"}]}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
subs, err := NewClient().AccountTopics(context.Background(), srv.URL, config.Auth{Token: "tk_test"})
|
||||
if err != nil || len(subs) != 1 || subs[0].Name != "alerts" {
|
||||
t.Fatalf("%v %+v", err, subs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user