bot_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. package tgbotapi
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/url"
  9. "sync"
  10. "testing"
  11. "github.com/stretchr/testify/assert"
  12. )
  13. const (
  14. APIToken = "123abc"
  15. BotID int64 = 12375639
  16. )
  17. type TestLogger struct {
  18. t *testing.T
  19. }
  20. func (logger TestLogger) Println(v ...interface{}) {
  21. logger.t.Log(v...)
  22. }
  23. func (logger TestLogger) Printf(format string, v ...interface{}) {
  24. logger.t.Logf(format, v...)
  25. }
  26. func getServerURL(tsURL string) string {
  27. return tsURL + "/bot%s/%s"
  28. }
  29. // BotRequestFile is a file that should be included as part of a request to the
  30. // Telegram Bot API.
  31. type BotRequestFile struct {
  32. // Name is the name of the field.
  33. Name string
  34. // Data is the contents of the file.
  35. Data []byte
  36. }
  37. // BotRequest is information about a mocked request to the Telegram Bot API.
  38. type BotRequest struct {
  39. // Endpoint is the method that should be called on the Bot API.
  40. Endpoint string
  41. // RequestData is the data that is sent to the endpoint.
  42. RequestData url.Values
  43. // Files are files that should exist as part of the request. If this is nil,
  44. // it is assumed the request should be treated as x-www-form-urlencoded,
  45. // otherwise it will be treated as multipart data.
  46. Files []BotRequestFile
  47. // ResponseData is the response body.
  48. ResponseData string
  49. }
  50. var getMeRequest = BotRequest{
  51. Endpoint: "getMe",
  52. ResponseData: fmt.Sprintf(`{"ok": true, "result": {"id": %d, "is_bot": true, "first_name": "Test", "username": "test_bot", "can_join_groups": true, "can_read_all_group_messages": false, "supports_inline_queries": true}}`, BotID),
  53. }
  54. func assertBotRequests(t *testing.T, token string, botID int64, requests []BotRequest) (*httptest.Server, *BotAPI) {
  55. logger := TestLogger{t}
  56. SetLogger(logger)
  57. currentRequest := 0
  58. lock := sync.Mutex{}
  59. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  60. lock.Lock()
  61. defer lock.Unlock()
  62. t.Logf("Got request to %s", r.URL.String())
  63. assert.Greater(t, len(requests), currentRequest, "got more requests than were configured")
  64. nextRequest := requests[currentRequest]
  65. expected := fmt.Sprintf("/bot%s/%s", token, nextRequest.Endpoint)
  66. t.Logf("Expecting request to %s", expected)
  67. url := r.URL.String()
  68. t.Logf("Got request to %s", url)
  69. switch url {
  70. case expected:
  71. if nextRequest.Files == nil {
  72. assert.NoError(t, r.ParseForm())
  73. assert.Equal(t, len(nextRequest.RequestData), len(r.Form), "request must have same number of values")
  74. for expectedValueKey, expectedValue := range nextRequest.RequestData {
  75. t.Logf("Checking if %s contains %v", expectedValueKey, expectedValue)
  76. assert.Len(t, expectedValue, 1, "each expected key should only have one value")
  77. foundValue := r.Form[expectedValueKey]
  78. t.Logf("Form contains %+v", foundValue)
  79. assert.Len(t, foundValue, 1, "each key should have exactly one value")
  80. assert.Equal(t, expectedValue[0], foundValue[0])
  81. }
  82. } else if nextRequest.Files != nil {
  83. assert.NoError(t, r.ParseMultipartForm(1024*1024*50), "request must be valid multipart form")
  84. assert.Equal(t, len(nextRequest.RequestData), len(r.MultipartForm.Value), "request must have correct number of values")
  85. assert.Equal(t, len(nextRequest.Files), len(r.MultipartForm.File), "request must have correct number of files")
  86. for expectedValueKey, expectedValue := range nextRequest.RequestData {
  87. t.Logf("Checking if %s contains %v", expectedValueKey, expectedValue)
  88. assert.Len(t, expectedValue, 1, "each expected key should only have one value")
  89. foundValue := r.MultipartForm.Value[expectedValueKey]
  90. assert.Len(t, foundValue, 1, "each key should have exactly one value")
  91. assert.Equal(t, expectedValue[0], foundValue[0])
  92. }
  93. for _, expectedFile := range nextRequest.Files {
  94. t.Logf("Checking if %s is set correctly", expectedFile.Name)
  95. foundFile := r.MultipartForm.File[expectedFile.Name]
  96. assert.Len(t, foundFile, 1, "each file must appear exactly once")
  97. f, err := foundFile[0].Open()
  98. assert.NoError(t, err, "should be able to open file")
  99. data, err := io.ReadAll(f)
  100. assert.NoError(t, err, "should be able to read file")
  101. assert.Equal(t, expectedFile.Data, data, "uploaded file should be the same")
  102. }
  103. }
  104. w.Header().Set("Content-Type", "application/json")
  105. fmt.Fprint(w, nextRequest.ResponseData)
  106. default:
  107. t.Errorf("expected request to %s but got request to %s", expected, url)
  108. }
  109. currentRequest += 1
  110. if currentRequest > len(requests) {
  111. t.Errorf("expected %d requests but got %d", len(requests), currentRequest)
  112. }
  113. }))
  114. bot, err := NewBotAPIWithAPIEndpoint(APIToken, getServerURL(ts.URL))
  115. if err != nil {
  116. t.Error(err)
  117. }
  118. bot.Debug = true
  119. return ts, bot
  120. }
  121. func TestNewBotAPI(t *testing.T) {
  122. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest})
  123. defer ts.Close()
  124. assert.Equal(t, BotID, bot.Self.ID, "Bot ID should be expected ID")
  125. }
  126. func TestMakeRequest(t *testing.T) {
  127. values := url.Values{}
  128. values.Set("param_name", "param_value")
  129. goodReq := BotRequest{
  130. Endpoint: "testEndpoint",
  131. RequestData: values,
  132. ResponseData: `{"ok": true, "result": true}`,
  133. }
  134. badReq := BotRequest{
  135. Endpoint: "testEndpoint",
  136. RequestData: values,
  137. ResponseData: `{"ok": false, "description": "msg", "error_code": 12343}`,
  138. }
  139. badReqWithParams := BotRequest{
  140. Endpoint: "testEndpoint",
  141. RequestData: values,
  142. ResponseData: `{"ok": false, "description": "msg", "error_code": 12343, "parameters": {"retry_after": 52, "migrate_to_chat_id": 245}}`,
  143. }
  144. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, goodReq, badReq, badReqWithParams})
  145. defer ts.Close()
  146. params := make(Params)
  147. params["param_name"] = "param_value"
  148. resp, err := bot.MakeRequest("testEndpoint", params)
  149. assert.NoError(t, err, "bot should be able to make request without errors")
  150. assert.Equal(t, &APIResponse{
  151. Ok: true,
  152. Result: []byte("true"),
  153. }, resp)
  154. resp, err = bot.MakeRequest("testEndpoint", params)
  155. assert.Error(t, err)
  156. assert.Equal(t, &Error{
  157. Code: 12343,
  158. Message: "msg",
  159. }, err)
  160. assert.NotNil(t, resp)
  161. resp, err = bot.MakeRequest("testEndpoint", params)
  162. assert.Error(t, err)
  163. assert.Equal(t, &Error{
  164. Code: 12343,
  165. Message: "msg",
  166. ResponseParameters: ResponseParameters{
  167. MigrateToChatID: 245,
  168. RetryAfter: 52,
  169. },
  170. }, err)
  171. assert.NotNil(t, resp)
  172. assert.Equal(t, &ResponseParameters{
  173. MigrateToChatID: 245,
  174. RetryAfter: 52,
  175. }, resp.Parameters)
  176. }
  177. func TestUploadFilesBasic(t *testing.T) {
  178. values := url.Values{}
  179. values.Set("param_name", "param_value")
  180. goodReq := BotRequest{
  181. Endpoint: "testEndpoint",
  182. RequestData: values,
  183. Files: []BotRequestFile{{
  184. Name: "file1",
  185. Data: []byte("data1"),
  186. }},
  187. ResponseData: `{"ok": true, "result": true}`,
  188. }
  189. badReq := BotRequest{
  190. Endpoint: "testEndpoint",
  191. RequestData: values,
  192. Files: []BotRequestFile{{
  193. Name: "file1",
  194. Data: []byte("data1"),
  195. }},
  196. ResponseData: `{"ok": false, "description": "msg", "error_code": 12343}`,
  197. }
  198. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, goodReq, badReq})
  199. defer ts.Close()
  200. params := make(Params)
  201. params["param_name"] = "param_value"
  202. files := []RequestFile{{
  203. Name: "file1",
  204. Data: FileBytes{Name: "file1", Bytes: []byte("data1")},
  205. }}
  206. resp, err := bot.UploadFiles("testEndpoint", params, files)
  207. assert.NoError(t, err, "bot should be able to make request without errors")
  208. assert.Equal(t, &APIResponse{
  209. Ok: true,
  210. Result: []byte("true"),
  211. }, resp)
  212. resp, err = bot.UploadFiles("testEndpoint", params, files)
  213. assert.Error(t, err)
  214. assert.Equal(t, &Error{
  215. Code: 12343,
  216. Message: "msg",
  217. }, err)
  218. assert.NotNil(t, resp)
  219. }
  220. func TestUploadFilesAllTypes(t *testing.T) {
  221. values := url.Values{}
  222. values.Set("param_name", "param_value")
  223. values.Set("file-url", "url")
  224. values.Set("file-id", "id")
  225. values.Set("file-attach", "attach")
  226. req := BotRequest{
  227. Endpoint: "uploadFiles",
  228. RequestData: values,
  229. Files: []BotRequestFile{
  230. {Name: "file-bytes", Data: []byte("byte-data")},
  231. {Name: "file-reader", Data: []byte("reader-data")},
  232. {Name: "file-path", Data: []byte("path-data\n")},
  233. },
  234. ResponseData: `{"ok": true, "result": true}`,
  235. }
  236. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, req})
  237. defer ts.Close()
  238. params := make(Params)
  239. params["param_name"] = "param_value"
  240. files := []RequestFile{{
  241. Name: "file-bytes",
  242. Data: FileBytes{
  243. Name: "file-bytes-name",
  244. Bytes: []byte("byte-data"),
  245. },
  246. }, {
  247. Name: "file-reader",
  248. Data: FileReader{
  249. Name: "file-reader-name",
  250. Reader: bytes.NewReader([]byte("reader-data")),
  251. },
  252. }, {
  253. Name: "file-path",
  254. Data: FilePath("tests/file-path"),
  255. }, {
  256. Name: "file-url",
  257. Data: FileURL("url"),
  258. }, {
  259. Name: "file-id",
  260. Data: FileID("id"),
  261. }, {
  262. Name: "file-attach",
  263. Data: fileAttach("attach"),
  264. }}
  265. resp, err := bot.UploadFiles("uploadFiles", params, files)
  266. assert.NoError(t, err, "bot should be able to make request without errors")
  267. assert.Equal(t, &APIResponse{
  268. Ok: true,
  269. Result: []byte("true"),
  270. }, resp)
  271. }
  272. func TestGetMe(t *testing.T) {
  273. goodReq := BotRequest{
  274. Endpoint: "getMe",
  275. ResponseData: `{"ok": true, "result": {}}`,
  276. }
  277. badReq := BotRequest{
  278. Endpoint: "getMe",
  279. ResponseData: `{"ok": false}`,
  280. }
  281. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, goodReq, badReq})
  282. defer ts.Close()
  283. _, err := bot.GetMe()
  284. assert.NoError(t, err)
  285. _, err = bot.GetMe()
  286. assert.Error(t, err)
  287. }
  288. func TestIsMessageToMe(t *testing.T) {
  289. testCases := []struct {
  290. Text string
  291. Caption string
  292. Entities []MessageEntity
  293. CaptionEntities []MessageEntity
  294. IsMention bool
  295. }{{
  296. Text: "asdf",
  297. Entities: []MessageEntity{},
  298. IsMention: false,
  299. }, {
  300. Text: "@test_bot",
  301. Entities: []MessageEntity{{
  302. Type: "mention",
  303. Offset: 0,
  304. Length: 9,
  305. }},
  306. IsMention: true,
  307. }, {
  308. Text: "prefix @test_bot suffix",
  309. Entities: []MessageEntity{{
  310. Type: "mention",
  311. Offset: 7,
  312. Length: 9,
  313. }},
  314. IsMention: true,
  315. }, {
  316. Text: "prefix @test_bot suffix",
  317. Entities: []MessageEntity{{
  318. Type: "link",
  319. Offset: 7,
  320. Length: 9,
  321. }},
  322. IsMention: false,
  323. }, {
  324. Text: "prefix @test_bot suffix",
  325. Entities: []MessageEntity{{
  326. Type: "link",
  327. Offset: 0,
  328. Length: 6,
  329. }, {
  330. Type: "mention",
  331. Offset: 7,
  332. Length: 9,
  333. }},
  334. IsMention: true,
  335. }, {
  336. Text: "prefix @test_bot suffix",
  337. IsMention: false,
  338. }, {
  339. Caption: "prefix @test_bot suffix",
  340. CaptionEntities: []MessageEntity{{
  341. Type: "mention",
  342. Offset: 7,
  343. Length: 9,
  344. }},
  345. IsMention: true,
  346. }}
  347. bot := BotAPI{
  348. Self: User{
  349. UserName: "test_bot",
  350. },
  351. }
  352. for _, test := range testCases {
  353. assert.Equal(t, test.IsMention, bot.IsMessageToMe(Message{
  354. Text: test.Text,
  355. Caption: test.Caption,
  356. Entities: test.Entities,
  357. CaptionEntities: test.CaptionEntities,
  358. }))
  359. }
  360. }
  361. func TestRequest(t *testing.T) {
  362. values1 := url.Values{}
  363. values1.Set("chat_id", "12356")
  364. values1.Set("thumb", "url")
  365. req1 := BotRequest{
  366. Endpoint: "sendPhoto",
  367. RequestData: values1,
  368. Files: []BotRequestFile{{
  369. Name: "photo",
  370. Data: []byte("photo-data"),
  371. }},
  372. ResponseData: `{"ok": true, "result": {"message_id": "asdf"}}`,
  373. }
  374. values2 := url.Values{}
  375. values2.Set("chat_id", "12356")
  376. values2.Set("photo", "id")
  377. req2 := BotRequest{
  378. Endpoint: "sendPhoto",
  379. RequestData: values2,
  380. ResponseData: `{"ok": true, "result": {"message_id": 123}}`,
  381. }
  382. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, req1, req2})
  383. defer ts.Close()
  384. resp, err := bot.Request(PhotoConfig{
  385. BaseFile: BaseFile{
  386. BaseChat: BaseChat{
  387. ChatID: 12356,
  388. },
  389. File: FileBytes{
  390. Name: "photo.jpg",
  391. Bytes: []byte("photo-data"),
  392. },
  393. },
  394. Thumb: FileURL("url"),
  395. })
  396. assert.NoError(t, err)
  397. assert.NotNil(t, resp)
  398. resp, err = bot.Request(PhotoConfig{
  399. BaseFile: BaseFile{
  400. BaseChat: BaseChat{
  401. ChatID: 12356,
  402. },
  403. File: FileID("id"),
  404. },
  405. })
  406. assert.NoError(t, err)
  407. assert.NotNil(t, resp)
  408. }
  409. func TestSend(t *testing.T) {
  410. values := url.Values{}
  411. values.Set("chat_id", "12356")
  412. values.Set("text", "text")
  413. req1 := BotRequest{
  414. Endpoint: "sendMessage",
  415. RequestData: values,
  416. ResponseData: `{"ok": true, "result": {"message_id": "asdf"}}`,
  417. }
  418. req2 := BotRequest{
  419. Endpoint: "sendMessage",
  420. RequestData: values,
  421. ResponseData: `{"ok": true, "result": {"message_id": 123}}`,
  422. }
  423. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, req1, req2})
  424. defer ts.Close()
  425. msg, err := bot.Send(MessageConfig{
  426. BaseChat: BaseChat{
  427. ChatID: 12356,
  428. },
  429. Text: "text",
  430. })
  431. assert.Error(t, err)
  432. assert.Empty(t, msg)
  433. msg, err = bot.Send(MessageConfig{
  434. BaseChat: BaseChat{
  435. ChatID: 12356,
  436. },
  437. Text: "text",
  438. })
  439. assert.NoError(t, err)
  440. assert.Equal(t, Message{MessageID: 123}, msg)
  441. }
  442. func TestSendMediaGroup(t *testing.T) {
  443. values := url.Values{}
  444. values.Set("chat_id", "125")
  445. values.Set("media", `[{"type":"photo","media":"attach://file-0"},{"type":"photo","media":"file-id"}]`)
  446. req1 := BotRequest{
  447. Endpoint: "sendMediaGroup",
  448. RequestData: values,
  449. Files: []BotRequestFile{{
  450. Name: "file-0",
  451. Data: []byte("path-data\n"),
  452. }},
  453. ResponseData: `{"ok": true, "result": [{"message_id": 123643}, {"message_id": 53452}]}`,
  454. }
  455. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, req1})
  456. defer ts.Close()
  457. group, err := bot.SendMediaGroup(MediaGroupConfig{})
  458. assert.ErrorIs(t, err, ErrEmptyMediaGroup)
  459. assert.Nil(t, group)
  460. group, err = bot.SendMediaGroup(MediaGroupConfig{
  461. ChatID: 125,
  462. Media: []interface{}{
  463. NewInputMediaPhoto(FilePath("tests/file-path")),
  464. NewInputMediaPhoto(FileID("file-id")),
  465. },
  466. })
  467. assert.NoError(t, err)
  468. assert.Len(t, group, 2)
  469. }
  470. func TestGetUserProfilePhotos(t *testing.T) {
  471. values := url.Values{}
  472. values.Set("user_id", "5426")
  473. req1 := BotRequest{
  474. Endpoint: "getUserProfilePhotos",
  475. RequestData: values,
  476. ResponseData: `{"ok": true, "result": {"total_count": 24, "photos": [[{"file_id": "abc123-file"}]]}}`,
  477. }
  478. ts, bot := assertBotRequests(t, APIToken, BotID, []BotRequest{getMeRequest, req1})
  479. defer ts.Close()
  480. profilePhotos, err := bot.GetUserProfilePhotos(UserProfilePhotosConfig{
  481. UserID: 5426,
  482. })
  483. assert.NoError(t, err)
  484. assert.Equal(t, UserProfilePhotos{
  485. TotalCount: 24,
  486. Photos: [][]PhotoSize{
  487. {{
  488. FileID: "abc123-file",
  489. }},
  490. },
  491. }, profilePhotos)
  492. }