Manage notification channels instead of an ntfy topic

Mirrors the server's channels API: add/remove channels per user from the Users
section. Requires the matching server release.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
Niklas Ye
2026-10-12 21:00:40 +02:00
parent 77f2d6775b
commit ec5df0449b
10 changed files with 405 additions and 174 deletions
+20 -24
View File
@@ -173,8 +173,10 @@ func TestClient_IncidentEndpoints(t *testing.T) {
http.MethodDelete, "/api/incidents/7/notes/12", ""},
{"stats", func(c *Client) error { _, err := c.GetIncidentStats(); return err },
http.MethodGet, "/api/stats/incidents", ""},
{"set notify target", func(c *Client) error { _, err := c.SetUserNotifyTarget(7, "t"); return err },
http.MethodPut, "/api/users/7/notify", ""},
{"add channel", func(c *Client) error { _, err := c.AddChannel(7, "ntfy", "t"); return err },
http.MethodPost, "/api/users/7/channels", `{}`},
{"remove channel", func(c *Client) error { return c.RemoveChannel(7, 2) },
http.MethodDelete, "/api/users/7/channels/2", ""},
}
for _, tt := range tests {
@@ -284,40 +286,34 @@ func TestClient_RequestBodies(t *testing.T) {
}
})
t.Run("set notify target", func(t *testing.T) {
c, got := stub(t, http.StatusOK, `{}`)
if _, err := c.SetUserNotifyTarget(3, "terdut-niklas"); err != nil {
t.Fatalf("set notify target: %v", err)
t.Run("add channel", func(t *testing.T) {
c, got := stub(t, http.StatusCreated, `{"id":4,"type":"ntfy","enabled":true}`)
ch, err := c.AddChannel(3, "ntfy", "terdut-niklas")
if err != nil {
t.Fatalf("add channel: %v", err)
}
if got.body != `{"ntfy_topic":"terdut-niklas"}` {
if got.body != `{"type":"ntfy","address":"terdut-niklas"}` {
t.Errorf("unexpected body %q", got.body)
}
})
// Clearing has to put an explicit empty string on the wire: omitting the
// field would leave the topic untouched instead of removing it.
t.Run("clear notify target", func(t *testing.T) {
c, got := stub(t, http.StatusOK, `{}`)
if _, err := c.SetUserNotifyTarget(3, ""); err != nil {
t.Fatalf("clear notify target: %v", err)
}
if got.body != `{"ntfy_topic":""}` {
t.Errorf("expected an explicit empty topic, got %q", got.body)
if ch.ID != 4 || ch.Type != "ntfy" || !ch.Enabled {
t.Errorf("unexpected channel %+v", ch)
}
})
}
func TestUser_TopicFlattensNilAndEmpty(t *testing.T) {
func TestUser_ChannelSummaryNamesKindsNeverAddresses(t *testing.T) {
var users []User
if err := json.Unmarshal([]byte(
`[{"id":1,"username":"a"},{"id":2,"username":"b","ntfy_topic":""},
{"id":3,"username":"c","ntfy_topic":"terdut-c"}]`), &users); err != nil {
`[{"id":1,"username":"a"},
{"id":2,"username":"b","channels":[{"id":1,"type":"ntfy","enabled":true}]},
{"id":3,"username":"c","channels":[{"id":2,"type":"ntfy","address":"secret","enabled":true},
{"id":3,"type":"mail","enabled":false}]}]`), &users); err != nil {
t.Fatalf("decode: %v", err)
}
want := []string{"", "", "terdut-c"}
want := []string{"", "ntfy", "ntfy, mail (off)"}
for i, u := range users {
if got := u.Topic(); got != want[i] {
t.Errorf("user %d: expected topic %q, got %q", u.ID, want[i], got)
if got := u.ChannelSummary(); got != want[i] {
t.Errorf("user %d: expected %q, got %q", u.ID, want[i], got)
}
}
}