Files
jules/chat/telegram/telegram_test.go
T

251 lines
5.6 KiB
Go

package telegram
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/armon/go-socks5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockTelegramServer struct {
mu sync.Mutex
messages []mockMessage
updateID int64
}
type mockMessage struct {
chatID int64
text string
}
func (m *mockTelegramServer) addMessage(chatID int64, text string) {
m.mu.Lock()
defer m.mu.Unlock()
m.messages = append(m.messages, mockMessage{chatID: chatID, text: text})
}
func (m *mockTelegramServer) handler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/bottest_token/getMe":
json.NewEncoder(w).Encode(map[string]any{
"ok": true,
"result": map[string]string{"username": "test_bot"},
})
case "/bottest_token/sendMessage":
json.NewEncoder(w).Encode(map[string]any{
"ok": true,
"result": map[string]any{"message_id": 1},
})
case "/bottest_token/getUpdates":
m.mu.Lock()
defer m.mu.Unlock()
updates := []map[string]any{}
for _, msg := range m.messages {
m.updateID++
updates = append(updates, map[string]any{
"update_id": m.updateID,
"message": map[string]any{
"message_id": m.updateID,
"chat": map[string]any{"id": msg.chatID},
"text": msg.text,
},
})
}
m.messages = nil
json.NewEncoder(w).Encode(map[string]any{
"ok": true,
"result": updates,
})
default:
w.WriteHeader(http.StatusNotFound)
}
}
}
func startMockSocks5Server(t *testing.T) (string, func()) {
conf := &socks5.Config{}
server, err := socks5.New(conf)
require.NoError(t, err)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
port := listener.Addr().(*net.TCPAddr).Port
proxyURL := fmt.Sprintf("socks5://127.0.0.1:%d", port)
go func() {
_ = server.Serve(listener)
}()
return proxyURL, func() {
listener.Close()
}
}
func newMockServer() (*httptest.Server, *mockTelegramServer) {
mock := &mockTelegramServer{}
return httptest.NewServer(mock.handler()), mock
}
func newTestBot(t *testing.T, server *httptest.Server) *Bot {
t.Helper()
bot, err := New("test_token", server.URL+"/bot%s/%s", "")
require.NoError(t, err)
return bot
}
func TestNew_MockAPI(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
assert.NotNil(t, bot)
}
func TestNew_Error(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
_, err := New("test_token", server.URL+"/invalid", "")
assert.Error(t, err)
}
func TestNew_GetMeError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/bottest_token/getMe" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"ok":false,"description":"Unauthorized"}`))
return
}
json.NewEncoder(w).Encode(map[string]any{"ok": true, "result": map[string]string{}})
}))
defer server.Close()
_, err := New("test_token", server.URL+"/bot%s/%s", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to connect to telegram")
}
func TestSend_MockAPI(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
err := bot.Send(t.Context(), "123456789", "hello")
assert.NoError(t, err)
}
func TestSend_InvalidID(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
err := bot.Send(t.Context(), "not_a_number", "test")
assert.Error(t, err)
}
func TestReceive_MockAPI(t *testing.T) {
server, mock := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
mock.addMessage(123456789, "hello world")
ctx2, cancel := context.WithCancel(t.Context())
defer cancel()
msgs := bot.Receive(ctx2)
select {
case msg := <-msgs:
assert.Equal(t, "123456789", msg.ID)
assert.Equal(t, "hello world", msg.Text)
assert.Equal(t, "telegram", msg.Chat)
case <-time.After(time.Second):
t.Fatal("timeout waiting for message")
}
}
func TestReceive_ContextCancel(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
ctx, cancel := context.WithCancel(t.Context())
msgs := bot.Receive(ctx)
cancel()
select {
case _, ok := <-msgs:
assert.False(t, ok)
case <-time.After(time.Second):
t.Fatal("channel not closed after context cancel")
}
}
func TestReceive_ContextCancelDuringSend(t *testing.T) {
server, mock := newMockServer()
defer server.Close()
bot := newTestBot(t, server)
mock.addMessage(123456789, "hello world")
ctx, cancel := context.WithCancel(t.Context())
msgs := bot.Receive(ctx)
time.Sleep(100 * time.Millisecond)
cancel()
select {
case _, ok := <-msgs:
assert.False(t, ok)
case <-time.After(time.Second):
t.Fatal("channel not closed")
}
}
func TestNew_WithProxy(t *testing.T) {
proxyURL, cleanup := startMockSocks5Server(t)
defer cleanup()
server, _ := newMockServer()
defer server.Close()
bot, err := New("test_token", server.URL+"/bot%s/%s", proxyURL)
require.NoError(t, err)
assert.NotNil(t, bot)
}
func TestNew_WithProxy_InvalidURL(t *testing.T) {
_, err := New("test_token", "http://api.telegram.org", "invalid-url")
assert.Error(t, err)
}
func TestNew_WithProxy_ConnectionRefused(t *testing.T) {
_, err := New("test_token", "http://api.telegram.org", "socks5://localhost:1081")
assert.Error(t, err)
}
func TestNew_WithoutProxy(t *testing.T) {
server, _ := newMockServer()
defer server.Close()
bot, err := New("test_token", server.URL+"/bot%s/%s", "")
require.NoError(t, err)
assert.NotNil(t, bot)
}