- Edit Bot now shows the current value of every field (Name/About/ Description/Botpic/Commands) like BotFather, with real botpic status via a new PeerHasAvatar port method. - After editing a field the dialog lands back on a fresh Edit Bot menu (working "Back to bot" / "Bots list" buttons) instead of ending, so a follow-up button press no longer reports the button as expired. - Service-bot messages now render @username as a tappable mention entity.
128 lines
4.6 KiB
Go
128 lines
4.6 KiB
Go
package bots
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store/memory"
|
|
)
|
|
|
|
type fakeBotAvatar struct {
|
|
ownerType domain.PeerType
|
|
ownerID int64
|
|
sourcePhotoID int64
|
|
calls int
|
|
err error
|
|
hasAvatar bool
|
|
}
|
|
|
|
func (f *fakeBotAvatar) PeerHasAvatar(_ context.Context, _ domain.PeerType, _ int64) (bool, error) {
|
|
return f.hasAvatar, nil
|
|
}
|
|
|
|
func (f *fakeBotAvatar) SetAvatarFromExistingPhoto(_ context.Context, ownerType domain.PeerType, ownerID, sourcePhotoID int64, _ int) (domain.Photo, error) {
|
|
f.calls++
|
|
f.ownerType, f.ownerID, f.sourcePhotoID = ownerType, ownerID, sourcePhotoID
|
|
if f.err != nil {
|
|
return domain.Photo{}, f.err
|
|
}
|
|
return domain.Photo{ID: 90210}, nil
|
|
}
|
|
|
|
func botFatherPhotoMsg(userID, photoID int64) domain.Message {
|
|
return domain.Message{
|
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: userID},
|
|
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.BotFatherUserID},
|
|
Media: &domain.MessageMedia{
|
|
Kind: domain.MessageMediaKindPhoto,
|
|
Photo: &domain.Photo{ID: photoID},
|
|
},
|
|
}
|
|
}
|
|
|
|
func openBotpicPrompt(t *testing.T, svc *Service, messages *memory.MessageStore, owner domain.User) {
|
|
t.Helper()
|
|
sendToBotFather(t, svc, messages, owner, "/mybots")
|
|
pressBotFather(t, svc, messages, owner.ID, "@pic_mb_bot")
|
|
pressBotFather(t, svc, messages, owner.ID, "Edit Bot")
|
|
_, prompt := pressBotFather(t, svc, messages, owner.ID, "Edit Botpic")
|
|
if !strings.Contains(prompt.Body, "profile picture") {
|
|
t.Fatalf("botpic prompt = %q", prompt.Body)
|
|
}
|
|
}
|
|
|
|
func TestMyBotsEditBotpicSetsPhoto(t *testing.T) {
|
|
users := memory.NewUserStore()
|
|
bots := memory.NewBotStore(users)
|
|
messages := memory.NewMessageStore(memory.NewDialogStore())
|
|
avatar := &fakeBotAvatar{}
|
|
svc := NewService(users, bots, messages, WithBotAvatarStore(avatar))
|
|
owner := newOwner(t, users, "+2100")
|
|
created, _, err := svc.CreateBot(context.Background(), owner.ID, "Pic Bot", "pic_mb_bot")
|
|
if err != nil {
|
|
t.Fatalf("create bot: %v", err)
|
|
}
|
|
|
|
openBotpicPrompt(t, svc, messages, owner)
|
|
|
|
// A non-photo message keeps the step and asks again.
|
|
if reply := sendToBotFather(t, svc, messages, owner, "here you go"); !strings.Contains(reply, "send a photo") {
|
|
t.Fatalf("text-instead-of-photo reply = %q", reply)
|
|
}
|
|
if avatar.calls != 0 {
|
|
t.Fatalf("avatar setter called for a non-photo message")
|
|
}
|
|
|
|
svc.respondAsBotFather(owner.ID, botFatherPhotoMsg(owner.ID, 7777))
|
|
reply := botFatherUserReply(t, messages, owner.ID)
|
|
if !strings.Contains(reply.Body, "Profile picture updated") {
|
|
t.Fatalf("botpic success reply = %q", reply.Body)
|
|
}
|
|
if avatar.calls != 1 || avatar.ownerID != created.ID || avatar.sourcePhotoID != 7777 || avatar.ownerType != domain.PeerTypeUser {
|
|
t.Fatalf("avatar setter got (calls=%d owner=%d photo=%d type=%s)", avatar.calls, avatar.ownerID, avatar.sourcePhotoID, avatar.ownerType)
|
|
}
|
|
// The dialog is done: a stray message no longer lands on the botpic step.
|
|
if reply := sendToBotFather(t, svc, messages, owner, "anything"); strings.Contains(reply, "profile picture") {
|
|
t.Fatalf("botpic step still active after success: %q", reply)
|
|
}
|
|
}
|
|
|
|
func TestMyBotsEditBotpicRejectsBadImage(t *testing.T) {
|
|
users := memory.NewUserStore()
|
|
bots := memory.NewBotStore(users)
|
|
messages := memory.NewMessageStore(memory.NewDialogStore())
|
|
avatar := &fakeBotAvatar{err: domain.ErrPhotoInvalid}
|
|
svc := NewService(users, bots, messages, WithBotAvatarStore(avatar))
|
|
owner := newOwner(t, users, "+2101")
|
|
if _, _, err := svc.CreateBot(context.Background(), owner.ID, "Pic Bot", "pic_mb_bot"); err != nil {
|
|
t.Fatalf("create bot: %v", err)
|
|
}
|
|
|
|
openBotpicPrompt(t, svc, messages, owner)
|
|
svc.respondAsBotFather(owner.ID, botFatherPhotoMsg(owner.ID, 7777))
|
|
reply := botFatherUserReply(t, messages, owner.ID)
|
|
if !strings.Contains(reply.Body, "couldn't use that image") {
|
|
t.Fatalf("bad image reply = %q", reply.Body)
|
|
}
|
|
// Step is kept so the user can send another photo.
|
|
if reply := sendToBotFather(t, svc, messages, owner, "x"); !strings.Contains(reply, "send a photo") {
|
|
t.Fatalf("after bad image, step not kept: %q", reply)
|
|
}
|
|
}
|
|
|
|
func TestMyBotsEditBotpicUnsupported(t *testing.T) {
|
|
svc, users, _, messages := newTestService(t) // no WithBotAvatarStore
|
|
owner := newOwner(t, users, "+2102")
|
|
if _, _, err := svc.CreateBot(context.Background(), owner.ID, "Pic Bot", "pic_mb_bot"); err != nil {
|
|
t.Fatalf("create bot: %v", err)
|
|
}
|
|
|
|
openBotpicPrompt(t, svc, messages, owner)
|
|
svc.respondAsBotFather(owner.ID, botFatherPhotoMsg(owner.ID, 7777))
|
|
reply := botFatherUserReply(t, messages, owner.ID)
|
|
if !strings.Contains(reply.Body, "isn't available on this server") {
|
|
t.Fatalf("unsupported reply = %q", reply.Body)
|
|
}
|
|
}
|