butterrobot/internal/plugin/gallerydl/gallerydl_test.go
Felipe M. d215471a66
All checks were successful
CI / goreleaser-lint (push) Successful in 11s
CI / format (push) Successful in 1m45s
CI / lint (push) Successful in 3m1s
CI / test (push) Successful in 1m33s
CI / build (push) Successful in 1m47s
Release / release (push) Successful in 2m56s
feat: reply to unauthorized !gallerydl users instead of ignoring them
Users not on the allowed_users allowlist now get an explicit "not
allowed" reply rather than the command silently doing nothing.
2026-07-05 20:18:52 +02:00

204 lines
5.8 KiB
Go

package gallerydl
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"git.nakama.town/fmartingr/butterrobot/internal/model"
)
// fakeDownloader returns a Downloader that optionally writes a file and/or
// returns an error, and records whether it was called and with which URL.
func fakeDownloader(writeFile bool, returnErr error, gotURL *string) Downloader {
return func(ctx context.Context, binPath, destDir, url string) error {
if gotURL != nil {
*gotURL = url
}
if writeFile {
_ = os.WriteFile(filepath.Join(destDir, "media.jpg"), []byte("data"), 0o600)
}
return returnErr
}
}
func newMessage(text, author, replyTo string, raw map[string]interface{}) *model.Message {
return &model.Message{
Text: text,
Author: author,
Chat: "chat1",
ID: "msg1",
ReplyTo: replyTo,
Raw: raw,
Channel: &model.Channel{Platform: "telegram", PlatformChannelID: "chat1"},
}
}
func telegramReplyRaw(originalText string) map[string]interface{} {
return map[string]interface{}{
"message": map[string]interface{}{
"reply_to_message": map[string]interface{}{
"text": originalText,
},
},
}
}
func TestOnMessage(t *testing.T) {
allowed := map[string]interface{}{"allowed_users": "alice, bob"}
tests := []struct {
name string
msg *model.Message
config map[string]interface{}
writeFile bool
downloadErr error
wantActions int
wantType model.ActionType
wantReplyTo string
wantText string // substring expected in a text reply (if any)
wantDownload bool
wantDownURL string
}{
{
name: "not the command",
msg: newMessage("hello there", "alice", "", nil),
config: allowed,
wantActions: 0,
},
{
name: "deny by default (empty allowlist)",
msg: newMessage("!gallerydl https://example.com/x", "alice", "", nil),
config: map[string]interface{}{},
wantActions: 1,
wantType: model.ActionSendMessage,
wantText: "not allowed",
},
{
name: "deny user not in allowlist",
msg: newMessage("!gallerydl https://example.com/x", "carol", "", nil),
config: allowed,
wantActions: 1,
wantType: model.ActionSendMessage,
wantText: "not allowed",
},
{
name: "authorized with URL in message",
msg: newMessage("!gallerydl https://example.com/gallery/1", "alice", "", nil),
config: allowed,
writeFile: true,
wantActions: 1,
wantType: model.ActionSendMedia,
wantReplyTo: "msg1",
wantDownload: true,
wantDownURL: "https://example.com/gallery/1",
},
{
name: "authorized with URL from replied-to message",
msg: newMessage("!gallerydl", "bob", "orig99", telegramReplyRaw("look https://example.com/gallery/2 nice")),
config: allowed,
writeFile: true,
wantActions: 1,
wantType: model.ActionSendMedia,
wantReplyTo: "orig99",
wantDownload: true,
wantDownURL: "https://example.com/gallery/2",
},
{
name: "authorized via @-prefixed allowlist entry",
msg: newMessage("!gallerydl https://example.com/z", "alice", "", nil),
config: map[string]interface{}{"allowed_users": "@Alice"},
writeFile: true,
wantActions: 1,
wantType: model.ActionSendMedia,
wantReplyTo: "msg1",
wantDownload: true,
},
{
name: "authorized but no link",
msg: newMessage("!gallerydl", "alice", "", nil),
config: allowed,
wantActions: 1,
wantType: model.ActionSendMessage,
wantText: "No link found",
},
{
name: "download error",
msg: newMessage("!gallerydl https://example.com/x", "alice", "", nil),
config: allowed,
downloadErr: errors.New("boom"),
wantActions: 1,
wantType: model.ActionSendMessage,
wantText: "Download failed",
wantDownload: true,
},
{
name: "nothing downloaded",
msg: newMessage("!gallerydl https://example.com/x", "alice", "", nil),
config: allowed,
writeFile: false,
wantActions: 1,
wantType: model.ActionSendMessage,
wantText: "Nothing was downloaded",
wantDownload: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := New()
var gotURL string
p.download = fakeDownloader(tt.writeFile, tt.downloadErr, &gotURL)
actions := p.OnMessage(tt.msg, tt.config, nil)
if len(actions) != tt.wantActions {
t.Fatalf("got %d actions, want %d", len(actions), tt.wantActions)
}
if tt.wantActions == 0 {
if gotURL != "" {
t.Errorf("downloader should not have run, got url %q", gotURL)
}
return
}
action := actions[0]
if action.Type != tt.wantType {
t.Fatalf("got action type %q, want %q", action.Type, tt.wantType)
}
switch action.Type {
case model.ActionSendMedia:
if action.Message.ReplyTo != tt.wantReplyTo {
t.Errorf("ReplyTo = %q, want %q", action.Message.ReplyTo, tt.wantReplyTo)
}
if len(action.Message.Files) == 0 {
t.Errorf("expected media files, got none")
}
if dir, ok := action.Message.Raw["cleanup_dir"].(string); !ok || dir == "" {
t.Errorf("expected cleanup_dir in Raw, got %v", action.Message.Raw)
} else {
t.Cleanup(func() { _ = os.RemoveAll(dir) })
}
case model.ActionSendMessage:
if tt.wantText != "" && !strings.Contains(action.Message.Text, tt.wantText) {
t.Errorf("reply text %q does not contain %q", action.Message.Text, tt.wantText)
}
}
if tt.wantDownload {
if gotURL == "" {
t.Errorf("expected downloader to run, but it did not")
}
if tt.wantDownURL != "" && gotURL != tt.wantDownURL {
t.Errorf("downloaded url = %q, want %q", gotURL, tt.wantDownURL)
}
} else if gotURL != "" {
t.Errorf("downloader should not have run, got url %q", gotURL)
}
})
}
}