219 lines
6.6 KiB
Go
219 lines
6.6 KiB
Go
package discord
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"plumber/internal/events"
|
|
"plumber/internal/mail"
|
|
"plumber/internal/pacific"
|
|
"plumber/internal/store"
|
|
)
|
|
|
|
func TestParseAdminMap(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := parseAdminMap(" 123:Plumber ,456:other,bad, :empty,789: ")
|
|
if got["123"] != "plumber" || got["456"] != "other" || len(got) != 2 {
|
|
t.Fatalf("parseAdminMap = %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestInboundCreatesSiteReply(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mem, homeowner, admin := seedInboundUsers(t)
|
|
links := newMemoryLinks()
|
|
api := &fakeAPI{}
|
|
mailer := &mail.Recording{}
|
|
bot := inboundTestBot(mem, links, api, mailer, admin.Username)
|
|
bus := events.New()
|
|
defer bus.Close()
|
|
bus.Subscribe(bot.Handle)
|
|
root := seedLinkedRoot(t, mem, links, homeowner.ID, "thread-1")
|
|
|
|
bot.handleInbound(inboundMessage{
|
|
ID: "d-reply-1",
|
|
ChannelID: "thread-1",
|
|
GuildID: "guild-1",
|
|
AuthorID: "snow-admin",
|
|
Content: "Replace the cartridge.",
|
|
})
|
|
|
|
thread, err := mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil || len(thread.Replies) != 1 {
|
|
t.Fatalf("thread = %+v, %v", thread, err)
|
|
}
|
|
reply := thread.Replies[0]
|
|
if reply.AuthorID != admin.ID || reply.Body != "Replace the cartridge." || reply.ParentID == nil || *reply.ParentID != root.ID {
|
|
t.Fatalf("reply = %+v", reply)
|
|
}
|
|
link, err := links.GetByPostID(context.Background(), reply.ID)
|
|
if err != nil || link.MessageID != "d-reply-1" || link.ThreadID != "" {
|
|
t.Fatalf("inbound link = %+v, %v", link, err)
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
if len(api.sends) != 0 || len(api.edits) != 0 {
|
|
t.Fatalf("inbound echoed to Discord: sends=%+v edits=%+v", api.sends, api.edits)
|
|
}
|
|
msgs := waitForMail(t, mailer, 1)
|
|
if msgs[0].ToEmail != homeowner.Email || msgs[0].ReplyID != reply.ID || msgs[0].RootID != root.ID {
|
|
t.Fatalf("mail = %+v", msgs[0])
|
|
}
|
|
}
|
|
|
|
func TestInboundParentsFromReference(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mem, homeowner, admin := seedInboundUsers(t)
|
|
links := newMemoryLinks()
|
|
bot := inboundTestBot(mem, links, &fakeAPI{}, &mail.Recording{}, admin.Username)
|
|
root := seedLinkedRoot(t, mem, links, homeowner.ID, "thread-1")
|
|
plumberReply := &store.Post{ParentID: &root.ID, AuthorID: admin.ID, Body: "First look."}
|
|
if err := mem.CreatePost(context.Background(), plumberReply); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := links.Upsert(context.Background(), store.DiscordLink{
|
|
PostID: plumberReply.ID,
|
|
MessageID: "d-plumber-1",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bot.handleInbound(inboundMessage{
|
|
ID: "d-nested",
|
|
ChannelID: "thread-1",
|
|
GuildID: "guild-1",
|
|
AuthorID: "snow-admin",
|
|
Content: "More detail.",
|
|
ReferencedMessageID: "d-plumber-1",
|
|
})
|
|
|
|
thread, err := mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil || len(thread.Replies) != 1 || len(thread.Replies[0].Replies) != 1 {
|
|
t.Fatalf("thread = %+v, %v", thread, err)
|
|
}
|
|
nested := thread.Replies[0].Replies[0]
|
|
if nested.ParentID == nil || *nested.ParentID != plumberReply.ID {
|
|
t.Fatalf("nested parent = %+v", nested)
|
|
}
|
|
}
|
|
|
|
func TestInboundIgnoresAllowlistHiddenAndEchoSources(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mem, homeowner, admin := seedInboundUsers(t)
|
|
links := newMemoryLinks()
|
|
api := &fakeAPI{}
|
|
bot := inboundTestBot(mem, links, api, mail.Nop{}, admin.Username)
|
|
root := seedLinkedRoot(t, mem, links, homeowner.ID, "thread-1")
|
|
hidden := &store.Post{
|
|
AuthorID: homeowner.ID,
|
|
Title: "Hidden",
|
|
Body: "No.",
|
|
PostDate: pacific.Today(),
|
|
PostState: store.PostStateHidden,
|
|
}
|
|
if err := mem.CreatePost(context.Background(), hidden); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := links.Upsert(context.Background(), store.DiscordLink{
|
|
PostID: hidden.ID,
|
|
MessageID: "d-hidden",
|
|
ThreadID: "thread-hidden",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []inboundMessage{
|
|
{ID: "bot", ChannelID: "thread-1", GuildID: "g", AuthorID: "snow-admin", Content: "x", Bot: true},
|
|
{ID: "dm", ChannelID: "thread-1", AuthorID: "snow-admin", Content: "x"},
|
|
{ID: "self", ChannelID: "thread-1", GuildID: "g", AuthorID: "bot-1", Content: "x"},
|
|
{ID: "stranger", ChannelID: "thread-1", GuildID: "g", AuthorID: "snow-other", Content: "x"},
|
|
{ID: "elsewhere", ChannelID: "other-thread", GuildID: "g", AuthorID: "snow-admin", Content: "x"},
|
|
{ID: "empty", ChannelID: "thread-1", GuildID: "g", AuthorID: "snow-admin", Content: " ", Attachments: 1},
|
|
{ID: "hidden", ChannelID: "thread-hidden", GuildID: "g", AuthorID: "snow-admin", Content: "x"},
|
|
{ID: "channel-root", ChannelID: "channel-1", GuildID: "g", AuthorID: "snow-admin", Content: "new question"},
|
|
}
|
|
for _, in := range cases {
|
|
bot.handleInbound(in)
|
|
}
|
|
thread, err := mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil || len(thread.Replies) != 0 {
|
|
t.Fatalf("unexpected replies: %+v, %v", thread, err)
|
|
}
|
|
if len(api.sends) != 0 {
|
|
t.Fatalf("unexpected discord sends %+v", api.sends)
|
|
}
|
|
}
|
|
|
|
func inboundTestBot(mem *store.Memory, links *memoryLinks, api *fakeAPI, mailer mail.Notifier, adminUsername string) *Bot {
|
|
bot := New("channel-1", links, api)
|
|
bot.store = mem
|
|
bot.mail = mailer
|
|
bot.admins = map[string]string{"snow-admin": adminUsername}
|
|
bot.botUserID = "bot-1"
|
|
return bot
|
|
}
|
|
|
|
func seedInboundUsers(t *testing.T) (*store.Memory, *store.User, *store.User) {
|
|
t.Helper()
|
|
mem := store.NewMemory()
|
|
homeowner := &store.User{
|
|
Username: "homeowner",
|
|
Name: "Sam",
|
|
Email: "sam@example.com",
|
|
PasswordHash: "x",
|
|
Role: store.RoleUser,
|
|
}
|
|
if err := mem.CreateUser(context.Background(), homeowner); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
admin := &store.User{
|
|
Username: "plumber",
|
|
Name: "Pat",
|
|
Email: "pat@example.com",
|
|
PasswordHash: "x",
|
|
Role: store.RoleAdmin,
|
|
}
|
|
if err := mem.CreateUser(context.Background(), admin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return mem, homeowner, admin
|
|
}
|
|
|
|
func seedLinkedRoot(t *testing.T, mem *store.Memory, links *memoryLinks, authorID, threadID string) *store.Post {
|
|
t.Helper()
|
|
root := &store.Post{
|
|
AuthorID: authorID,
|
|
Title: "Leaky sink",
|
|
Body: "It drips.",
|
|
PostDate: pacific.Today(),
|
|
}
|
|
if err := mem.CreatePost(context.Background(), root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := links.Upsert(context.Background(), store.DiscordLink{
|
|
PostID: root.ID,
|
|
MessageID: "d-root",
|
|
ThreadID: threadID,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return root
|
|
}
|
|
|
|
func waitForMail(t *testing.T, recording *mail.Recording, want int) []mail.PostReply {
|
|
t.Helper()
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if recording.Len() >= want {
|
|
return recording.Snapshot()
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("recorded %d notifications, want %d", recording.Len(), want)
|
|
return nil
|
|
}
|