Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
package help
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
// Discord refuses an embed that is over its limits, and then /help would send
|
|
// nothing. This catches it when a command is added to the text.
|
|
func TestHelpFitsDiscordsLimits(t *testing.T) {
|
|
list := embeds(&discordgo.MessageEmbedAuthor{Name: "Velox Bot"})
|
|
if len(list) == 0 {
|
|
t.Fatal("no help")
|
|
}
|
|
seen := 0
|
|
for n, e := range list {
|
|
total := utf8.RuneCountInString(e.Title) + utf8.RuneCountInString(e.Description)
|
|
if len(e.Fields) > 25 {
|
|
t.Errorf("embed %d has %d fields, at most 25", n, len(e.Fields))
|
|
}
|
|
for _, f := range e.Fields {
|
|
seen++
|
|
if l := utf8.RuneCountInString(f.Value); l == 0 || l > maxFieldChars {
|
|
t.Errorf("field %q has %d characters, must be 1 to %d", f.Name, l, maxFieldChars)
|
|
}
|
|
if l := utf8.RuneCountInString(f.Name); l == 0 || l > 256 {
|
|
t.Errorf("field name %q has %d characters, at most 256", f.Name, l)
|
|
}
|
|
total += fieldSize(f)
|
|
}
|
|
if total > 6000 {
|
|
t.Errorf("embed %d has %d characters, Discord allows 6000", n, total)
|
|
}
|
|
}
|
|
if seen != len(fields()) {
|
|
t.Errorf("%d fields shown of %d", seen, len(fields()))
|
|
}
|
|
}
|
|
|
|
// fakeDiscord answers the calls /help makes and records what the member was
|
|
// shown. dm decides whether the direct message works.
|
|
type fakeDiscord struct {
|
|
dm bool
|
|
edited []byte
|
|
callbacks int
|
|
sent int
|
|
}
|
|
|
|
func (f *fakeDiscord) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
reply := func(code int, body string) (*http.Response, error) {
|
|
return &http.Response{StatusCode: code, Body: io.NopCloser(strings.NewReader(body)), Header: http.Header{"Content-Type": {"application/json"}}, Request: r}, nil
|
|
}
|
|
switch {
|
|
case strings.HasSuffix(r.URL.Path, "/callback"):
|
|
f.callbacks++
|
|
return reply(204, "")
|
|
case strings.HasSuffix(r.URL.Path, "/users/@me/channels"):
|
|
if !f.dm {
|
|
return reply(403, `{"message":"Cannot send messages to this user","code":50007}`)
|
|
}
|
|
return reply(200, `{"id":"900","type":1}`)
|
|
case strings.Contains(r.URL.Path, "/channels/900/messages"):
|
|
f.sent++
|
|
return reply(200, `{"id":"1"}`)
|
|
case strings.Contains(r.URL.Path, "/messages/@original"):
|
|
f.edited, _ = io.ReadAll(r.Body)
|
|
return reply(200, `{"id":"2"}`)
|
|
}
|
|
return reply(404, `{"message":"unexpected `+r.URL.Path+`"}`)
|
|
}
|
|
|
|
func run(t *testing.T, f *fakeDiscord, guild string) {
|
|
t.Helper()
|
|
s, err := discordgo.New("Bot x")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s.Client = &http.Client{Transport: f}
|
|
s.MaxRestRetries = 0
|
|
i := &discordgo.InteractionCreate{Interaction: &discordgo.Interaction{
|
|
ID: "1", Token: "tok", AppID: "100", GuildID: guild,
|
|
Member: &discordgo.Member{User: &discordgo.User{ID: "42"}},
|
|
}}
|
|
HelpHandler(s, i)
|
|
}
|
|
|
|
func TestHelpGoesToDMs(t *testing.T) {
|
|
f := &fakeDiscord{dm: true}
|
|
run(t, f, "5")
|
|
if f.sent == 0 || !strings.Contains(string(f.edited), "Check your DMs") || strings.Contains(string(f.edited), `"embeds"`) {
|
|
t.Errorf("sent=%d edited=%s", f.sent, f.edited)
|
|
}
|
|
}
|
|
|
|
func TestHelpFallsBackToTheChannelWhenDMsAreOff(t *testing.T) {
|
|
f := &fakeDiscord{dm: false}
|
|
run(t, f, "5")
|
|
got := string(f.edited)
|
|
if f.sent != 0 || !strings.Contains(got, "couldn't DM you") || !strings.Contains(got, `"embeds"`) || !strings.Contains(got, "Velox Bot Help") {
|
|
t.Errorf("the help should be shown in the reply: sent=%d edited=%s", f.sent, got)
|
|
}
|
|
}
|
|
|
|
func TestHelpInADMIsShownDirectly(t *testing.T) {
|
|
f := &fakeDiscord{dm: true}
|
|
run(t, f, "")
|
|
if f.callbacks != 1 || f.sent != 0 || f.edited != nil {
|
|
t.Errorf("one direct answer, no DM: %+v", f)
|
|
}
|
|
}
|