Files
Cyrene_v2/plugins/channels/telegram/send_test.go
T

325 lines
9.6 KiB
Go

package telegram
import (
"context"
"errors"
"net/http"
"sync"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
func TestSendMessage_TextConstruction(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "sendMessage" {
http.Error(w, "unexpected method: "+methodOf(r), http.StatusNotFound)
return
}
mu.Lock()
body = decodeBody(r)
mu.Unlock()
okResponse(w, map[string]any{"message_id": 100})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
resp := adapter.UnifiedResponse{
ReplyTo: "42",
Messages: []adapter.UnifiedMessage{
{ChannelID: "200", ChannelType: adapter.ChannelDirect, Content: "hello", ContentType: adapter.ContentText},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if body["chat_id"] != "200" {
t.Errorf("chat_id = %v, want 200", body["chat_id"])
}
if body["text"] != "hello" {
t.Errorf("text = %v, want hello", body["text"])
}
if body["parse_mode"] != "Markdown" {
t.Errorf("parse_mode = %v, want Markdown", body["parse_mode"])
}
if body["reply_to_message_id"] != float64(42) {
t.Errorf("reply_to_message_id = %v, want 42", body["reply_to_message_id"])
}
}
func TestSendMessage_ImageByFileID(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
var method string
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := methodOf(r)
if m != "sendPhoto" {
http.Error(w, "unexpected method: "+m, http.StatusNotFound)
return
}
mu.Lock()
method = m
body = decodeBody(r)
mu.Unlock()
okResponse(w, map[string]any{"message_id": 101})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
resp := adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{
{
ChannelID: "200",
ChannelType: adapter.ChannelDirect,
Content: "a caption",
ContentType: adapter.ContentImage,
Media: []adapter.Media{{Kind: adapter.MediaImage, URL: "file-abc"}},
},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if method != "sendPhoto" {
t.Errorf("method = %q, want sendPhoto", method)
}
if body["chat_id"] != "200" || body["photo"] != "file-abc" {
t.Errorf("body = %v, want chat_id 200 and photo file-abc", body)
}
if body["caption"] != "a caption" || body["parse_mode"] != "Markdown" {
t.Errorf("caption/parse_mode = %v/%v", body["caption"], body["parse_mode"])
}
}
func TestSendMessage_ImageNoFileIDFallsBackToText(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var method string
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := methodOf(r)
mu.Lock()
method = m
mu.Unlock()
if m != "sendMessage" {
http.Error(w, "unexpected method: "+m, http.StatusNotFound)
return
}
okResponse(w, map[string]any{"message_id": 102})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
resp := adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{
{ChannelID: "200", ContentType: adapter.ContentImage, Content: "no file_id so text"},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if method != "sendMessage" {
t.Errorf("method = %q, want fallback sendMessage", method)
}
}
func TestSendMessage_VoiceAndDocument(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
ct adapter.ContentType
media adapter.Media
wantMethod string
wantField string
}{
{name: "voice", ct: adapter.ContentVoice, media: adapter.Media{Kind: adapter.MediaVoice, URL: "voice-1"}, wantMethod: "sendVoice", wantField: "voice"},
{name: "document", ct: adapter.ContentFile, media: adapter.Media{Kind: adapter.MediaFile, URL: "doc-1", Name: "x.pdf"}, wantMethod: "sendDocument", wantField: "document"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
var method string
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := methodOf(r)
mu.Lock()
method = m
body = decodeBody(r)
mu.Unlock()
if m != tc.wantMethod {
http.Error(w, "unexpected method: "+m, http.StatusNotFound)
return
}
okResponse(w, map[string]any{"message_id": 103})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
resp := adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{
{ChannelID: "200", ChannelType: adapter.ChannelDirect, ContentType: tc.ct, Media: []adapter.Media{tc.media}},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if method != tc.wantMethod {
t.Errorf("method = %q, want %q", method, tc.wantMethod)
}
field := tc.media.URL
if body[tc.wantField] != field {
t.Errorf("%s = %v, want %v", tc.wantField, body[tc.wantField], field)
}
})
}
}
func TestSendProactive(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "sendMessage" {
http.Error(w, "unexpected method", http.StatusNotFound)
return
}
mu.Lock()
body = decodeBody(r)
mu.Unlock()
okResponse(w, map[string]any{"message_id": 104})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
msg := adapter.UnifiedMessage{
ChannelID: "300", ChannelType: adapter.ChannelDirect,
Content: "reminder", ContentType: adapter.ContentText,
}
if err := a.SendProactive(context.Background(), msg); err != nil {
t.Fatalf("SendProactive: %v", err)
}
mu.Lock()
defer mu.Unlock()
if body["chat_id"] != "300" || body["text"] != "reminder" {
t.Errorf("body = %v, want chat_id 300 text reminder", body)
}
if _, ok := body["reply_to_message_id"]; ok {
t.Errorf("proactive message should not carry reply_to_message_id, got %v", body)
}
}
func TestSendMessage_NotConnected(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
err := a.SendMessage(context.Background(), adapter.UnifiedResponse{})
if !errors.Is(err, adapter.ErrNotConnected) {
t.Fatalf("err = %v, want ErrNotConnected", err)
}
if err := a.SendProactive(context.Background(), adapter.UnifiedMessage{}); !errors.Is(err, adapter.ErrNotConnected) {
t.Fatalf("SendProactive err = %v, want ErrNotConnected", err)
}
}
func TestSendMessage_NoTargetChat(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "unexpected method", http.StatusNotFound)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
err := a.SendMessage(context.Background(), adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{{ContentType: adapter.ContentText, Content: "hi"}},
})
if !errors.Is(err, ErrNoChatTarget) {
t.Fatalf("err = %v, want ErrNoChatTarget", err)
}
}
func TestSendMessage_APIError(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "sendMessage" {
http.Error(w, "unexpected method", http.StatusNotFound)
return
}
failResponse(w, "Bad Request: chat not found", 400)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
err := a.SendMessage(context.Background(), adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{{ChannelID: "999", ContentType: adapter.ContentText, Content: "hi"}},
})
if !errors.Is(err, ErrBadResponse) {
t.Fatalf("err = %v, want ErrBadResponse", err)
}
}
func TestProcessUpdate_DeliversToHandler(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "unexpected method", http.StatusNotFound)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
var mu sync.Mutex
var got *adapter.UnifiedMessage
a.SetMessageHandler(func(ctx context.Context, m adapter.UnifiedMessage) {
mu.Lock()
got = &m
mu.Unlock()
})
raw := []byte(`{"update_id":1,"message":{"message_id":5,"from":{"id":1,"first_name":"Ada"},"chat":{"id":10,"type":"private"},"date":1700000000,"text":"webhook hi"}}`)
n, err := a.ProcessUpdate(context.Background(), raw)
if err != nil {
t.Fatalf("ProcessUpdate: %v", err)
}
if n != 1 {
t.Errorf("delivered %d messages, want 1", n)
}
mu.Lock()
defer mu.Unlock()
if got == nil {
t.Fatal("handler was not called")
}
if got.Content != "webhook hi" || got.Platform != "telegram" {
t.Errorf("message = %+v", got)
}
}
func TestProcessUpdate_NonMessageIgnored(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
raw := []byte(`{"update_id":2,"callback_query":{"id":"cq","from":{"id":1},"data":"btn"}}`)
n, err := a.ProcessUpdate(context.Background(), raw)
if err != nil {
t.Fatalf("ProcessUpdate: %v", err)
}
if n != 0 {
t.Errorf("delivered %d messages, want 0 for callback query", n)
}
}
func TestProcessUpdate_InvalidJSON(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
if _, err := a.ProcessUpdate(context.Background(), []byte("{not json")); err == nil {
t.Fatal("expected error decoding invalid JSON")
}
}