- 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.
177 lines
4.5 KiB
Go
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[:]))
|
|
}
|