Files
ratatoskr-go/internal/chat/telegram/telegram_test.go
ki.sagidullin 1459670ce9
Some checks failed
CI / test (pull_request) Failing after 34s
CI / build-and-package (amd64, linux) (pull_request) Successful in 34s
CI / build-and-package (amd64, windows) (pull_request) Successful in 36s
refactor: чистка мёртвого кода, лимит ходов D3, HTML-экранирование и UTF-8 обрезка в Telegram
2026-08-18 23:13:04 +05:00

178 lines
4.3 KiB
Go

package telegram
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
"unicode/utf8"
"github.com/kamelion/ratatoskr-go/internal/chat"
)
// fakeTG — тестовый сервер, имитирующий Telegram Bot API.
type fakeTG struct {
srv *httptest.Server
mu sync.Mutex
sent []sendReq
updCh chan []update // следующая пачка обновлений
closed bool
}
type sendReq struct {
ChatID string `json:"chat_id"`
Text string `json:"text"`
}
func newFakeTG(t *testing.T) *fakeTG {
t.Helper()
f := &fakeTG{updCh: make(chan []update, 10)}
mux := http.NewServeMux()
mux.HandleFunc("/botTOKEN/sendMessage", func(w http.ResponseWriter, r *http.Request) {
var req sendReq
json.NewDecoder(r.Body).Decode(&req)
f.mu.Lock()
f.sent = append(f.sent, req)
f.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
})
mux.HandleFunc("/botTOKEN/getUpdates", func(w http.ResponseWriter, r *http.Request) {
f.mu.Lock()
upds := []update{}
if len(f.updCh) > 0 {
upds = <-f.updCh
}
f.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"ok": true, "result": upds})
})
f.srv = httptest.NewServer(mux)
t.Cleanup(f.srv.Close)
return f
}
func (f *fakeTG) push(text string) {
msg := message{MessageID: int64(len(f.sent) + 1), Text: text}
msg.Chat.ID = 123
f.updCh <- []update{{
UpdateID: int64(len(f.sent) + 1),
Message: &msg,
}}
}
func TestSend(t *testing.T) {
f := newFakeTG(t)
ch := New("TOKEN", time.Second)
ch.client = f.srv.Client()
// подменяем baseURL на тестовый
ch.apiURL = f.srv.URL + "/bot%s/"
err := ch.Send(context.Background(), "tg://123", chat.Message{Text: "hello"})
if err != nil {
t.Fatalf("Send: %v", err)
}
if len(f.sent) != 1 {
t.Fatal("не отправлено")
}
if f.sent[0].ChatID != "123" {
t.Errorf("chat_id = %q, want 123 (без префикса tg://)", f.sent[0].ChatID)
}
if f.sent[0].Text != "hello" {
t.Errorf("text = %q", f.sent[0].Text)
}
}
func TestSendWithOptions(t *testing.T) {
f := newFakeTG(t)
ch := New("TOKEN", time.Second)
ch.client = f.srv.Client()
ch.apiURL = f.srv.URL + "/bot%s/"
err := ch.Send(context.Background(), "tg://123", chat.Message{
Text: "Выбери:",
Options: []chat.Option{
{ID: "a", Label: "Чай"},
{ID: "b", Label: "Кофе"},
},
})
if err != nil {
t.Fatalf("Send: %v", err)
}
if !strings.Contains(f.sent[0].Text, "<b>1.</b> Чай") {
t.Errorf("формат options: %s", f.sent[0].Text)
}
if !strings.Contains(f.sent[0].Text, "<b>2.</b> Кофе") {
t.Errorf("формат options: %s", f.sent[0].Text)
}
}
func TestFormatOutgoingEscapesText(t *testing.T) {
if got := formatOutgoing(chat.Message{Text: "2 < 3 & 4 > 1"}); got != "2 &lt; 3 &amp; 4 &gt; 1" {
t.Errorf("text escape = %q", got)
}
got := formatOutgoing(chat.Message{
Text: "a<b",
Options: []chat.Option{{ID: "x", Label: "l&l"}},
})
if !strings.Contains(got, "a&lt;b") || !strings.Contains(got, "l&amp;l") {
t.Errorf("options escape = %q", got)
}
}
func TestTruncateUTF8(t *testing.T) {
long := strings.Repeat("я", 5000)
tr := truncateUTF8(long, 4000)
if len(tr) != 4000 {
t.Fatalf("len = %d, want 4000", len(tr))
}
if !utf8.ValidString(tr) {
t.Fatal("truncated string is not valid UTF-8")
}
if got := truncateUTF8("привет", 4000); got != "привет" {
t.Fatalf("short text changed: %q", got)
}
if got := truncateUTF8("", 4000); got != "" {
t.Fatalf("empty text changed: %q", got)
}
}
func TestIncoming(t *testing.T) {
f := newFakeTG(t)
ch := New("TOKEN", time.Second)
ch.client = f.srv.Client()
ch.apiURL = f.srv.URL + "/bot%s/"
incoming := make(chan chat.Incoming, 1)
ch.OnMessage(func(inc chat.Incoming) {
incoming <- inc
})
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go ch.Run(ctx)
f.push("тест")
select {
case inc := <-incoming:
if inc.UserID != "123" {
t.Errorf("UserID = %q, want 123", inc.UserID)
}
if inc.Address != "tg://123" {
t.Errorf("Address = %q", inc.Address)
}
if inc.Msg.Text != "тест" {
t.Errorf("text = %q", inc.Msg.Text)
}
case <-ctx.Done():
t.Fatal("timeout ожидания входящего")
}
}