yao/integrations/telegram/telegram_test.go
Max ea9e070f29 Enhance robot integration with Telegram and improve event handling
- Add Telegram integration support by introducing a dispatcher for handling Telegram events and messages.
- Implement event notifications for robot configuration changes (creation, update, deletion) to facilitate integration with external services.
- Refactor the robot initialization process to load robots into cache and start the dispatcher, improving the overall system setup.
- Update the delivery event structure to include additional metadata for better context during message handling.
- Enhance logging capabilities for better observability during robot execution and event processing.
2026-03-01 22:03:25 +08:00

177 lines
4.5 KiB
Go

package telegram
import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/gotd/td/session"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/uploader"
"github.com/gotd/td/tg"
"github.com/yaoapp/yao/attachment"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/test"
)
var testBotToken string
func TestMain(m *testing.M) {
testBotToken = os.Getenv("TELEGRAM_TEST_BOT_TOKEN")
os.Exit(m.Run())
}
func prepare(t *testing.T) {
t.Helper()
test.Prepare(t, config.Conf)
if err := attachment.Load(config.Conf); err != nil {
t.Fatalf("load attachment: %v", err)
}
}
func cleanup() {
test.Clean()
}
func skipIfNoToken(t *testing.T) {
t.Helper()
if testBotToken == "" {
t.Skip("TELEGRAM_TEST_BOT_TOKEN not set, skipping E2E test")
}
}
func testBot(opts ...BotOption) *Bot {
if host := os.Getenv("TELEGRAM_TEST_HOST"); host != "" {
opts = append([]BotOption{WithAPIBase(host)}, opts...)
}
return NewBot(testBotToken, "", opts...)
}
// seedBotMessages uses the persisted MTProto user session to send a text
// message and a small PNG photo to the test bot, so that subsequent
// GetUpdates calls have real data to work with.
// Requires TG_TEST_SESSION and TG_TEST_BOT_USERNAME env vars.
func seedBotMessages(t *testing.T) {
t.Helper()
sessionPath := os.Getenv("TG_TEST_SESSION")
botUsername := os.Getenv("TG_TEST_BOT_USERNAME")
if sessionPath == "" || botUsername == "" {
t.Skip("TG_TEST_SESSION or TG_TEST_BOT_USERNAME not set, cannot seed messages")
}
if !filepath.IsAbs(sessionPath) {
if root := os.Getenv("YAO_DEV"); root != "" {
sessionPath = filepath.Join(root, sessionPath)
}
}
if _, err := os.Stat(sessionPath); os.IsNotExist(err) {
t.Skipf("session file %s not found, run tg-login first", sessionPath)
}
t.Logf("seed: using session %s, bot @%s", sessionPath, botUsername)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
storage := &session.FileStorage{Path: sessionPath}
client := telegram.NewClient(17349, "344583e45741c457fe1862106095a5eb", telegram.Options{
SessionStorage: storage,
})
err := client.Run(ctx, func(ctx context.Context) error {
status, err := client.Auth().Status(ctx)
if err != nil {
return fmt.Errorf("auth status: %w", err)
}
if !status.Authorized {
return fmt.Errorf("not authorized — run tg-login first")
}
api := client.API()
resolved, err := api.ContactsResolveUsername(ctx, &tg.ContactsResolveUsernameRequest{
Username: botUsername,
})
if err != nil {
return fmt.Errorf("resolve @%s: %w", botUsername, err)
}
if len(resolved.Users) == 0 {
return fmt.Errorf("bot @%s not found", botUsername)
}
u, ok := resolved.Users[0].(*tg.User)
if !ok {
return fmt.Errorf("resolved entity is not a user")
}
peer := &tg.InputPeerUser{UserID: u.ID, AccessHash: u.AccessHash}
tag := time.Now().Format("15:04:05")
_, err = api.MessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
Peer: peer,
Message: fmt.Sprintf("[e2e-test] hello at %s", tag),
RandomID: mtpRandID(),
})
if err != nil {
return fmt.Errorf("send text: %w", err)
}
t.Log("seed: sent text")
up := uploader.NewUploader(api)
seedFiles := []struct {
path string
photo bool
attrs []tg.DocumentAttributeClass
}{
{"../testdata/test.jpg", true, nil},
{"../testdata/test.mp3", false, []tg.DocumentAttributeClass{
&tg.DocumentAttributeAudio{Duration: 5, Title: "e2e-test"},
}},
{"../testdata/test.pdf", false, []tg.DocumentAttributeClass{
&tg.DocumentAttributeFilename{FileName: "test.pdf"},
}},
}
for _, sf := range seedFiles {
f, err := up.FromPath(ctx, sf.path)
if err != nil {
return fmt.Errorf("upload %s: %w", sf.path, err)
}
var media tg.InputMediaClass
if sf.photo {
media = &tg.InputMediaUploadedPhoto{File: f}
} else {
media = &tg.InputMediaUploadedDocument{
File: f,
Attributes: sf.attrs,
}
}
_, err = api.MessagesSendMedia(ctx, &tg.MessagesSendMediaRequest{
Peer: peer,
Media: media,
Message: fmt.Sprintf("[e2e-test] %s at %s", filepath.Base(sf.path), tag),
RandomID: mtpRandID(),
})
if err != nil {
return fmt.Errorf("send %s: %w", sf.path, err)
}
t.Logf("seed: sent %s", filepath.Base(sf.path))
}
time.Sleep(time.Second)
return nil
})
if err != nil {
t.Fatalf("seedBotMessages: %v", err)
}
}
func mtpRandID() int64 {
var b [8]byte
_, _ = rand.Read(b[:])
return int64(binary.LittleEndian.Uint64(b[:]))
}