Client.SendMessage(title, raw, targets) creates a private-message topic by posting to /posts.json with target_usernames / target_group_names / target_emails (comma-joined on the wire, as Discourse expects, never JSON arrays) instead of a category. The result reuses the topic ids type, so URL() renders the PM link. Local validation requires title, body and at least one target; undeliverable recipients surface as the typed ErrForbidden class. Fake-server tests assert the wire shape and the recorded recipients. Part of Redmine 507 (Discourse Go client): PM delivery is how the 495 pipeline pings humans that a briefing has landed.
97 lines
3.1 KiB
Go
97 lines
3.1 KiB
Go
package discourse
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSendMessageRequiresTargets(t *testing.T) {
|
|
f := newFake(t)
|
|
c := testClient(t, f)
|
|
_, err := c.SendMessage(context.Background(), SendMessageRequest{Title: "t", Raw: "r"})
|
|
if !errors.Is(err, ErrInvalidRequest) {
|
|
t.Fatalf("want ErrInvalidRequest without targets, got %v", err)
|
|
}
|
|
if f.saw("/posts.json") {
|
|
t.Fatal("no request should have been sent without targets")
|
|
}
|
|
}
|
|
|
|
func TestSendMessageRequiresTitleAndRaw(t *testing.T) {
|
|
c, _ := New("https://ok.example", testKey, "")
|
|
if _, err := c.SendMessage(context.Background(), SendMessageRequest{Raw: "r", TargetUsernames: []string{"ops"}}); !errors.Is(err, ErrInvalidRequest) {
|
|
t.Fatalf("missing title: want ErrInvalidRequest, got %v", err)
|
|
}
|
|
if _, err := c.SendMessage(context.Background(), SendMessageRequest{Title: "t", TargetUsernames: []string{"ops"}}); !errors.Is(err, ErrInvalidRequest) {
|
|
t.Fatalf("missing raw: want ErrInvalidRequest, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSendMessage(t *testing.T) {
|
|
f := newFake(t)
|
|
c := testClient(t, f)
|
|
res, err := c.SendMessage(context.Background(), SendMessageRequest{
|
|
Title: "Fleet ping",
|
|
Raw: "check the briefing",
|
|
TargetUsernames: []string{"ops", "reachableceo"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SendMessage: %v", err)
|
|
}
|
|
if res.TopicID == 0 || res.TopicSlug == "" {
|
|
t.Fatalf("want topic ids in result, got %+v", res)
|
|
}
|
|
if u := res.URL(f.srv.URL); !strings.Contains(u, "/t/fleet-ping/") {
|
|
t.Fatalf("PM URL should be /t/<slug>/<id>, got %q", u)
|
|
}
|
|
body := f.lastBody
|
|
for _, want := range []string{`"title":"Fleet ping"`, `"target_usernames":"ops,reachableceo"`} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("wire body missing %s: %s", want, body)
|
|
}
|
|
}
|
|
if strings.Contains(body, `"target_usernames":["`) {
|
|
t.Fatalf("targets must be comma-joined, not a JSON array: %s", body)
|
|
}
|
|
if got := f.pms; len(got) != 1 || got[0].TopicID != res.TopicID || got[0].Title != "Fleet ping" {
|
|
t.Fatalf("fake did not record the PM: %+v", got)
|
|
}
|
|
if got := f.pms[0].Targets; len(got) != 2 || got[0] != "ops" || got[1] != "reachableceo" {
|
|
t.Fatalf("recipients not recorded: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSendMessageGroupAndEmailTargets(t *testing.T) {
|
|
f := newFake(t)
|
|
c := testClient(t, f)
|
|
_, err := c.SendMessage(context.Background(), SendMessageRequest{
|
|
Title: "Bulk",
|
|
Raw: "hi",
|
|
TargetGroupNames: []string{"staff", "admins"},
|
|
TargetEmails: []string{"a@x.test"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SendMessage: %v", err)
|
|
}
|
|
for _, want := range []string{`"target_group_names":"staff,admins"`, `"target_emails":"a@x.test"`} {
|
|
if !strings.Contains(f.lastBody, want) {
|
|
t.Fatalf("wire body missing %s: %s", want, f.lastBody)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSendMessageForbiddenIsTyped(t *testing.T) {
|
|
f := newFake(t)
|
|
c := testClient(t, f)
|
|
_, err := c.SendMessage(context.Background(), SendMessageRequest{
|
|
Title: "nope",
|
|
Raw: "x",
|
|
TargetUsernames: []string{"denied"},
|
|
})
|
|
if !errors.Is(err, ErrForbidden) {
|
|
t.Fatalf("want ErrForbidden for undeliverable recipient, got %v", err)
|
|
}
|
|
}
|