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