Files
mopac-discourse-go/messages_test.go
T
mrcharles 6891f3febc feat: private-message send via POST /posts.json targets
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.
2026-08-29 16:36:54 -05:00

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)
}
}