diff --git a/.env b/.env new file mode 100644 index 0000000..30f3f41 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +# Env vars used to run integration tests with Telegram Bot API +TELEGRAM_TESTBOT_TOKEN= +TELEGRAM_SUPERGROUP_CHAT_ID= +TELEGRAM_CHANNEL= +TELEGRAM_CHAT_ID= +TELEGRAM_REPLY_TO_MESSAGE_ID= \ No newline at end of file diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml new file mode 100644 index 0000000..c345c73 --- /dev/null +++ b/.github/workflows/integration_tests.yml @@ -0,0 +1,39 @@ +name: Integration Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + name: Test + runs-on: ubuntu-latest + env: + TELEGRAM_TESTBOT_TOKEN: ${{ secrets.TELEGRAM_TESTBOT_TOKEN }} + TELEGRAM_CHANNEL: ${{ secrets.TELEGRAM_CHANNEL }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + TELEGRAM_REPLY_TO_MESSAGE_ID: ${{ secrets.TELEGRAM_REPLY_TO_MESSAGE_ID }} + TELEGRAM_SUPERGROUP_CHAT_ID: ${{ secrets.TELEGRAM_SUPERGROUP_CHAT_ID }} + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.21 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Build + run: go build -v . + + - name: Test + run: go test -coverprofile=coverage.out -covermode=atomic -v ./tests/. + + - name: Upload coverage report + uses: codecov/codecov-action@v1 + with: + file: ./coverage.out diff --git a/.github/workflows/test.yml b/.github/workflows/unit_tests.yml similarity index 83% rename from .github/workflows/test.yml rename to .github/workflows/unit_tests.yml index 48b2859..22ce96e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/unit_tests.yml @@ -1,10 +1,9 @@ -name: Test +name: Unit Tests on: push: branches: - - master - - develop + - '**' pull_request: jobs: @@ -15,7 +14,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - go-version: ^1.15 + go-version: ^1.21 id: go - name: Check out code into the Go module directory diff --git a/.gitignore b/.gitignore index d3083e5..b76b94f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage.out tmp/ book/ .vscode/ + +.env \ No newline at end of file diff --git a/bot_mock_test.go b/bot_mock_test.go new file mode 100644 index 0000000..731e5f7 --- /dev/null +++ b/bot_mock_test.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: bot.go +// +// Generated by this command: +// +// mockgen -destination=bot_mock_test.go -package=tgbotapi -source=bot.go +// + +// Package tgbotapi is a generated GoMock package. +package tgbotapi + +import ( + http "net/http" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockHTTPClient is a mock of HTTPClient interface. +type MockHTTPClient struct { + ctrl *gomock.Controller + recorder *MockHTTPClientMockRecorder +} + +// MockHTTPClientMockRecorder is the mock recorder for MockHTTPClient. +type MockHTTPClientMockRecorder struct { + mock *MockHTTPClient +} + +// NewMockHTTPClient creates a new mock instance. +func NewMockHTTPClient(ctrl *gomock.Controller) *MockHTTPClient { + mock := &MockHTTPClient{ctrl: ctrl} + mock.recorder = &MockHTTPClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTPClient) EXPECT() *MockHTTPClientMockRecorder { + return m.recorder +} + +// Do mocks base method. +func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Do", req) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Do indicates an expected call of Do. +func (mr *MockHTTPClientMockRecorder) Do(req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHTTPClient)(nil).Do), req) +} diff --git a/bot_test.go b/bot_test.go index 40633d6..35a5d5a 100644 --- a/bot_test.go +++ b/bot_test.go @@ -1,1062 +1,260 @@ package tgbotapi import ( + "bytes" + "fmt" + "io" "net/http" - "os" "testing" - "time" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" ) const ( - TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I" - ChatID = 76918703 - Channel = "@tgbotapitest" - SupergroupChatID = -1001120141283 - ReplyToMessageID = 35 - ExistingPhotoFileID = "AgACAgIAAxkDAAEBFUZhIALQ9pZN4BUe8ZSzUU_2foSo1AACnrMxG0BucEhezsBWOgcikQEAAwIAA20AAyAE" - ExistingDocumentFileID = "BQADAgADOQADjMcoCcioX1GrDvp3Ag" - ExistingAudioFileID = "BQADAgADRgADjMcoCdXg3lSIN49lAg" - ExistingVoiceFileID = "AwADAgADWQADjMcoCeul6r_q52IyAg" - ExistingVideoFileID = "BAADAgADZgADjMcoCav432kYe0FRAg" - ExistingVideoNoteFileID = "DQADAgADdQAD70cQSUK41dLsRMqfAg" - ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg" + TestToken = "ThisIsATestTokenNoMatterWhatItContains" + ChatID = 111 + SupergroupChatID = -1111 + ReplyToMessageID = 1 ) -type testLogger struct { - t *testing.T +func prepareHttpClient(t *testing.T) *MockHTTPClient { + ctrl := gomock.NewController(t) + httpMock := NewMockHTTPClient(ctrl) + return httpMock } -func (t testLogger) Println(v ...interface{}) { - t.t.Log(v...) +func expectGetMe(t *testing.T, c *MockHTTPClient) { + meResp := `{"ok": true, "result": {"id": 123456789, "is_bot": true, "first_name": "MyBot", "username": "my_bot"}}` + c.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "getMe") + require.True(t, valid) + return newOKResponse(meResp), nil + }) } -func (t testLogger) Printf(format string, v ...interface{}) { - t.t.Logf(format, v...) -} - -func getBot(t *testing.T) (*BotAPI, error) { - bot, err := NewBotAPI(TestToken) - bot.Debug = true - - logger := testLogger{t} - SetLogger(logger) - - if err != nil { - t.Error(err) - } - - return bot, err -} - -func TestNewBotAPI_notoken(t *testing.T) { +func TestNewBotInstance_WithEmptyToken(t *testing.T) { _, err := NewBotAPI("") + require.Error(t, err) +} - if err == nil { - t.Error(err) +func isRequestValid(r *http.Request, token, path string) bool { + return r.Method == http.MethodPost && + r.URL.Path == fmt.Sprintf("/bot%s/%s", token, path) && + r.URL.Scheme == "https" +} + +func newOKResponse(body string) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Status: http.StatusText(http.StatusOK), + Body: io.NopCloser(bytes.NewBufferString(body)), + Header: make(http.Header), } } func TestGetUpdates(t *testing.T) { - bot, _ := getBot(t) + updateResp := `{ + "ok": true, + "result": [ + { + "update_id": 123456789, + "message": { + "message_id": 111, + "from": { + "id": 222, + "is_bot": false, + "first_name": "John", + "username": "john_doe" + }, + "chat": { + "id": 333, + "first_name": "John", + "username": "john_doe", + "type": "private" + }, + "date": 1640001112, + "text": "Hello, bot!" + } + } + ] + }` + + client := prepareHttpClient(t) + defer client.ctrl.Finish() + expectGetMe(t, client) + + client.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "getUpdates") + require.True(t, valid) + return newOKResponse(updateResp), nil + }) + + bot, err := NewBotAPIWithClient(TestToken, APIEndpoint, client) + require.NoError(t, err) u := NewUpdate(0) - - _, err := bot.GetUpdates(u) - - if err != nil { - t.Error(err) - } + _, err = bot.GetUpdates(u) + require.NoError(t, err) } func TestSendWithMessage(t *testing.T) { - bot, _ := getBot(t) + client := prepareHttpClient(t) + defer client.ctrl.Finish() + + responseBody := `{ + "ok": true, + "result": { + "message_id": 123, + "from": { + "id": 123456789, + "is_bot": true, + "first_name": "MyBot", + "username": "my_bot" + }, + "chat": { + "id": 987654321, + "first_name": "John", + "username": "john_doe", + "type": "private" + }, + "date": 1640001112, + "text": "Hello, John!" + } + }` + + expectGetMe(t, client) + + client.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "sendMessage") + require.True(t, valid) + return newOKResponse(responseBody), nil + }) + + bot, err := NewBotAPIWithClient(TestToken, APIEndpoint, client) + require.NoError(t, err) msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg.ParseMode = ModeMarkdown - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } + _, err = bot.Send(msg) + require.NoError(t, err) } func TestSendWithMessageReply(t *testing.T) { - bot, _ := getBot(t) + client := prepareHttpClient(t) + defer client.ctrl.Finish() + + responseBody := `{ + "ok": true, + "result": { + "message_id": 123, + "from": { + "id": 123456789, + "is_bot": true, + "first_name": "MyBot", + "username": "my_bot" + }, + "chat": { + "id": 987654321, + "first_name": "John", + "username": "john_doe", + "type": "private" + }, + "date": 1640001112, + "text": "Hello, John!" + } + }` + + expectGetMe(t, client) + + client.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "sendMessage") + require.True(t, valid) + return newOKResponse(responseBody), nil + }) + + bot, err := NewBotAPIWithClient(TestToken, APIEndpoint, client) + require.NoError(t, err) msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg.ReplyParameters.MessageID = ReplyToMessageID - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithMessageForward(t *testing.T) { - bot, _ := getBot(t) - - msg := NewForward(ChatID, ChatID, ReplyToMessageID) - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } + _, err = bot.Send(msg) + require.NoError(t, err) } func TestCopyMessage(t *testing.T) { - bot, _ := getBot(t) + client := prepareHttpClient(t) + defer client.ctrl.Finish() + expectGetMe(t, client) + + responseBody := `{ + "ok": true, + "result": { + "message_id": 123, + "from": { + "id": 123456789, + "is_bot": true, + "first_name": "MyBot", + "username": "my_bot" + }, + "chat": { + "id": 987654321, + "first_name": "John", + "username": "john_doe", + "type": "private" + }, + "date": 1640001112, + "text": "Hello, John!" + } + }` + + client.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "sendMessage") + require.True(t, valid) + return newOKResponse(responseBody), nil + }) + + client.EXPECT(). + Do(gomock.Any()). + DoAndReturn(func(req *http.Request) (*http.Response, error) { + valid := isRequestValid(req, TestToken, "copyMessage") + require.True(t, valid) + return newOKResponse(responseBody), nil + }) + + bot, err := NewBotAPIWithClient(TestToken, APIEndpoint, client) + require.NoError(t, err) msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") message, err := bot.Send(msg) - if err != nil { - t.Error(err) - } + require.NoError(t, err) copyMessageConfig := NewCopyMessage(SupergroupChatID, message.Chat.ID, message.MessageID) messageID, err := bot.CopyMessage(copyMessageConfig) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if messageID.MessageID == message.MessageID { - t.Error("copied message ID was the same as original message") - } + require.Equal(t, messageID.MessageID, message.MessageID) } -func TestSendWithNewPhoto(t *testing.T) { - bot, _ := getBot(t) - - msg := NewPhoto(ChatID, FilePath("tests/image.jpg")) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewPhotoWithFileBytes(t *testing.T) { - bot, _ := getBot(t) - - data, _ := os.ReadFile("tests/image.jpg") - b := FileBytes{Name: "image.jpg", Bytes: data} - - msg := NewPhoto(ChatID, b) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewPhotoWithFileReader(t *testing.T) { - bot, _ := getBot(t) - - f, _ := os.Open("tests/image.jpg") - reader := FileReader{Name: "image.jpg", Reader: f} - - msg := NewPhoto(ChatID, reader) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewPhotoReply(t *testing.T) { - bot, _ := getBot(t) - - msg := NewPhoto(ChatID, FilePath("tests/image.jpg")) - msg.ReplyParameters.MessageID = ReplyToMessageID - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendNewPhotoToChannel(t *testing.T) { - bot, _ := getBot(t) - - msg := NewPhotoToChannel(Channel, FilePath("tests/image.jpg")) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - t.Fail() - } -} - -func TestSendNewPhotoToChannelFileBytes(t *testing.T) { - bot, _ := getBot(t) - - data, _ := os.ReadFile("tests/image.jpg") - b := FileBytes{Name: "image.jpg", Bytes: data} - - msg := NewPhotoToChannel(Channel, b) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - t.Fail() - } -} - -func TestSendNewPhotoToChannelFileReader(t *testing.T) { - bot, _ := getBot(t) - - f, _ := os.Open("tests/image.jpg") - reader := FileReader{Name: "image.jpg", Reader: f} - - msg := NewPhotoToChannel(Channel, reader) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - t.Fail() - } -} - -func TestSendWithExistingPhoto(t *testing.T) { - bot, _ := getBot(t) - - msg := NewPhoto(ChatID, FileID(ExistingPhotoFileID)) - msg.Caption = "Test" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewDocument(t *testing.T) { - bot, _ := getBot(t) - - msg := NewDocument(ChatID, FilePath("tests/image.jpg")) - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewDocumentAndThumb(t *testing.T) { - bot, _ := getBot(t) - - msg := NewDocument(ChatID, FilePath("tests/voice.ogg")) - msg.Thumb = FilePath("tests/image.jpg") - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingDocument(t *testing.T) { - bot, _ := getBot(t) - - msg := NewDocument(ChatID, FileID(ExistingDocumentFileID)) - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewAudio(t *testing.T) { - bot, _ := getBot(t) - - msg := NewAudio(ChatID, FilePath("tests/audio.mp3")) - msg.Title = "TEST" - msg.Duration = 10 - msg.Performer = "TEST" - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingAudio(t *testing.T) { - bot, _ := getBot(t) - - msg := NewAudio(ChatID, FileID(ExistingAudioFileID)) - msg.Title = "TEST" - msg.Duration = 10 - msg.Performer = "TEST" - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewVoice(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVoice(ChatID, FilePath("tests/voice.ogg")) - msg.Duration = 10 - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingVoice(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVoice(ChatID, FileID(ExistingVoiceFileID)) - msg.Duration = 10 - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithContact(t *testing.T) { - bot, _ := getBot(t) - - contact := NewContact(ChatID, "5551234567", "Test") - - if _, err := bot.Send(contact); err != nil { - t.Error(err) - } -} - -func TestSendWithLocation(t *testing.T) { - bot, _ := getBot(t) - - _, err := bot.Send(NewLocation(ChatID, 40, 40)) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithVenue(t *testing.T) { - bot, _ := getBot(t) - - venue := NewVenue(ChatID, "A Test Location", "123 Test Street", 40, 40) - - if _, err := bot.Send(venue); err != nil { - t.Error(err) - } -} - -func TestSendWithNewVideo(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVideo(ChatID, FilePath("tests/video.mp4")) - msg.Duration = 10 - msg.Caption = "TEST" - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingVideo(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVideo(ChatID, FileID(ExistingVideoFileID)) - msg.Duration = 10 - msg.Caption = "TEST" - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewVideoNote(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVideoNote(ChatID, 240, FilePath("tests/videonote.mp4")) - msg.Duration = 10 - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingVideoNote(t *testing.T) { - bot, _ := getBot(t) - - msg := NewVideoNote(ChatID, 240, FileID(ExistingVideoNoteFileID)) - msg.Duration = 10 - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewSticker(t *testing.T) { - bot, _ := getBot(t) - - msg := NewSticker(ChatID, FilePath("tests/image.jpg")) - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingSticker(t *testing.T) { - bot, _ := getBot(t) - - msg := NewSticker(ChatID, FileID(ExistingStickerFileID)) - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { - bot, _ := getBot(t) - - msg := NewSticker(ChatID, FilePath("tests/image.jpg")) - msg.ReplyMarkup = ReplyKeyboardRemove{ - RemoveKeyboard: true, - Selective: false, - } - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) { - bot, _ := getBot(t) - - msg := NewSticker(ChatID, FileID(ExistingStickerFileID)) - msg.ReplyMarkup = ReplyKeyboardRemove{ - RemoveKeyboard: true, - Selective: false, - } - - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - } -} - -func TestSendWithDice(t *testing.T) { - bot, _ := getBot(t) - - msg := NewDice(ChatID) - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - t.Fail() - } - -} - -func TestSendWithDiceWithEmoji(t *testing.T) { - bot, _ := getBot(t) - - msg := NewDiceWithEmoji(ChatID, "🏀") - _, err := bot.Send(msg) - - if err != nil { - t.Error(err) - t.Fail() - } - -} - -func TestGetFile(t *testing.T) { - bot, _ := getBot(t) - - file := FileConfig{ - FileID: ExistingPhotoFileID, - } - - _, err := bot.GetFile(file) - - if err != nil { - t.Error(err) - } -} - -func TestSendChatConfig(t *testing.T) { - bot, _ := getBot(t) - - _, err := bot.Request(NewChatAction(ChatID, ChatTyping)) - - if err != nil { - t.Error(err) - } -} - -// TODO: identify why this isn't working -// func TestSendEditMessage(t *testing.T) { -// bot, _ := getBot(t) - -// msg, err := bot.Send(NewMessage(ChatID, "Testing editing.")) -// if err != nil { -// t.Error(err) -// } - -// edit := EditMessageTextConfig{ -// BaseEdit: BaseEdit{ -// ChatID: ChatID, -// MessageID: msg.MessageID, -// }, -// Text: "Updated text.", -// } - -// _, err = bot.Send(edit) -// if err != nil { -// t.Error(err) -// } -// } - -func TestGetUserProfilePhotos(t *testing.T) { - bot, _ := getBot(t) - - _, err := bot.GetUserProfilePhotos(NewUserProfilePhotos(ChatID)) - if err != nil { - t.Error(err) - } -} - -func TestSetWebhookWithCert(t *testing.T) { - bot, _ := getBot(t) - - time.Sleep(time.Second * 2) - - bot.Request(DeleteWebhookConfig{}) - - wh, err := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, FilePath("tests/cert.pem")) - - if err != nil { - t.Error(err) - } - _, err = bot.Request(wh) - - if err != nil { - t.Error(err) - } - - _, err = bot.GetWebhookInfo() - - if err != nil { - t.Error(err) - } - - bot.Request(DeleteWebhookConfig{}) -} - -func TestSetWebhookWithoutCert(t *testing.T) { - bot, _ := getBot(t) - - time.Sleep(time.Second * 2) - - bot.Request(DeleteWebhookConfig{}) - - wh, err := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token) - - if err != nil { - t.Error(err) - } - - _, err = bot.Request(wh) - - if err != nil { - t.Error(err) - } - - info, err := bot.GetWebhookInfo() - - if err != nil { - t.Error(err) - } - if info.MaxConnections == 0 { - t.Errorf("Expected maximum connections to be greater than 0") - } - if info.LastErrorDate != 0 { - t.Errorf("failed to set webhook: %s", info.LastErrorMessage) - } - - bot.Request(DeleteWebhookConfig{}) -} - -func TestSendWithMediaGroupPhotoVideo(t *testing.T) { - bot, _ := getBot(t) - - cfg := NewMediaGroup(ChatID, []interface{}{ - NewInputMediaPhoto(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")), - NewInputMediaPhoto(FilePath("tests/image.jpg")), - NewInputMediaVideo(FilePath("tests/video.mp4")), - }) - - messages, err := bot.SendMediaGroup(cfg) - if err != nil { - t.Error(err) - } - - if messages == nil { - t.Error("No received messages") - } - - if len(messages) != len(cfg.Media) { - t.Errorf("Different number of messages: %d", len(messages)) - } -} - -func TestSendWithMediaGroupDocument(t *testing.T) { - bot, _ := getBot(t) - - cfg := NewMediaGroup(ChatID, []interface{}{ - NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")), - NewInputMediaDocument(FilePath("tests/image.jpg")), - }) - - messages, err := bot.SendMediaGroup(cfg) - if err != nil { - t.Error(err) - } - - if messages == nil { - t.Error("No received messages") - } - - if len(messages) != len(cfg.Media) { - t.Errorf("Different number of messages: %d", len(messages)) - } -} - -func TestSendWithMediaGroupAudio(t *testing.T) { - bot, _ := getBot(t) - - cfg := NewMediaGroup(ChatID, []interface{}{ - NewInputMediaAudio(FilePath("tests/audio.mp3")), - NewInputMediaAudio(FilePath("tests/audio.mp3")), - }) - - messages, err := bot.SendMediaGroup(cfg) - if err != nil { - t.Error(err) - } - - if messages == nil { - t.Error("No received messages") - } - - if len(messages) != len(cfg.Media) { - t.Errorf("Different number of messages: %d", len(messages)) - } -} - -func ExampleNewBotAPI() { - bot, err := NewBotAPI("MyAwesomeBotToken") - if err != nil { - panic(err) - } - - bot.Debug = true - - log.Printf("Authorized on account %s", bot.Self.UserName) - - u := NewUpdate(0) - u.Timeout = 60 - - updates := bot.GetUpdatesChan(u) - - // Optional: wait for updates and clear them if you don't want to handle - // a large backlog of old messages - time.Sleep(time.Millisecond * 500) - updates.Clear() - - for update := range updates { - if update.Message == nil { - continue - } - - log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) - - msg := NewMessage(update.Message.Chat.ID, update.Message.Text) - msg.ReplyParameters.MessageID = update.Message.MessageID - - bot.Send(msg) - } -} - -func ExampleNewWebhook() { - bot, err := NewBotAPI("MyAwesomeBotToken") - if err != nil { - panic(err) - } - - bot.Debug = true - - log.Printf("Authorized on account %s", bot.Self.UserName) - - wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) - - if err != nil { - panic(err) - } - - _, err = bot.Request(wh) - - if err != nil { - panic(err) - } - - info, err := bot.GetWebhookInfo() - - if err != nil { - panic(err) - } - - if info.LastErrorDate != 0 { - log.Printf("failed to set webhook: %s", info.LastErrorMessage) - } - - updates := bot.ListenForWebhook("/" + bot.Token) - go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) - - for update := range updates { - log.Printf("%+v\n", update) - } -} - -func ExampleWebhookHandler() { - bot, err := NewBotAPI("MyAwesomeBotToken") - if err != nil { - panic(err) - } - - bot.Debug = true - - log.Printf("Authorized on account %s", bot.Self.UserName) - - wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) - - if err != nil { - panic(err) - } - - _, err = bot.Request(wh) - if err != nil { - panic(err) - } - info, err := bot.GetWebhookInfo() - if err != nil { - panic(err) - } - if info.LastErrorDate != 0 { - log.Printf("[Telegram callback failed]%s", info.LastErrorMessage) - } - - http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) { - update, err := bot.HandleUpdate(r) - if err != nil { - log.Printf("%+v\n", err.Error()) - } else { - log.Printf("%+v\n", *update) - } - }) - - go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) -} - -func ExampleInlineConfig() { - bot, err := NewBotAPI("MyAwesomeBotToken") // create new bot - if err != nil { - panic(err) - } - - log.Printf("Authorized on account %s", bot.Self.UserName) - - u := NewUpdate(0) - u.Timeout = 60 - - updates := bot.GetUpdatesChan(u) - - for update := range updates { - if update.InlineQuery == nil { // if no inline query, ignore it - continue - } - - article := NewInlineQueryResultArticle(update.InlineQuery.ID, "Echo", update.InlineQuery.Query) - article.Description = update.InlineQuery.Query - - inlineConf := InlineConfig{ - InlineQueryID: update.InlineQuery.ID, - IsPersonal: true, - CacheTime: 0, - Results: []interface{}{article}, - } - - if _, err := bot.Request(inlineConf); err != nil { - log.Println(err) - } - } -} - -func TestDeleteMessage(t *testing.T) { - bot, _ := getBot(t) - - msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") - msg.ParseMode = ModeMarkdown - message, _ := bot.Send(msg) - - deleteMessageConfig := DeleteMessageConfig{ - BaseChatMessage: BaseChatMessage{ - ChatConfig: ChatConfig{ - ChatID: message.Chat.ID, - }, - MessageID: message.MessageID, - }, - } - _, err := bot.Request(deleteMessageConfig) - - if err != nil { - t.Error(err) - } -} - -func TestPinChatMessage(t *testing.T) { - bot, _ := getBot(t) - - msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") - msg.ParseMode = ModeMarkdown - message, _ := bot.Send(msg) - - pinChatMessageConfig := PinChatMessageConfig{ - BaseChatMessage: BaseChatMessage{ - ChatConfig: ChatConfig{ - ChatID: ChatID, - }, - MessageID: message.MessageID, - }, - DisableNotification: false, - } - _, err := bot.Request(pinChatMessageConfig) - - if err != nil { - t.Error(err) - } -} - -func TestUnpinChatMessage(t *testing.T) { - bot, _ := getBot(t) - - msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") - msg.ParseMode = ModeMarkdown - message, _ := bot.Send(msg) - - // We need pin message to unpin something - pinChatMessageConfig := PinChatMessageConfig{ - BaseChatMessage: BaseChatMessage{ - ChatConfig: ChatConfig{ - ChatID: message.Chat.ID, - }, - MessageID: message.MessageID, - }, - DisableNotification: false, - } - - if _, err := bot.Request(pinChatMessageConfig); err != nil { - t.Error(err) - } - - unpinChatMessageConfig := UnpinChatMessageConfig{ - BaseChatMessage: BaseChatMessage{ - ChatConfig: ChatConfig{ - ChatID: message.Chat.ID, - }, - MessageID: message.MessageID, - }, - } - - if _, err := bot.Request(unpinChatMessageConfig); err != nil { - t.Error(err) - } -} - -func TestUnpinAllChatMessages(t *testing.T) { - bot, _ := getBot(t) - - msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") - msg.ParseMode = ModeMarkdown - message, _ := bot.Send(msg) - - pinChatMessageConfig := PinChatMessageConfig{ - BaseChatMessage: BaseChatMessage{ - ChatConfig: ChatConfig{ - ChatID: message.Chat.ID, - }, - MessageID: message.MessageID, - }, - DisableNotification: true, - } - - if _, err := bot.Request(pinChatMessageConfig); err != nil { - t.Error(err) - } - - unpinAllChatMessagesConfig := UnpinAllChatMessagesConfig{ - ChatConfig: ChatConfig{ChatID: message.Chat.ID}, - } - - if _, err := bot.Request(unpinAllChatMessagesConfig); err != nil { - t.Error(err) - } -} - -func TestPolls(t *testing.T) { - bot, _ := getBot(t) - - poll := NewPoll(SupergroupChatID, "Are polls working?", "Yes", "No") - - msg, err := bot.Send(poll) - if err != nil { - t.Error(err) - } - - result, err := bot.StopPoll(NewStopPoll(SupergroupChatID, msg.MessageID)) - if err != nil { - t.Error(err) - } - - if result.Question != "Are polls working?" { - t.Error("Poll question did not match") - } - - if !result.IsClosed { - t.Error("Poll did not end") - } - - if result.Options[0].Text != "Yes" || result.Options[0].VoterCount != 0 || result.Options[1].Text != "No" || result.Options[1].VoterCount != 0 { - t.Error("Poll options were incorrect") - } -} - -func TestSendDice(t *testing.T) { - bot, _ := getBot(t) - - dice := NewDice(ChatID) - - msg, err := bot.Send(dice) - if err != nil { - t.Error("Unable to send dice roll") - } - - if msg.Dice == nil { - t.Error("Dice roll was not received") - } -} - -func TestCommands(t *testing.T) { - bot, _ := getBot(t) - - setCommands := NewSetMyCommands(BotCommand{ - Command: "test", - Description: "a test command", - }) - - if _, err := bot.Request(setCommands); err != nil { - t.Error("Unable to set commands") - } - - commands, err := bot.GetMyCommands() - if err != nil { - t.Error("Unable to get commands") - } - - if len(commands) != 1 { - t.Error("Incorrect number of commands returned") - } - - if commands[0].Command != "test" || commands[0].Description != "a test command" { - t.Error("Commands were incorrectly set") - } - - setCommands = NewSetMyCommandsWithScope(NewBotCommandScopeAllPrivateChats(), BotCommand{ - Command: "private", - Description: "a private command", - }) - - if _, err := bot.Request(setCommands); err != nil { - t.Error("Unable to set commands") - } - - commands, err = bot.GetMyCommandsWithConfig(NewGetMyCommandsWithScope(NewBotCommandScopeAllPrivateChats())) - if err != nil { - t.Error("Unable to get commands") - } - - if len(commands) != 1 { - t.Error("Incorrect number of commands returned") - } - - if commands[0].Command != "private" || commands[0].Description != "a private command" { - t.Error("Commands were incorrectly set") - } -} - -// TODO: figure out why test is failing -// -// func TestEditMessageMedia(t *testing.T) { -// bot, _ := getBot(t) - -// msg := NewPhoto(ChatID, "tests/image.jpg") -// msg.Caption = "Test" -// m, err := bot.Send(msg) - -// if err != nil { -// t.Error(err) -// } - -// edit := EditMessageMediaConfig{ -// BaseEdit: BaseEdit{ -// ChatID: ChatID, -// MessageID: m.MessageID, -// }, -// Media: NewInputMediaVideo(FilePath("tests/video.mp4")), -// } - -// _, err = bot.Request(edit) -// if err != nil { -// t.Error(err) -// } -// } - func TestPrepareInputMediaForParams(t *testing.T) { - media := []interface{}{ - NewInputMediaPhoto(FilePath("tests/image.jpg")), + media := []any{ + NewInputMediaPhoto(FilePath("./image.jpg")), NewInputMediaVideo(FileID("test")), } prepared := prepareInputMediaForParams(media) - if media[0].(InputMediaPhoto).Media != FilePath("tests/image.jpg") { + if media[0].(InputMediaPhoto).Media != FilePath("./image.jpg") { t.Error("Original media was changed") } diff --git a/example_bot.go b/example_bot.go new file mode 100644 index 0000000..b268f26 --- /dev/null +++ b/example_bot.go @@ -0,0 +1,123 @@ +package tgbotapi + +import ( + "fmt" + "net/http" + "time" +) + +func ExampleNewBotAPI() { + bot, err := NewBotAPI("MyAwesomeBotToken") + if err != nil { + panic(err) + } + + bot.Debug = true + + fmt.Printf("Authorized on account %s", bot.Self.UserName) + u := NewUpdate(0) + u.Timeout = 60 + + updates := bot.GetUpdatesChan(u) + + // Optional: wait for updates and clear them if you don't want to handle + // a large backlog of old messages + time.Sleep(time.Millisecond * 500) + updates.Clear() + + for update := range updates { + if update.Message == nil { + continue + } + + fmt.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) + + msg := NewMessage(update.Message.Chat.ID, update.Message.Text) + msg.ReplyParameters.MessageID = update.Message.MessageID + + _, err := bot.Send(msg) + if err != nil { + panic(err) + } + } +} + +func ExampleNewWebhook() { + bot, err := NewBotAPI("MyAwesomeBotToken") + if err != nil { + panic(err) + } + + bot.Debug = true + + fmt.Printf("Authorized on account %s", bot.Self.UserName) + + wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) + + if err != nil { + panic(err) + } + + _, err = bot.Request(wh) + + if err != nil { + panic(err) + } + + info, err := bot.GetWebhookInfo() + + if err != nil { + panic(err) + } + + if info.LastErrorDate != 0 { + fmt.Printf("failed to set webhook: %s", info.LastErrorMessage) + } + + updates := bot.ListenForWebhook("/" + bot.Token) + go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) + + for update := range updates { + fmt.Printf("%+v\n", update) + } +} + +func ExampleWebhookHandler() { + bot, err := NewBotAPI("MyAwesomeBotToken") + if err != nil { + panic(err) + } + + bot.Debug = true + + fmt.Printf("Authorized on account %s", bot.Self.UserName) + + wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) + + if err != nil { + panic(err) + } + + _, err = bot.Request(wh) + if err != nil { + panic(err) + } + info, err := bot.GetWebhookInfo() + if err != nil { + panic(err) + } + if info.LastErrorDate != 0 { + fmt.Printf("[Telegram callback failed]%s", info.LastErrorMessage) + } + + http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) { + update, err := bot.HandleUpdate(r) + if err != nil { + fmt.Printf("%+v\n", err.Error()) + } else { + fmt.Printf("%+v\n", *update) + } + }) + + go http.ListenAndServeTLS("0.0.0.0:8443", "./tests/cert.pem", "./tests/key.pem", nil) +} diff --git a/go.mod b/go.mod index 167e5e4..50de042 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,15 @@ -module github.com/go-telegram-bot-api/telegram-bot-api/v5 +module github.com/eli-l/telegram-bot-api/v5 -go 1.16 +go 1.21 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.17.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index e69de29..47c3a79 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mockgen.go b/mockgen.go new file mode 100644 index 0000000..ba1b254 --- /dev/null +++ b/mockgen.go @@ -0,0 +1,3 @@ +package tgbotapi + +//go:generate mockgen -destination=bot_mock_test.go -package=tgbotapi -source=bot.go diff --git a/tests/bot_integration_test.go b/tests/bot_integration_test.go new file mode 100644 index 0000000..629a0dd --- /dev/null +++ b/tests/bot_integration_test.go @@ -0,0 +1,874 @@ +package tests + +import ( + "fmt" + "os" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/eli-l/telegram-bot-api/v5" +) + +var ( + TestToken string + Channel string + ChatID int64 + SupergroupChatID int64 + ReplyToMessageID int + Debug = false +) + +func init() { + var err error + TestToken = os.Getenv("TELEGRAM_TESTBOT_TOKEN") + SupergroupChatID, err = strconv.ParseInt(os.Getenv("TELEGRAM_SUPERGROUP_CHAT_ID"), 10, 64) + if err != nil { + panic(err) + } + Channel = os.Getenv("TELEGRAM_CHANNEL") + ChatID, err = strconv.ParseInt(os.Getenv("TELEGRAM_CHAT_ID"), 10, 64) + if err != nil { + panic(err) + } + ReplyToMessageID, err = strconv.Atoi(os.Getenv("TELEGRAM_REPLY_TO_MESSAGE_ID")) + if err != nil { + panic(err) + } +} + +type testLogger struct { + t *testing.T +} + +func (t testLogger) Println(v ...interface{}) { + t.t.Log(v...) +} + +func (t testLogger) Printf(format string, v ...interface{}) { + t.t.Logf(format, v...) +} + +func getBot(t *testing.T) (*tgbotapi.BotAPI, error) { + bot, err := tgbotapi.NewBotAPI(TestToken) + require.NoError(t, err) + bot.Debug = Debug + + logger := testLogger{t} + err = tgbotapi.SetLogger(logger) + require.NoError(t, err) + + return bot, err +} + +func TestNewBotAPI_notoken(t *testing.T) { + bot, err := tgbotapi.NewBotAPI("") + require.Error(t, err) + require.Nil(t, bot) +} + +func TestGetUpdates(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + u := tgbotapi.NewUpdate(0) + + up, err := bot.GetUpdates(u) + require.NoError(t, err) + require.NotNil(t, up) +} + +func TestSendWithMessage(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg.ParseMode = tgbotapi.ModeMarkdown + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithMessageReply(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg.ReplyParameters.MessageID = ReplyToMessageID + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithMessageForward(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewForward(ChatID, ChatID, ReplyToMessageID) + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestCopyMessage(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + message, err := bot.Send(msg) + require.NoError(t, err) + + copyMessageConfig := tgbotapi.NewCopyMessage(SupergroupChatID, message.Chat.ID, message.MessageID) + messageID, err := bot.CopyMessage(copyMessageConfig) + require.NoError(t, err) + require.NotEqual(t, message.MessageID, messageID.MessageID) +} + +func TestSendWithNewPhoto(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewPhoto(ChatID, tgbotapi.FilePath("./image.jpg")) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithNewPhotoWithFileBytes(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + data, err := os.ReadFile("./image.jpg") + require.NoError(t, err) + b := tgbotapi.FileBytes{Name: "image.jpg", Bytes: data} + + msg := tgbotapi.NewPhoto(ChatID, b) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithNewPhotoWithFileReader(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + f, err := os.Open("./image.jpg") + require.NoError(t, err) + reader := tgbotapi.FileReader{Name: "image.jpg", Reader: f} + + msg := tgbotapi.NewPhoto(ChatID, reader) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithNewPhotoReply(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewPhoto(ChatID, tgbotapi.FilePath("./image.jpg")) + msg.ReplyParameters.MessageID = ReplyToMessageID + + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendNewPhotoToChannel(t *testing.T) { + var photoID string + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send photo to channel", func(t *testing.T) { + msg := tgbotapi.NewPhotoToChannel(Channel, tgbotapi.FilePath("./image.jpg")) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + pl := len(m.Photo) > 0 + require.True(t, pl) + photoID = m.Photo[0].FileID + }) + + t.Run("send photo to channel with existing photo", func(t *testing.T) { + require.NotEmpty(t, photoID) + msg := tgbotapi.NewPhoto(ChatID, tgbotapi.FileID(photoID)) + msg.Caption = "Test existing" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotEmpty(t, m) + }) + +} + +func TestSendNewPhotoToChannelFileBytes(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + data, err := os.ReadFile("./image.jpg") + require.NoError(t, err) + b := tgbotapi.FileBytes{Name: "image.jpg", Bytes: data} + + msg := tgbotapi.NewPhotoToChannel(Channel, b) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendNewPhotoToChannelFileReader(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + f, err := os.Open("./image.jpg") + require.NoError(t, err) + reader := tgbotapi.FileReader{Name: "image.jpg", Reader: f} + + msg := tgbotapi.NewPhotoToChannel(Channel, reader) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + +} + +func TestSendWithNewDocument(t *testing.T) { + var FileID string + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new document", func(t *testing.T) { + msg := tgbotapi.NewDocument(ChatID, tgbotapi.FilePath("./image.jpg")) + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Document.FileID) + FileID = m.Document.FileID + }) + + t.Run("get document", func(t *testing.T) { + f, err := bot.GetFile(tgbotapi.FileConfig{FileID: FileID}) + require.NoError(t, err) + require.NotNil(t, f) + require.Equal(t, FileID, f.FileID) + }) + +} + +func TestSendWithNewDocumentAndThumb(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new document and thumb", func(t *testing.T) { + msg := tgbotapi.NewDocument(ChatID, tgbotapi.FilePath("./voice.ogg")) + msg.Thumb = tgbotapi.FilePath("./image.jpg") + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Document.FileID) + FileID = m.Document.FileID + }) + + t.Run("send existing document", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewDocument(ChatID, tgbotapi.FileID(FileID)) + m, err := bot.Send(msg) + require.NotNil(t, m) + require.NoError(t, err) + }) + +} + +func TestSendWithAudio(t *testing.T) { + var FileID string + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new audio file", func(t *testing.T) { + + msg := tgbotapi.NewAudio(ChatID, tgbotapi.FilePath("./audio.mp3")) + msg.Title = "TEST" + msg.Duration = 10 + msg.Performer = "TEST" + m, err := bot.Send(msg) + require.NotNil(t, m) + require.NoError(t, err) + require.NotEmpty(t, m.Audio.FileID) + FileID = m.Audio.FileID + }) + + t.Run("send existing audio file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msgExist := tgbotapi.NewAudio(ChatID, tgbotapi.FileID(FileID)) + msgExist.Title = "TEST EXIST" + msgExist.Duration = 10 + msgExist.Performer = "TEST EXIST" + m, err := bot.Send(msgExist) + require.NotNil(t, m) + require.NoError(t, err) + }) +} + +func TestSendWithNewVoice(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new voice file", func(t *testing.T) { + msg := tgbotapi.NewVoice(ChatID, tgbotapi.FilePath("./voice.ogg")) + msg.Duration = 10 + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Voice.FileID) + FileID = m.Voice.FileID + }) + + t.Run("send existing voice file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewVoice(ChatID, tgbotapi.FileID(FileID)) + msg.Duration = 10 + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + }) +} + +func TestSendWithContact(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + contact := tgbotapi.NewContact(ChatID, "5551234567", "Test") + + m, err := bot.Send(contact) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithLocation(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + m, err := bot.Send(tgbotapi.NewLocation(ChatID, 40, 40)) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithVenue(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + venue := tgbotapi.NewVenue(ChatID, "A Test Location", "123 Test Street", 40, 40) + + m, err := bot.Send(venue) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithNewVideo(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new video file", func(t *testing.T) { + msg := tgbotapi.NewVideo(ChatID, tgbotapi.FilePath("./video.mp4")) + msg.Duration = 10 + msg.Caption = "TEST" + + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Video.FileID) + FileID = m.Video.FileID + }) + + t.Run("send existing video file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewVideo(ChatID, tgbotapi.FileID(FileID)) + msg.Duration = 10 + msg.Caption = "TEST EXIST" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + }) +} + +func TestSendWithNewVideoNote(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new video note file", func(t *testing.T) { + msg := tgbotapi.NewVideoNote(ChatID, 240, tgbotapi.FilePath("./videonote.mp4")) + msg.Duration = 10 + + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotEmpty(t, m.VideoNote.FileID) + FileID = m.VideoNote.FileID + }) + + t.Run("send existing video note file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewVideoNote(ChatID, 240, tgbotapi.FileID(FileID)) + msg.Duration = 10 + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + }) +} + +func TestSendWithNewSticker(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new sticker file", func(t *testing.T) { + msg := tgbotapi.NewSticker(ChatID, tgbotapi.FilePath("./image.jpg")) + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Sticker.FileID) + FileID = m.Sticker.FileID + }) + + t.Run("send existing sticker file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewSticker(ChatID, tgbotapi.FileID(FileID)) + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + }) +} + +func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { + var FileID string + + bot, err := getBot(t) + require.NoError(t, err) + + t.Run("send new sticker file", func(t *testing.T) { + msg := tgbotapi.NewSticker(ChatID, tgbotapi.FilePath("./image.jpg")) + msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ + RemoveKeyboard: true, + Selective: false, + } + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + require.NotEmpty(t, m.Sticker.FileID) + FileID = m.Sticker.FileID + }) + + t.Run("send existing sticker file", func(t *testing.T) { + require.NotEmpty(t, FileID) + msg := tgbotapi.NewSticker(ChatID, tgbotapi.FileID(FileID)) + msg.ReplyMarkup = tgbotapi.ReplyKeyboardRemove{ + RemoveKeyboard: true, + Selective: false, + } + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + }) +} + +func TestSendWithDice(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewDice(ChatID) + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendWithDiceWithEmoji(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewDiceWithEmoji(ChatID, "🏀") + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendChatConfig(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + m, err := bot.Request(tgbotapi.NewChatAction(ChatID, tgbotapi.ChatTyping)) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestSendEditMessage(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg, err := bot.Send(tgbotapi.NewMessage(ChatID, "Testing editing.")) + require.NoError(t, err) + require.NotNil(t, msg) + + edit := tgbotapi.EditMessageTextConfig{ + BaseEdit: tgbotapi.BaseEdit{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + MessageID: msg.MessageID, + ChatConfig: tgbotapi.ChatConfig{ + ChatID: ChatID, + }, + }, + }, + Text: "Updated text.", + } + + m, err := bot.Send(edit) + require.NoError(t, err) + require.NotNil(t, m) +} + +func TestGetUserProfilePhotos(t *testing.T) { + bot, _ := getBot(t) + + _, err := bot.GetUserProfilePhotos(tgbotapi.NewUserProfilePhotos(ChatID)) + require.NoError(t, err) +} + +func TestSetWebhookWithCert(t *testing.T) { + bot, _ := getBot(t) + + time.Sleep(time.Second * 2) + + bot.Request(tgbotapi.DeleteWebhookConfig{}) + + wh, err := tgbotapi.NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, tgbotapi.FilePath("./cert.pem")) + require.NoError(t, err) + _, err = bot.Request(wh) + require.NoError(t, err) + + _, err = bot.GetWebhookInfo() + require.NoError(t, err) + + _, err = bot.Request(tgbotapi.DeleteWebhookConfig{}) + require.NoError(t, err) +} + +func TestSetWebhookWithoutCert(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + time.Sleep(time.Second * 2) + + bot.Request(tgbotapi.DeleteWebhookConfig{}) + + wh, err := tgbotapi.NewWebhook("https://example.com/tgbotapi-test/" + bot.Token) + require.NoError(t, err) + + _, err = bot.Request(wh) + require.NoError(t, err) + + info, err := bot.GetWebhookInfo() + require.NoError(t, err) + require.NotEqual(t, 0, info.MaxConnections) + require.Equal(t, 0, info.LastErrorDate) + _, err = bot.Request(tgbotapi.DeleteWebhookConfig{}) + require.NoError(t, err) +} + +func TestSendWithMediaGroupPhotoVideo(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ + tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")), + tgbotapi.NewInputMediaPhoto(tgbotapi.FilePath("./image.jpg")), + tgbotapi.NewInputMediaVideo(tgbotapi.FilePath("./video.mp4")), + }) + + messages, err := bot.SendMediaGroup(cfg) + require.NoError(t, err) + require.NotNil(t, messages) + require.Equal(t, len(cfg.Media), len(messages)) +} + +func TestSendWithMediaGroupDocument(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ + tgbotapi.NewInputMediaDocument(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg")), + tgbotapi.NewInputMediaDocument(tgbotapi.FilePath("./image.jpg")), + }) + + messages, err := bot.SendMediaGroup(cfg) + require.NoError(t, err) + require.NotNil(t, messages) + require.Equal(t, len(cfg.Media), len(messages)) +} + +func TestSendWithMediaGroupAudio(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + cfg := tgbotapi.NewMediaGroup(ChatID, []interface{}{ + tgbotapi.NewInputMediaAudio(tgbotapi.FilePath("./audio.mp3")), + tgbotapi.NewInputMediaAudio(tgbotapi.FilePath("./audio.mp3")), + }) + + messages, err := bot.SendMediaGroup(cfg) + require.NoError(t, err) + require.NotNil(t, messages) + require.Equal(t, len(cfg.Media), len(messages)) +} + +func ExampleInlineConfig() { + bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken") // create new bot + if err != nil { + panic(err) + } + + fmt.Printf("Authorized on account %s", bot.Self.UserName) + + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 + + updates := bot.GetUpdatesChan(u) + + for update := range updates { + if update.InlineQuery == nil { // if no inline query, ignore it + continue + } + + article := tgbotapi.NewInlineQueryResultArticle(update.InlineQuery.ID, "Echo", update.InlineQuery.Query) + article.Description = update.InlineQuery.Query + + inlineConf := tgbotapi.InlineConfig{ + InlineQueryID: update.InlineQuery.ID, + IsPersonal: true, + CacheTime: 0, + Results: []interface{}{article}, + } + + if _, err := bot.Request(inlineConf); err != nil { + fmt.Println(err) + } + } +} + +func TestDeleteMessage(t *testing.T) { + bot, _ := getBot(t) + + msg := tgbotapi.NewMessage(ChatID, "A test message from the test library in telegram-bot-api") + msg.ParseMode = tgbotapi.ModeMarkdown + message, _ := bot.Send(msg) + + deleteMessageConfig := tgbotapi.DeleteMessageConfig{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + ChatConfig: tgbotapi.ChatConfig{ + ChatID: message.Chat.ID, + }, + MessageID: message.MessageID, + }, + } + _, err := bot.Request(deleteMessageConfig) + + if err != nil { + t.Error(err) + } +} + +func TestPinChatMessage(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") + msg.ParseMode = tgbotapi.ModeMarkdown + message, err := bot.Send(msg) + require.NoError(t, err) + + pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + ChatConfig: tgbotapi.ChatConfig{ + ChatID: ChatID, + }, + MessageID: message.MessageID, + }, + DisableNotification: false, + } + res, err := bot.Request(pinChatMessageConfig) + require.NoError(t, err) + require.NotNil(t, res) +} + +func TestUnpinChatMessage(t *testing.T) { + bot, _ := getBot(t) + + msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") + msg.ParseMode = tgbotapi.ModeMarkdown + message, err := bot.Send(msg) + require.NoError(t, err) + + // We need pin message to unpin something + pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + ChatConfig: tgbotapi.ChatConfig{ + ChatID: message.Chat.ID, + }, + MessageID: message.MessageID, + }, + DisableNotification: false, + } + + _, err = bot.Request(pinChatMessageConfig) + require.NoError(t, err) + + unpinChatMessageConfig := tgbotapi.UnpinChatMessageConfig{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + ChatConfig: tgbotapi.ChatConfig{ + ChatID: message.Chat.ID, + }, + MessageID: message.MessageID, + }, + } + + _, err = bot.Request(unpinChatMessageConfig) + require.NoError(t, err) +} + +func TestUnpinAllChatMessages(t *testing.T) { + bot, _ := getBot(t) + + msg := tgbotapi.NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") + msg.ParseMode = tgbotapi.ModeMarkdown + message, _ := bot.Send(msg) + + pinChatMessageConfig := tgbotapi.PinChatMessageConfig{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + ChatConfig: tgbotapi.ChatConfig{ + ChatID: message.Chat.ID, + }, + MessageID: message.MessageID, + }, + DisableNotification: true, + } + + _, err := bot.Request(pinChatMessageConfig) + require.NoError(t, err) + + unpinAllChatMessagesConfig := tgbotapi.UnpinAllChatMessagesConfig{ + ChatConfig: tgbotapi.ChatConfig{ChatID: message.Chat.ID}, + } + + _, err = bot.Request(unpinAllChatMessagesConfig) + require.NoError(t, err) +} + +func TestPolls(t *testing.T) { + bot, _ := getBot(t) + + poll := tgbotapi.NewPoll(SupergroupChatID, "Are polls working?", "Yes", "No") + + msg, err := bot.Send(poll) + if err != nil { + t.Error(err) + } + + result, err := bot.StopPoll(tgbotapi.NewStopPoll(SupergroupChatID, msg.MessageID)) + if err != nil { + t.Error(err) + } + + if result.Question != "Are polls working?" { + t.Error("Poll question did not match") + } + + if !result.IsClosed { + t.Error("Poll did not end") + } + + if result.Options[0].Text != "Yes" || result.Options[0].VoterCount != 0 || result.Options[1].Text != "No" || result.Options[1].VoterCount != 0 { + t.Error("Poll options were incorrect") + } +} + +func TestSendDice(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + dice := tgbotapi.NewDice(ChatID) + + msg, err := bot.Send(dice) + require.NoError(t, err) + require.NotNil(t, msg.Dice) +} + +func TestCommands(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + setCommands := tgbotapi.NewSetMyCommands(tgbotapi.BotCommand{ + Command: "test", + Description: "a test command", + }) + + _, err = bot.Request(setCommands) + require.NoError(t, err) + + commands, err := bot.GetMyCommands() + require.NoError(t, err) + require.Equal(t, 1, len(commands)) + require.Equal(t, "test", commands[0].Command) + require.Equal(t, "a test command", commands[0].Description) + + setCommands = tgbotapi.NewSetMyCommandsWithScope(tgbotapi.NewBotCommandScopeAllPrivateChats(), tgbotapi.BotCommand{ + Command: "private", + Description: "a private command", + }) + + _, err = bot.Request(setCommands) + require.NoError(t, err) + + commands, err = bot.GetMyCommandsWithConfig(tgbotapi.NewGetMyCommandsWithScope(tgbotapi.NewBotCommandScopeAllPrivateChats())) + require.NoError(t, err) + require.Equal(t, 1, len(commands)) + require.Equal(t, "private", commands[0].Command) + require.Equal(t, "a private command", commands[0].Description) +} + +// TODO: figure out why test is failing +func TestEditMessageMedia(t *testing.T) { + bot, err := getBot(t) + require.NoError(t, err) + + msg := tgbotapi.NewPhoto(ChatID, tgbotapi.FilePath("./image.jpg")) + msg.Caption = "Test" + m, err := bot.Send(msg) + require.NoError(t, err) + require.NotNil(t, m) + + edit := tgbotapi.EditMessageMediaConfig{ + BaseEdit: tgbotapi.BaseEdit{ + BaseChatMessage: tgbotapi.BaseChatMessage{ + MessageID: m.MessageID, + ChatConfig: tgbotapi.ChatConfig{ + ChatID: ChatID, + }, + }, + }, + Media: tgbotapi.NewInputMediaVideo(tgbotapi.FilePath("./video.mp4")), + } + + res, err := bot.Request(edit) + require.NoError(t, err) + require.NotNil(t, res) +}