github.com/line/line-bot-sdk-go/v7@v7.21.0/linebot/send_message_test.go (about) 1 // Copyright 2016 LINE Corporation 2 // 3 // LINE Corporation licenses this file to you under the Apache License, 4 // version 2.0 (the "License"); you may not use this file except in compliance 5 // with the License. You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 package linebot 16 17 import ( 18 "context" 19 "io" 20 "net/http" 21 "net/http/httptest" 22 "reflect" 23 "strconv" 24 "testing" 25 "time" 26 ) 27 28 func TestPushMessages(t *testing.T) { 29 toUserID := "U0cc15697597f61dd8b01cea8b027050e" 30 type want struct { 31 RequestBody []byte 32 Response *BasicResponse 33 Error error 34 } 35 testCases := []struct { 36 Label string 37 Messages []SendingMessage 38 Response []byte 39 ResponseCode int 40 Want want 41 }{ 42 { 43 Label: "A text message", 44 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 45 ResponseCode: 200, 46 Response: []byte(`{}`), 47 Want: want{ 48 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 49 Response: &BasicResponse{}, 50 }, 51 }, 52 { 53 Label: "A image message", 54 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 55 ResponseCode: 200, 56 Response: []byte(`{}`), 57 Want: want{ 58 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 59 Response: &BasicResponse{}, 60 }, 61 }, 62 { 63 Label: "A video message", 64 Messages: []SendingMessage{NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg")}, 65 ResponseCode: 200, 66 Response: []byte(`{}`), 67 Want: want{ 68 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 69 Response: &BasicResponse{}, 70 }, 71 }, 72 { 73 Label: "A audio message", 74 Messages: []SendingMessage{NewAudioMessage("https://example.com/original.m4a", 1000)}, 75 ResponseCode: 200, 76 Response: []byte(`{}`), 77 Want: want{ 78 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000}]}` + "\n"), 79 Response: &BasicResponse{}, 80 }, 81 }, 82 { 83 Label: "A location message", 84 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 85 ResponseCode: 200, 86 Response: []byte(`{}`), 87 Want: want{ 88 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 89 Response: &BasicResponse{}, 90 }, 91 }, 92 { 93 Label: "A sticker message", 94 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 95 ResponseCode: 200, 96 Response: []byte(`{}`), 97 Want: want{ 98 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 99 Response: &BasicResponse{}, 100 }, 101 }, 102 { 103 Label: "A buttons template message", 104 Messages: []SendingMessage{ 105 NewTemplateMessage( 106 "this is a buttons template", 107 NewButtonsTemplate( 108 "https://example.com/bot/images/image.jpg", 109 "Menu", 110 "Please select", 111 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 112 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 113 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionCloseRichMenu, ""), 114 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 115 NewURIAction("View detail", "https://example.com/page/123"), 116 ), 117 ), 118 }, 119 ResponseCode: 200, 120 Response: []byte(`{}`), 121 Want: want{ 122 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","title":"Menu","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"closeRichMenu"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 123 Response: &BasicResponse{}, 124 }, 125 }, 126 { 127 Label: "A buttons template message with datetimepicker action", 128 Messages: []SendingMessage{ 129 NewTemplateMessage( 130 "this is a buttons template", 131 NewButtonsTemplate( 132 "https://example.com/bot/images/image.jpg", 133 "Menu", 134 "Please select a date, time or datetime", 135 NewDatetimePickerAction("Date", "action=sel&only=date", "date", "2017-09-01", "2017-09-03", ""), 136 NewDatetimePickerAction("Time", "action=sel&only=time", "time", "", "23:59", "00:00"), 137 NewDatetimePickerAction("DateTime", "action=sel", "datetime", "2017-09-01T12:00", "", ""), 138 ), 139 ), 140 }, 141 ResponseCode: 200, 142 Response: []byte(`{}`), 143 Want: want{ 144 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","title":"Menu","text":"Please select a date, time or datetime","actions":[{"type":"datetimepicker","label":"Date","data":"action=sel\u0026only=date","mode":"date","initial":"2017-09-01","max":"2017-09-03"},{"type":"datetimepicker","label":"Time","data":"action=sel\u0026only=time","mode":"time","max":"23:59","min":"00:00"},{"type":"datetimepicker","label":"DateTime","data":"action=sel","mode":"datetime","initial":"2017-09-01T12:00"}]}}]}` + "\n"), 145 Response: &BasicResponse{}, 146 }, 147 }, 148 { 149 Label: "A buttons template message without thumbnailImageURL", 150 Messages: []SendingMessage{ 151 NewTemplateMessage( 152 "this is a buttons template", 153 NewButtonsTemplate( 154 "", 155 "Menu", 156 "Please select", 157 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 158 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 159 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenRichMenu, ""), 160 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 161 NewURIAction("View detail", "https://example.com/page/123"), 162 ), 163 ), 164 }, 165 ResponseCode: 200, 166 Response: []byte(`{}`), 167 Want: want{ 168 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","title":"Menu","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openRichMenu"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 169 Response: &BasicResponse{}, 170 }, 171 }, 172 { 173 Label: "A buttons template message without title", 174 Messages: []SendingMessage{ 175 NewTemplateMessage( 176 "this is a buttons template", 177 NewButtonsTemplate( 178 "https://example.com/bot/images/image.jpg", 179 "", 180 "Please select", 181 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 182 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 183 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenVoice, ""), 184 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 185 NewURIAction("View detail", "https://example.com/page/123"), 186 ), 187 ), 188 }, 189 ResponseCode: 200, 190 Response: []byte(`{}`), 191 Want: want{ 192 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openVoice"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 193 Response: &BasicResponse{}, 194 }, 195 }, 196 { 197 Label: "A buttons template message without title, with image options", 198 Messages: []SendingMessage{ 199 NewTemplateMessage( 200 "this is a buttons template", 201 NewButtonsTemplate( 202 "https://example.com/bot/images/image.jpg", 203 "", 204 "Please select", 205 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 206 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 207 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, ""), 208 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 209 NewURIAction("View detail", "https://example.com/page/123"), 210 ).WithImageOptions("rectangle", "cover", "#FFFFFF"), 211 ), 212 }, 213 ResponseCode: 200, 214 Response: []byte(`{}`), 215 Want: want{ 216 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","imageAspectRatio":"rectangle","imageSize":"cover","imageBackgroundColor":"#FFFFFF","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 217 Response: &BasicResponse{}, 218 }, 219 }, 220 { 221 Label: "A buttons template message without thumbnailImageURL and title", 222 Messages: []SendingMessage{ 223 NewTemplateMessage( 224 "this is a buttons template", 225 NewButtonsTemplate( 226 "", 227 "", 228 "Please select", 229 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 230 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 231 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 232 NewURIAction("View detail", "https://example.com/page/123"), 233 ), 234 ), 235 }, 236 ResponseCode: 200, 237 Response: []byte(`{}`), 238 Want: want{ 239 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 240 Response: &BasicResponse{}, 241 }, 242 }, 243 { 244 Label: "A confirm template message", 245 Messages: []SendingMessage{ 246 NewTemplateMessage( 247 "this is a confirm template", 248 NewConfirmTemplate( 249 "Are you sure?", 250 NewMessageAction("Yes", "yes"), 251 NewMessageAction("No", "no"), 252 ), 253 ), 254 }, 255 ResponseCode: 200, 256 Response: []byte(`{}`), 257 Want: want{ 258 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a confirm template","template":{"type":"confirm","text":"Are you sure?","actions":[{"type":"message","label":"Yes","text":"yes"},{"type":"message","label":"No","text":"no"}]}}]}` + "\n"), 259 Response: &BasicResponse{}, 260 }, 261 }, 262 { 263 Label: "A carousel template message", 264 Messages: []SendingMessage{ 265 NewTemplateMessage( 266 "this is a carousel template", 267 NewCarouselTemplate( 268 NewCarouselColumn( 269 "https://example.com/bot/images/item1.jpg", 270 "this is menu", 271 "description", 272 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 273 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 274 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 275 NewURIAction("View detail", "https://example.com/page/111"), 276 ), 277 ), 278 ), 279 }, 280 ResponseCode: 200, 281 Response: []byte(`{}`), 282 Want: want{ 283 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a carousel template","template":{"type":"carousel","columns":[{"thumbnailImageUrl":"https://example.com/bot/images/item1.jpg","title":"this is menu","text":"description","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=111"},{"type":"postback","label":"Add to cart","data":"action=add\u0026itemid=111"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}]}]}}]}` + "\n"), 284 Response: &BasicResponse{}, 285 }, 286 }, 287 { 288 Label: "A carousel template message, with new image options", 289 Messages: []SendingMessage{ 290 NewTemplateMessage( 291 "this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor", 292 NewCarouselTemplate( 293 NewCarouselColumn( 294 "https://example.com/bot/images/item1.jpg", 295 "this is menu", 296 "description", 297 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 298 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 299 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 300 NewURIAction("View detail", "https://example.com/page/111"), 301 ).WithImageOptions("#FFFFFF"), 302 ).WithImageOptions("rectangle", "cover"), 303 ), 304 }, 305 ResponseCode: 200, 306 Response: []byte(`{}`), 307 Want: want{ 308 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor","template":{"type":"carousel","columns":[{"thumbnailImageUrl":"https://example.com/bot/images/item1.jpg","imageBackgroundColor":"#FFFFFF","title":"this is menu","text":"description","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=111"},{"type":"postback","label":"Add to cart","data":"action=add\u0026itemid=111"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}]}],"imageAspectRatio":"rectangle","imageSize":"cover"}}]}` + "\n"), 309 Response: &BasicResponse{}, 310 }, 311 }, 312 { 313 Label: "A imagecarousel template message", 314 Messages: []SendingMessage{ 315 NewTemplateMessage( 316 "this is a image carousel template", 317 NewImageCarouselTemplate( 318 NewImageCarouselColumn( 319 "https://example.com/bot/images/item1.jpg", 320 NewURIAction("View detail", "https://example.com/page/111"), 321 ), 322 ), 323 ), 324 }, 325 ResponseCode: 200, 326 Response: []byte(`{}`), 327 Want: want{ 328 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"template","altText":"this is a image carousel template","template":{"type":"image_carousel","columns":[{"imageUrl":"https://example.com/bot/images/item1.jpg","action":{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}}]}}]}` + "\n"), 329 Response: &BasicResponse{}, 330 }, 331 }, 332 { 333 Label: "A imagemap message", 334 Messages: []SendingMessage{ 335 NewImagemapMessage( 336 "https://example.com/bot/images/rm001", 337 "this is an imagemap", 338 ImagemapBaseSize{1040, 1040}, 339 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 340 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 341 ), 342 }, 343 ResponseCode: 200, 344 Response: []byte(`{}`), 345 Want: want{ 346 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}]}]}` + "\n"), 347 Response: &BasicResponse{}, 348 }, 349 }, 350 { 351 Label: "A imagemap messages with video 1", 352 Messages: []SendingMessage{ 353 NewImagemapMessage( 354 "https://example.com/bot/images/rm001", 355 "this is an imagemap with video", 356 ImagemapBaseSize{1040, 1040}, 357 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 358 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 359 ).WithVideo(&ImagemapVideo{ 360 OriginalContentURL: "https://example.com/original.mp4", 361 PreviewImageURL: "https://example.com/preview.jpg", 362 Area: ImagemapArea{10, 10, 100, 200}, 363 }), 364 }, 365 ResponseCode: 200, 366 Response: []byte(`{}`), 367 Want: want{ 368 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap with video","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}],"video":{"originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","area":{"x":10,"y":10,"width":100,"height":200}}}]}` + "\n"), 369 Response: &BasicResponse{}, 370 }, 371 }, 372 { 373 Label: "A imagemap messages with video 2", 374 Messages: []SendingMessage{ 375 NewImagemapMessage( 376 "https://example.com/bot/images/rm001", 377 "this is an imagemap with video and external link", 378 ImagemapBaseSize{1040, 1040}, 379 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 380 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 381 ).WithVideo(&ImagemapVideo{ 382 OriginalContentURL: "https://example.com/original.mp4", 383 PreviewImageURL: "https://example.com/preview.jpg", 384 Area: ImagemapArea{10, 10, 100, 200}, 385 ExternalLink: &ImagemapVideoExternalLink{ 386 LinkURI: "https://example.com/", 387 Label: "external link", 388 }, 389 }), 390 }, 391 ResponseCode: 200, 392 Response: []byte(`{}`), 393 Want: want{ 394 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap with video and external link","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}],"video":{"originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","area":{"x":10,"y":10,"width":100,"height":200},"externalLink":{"linkUri":"https://example.com/","label":"external link"}}}]}` + "\n"), 395 Response: &BasicResponse{}, 396 }, 397 }, 398 { 399 Label: "A flex message", 400 Messages: []SendingMessage{ 401 NewFlexMessage( 402 "this is a flex message", 403 &BubbleContainer{ 404 Type: FlexContainerTypeBubble, 405 Body: &BoxComponent{ 406 Type: FlexComponentTypeBox, 407 Layout: FlexBoxLayoutTypeVertical, 408 Contents: []FlexComponent{ 409 &TextComponent{ 410 Type: FlexComponentTypeText, 411 Text: "hello", 412 }, 413 &TextComponent{ 414 Type: FlexComponentTypeText, 415 Text: "world", 416 Flex: IntPtr(0), 417 }, 418 &SpacerComponent{ 419 Type: FlexComponentTypeSpacer, 420 }, 421 }, 422 }, 423 }, 424 ), 425 }, 426 ResponseCode: 200, 427 Response: []byte(`{}`), 428 Want: want{ 429 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"flex","altText":"this is a flex message","contents":{"type":"bubble","body":{"type":"box","layout":"vertical","contents":[{"type":"text","text":"hello"},{"type":"text","text":"world","flex":0},{"type":"spacer"}]}}}]}` + "\n"), 430 Response: &BasicResponse{}, 431 }, 432 }, 433 { 434 Label: "A text message with quick replies", 435 Messages: []SendingMessage{ 436 NewTextMessage( 437 "Select your favorite food category or send me your location!", 438 ).WithQuickReplies( 439 NewQuickReplyItems( 440 NewQuickReplyButton("https://example.com/sushi.png", NewMessageAction("Sushi", "Sushi")), 441 NewQuickReplyButton("https://example.com/tempura.png", NewMessageAction("Tempura", "Tempura")), 442 NewQuickReplyButton("", NewLocationAction("Send location")), 443 NewQuickReplyButton("", NewCameraAction("Open camera")), 444 NewQuickReplyButton("", NewURIAction("Phone order", "tel:09001234567")), 445 ), 446 ), 447 }, 448 ResponseCode: 200, 449 Response: []byte(`{}`), 450 Want: want{ 451 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Select your favorite food category or send me your location!","quickReply":{"items":[{"type":"action","imageUrl":"https://example.com/sushi.png","action":{"type":"message","label":"Sushi","text":"Sushi"}},{"type":"action","imageUrl":"https://example.com/tempura.png","action":{"type":"message","label":"Tempura","text":"Tempura"}},{"type":"action","action":{"type":"location","label":"Send location"}},{"type":"action","action":{"type":"camera","label":"Open camera"}},{"type":"action","action":{"type":"uri","label":"Phone order","uri":"tel:09001234567"}}]}}]}` + "\n"), 452 Response: &BasicResponse{}, 453 }, 454 }, 455 { 456 Label: "Multiple messages", 457 Messages: []SendingMessage{NewTextMessage("Hello, world1"), NewTextMessage("Hello, world2")}, 458 ResponseCode: 200, 459 Response: []byte(`{}`), 460 Want: want{ 461 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Hello, world1"},{"type":"text","text":"Hello, world2"}]}` + "\n"), 462 Response: &BasicResponse{}, 463 }, 464 }, 465 { 466 Label: "Bad request", 467 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 468 ResponseCode: 400, 469 Response: []byte(`{"message":"Request body has 2 error(s).","details":[{"message":"may not be empty","property":"messages[0].text"},{"message":"may not be empty","property":"messages[1].text"}]}`), 470 Want: want{ 471 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 472 Error: &APIError{ 473 Code: 400, 474 Response: &ErrorResponse{ 475 Message: "Request body has 2 error(s).", 476 Details: []errorResponseDetail{ 477 { 478 Message: "may not be empty", 479 Property: "messages[0].text", 480 }, 481 { 482 Message: "may not be empty", 483 Property: "messages[1].text", 484 }, 485 }, 486 }, 487 }, 488 }, 489 }, 490 } 491 492 var currentTestIdx int 493 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 494 defer r.Body.Close() 495 tc := testCases[currentTestIdx] 496 if r.Method != http.MethodPost { 497 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 498 } 499 if r.URL.Path != APIEndpointPushMessage { 500 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointPushMessage) 501 } 502 body, err := io.ReadAll(r.Body) 503 if err != nil { 504 t.Fatal(err) 505 } 506 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 507 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 508 } 509 w.WriteHeader(tc.ResponseCode) 510 w.Write(tc.Response) 511 })) 512 defer server.Close() 513 514 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 515 defer r.Body.Close() 516 t.Error("Unexpected data API call") 517 w.WriteHeader(404) 518 w.Write([]byte(`{"message":"Not found"}`)) 519 })) 520 defer dataServer.Close() 521 522 client, err := mockClient(server, dataServer) 523 if err != nil { 524 t.Fatal(err) 525 } 526 for i, tc := range testCases { 527 currentTestIdx = i 528 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 529 res, err := client.PushMessage(toUserID, tc.Messages...).Do() 530 if tc.Want.Error != nil { 531 if !reflect.DeepEqual(err, tc.Want.Error) { 532 t.Errorf("Error %v; want %v", err, tc.Want.Error) 533 } 534 } else { 535 if err != nil { 536 t.Error(err) 537 } 538 } 539 if tc.Want.Response != nil { 540 if !reflect.DeepEqual(res, tc.Want.Response) { 541 t.Errorf("Response %v; want %v", res, tc.Want.Response) 542 } 543 } 544 }) 545 } 546 } 547 548 func TestPushMessagesWithContext(t *testing.T) { 549 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 550 defer r.Body.Close() 551 time.Sleep(10 * time.Millisecond) 552 w.Write([]byte("{}")) 553 })) 554 defer server.Close() 555 556 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 557 defer r.Body.Close() 558 t.Error("Unexpected data API call") 559 w.WriteHeader(404) 560 w.Write([]byte(`{"message":"Not found"}`)) 561 })) 562 defer dataServer.Close() 563 564 client, err := mockClient(server, dataServer) 565 if err != nil { 566 t.Fatal(err) 567 } 568 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 569 defer cancel() 570 _, err = client.PushMessage("U0cc15697597f61dd8b01cea8b027050e", NewTextMessage("Hello, world")).WithContext(ctx).Do() 571 expectCtxDeadlineExceed(ctx, err, t) 572 } 573 574 func TestReplyMessages(t *testing.T) { 575 replyToken := "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA" 576 type want struct { 577 RequestBody []byte 578 Response *BasicResponse 579 Error error 580 } 581 testCases := []struct { 582 Label string 583 Messages []SendingMessage 584 Response []byte 585 ResponseCode int 586 Want want 587 }{ 588 { 589 Label: "A text message", 590 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 591 ResponseCode: 200, 592 Response: []byte(`{}`), 593 Want: want{ 594 RequestBody: []byte(`{"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA","messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 595 Response: &BasicResponse{}, 596 }, 597 }, 598 { 599 Label: "A location message", 600 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 601 ResponseCode: 200, 602 Response: []byte(`{}`), 603 Want: want{ 604 RequestBody: []byte(`{"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA","messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 605 Response: &BasicResponse{}, 606 }, 607 }, 608 { 609 Label: "A image message", 610 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 611 ResponseCode: 200, 612 Response: []byte(`{}`), 613 Want: want{ 614 RequestBody: []byte(`{"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA","messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 615 Response: &BasicResponse{}, 616 }, 617 }, 618 { 619 Label: "A sticker message", 620 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 621 ResponseCode: 200, 622 Response: []byte(`{}`), 623 Want: want{ 624 RequestBody: []byte(`{"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA","messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 625 Response: &BasicResponse{}, 626 }, 627 }, 628 { 629 Label: "Bad request", 630 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 631 ResponseCode: 400, 632 Response: []byte(`{"message":"Request body has 2 error(s).","details":[{"message":"may not be empty","property":"messages[0].text"},{"message":"may not be empty","property":"messages[1].text"}]}`), 633 Want: want{ 634 RequestBody: []byte(`{"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA","messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 635 Error: &APIError{ 636 Code: 400, 637 Response: &ErrorResponse{ 638 Message: "Request body has 2 error(s).", 639 Details: []errorResponseDetail{ 640 { 641 Message: "may not be empty", 642 Property: "messages[0].text", 643 }, 644 { 645 Message: "may not be empty", 646 Property: "messages[1].text", 647 }, 648 }, 649 }, 650 }, 651 }, 652 }, 653 } 654 655 var currentTestIdx int 656 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 657 defer r.Body.Close() 658 if r.Method != http.MethodPost { 659 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 660 } 661 if r.URL.Path != APIEndpointReplyMessage { 662 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointReplyMessage) 663 } 664 body, err := io.ReadAll(r.Body) 665 if err != nil { 666 t.Fatal(err) 667 } 668 tc := testCases[currentTestIdx] 669 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 670 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 671 } 672 w.WriteHeader(tc.ResponseCode) 673 w.Write(tc.Response) 674 })) 675 defer server.Close() 676 677 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 678 defer r.Body.Close() 679 t.Error("Unexpected data API call") 680 w.WriteHeader(404) 681 w.Write([]byte(`{"message":"Not found"}`)) 682 })) 683 defer dataServer.Close() 684 685 client, err := mockClient(server, dataServer) 686 if err != nil { 687 t.Fatal(err) 688 } 689 for i, tc := range testCases { 690 currentTestIdx = i 691 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 692 res, err := client.ReplyMessage(replyToken, tc.Messages...).Do() 693 if tc.Want.Error != nil { 694 if !reflect.DeepEqual(err, tc.Want.Error) { 695 t.Errorf("Error %v; want %v", err, tc.Want.Error) 696 } 697 } else { 698 if err != nil { 699 t.Error(err) 700 } 701 } 702 if tc.Want.Response != nil { 703 if !reflect.DeepEqual(res, tc.Want.Response) { 704 t.Errorf("Response %v; want %v", res, tc.Want.Response) 705 } 706 } 707 }) 708 } 709 } 710 711 func TestReplyMessagesWithContext(t *testing.T) { 712 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 713 defer r.Body.Close() 714 time.Sleep(10 * time.Millisecond) 715 w.Write([]byte("{}")) 716 })) 717 defer server.Close() 718 719 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 720 defer r.Body.Close() 721 t.Error("Unexpected data API call") 722 w.WriteHeader(404) 723 w.Write([]byte(`{"message":"Not found"}`)) 724 })) 725 defer dataServer.Close() 726 727 client, err := mockClient(server, dataServer) 728 if err != nil { 729 t.Fatal(err) 730 } 731 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 732 defer cancel() 733 _, err = client.ReplyMessage("nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", NewTextMessage("Hello, world")).WithContext(ctx).Do() 734 expectCtxDeadlineExceed(ctx, err, t) 735 } 736 737 func TestMulticastMessages(t *testing.T) { 738 toUserIDs := []string{ 739 "U0cc15697597f61dd8b01cea8b027050e", 740 "U38ecbecfade326557b6971140741a4a6", 741 } 742 type want struct { 743 RequestBody []byte 744 Response *BasicResponse 745 Error error 746 } 747 testCases := []struct { 748 Label string 749 Messages []SendingMessage 750 Response []byte 751 ResponseCode int 752 Want want 753 }{ 754 { 755 Label: "A text message", 756 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 757 ResponseCode: 200, 758 Response: []byte(`{}`), 759 Want: want{ 760 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 761 Response: &BasicResponse{}, 762 }, 763 }, 764 { 765 Label: "A location message", 766 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 767 ResponseCode: 200, 768 Response: []byte(`{}`), 769 Want: want{ 770 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 771 Response: &BasicResponse{}, 772 }, 773 }, 774 { 775 Label: "A image message", 776 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 777 ResponseCode: 200, 778 Response: []byte(`{}`), 779 Want: want{ 780 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 781 Response: &BasicResponse{}, 782 }, 783 }, 784 { 785 Label: "A sticker message", 786 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 787 ResponseCode: 200, 788 Response: []byte(`{}`), 789 Want: want{ 790 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 791 Response: &BasicResponse{}, 792 }, 793 }, 794 { 795 Label: "Bad request", 796 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 797 ResponseCode: 400, 798 Response: []byte(`{"message":"Request body has 2 error(s).","details":[{"message":"may not be empty","property":"messages[0].text"},{"message":"may not be empty","property":"messages[1].text"}]}`), 799 Want: want{ 800 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 801 Error: &APIError{ 802 Code: 400, 803 Response: &ErrorResponse{ 804 Message: "Request body has 2 error(s).", 805 Details: []errorResponseDetail{ 806 { 807 Message: "may not be empty", 808 Property: "messages[0].text", 809 }, 810 { 811 Message: "may not be empty", 812 Property: "messages[1].text", 813 }, 814 }, 815 }, 816 }, 817 }, 818 }, 819 } 820 821 var currentTestIdx int 822 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 823 defer r.Body.Close() 824 if r.Method != http.MethodPost { 825 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 826 } 827 if r.URL.Path != APIEndpointMulticast { 828 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointMulticast) 829 } 830 body, err := io.ReadAll(r.Body) 831 if err != nil { 832 t.Fatal(err) 833 } 834 tc := testCases[currentTestIdx] 835 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 836 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 837 } 838 w.WriteHeader(tc.ResponseCode) 839 w.Write(tc.Response) 840 })) 841 defer server.Close() 842 843 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 844 defer r.Body.Close() 845 t.Error("Unexpected data API call") 846 w.WriteHeader(404) 847 w.Write([]byte(`{"message":"Not found"}`)) 848 })) 849 defer dataServer.Close() 850 851 client, err := mockClient(server, dataServer) 852 if err != nil { 853 t.Fatal(err) 854 } 855 for i, tc := range testCases { 856 currentTestIdx = i 857 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 858 res, err := client.Multicast(toUserIDs, tc.Messages...).Do() 859 if tc.Want.Error != nil { 860 if !reflect.DeepEqual(err, tc.Want.Error) { 861 t.Errorf("Error %v; want %v", err, tc.Want.Error) 862 } 863 } else { 864 if err != nil { 865 t.Error(err) 866 } 867 } 868 if tc.Want.Response != nil { 869 if !reflect.DeepEqual(res, tc.Want.Response) { 870 t.Errorf("Response %v; want %v", res, tc.Want.Response) 871 } 872 } 873 }) 874 } 875 } 876 877 func TestMulticastMessagesWithContext(t *testing.T) { 878 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 879 defer r.Body.Close() 880 time.Sleep(10 * time.Millisecond) 881 w.Write([]byte("{}")) 882 })) 883 defer server.Close() 884 885 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 886 defer r.Body.Close() 887 t.Error("Unexpected data API call") 888 w.WriteHeader(404) 889 w.Write([]byte(`{"message":"Not found"}`)) 890 })) 891 defer dataServer.Close() 892 893 client, err := mockClient(server, dataServer) 894 if err != nil { 895 t.Fatal(err) 896 } 897 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 898 defer cancel() 899 _, err = client.Multicast([]string{"U0cc15697597f61dd8b01cea8b027050e", "U38ecbecfade326557b6971140741a4a6"}, NewTextMessage("Hello, world")).WithContext(ctx).Do() 900 expectCtxDeadlineExceed(ctx, err, t) 901 } 902 903 func TestBroadcastMessages(t *testing.T) { 904 type want struct { 905 RequestBody []byte 906 Response *BasicResponse 907 Error error 908 } 909 testCases := []struct { 910 Messages []SendingMessage 911 Response []byte 912 ResponseCode int 913 Want want 914 }{ 915 { 916 // A text message 917 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 918 ResponseCode: 200, 919 Response: []byte(`{}`), 920 Want: want{ 921 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 922 Response: &BasicResponse{}, 923 }, 924 }, 925 { 926 // A image message 927 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 928 ResponseCode: 200, 929 Response: []byte(`{}`), 930 Want: want{ 931 RequestBody: []byte(`{"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 932 Response: &BasicResponse{}, 933 }, 934 }, 935 { 936 // A video message 937 Messages: []SendingMessage{NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg")}, 938 ResponseCode: 200, 939 Response: []byte(`{}`), 940 Want: want{ 941 RequestBody: []byte(`{"messages":[{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 942 Response: &BasicResponse{}, 943 }, 944 }, 945 { 946 // A audio message 947 Messages: []SendingMessage{NewAudioMessage("https://example.com/original.m4a", 1000)}, 948 ResponseCode: 200, 949 Response: []byte(`{}`), 950 Want: want{ 951 RequestBody: []byte(`{"messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000}]}` + "\n"), 952 Response: &BasicResponse{}, 953 }, 954 }, 955 { 956 // A location message 957 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 958 ResponseCode: 200, 959 Response: []byte(`{}`), 960 Want: want{ 961 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 962 Response: &BasicResponse{}, 963 }, 964 }, 965 { 966 // A sticker message 967 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 968 ResponseCode: 200, 969 Response: []byte(`{}`), 970 Want: want{ 971 RequestBody: []byte(`{"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 972 Response: &BasicResponse{}, 973 }, 974 }, 975 { 976 // A buttons template message 977 Messages: []SendingMessage{ 978 NewTemplateMessage( 979 "this is a buttons template", 980 NewButtonsTemplate( 981 "https://example.com/bot/images/image.jpg", 982 "Menu", 983 "Please select", 984 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 985 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 986 NewURIAction("View detail", "https://example.com/page/123"), 987 ), 988 ), 989 }, 990 ResponseCode: 200, 991 Response: []byte(`{}`), 992 Want: want{ 993 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","title":"Menu","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 994 Response: &BasicResponse{}, 995 }, 996 }, 997 { 998 // A buttons template message with datetimepicker action 999 Messages: []SendingMessage{ 1000 NewTemplateMessage( 1001 "this is a buttons template", 1002 NewButtonsTemplate( 1003 "https://example.com/bot/images/image.jpg", 1004 "Menu", 1005 "Please select a date, time or datetime", 1006 NewDatetimePickerAction("Date", "action=sel&only=date", "date", "2017-09-01", "2017-09-03", ""), 1007 NewDatetimePickerAction("Time", "action=sel&only=time", "time", "", "23:59", "00:00"), 1008 NewDatetimePickerAction("DateTime", "action=sel", "datetime", "2017-09-01T12:00", "", ""), 1009 ), 1010 ), 1011 }, 1012 ResponseCode: 200, 1013 Response: []byte(`{}`), 1014 Want: want{ 1015 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","title":"Menu","text":"Please select a date, time or datetime","actions":[{"type":"datetimepicker","label":"Date","data":"action=sel\u0026only=date","mode":"date","initial":"2017-09-01","max":"2017-09-03"},{"type":"datetimepicker","label":"Time","data":"action=sel\u0026only=time","mode":"time","max":"23:59","min":"00:00"},{"type":"datetimepicker","label":"DateTime","data":"action=sel","mode":"datetime","initial":"2017-09-01T12:00"}]}}]}` + "\n"), 1016 Response: &BasicResponse{}, 1017 }, 1018 }, 1019 { 1020 // A buttons template message without thumbnailImageURL 1021 Messages: []SendingMessage{ 1022 NewTemplateMessage( 1023 "this is a buttons template", 1024 NewButtonsTemplate( 1025 "", 1026 "Menu", 1027 "Please select", 1028 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 1029 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 1030 NewURIAction("View detail", "https://example.com/page/123"), 1031 ), 1032 ), 1033 }, 1034 ResponseCode: 200, 1035 Response: []byte(`{}`), 1036 Want: want{ 1037 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","title":"Menu","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 1038 Response: &BasicResponse{}, 1039 }, 1040 }, 1041 { 1042 // A buttons template message without title 1043 Messages: []SendingMessage{ 1044 NewTemplateMessage( 1045 "this is a buttons template", 1046 NewButtonsTemplate( 1047 "https://example.com/bot/images/image.jpg", 1048 "", 1049 "Please select", 1050 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 1051 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 1052 NewURIAction("View detail", "https://example.com/page/123"), 1053 ), 1054 ), 1055 }, 1056 ResponseCode: 200, 1057 Response: []byte(`{}`), 1058 Want: want{ 1059 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 1060 Response: &BasicResponse{}, 1061 }, 1062 }, 1063 { 1064 // A buttons template message without title, with image options and default action 1065 Messages: []SendingMessage{ 1066 NewTemplateMessage( 1067 "this is a buttons template", 1068 NewButtonsTemplate( 1069 "https://example.com/bot/images/image.jpg", 1070 "", 1071 "Please select", 1072 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 1073 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 1074 NewURIAction("View detail", "https://example.com/page/123"), 1075 ).WithImageOptions( 1076 "rectangle", 1077 "cover", 1078 "#FFFFFF", 1079 ).WithDefaultAction( 1080 NewURIAction("View detail", "https://example.com/page/123"), 1081 ), 1082 ), 1083 }, 1084 ResponseCode: 200, 1085 Response: []byte(`{}`), 1086 Want: want{ 1087 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","thumbnailImageUrl":"https://example.com/bot/images/image.jpg","imageAspectRatio":"rectangle","imageSize":"cover","imageBackgroundColor":"#FFFFFF","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}],"defaultAction":{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}}}]}` + "\n"), 1088 Response: &BasicResponse{}, 1089 }, 1090 }, 1091 { 1092 // A buttons template message without thumbnailImageURL and title 1093 Messages: []SendingMessage{ 1094 NewTemplateMessage( 1095 "this is a buttons template", 1096 NewButtonsTemplate( 1097 "", 1098 "", 1099 "Please select", 1100 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 1101 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 1102 NewURIAction("View detail", "https://example.com/page/123"), 1103 ), 1104 ), 1105 }, 1106 ResponseCode: 200, 1107 Response: []byte(`{}`), 1108 Want: want{ 1109 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a buttons template","template":{"type":"buttons","text":"Please select","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","displayText":"displayText"},{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=123","text":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 1110 Response: &BasicResponse{}, 1111 }, 1112 }, 1113 { 1114 // A confirm template message 1115 Messages: []SendingMessage{ 1116 NewTemplateMessage( 1117 "this is a confirm template", 1118 NewConfirmTemplate( 1119 "Are you sure?", 1120 NewMessageAction("Yes", "yes"), 1121 NewMessageAction("No", "no"), 1122 ), 1123 ), 1124 }, 1125 ResponseCode: 200, 1126 Response: []byte(`{}`), 1127 Want: want{ 1128 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a confirm template","template":{"type":"confirm","text":"Are you sure?","actions":[{"type":"message","label":"Yes","text":"yes"},{"type":"message","label":"No","text":"no"}]}}]}` + "\n"), 1129 Response: &BasicResponse{}, 1130 }, 1131 }, 1132 { 1133 // A carousel template message 1134 Messages: []SendingMessage{ 1135 NewTemplateMessage( 1136 "this is a carousel template", 1137 NewCarouselTemplate( 1138 NewCarouselColumn( 1139 "https://example.com/bot/images/item1.jpg", 1140 "this is menu", 1141 "description", 1142 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 1143 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 1144 NewURIAction("View detail", "https://example.com/page/111"), 1145 ), 1146 ), 1147 ), 1148 }, 1149 ResponseCode: 200, 1150 Response: []byte(`{}`), 1151 Want: want{ 1152 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a carousel template","template":{"type":"carousel","columns":[{"thumbnailImageUrl":"https://example.com/bot/images/item1.jpg","title":"this is menu","text":"description","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=111"},{"type":"postback","label":"Add to cart","data":"action=add\u0026itemid=111"},{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}]}]}}]}` + "\n"), 1153 Response: &BasicResponse{}, 1154 }, 1155 }, 1156 { 1157 // A carousel template message, with new image options and default action 1158 Messages: []SendingMessage{ 1159 NewTemplateMessage( 1160 "this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor", 1161 NewCarouselTemplate( 1162 NewCarouselColumn( 1163 "https://example.com/bot/images/item1.jpg", 1164 "this is menu", 1165 "description", 1166 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 1167 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 1168 NewURIAction("View detail", "https://example.com/page/111"), 1169 ).WithImageOptions( 1170 "#FFFFFF", 1171 ).WithDefaultAction( 1172 NewURIAction("View detail", "http://example.com/page/123"), 1173 ), 1174 ).WithImageOptions("rectangle", "cover"), 1175 ), 1176 }, 1177 ResponseCode: 200, 1178 Response: []byte(`{}`), 1179 Want: want{ 1180 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor","template":{"type":"carousel","columns":[{"thumbnailImageUrl":"https://example.com/bot/images/item1.jpg","imageBackgroundColor":"#FFFFFF","title":"this is menu","text":"description","actions":[{"type":"postback","label":"Buy","data":"action=buy\u0026itemid=111"},{"type":"postback","label":"Add to cart","data":"action=add\u0026itemid=111"},{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}],"defaultAction":{"type":"uri","label":"View detail","uri":"http://example.com/page/123"}}],"imageAspectRatio":"rectangle","imageSize":"cover"}}]}` + "\n"), 1181 Response: &BasicResponse{}, 1182 }, 1183 }, 1184 { 1185 // A imagecarousel template message 1186 Messages: []SendingMessage{ 1187 NewTemplateMessage( 1188 "this is a image carousel template", 1189 NewImageCarouselTemplate( 1190 NewImageCarouselColumn( 1191 "https://example.com/bot/images/item1.jpg", 1192 NewURIAction("View detail", "https://example.com/page/111"), 1193 ), 1194 ), 1195 ), 1196 }, 1197 ResponseCode: 200, 1198 Response: []byte(`{}`), 1199 Want: want{ 1200 RequestBody: []byte(`{"messages":[{"type":"template","altText":"this is a image carousel template","template":{"type":"image_carousel","columns":[{"imageUrl":"https://example.com/bot/images/item1.jpg","action":{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}}]}}]}` + "\n"), 1201 Response: &BasicResponse{}, 1202 }, 1203 }, 1204 { 1205 // A imagemap message 1206 Messages: []SendingMessage{ 1207 NewImagemapMessage( 1208 "https://example.com/bot/images/rm001", 1209 "this is an imagemap", 1210 ImagemapBaseSize{1040, 1040}, 1211 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1212 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1213 ), 1214 }, 1215 ResponseCode: 200, 1216 Response: []byte(`{}`), 1217 Want: want{ 1218 RequestBody: []byte(`{"messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}]}]}` + "\n"), 1219 Response: &BasicResponse{}, 1220 }, 1221 }, 1222 { 1223 // A imagemap messages with video 1 1224 Messages: []SendingMessage{ 1225 NewImagemapMessage( 1226 "https://example.com/bot/images/rm001", 1227 "this is an imagemap with video", 1228 ImagemapBaseSize{1040, 1040}, 1229 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1230 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1231 ).WithVideo(&ImagemapVideo{ 1232 OriginalContentURL: "https://example.com/original.mp4", 1233 PreviewImageURL: "https://example.com/preview.jpg", 1234 Area: ImagemapArea{10, 10, 100, 200}, 1235 }), 1236 }, 1237 ResponseCode: 200, 1238 Response: []byte(`{}`), 1239 Want: want{ 1240 RequestBody: []byte(`{"messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap with video","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}],"video":{"originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","area":{"x":10,"y":10,"width":100,"height":200}}}]}` + "\n"), 1241 Response: &BasicResponse{}, 1242 }, 1243 }, 1244 { 1245 // A imagemap messages with video 2 1246 Messages: []SendingMessage{ 1247 NewImagemapMessage( 1248 "https://example.com/bot/images/rm001", 1249 "this is an imagemap with video and external link", 1250 ImagemapBaseSize{1040, 1040}, 1251 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1252 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1253 ).WithVideo(&ImagemapVideo{ 1254 OriginalContentURL: "https://example.com/original.mp4", 1255 PreviewImageURL: "https://example.com/preview.jpg", 1256 Area: ImagemapArea{10, 10, 100, 200}, 1257 ExternalLink: &ImagemapVideoExternalLink{ 1258 LinkURI: "https://example.com/", 1259 Label: "external link", 1260 }, 1261 }), 1262 }, 1263 ResponseCode: 200, 1264 Response: []byte(`{}`), 1265 Want: want{ 1266 RequestBody: []byte(`{"messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap with video and external link","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}],"video":{"originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","area":{"x":10,"y":10,"width":100,"height":200},"externalLink":{"linkUri":"https://example.com/","label":"external link"}}}]}` + "\n"), 1267 Response: &BasicResponse{}, 1268 }, 1269 }, 1270 { 1271 // A flex message 1272 Messages: []SendingMessage{ 1273 NewFlexMessage( 1274 "this is a flex message", 1275 &BubbleContainer{ 1276 Type: FlexContainerTypeBubble, 1277 Body: &BoxComponent{ 1278 Type: FlexComponentTypeBox, 1279 Layout: FlexBoxLayoutTypeVertical, 1280 Contents: []FlexComponent{ 1281 &TextComponent{ 1282 Type: FlexComponentTypeText, 1283 Text: "hello", 1284 }, 1285 &TextComponent{ 1286 Type: FlexComponentTypeText, 1287 Text: "world", 1288 Flex: IntPtr(0), 1289 }, 1290 &SpacerComponent{ 1291 Type: FlexComponentTypeSpacer, 1292 }, 1293 }, 1294 }, 1295 }, 1296 ), 1297 }, 1298 ResponseCode: 200, 1299 Response: []byte(`{}`), 1300 Want: want{ 1301 RequestBody: []byte(`{"messages":[{"type":"flex","altText":"this is a flex message","contents":{"type":"bubble","body":{"type":"box","layout":"vertical","contents":[{"type":"text","text":"hello"},{"type":"text","text":"world","flex":0},{"type":"spacer"}]}}}]}` + "\n"), 1302 Response: &BasicResponse{}, 1303 }, 1304 }, 1305 { 1306 // A text message with quick replies 1307 Messages: []SendingMessage{ 1308 NewTextMessage( 1309 "Select your favorite food category or send me your location!", 1310 ).WithQuickReplies( 1311 NewQuickReplyItems( 1312 NewQuickReplyButton("https://example.com/sushi.png", NewMessageAction("Sushi", "Sushi")), 1313 NewQuickReplyButton("https://example.com/tempura.png", NewMessageAction("Tempura", "Tempura")), 1314 NewQuickReplyButton("", NewLocationAction("Send location")), 1315 ), 1316 ), 1317 }, 1318 ResponseCode: 200, 1319 Response: []byte(`{}`), 1320 Want: want{ 1321 RequestBody: []byte(`{"messages":[{"type":"text","text":"Select your favorite food category or send me your location!","quickReply":{"items":[{"type":"action","imageUrl":"https://example.com/sushi.png","action":{"type":"message","label":"Sushi","text":"Sushi"}},{"type":"action","imageUrl":"https://example.com/tempura.png","action":{"type":"message","label":"Tempura","text":"Tempura"}},{"type":"action","action":{"type":"location","label":"Send location"}}]}}]}` + "\n"), 1322 Response: &BasicResponse{}, 1323 }, 1324 }, 1325 { 1326 // Multiple messages 1327 Messages: []SendingMessage{NewTextMessage("Hello, world1"), NewTextMessage("Hello, world2")}, 1328 ResponseCode: 200, 1329 Response: []byte(`{}`), 1330 Want: want{ 1331 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world1"},{"type":"text","text":"Hello, world2"}]}` + "\n"), 1332 Response: &BasicResponse{}, 1333 }, 1334 }, 1335 // Single message with sender 1336 { 1337 Messages: []SendingMessage{ 1338 NewAudioMessage("https://example.com/original.m4a", 1000).WithSender( 1339 NewSender("Cony", "https://line.me/conyprof"), 1340 ), 1341 }, 1342 ResponseCode: 200, 1343 Response: []byte(`{}`), 1344 Want: want{ 1345 RequestBody: []byte(`{"messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000,"sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1346 Response: &BasicResponse{}, 1347 }, 1348 }, 1349 // Single location message with sender 1350 { 1351 Messages: []SendingMessage{ 1352 NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203).WithSender( 1353 NewSender("Cony", "https://line.me/conyprof"), 1354 ), 1355 }, 1356 ResponseCode: 200, 1357 Response: []byte(`{}`), 1358 Want: want{ 1359 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203,"sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1360 Response: &BasicResponse{}, 1361 }, 1362 }, 1363 { 1364 // A imagemap message with sender 1365 Messages: []SendingMessage{ 1366 NewImagemapMessage( 1367 "https://example.com/bot/images/rm001", 1368 "this is an imagemap", 1369 ImagemapBaseSize{1040, 1040}, 1370 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1371 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1372 ).WithSender( 1373 NewSender("Cony", "https://line.me/conyprof"), 1374 ), 1375 }, 1376 ResponseCode: 200, 1377 Response: []byte(`{}`), 1378 Want: want{ 1379 RequestBody: []byte(`{"messages":[{"type":"imagemap","baseUrl":"https://example.com/bot/images/rm001","altText":"this is an imagemap","baseSize":{"width":1040,"height":1040},"actions":[{"type":"uri","label":"example","linkUri":"https://example.com/","area":{"x":520,"y":0,"width":520,"height":1040}},{"type":"message","label":"hello","text":"hello","area":{"x":520,"y":0,"width":520,"height":1040}}],"sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1380 Response: &BasicResponse{}, 1381 }, 1382 }, 1383 { 1384 Messages: []SendingMessage{ 1385 NewTextMessage("Hello, I am Cony!!").WithSender( 1386 NewSender("Cony", "https://line.me/conyprof"), 1387 ), 1388 NewStickerMessage("1", "1").WithSender( 1389 NewSender("Cony", "https://line.me/conyprof"), 1390 ), 1391 NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg").WithSender( 1392 NewSender("Cony", "https://line.me/conyprof"), 1393 ), 1394 }, 1395 ResponseCode: 200, 1396 Response: []byte(`{}`), 1397 Want: want{ 1398 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, I am Cony!!","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}},{"type":"sticker","packageId":"1","stickerId":"1","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}},{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1399 Response: &BasicResponse{}, 1400 }, 1401 }, 1402 { 1403 // Multiple messages with sender 1404 Messages: []SendingMessage{ 1405 NewTextMessage("Hello, I am Cony!!").WithSender( 1406 NewSender("Cony", "https://line.me/conyprof"), 1407 ), 1408 NewStickerMessage("1", "1").WithSender( 1409 NewSender("Cony", "https://line.me/conyprof"), 1410 ), 1411 NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg").WithSender( 1412 NewSender("Cony", "https://line.me/conyprof"), 1413 ), 1414 }, 1415 ResponseCode: 200, 1416 Response: []byte(`{}`), 1417 Want: want{ 1418 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, I am Cony!!","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}},{"type":"sticker","packageId":"1","stickerId":"1","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}},{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg","sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1419 Response: &BasicResponse{}, 1420 }, 1421 }, 1422 { 1423 // A text message with emojis 1424 Messages: []SendingMessage{ 1425 NewTextMessage("$ Look at this: $ It's a LINE emoji!").AddEmoji( 1426 NewEmoji(0, "5ac1bfd5040ab15980c9b435", "086")).AddEmoji( 1427 NewEmoji(14, "5ac1bfd5040ab15980c9b435", "001"), 1428 ), 1429 }, 1430 ResponseCode: 200, 1431 Response: []byte(`{}`), 1432 Want: want{ 1433 RequestBody: []byte(`{"messages":[{"type":"text","text":"$ Look at this: $ It's a LINE emoji!","emojis":[{"index":0,"productId":"5ac1bfd5040ab15980c9b435","emojiId":"086"},{"index":14,"productId":"5ac1bfd5040ab15980c9b435","emojiId":"001"}]}]}` + "\n"), 1434 Response: &BasicResponse{}, 1435 }, 1436 }, 1437 { 1438 // Bad request 1439 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 1440 ResponseCode: 400, 1441 Response: []byte(`{"message":"Request body has 2 error(s).","details":[{"message":"may not be empty","property":"messages[0].text"},{"message":"may not be empty","property":"messages[1].text"}]}`), 1442 Want: want{ 1443 RequestBody: []byte(`{"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 1444 Error: &APIError{ 1445 Code: 400, 1446 Response: &ErrorResponse{ 1447 Message: "Request body has 2 error(s).", 1448 Details: []errorResponseDetail{ 1449 { 1450 Message: "may not be empty", 1451 Property: "messages[0].text", 1452 }, 1453 { 1454 Message: "may not be empty", 1455 Property: "messages[1].text", 1456 }, 1457 }, 1458 }, 1459 }, 1460 }, 1461 }, 1462 } 1463 1464 var currentTestIdx int 1465 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1466 defer r.Body.Close() 1467 tc := testCases[currentTestIdx] 1468 if r.Method != http.MethodPost { 1469 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1470 } 1471 if r.URL.Path != APIEndpointBroadcastMessage { 1472 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointBroadcastMessage) 1473 } 1474 body, err := io.ReadAll(r.Body) 1475 if err != nil { 1476 t.Fatal(err) 1477 } 1478 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1479 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1480 } 1481 w.WriteHeader(tc.ResponseCode) 1482 w.Write(tc.Response) 1483 })) 1484 defer server.Close() 1485 1486 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1487 defer r.Body.Close() 1488 t.Error("Unexpected data API call") 1489 w.WriteHeader(404) 1490 w.Write([]byte(`{"message":"Not found"}`)) 1491 })) 1492 defer dataServer.Close() 1493 1494 client, err := mockClient(server, dataServer) 1495 if err != nil { 1496 t.Fatal(err) 1497 } 1498 for i, tc := range testCases { 1499 currentTestIdx = i 1500 res, err := client.BroadcastMessage(tc.Messages...).Do() 1501 if tc.Want.Error != nil { 1502 if !reflect.DeepEqual(err, tc.Want.Error) { 1503 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1504 } 1505 } else { 1506 if err != nil { 1507 t.Error(err) 1508 } 1509 } 1510 if tc.Want.Response != nil { 1511 if !reflect.DeepEqual(res, tc.Want.Response) { 1512 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1513 } 1514 } 1515 } 1516 } 1517 1518 func TestBroadcastMessagesWithContext(t *testing.T) { 1519 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1520 defer r.Body.Close() 1521 time.Sleep(10 * time.Millisecond) 1522 w.Write([]byte("{}")) 1523 })) 1524 defer server.Close() 1525 1526 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1527 defer r.Body.Close() 1528 t.Error("Unexpected data API call") 1529 w.WriteHeader(404) 1530 w.Write([]byte(`{"message":"Not found"}`)) 1531 })) 1532 defer dataServer.Close() 1533 1534 client, err := mockClient(server, dataServer) 1535 if err != nil { 1536 t.Fatal(err) 1537 } 1538 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 1539 defer cancel() 1540 _, err = client.BroadcastMessage(NewTextMessage("Hello, world")).WithContext(ctx).Do() 1541 expectCtxDeadlineExceed(ctx, err, t) 1542 } 1543 1544 func TestMessagesWithNotificationDisabled(t *testing.T) { 1545 type testMethod interface { 1546 Do() (*BasicResponse, error) 1547 } 1548 toUserIDs := []string{ 1549 "U0cc15697597f61dd8b01cea8b027050e", 1550 "U38ecbecfade326557b6971140741a4a6", 1551 } 1552 type want struct { 1553 RequestBody []byte 1554 Response *BasicResponse 1555 Error error 1556 } 1557 testCases := []struct { 1558 Label string 1559 TestMethod testMethod 1560 Messages []SendingMessage 1561 Response []byte 1562 ResponseCode int 1563 Want want 1564 }{ 1565 { 1566 Label: "A text message for Push Message", 1567 TestMethod: new(PushMessageCall), 1568 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1569 ResponseCode: 200, 1570 Response: []byte(`{}`), 1571 Want: want{ 1572 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Hello, world"}],"notificationDisabled":true}` + "\n"), 1573 Response: &BasicResponse{}, 1574 }, 1575 }, 1576 { 1577 Label: "A text message for Reply Message", 1578 TestMethod: new(ReplyMessageCall), 1579 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1580 ResponseCode: 200, 1581 Response: []byte(`{}`), 1582 Want: want{ 1583 RequestBody: []byte(`{"replyToken":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Hello, world"}],"notificationDisabled":true}` + "\n"), 1584 Response: &BasicResponse{}, 1585 }, 1586 }, 1587 { 1588 Label: "A text message for Multicast", 1589 TestMethod: new(MulticastCall), 1590 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1591 ResponseCode: 200, 1592 Response: []byte(`{}`), 1593 Want: want{ 1594 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"text","text":"Hello, world"}],"notificationDisabled":true}` + "\n"), 1595 Response: &BasicResponse{}, 1596 }, 1597 }, 1598 } 1599 1600 var currentTestIdx int 1601 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1602 defer r.Body.Close() 1603 if r.Method != http.MethodPost { 1604 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1605 } 1606 body, err := io.ReadAll(r.Body) 1607 if err != nil { 1608 t.Fatal(err) 1609 } 1610 tc := testCases[currentTestIdx] 1611 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1612 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1613 } 1614 w.WriteHeader(tc.ResponseCode) 1615 w.Write(tc.Response) 1616 })) 1617 defer server.Close() 1618 1619 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1620 defer r.Body.Close() 1621 t.Error("Unexpected data API call") 1622 w.WriteHeader(404) 1623 w.Write([]byte(`{"message":"Not found"}`)) 1624 })) 1625 defer dataServer.Close() 1626 1627 client, err := mockClient(server, dataServer) 1628 if err != nil { 1629 t.Fatal(err) 1630 } 1631 var res *BasicResponse 1632 for i, tc := range testCases { 1633 currentTestIdx = i 1634 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1635 switch tc.TestMethod.(type) { 1636 case *PushMessageCall: 1637 res, err = client.PushMessage(toUserIDs[0], tc.Messages...).WithNotificationDisabled().Do() 1638 case *ReplyMessageCall: // use toUserIDs as replyToken because it doesn't matter 1639 res, err = client.ReplyMessage(toUserIDs[0], tc.Messages...).WithNotificationDisabled().Do() 1640 case *MulticastCall: 1641 res, err = client.Multicast(toUserIDs, tc.Messages...).WithNotificationDisabled().Do() 1642 } 1643 if tc.Want.Error != nil { 1644 if !reflect.DeepEqual(err, tc.Want.Error) { 1645 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1646 } 1647 } else { 1648 if err != nil { 1649 t.Error(err) 1650 } 1651 } 1652 if tc.Want.Response != nil { 1653 if !reflect.DeepEqual(res, tc.Want.Response) { 1654 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1655 } 1656 } 1657 }) 1658 } 1659 } 1660 1661 func TestNarrowcastMessages(t *testing.T) { 1662 type want struct { 1663 RequestBody []byte 1664 Response *BasicResponse 1665 Error error 1666 } 1667 testCases := []struct { 1668 Label string 1669 Messages []SendingMessage 1670 Recipient Recipient 1671 Demographic DemographicFilter 1672 Max int 1673 UpToRemainingQuota bool 1674 RequestID string 1675 Response []byte 1676 ResponseCode int 1677 Want want 1678 }{ 1679 { 1680 Label: "A text message for Narrowcast Message with Audience", 1681 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1682 Recipient: RecipientOperatorAnd( 1683 NewAudienceObject(5614991017776), 1684 RecipientOperatorNot( 1685 NewAudienceObject(4389303728991), 1686 ), 1687 ), 1688 Demographic: nil, 1689 Max: 0, 1690 RequestID: "12222", 1691 Response: []byte(`{}`), 1692 ResponseCode: 202, 1693 Want: want{ 1694 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}],"recipient":{"type":"operator","and":[{"type":"audience","audienceGroupId":5614991017776},{"type":"operator","not":{"type":"audience","audienceGroupId":4389303728991}}]}}` + "\n"), 1695 Response: &BasicResponse{RequestID: "12222"}, 1696 }, 1697 }, 1698 { 1699 Label: "A text message for Narrowcast Message with Redelivery", 1700 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1701 Recipient: RecipientOperatorAnd( 1702 NewRedeliveryObject("f70dd685-499a-4231-a441-f24b8d4fba21"), 1703 RecipientOperatorNot( 1704 NewRedeliveryObject("x88dd9k2-gdd4-7fs0-a441-va668d4fb0x9"), 1705 ), 1706 ), 1707 Demographic: nil, 1708 Max: 0, 1709 RequestID: "12222", 1710 Response: []byte(`{}`), 1711 ResponseCode: 202, 1712 Want: want{ 1713 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}],"recipient":{"type":"operator","and":[{"type":"redelivery","requestId":"f70dd685-499a-4231-a441-f24b8d4fba21"},{"type":"operator","not":{"type":"redelivery","requestId":"x88dd9k2-gdd4-7fs0-a441-va668d4fb0x9"}}]}}` + "\n"), 1714 Response: &BasicResponse{RequestID: "12222"}, 1715 }, 1716 }, 1717 { 1718 Label: "A text message for Narrowcast Message for android", 1719 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1720 Recipient: nil, 1721 Demographic: NewAppTypeFilter(AppTypeAndroid), 1722 Max: 0, 1723 RequestID: "22222", 1724 Response: []byte(`{}`), 1725 ResponseCode: 202, 1726 Want: want{ 1727 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}],"filter":{"demographic":{"type":"appType","oneOf":["android"]}}}` + "\n"), 1728 Response: &BasicResponse{RequestID: "22222"}, 1729 }, 1730 }, 1731 { 1732 Label: "A text message for Narrowcast Message for male and age >= 30 and limit max to 10", 1733 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1734 Recipient: nil, 1735 Demographic: DemographicFilterOperatorAnd(NewGenderFilter(GenderMale), NewAgeFilter(Age30, AgeEmpty)), 1736 Max: 10, 1737 UpToRemainingQuota: true, 1738 RequestID: "32222", 1739 Response: []byte(`{}`), 1740 ResponseCode: 202, 1741 Want: want{ 1742 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}],"filter":{"demographic":{"type":"operator","and":[{"type":"gender","oneOf":["male"]},{"type":"age","gte":"age_30"}]}},"limit":{"max":10,"upToRemainingQuota":true}}` + "\n"), 1743 Response: &BasicResponse{RequestID: "32222"}, 1744 }, 1745 }, 1746 { 1747 Label: "An example message for sending narrowcast message based on official documentation", 1748 Messages: []SendingMessage{NewTextMessage("test message")}, 1749 Recipient: RecipientOperatorAnd( 1750 NewAudienceObject(5614991017776), 1751 RecipientOperatorNot( 1752 NewAudienceObject(4389303728991), 1753 ), 1754 ), 1755 Demographic: DemographicFilterOperatorOr( 1756 DemographicFilterOperatorAnd( 1757 NewGenderFilter(GenderMale, GenderFemale), 1758 NewAgeFilter(Age20, Age25), 1759 NewAppTypeFilter(AppTypeAndroid, AppTypeIOS), 1760 NewAreaFilter(AreaJPAichi, AreaJPAkita), 1761 NewSubscriptionPeriodFilter(PeriodDay7, PeriodDay30), 1762 ), 1763 DemographicFilterOperatorAnd( 1764 NewAgeFilter(Age35, Age40), 1765 DemographicFilterOperatorNot(NewGenderFilter(GenderMale)), 1766 ), 1767 ), 1768 Max: 100, 1769 RequestID: "32222", 1770 Response: []byte(`{}`), 1771 ResponseCode: 202, 1772 Want: want{ 1773 RequestBody: []byte(`{"messages":[{"type":"text","text":"test message"}],"recipient":{"type":"operator","and":[{"type":"audience","audienceGroupId":5614991017776},{"type":"operator","not":{"type":"audience","audienceGroupId":4389303728991}}]},"filter":{"demographic":{"type":"operator","or":[{"type":"operator","and":[{"type":"gender","oneOf":["male","female"]},{"type":"age","gte":"age_20","lt":"age_25"},{"type":"appType","oneOf":["android","ios"]},{"type":"area","oneOf":["jp_23","jp_05"]},{"type":"subscriptionPeriod","gte":"day_7","lt":"day_30"}]},{"type":"operator","and":[{"type":"age","gte":"age_35","lt":"age_40"},{"type":"operator","not":{"type":"gender","oneOf":["male"]}}]}]}},"limit":{"max":100}}` + "\n"), 1774 Response: &BasicResponse{RequestID: "32222"}, 1775 }, 1776 }, 1777 } 1778 1779 var currentTestIdx int 1780 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1781 defer r.Body.Close() 1782 if r.Method != http.MethodPost { 1783 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1784 } 1785 body, err := io.ReadAll(r.Body) 1786 if err != nil { 1787 t.Fatal(err) 1788 } 1789 tc := testCases[currentTestIdx] 1790 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1791 t.Errorf("RequestBody \n%s; want \n%s", body, tc.Want.RequestBody) 1792 } 1793 w.Header().Set("X-Line-Request-Id", tc.RequestID) 1794 w.WriteHeader(tc.ResponseCode) 1795 w.Write(tc.Response) 1796 })) 1797 defer server.Close() 1798 1799 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1800 defer r.Body.Close() 1801 t.Error("Unexpected data API call") 1802 w.WriteHeader(404) 1803 w.Write([]byte(`{"message":"Not found"}`)) 1804 })) 1805 defer dataServer.Close() 1806 1807 client, err := mockClient(server, dataServer) 1808 if err != nil { 1809 t.Fatal(err) 1810 } 1811 for i, tc := range testCases { 1812 currentTestIdx = i 1813 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1814 narrowCast := client.Narrowcast(tc.Messages...) 1815 if tc.Recipient != nil { 1816 narrowCast = narrowCast.WithRecipient(tc.Recipient) 1817 } 1818 if tc.Demographic != nil { 1819 narrowCast = narrowCast.WithDemographic(tc.Demographic) 1820 } 1821 if tc.Max > 0 { 1822 narrowCast = narrowCast.WithLimitMax(tc.Max) 1823 } 1824 if tc.UpToRemainingQuota { 1825 narrowCast = narrowCast.WithLimitMaxUpToRemainingQuota(tc.Max, tc.UpToRemainingQuota) 1826 } 1827 res, err := narrowCast.Do() 1828 if tc.Want.Error != nil { 1829 if !reflect.DeepEqual(err, tc.Want.Error) { 1830 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1831 } 1832 } else { 1833 if err != nil { 1834 t.Error(err) 1835 } 1836 } 1837 if tc.Want.Response != nil { 1838 if !reflect.DeepEqual(res, tc.Want.Response) { 1839 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1840 } 1841 } 1842 }) 1843 } 1844 } 1845 1846 func BenchmarkPushMessages(b *testing.B) { 1847 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1848 defer r.Body.Close() 1849 w.Write([]byte("{}")) 1850 })) 1851 defer server.Close() 1852 1853 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1854 defer r.Body.Close() 1855 b.Error("Unexpected data API call") 1856 w.WriteHeader(404) 1857 w.Write([]byte(`{"message":"Not found"}`)) 1858 })) 1859 defer dataServer.Close() 1860 1861 client, err := mockClient(server, dataServer) 1862 if err != nil { 1863 b.Fatal(err) 1864 } 1865 b.ResetTimer() 1866 for i := 0; i < b.N; i++ { 1867 client.PushMessage("U0cc15697597f61dd8b01cea8b027050e", NewTextMessage("Hello, world")).Do() 1868 } 1869 } 1870 1871 func TestMessagesWithRetryKey(t *testing.T) { 1872 type testMethod interface { 1873 Do() (*BasicResponse, error) 1874 } 1875 toUserIDs := []string{ 1876 "U0cc15697597f61dd8b01cea8b027050e", 1877 "U38ecbecfade326557b6971140741a4a6", 1878 } 1879 var msgUUIDs = "123e4567-e89b-12d3-a456-426655440002" 1880 1881 type want struct { 1882 RequestBody []byte 1883 Response *BasicResponse 1884 Error error 1885 } 1886 testCases := []struct { 1887 Label string 1888 TestMethod testMethod 1889 Messages []SendingMessage 1890 Response []byte 1891 ResponseCode int 1892 Want want 1893 }{ 1894 { 1895 Label: "A text message for Push Message", 1896 TestMethod: new(PushMessageCall), 1897 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1898 ResponseCode: 200, 1899 Response: []byte(`{}`), 1900 Want: want{ 1901 RequestBody: []byte(`{"to":"U0cc15697597f61dd8b01cea8b027050e","messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 1902 Response: &BasicResponse{}, 1903 }, 1904 }, 1905 { 1906 Label: "A text message for Multicast", 1907 TestMethod: new(MulticastCall), 1908 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1909 ResponseCode: 200, 1910 Response: []byte(`{}`), 1911 Want: want{ 1912 RequestBody: []byte(`{"to":["U0cc15697597f61dd8b01cea8b027050e","U38ecbecfade326557b6971140741a4a6"],"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 1913 Response: &BasicResponse{}, 1914 }, 1915 }, 1916 { 1917 Label: "A text message for Narrowcast", 1918 TestMethod: new(NarrowcastCall), 1919 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1920 ResponseCode: 200, 1921 Response: []byte(`{}`), 1922 Want: want{ 1923 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 1924 Response: &BasicResponse{}, 1925 }, 1926 }, 1927 { 1928 Label: "A text message for Broadcast", 1929 TestMethod: new(BroadcastMessageCall), 1930 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1931 ResponseCode: 200, 1932 Response: []byte(`{}`), 1933 Want: want{ 1934 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 1935 Response: &BasicResponse{}, 1936 }, 1937 }, 1938 } 1939 1940 var currentTestIdx int 1941 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1942 defer r.Body.Close() 1943 if r.Method != http.MethodPost { 1944 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1945 } 1946 body, err := io.ReadAll(r.Body) 1947 if err != nil { 1948 t.Fatal(err) 1949 } 1950 tc := testCases[currentTestIdx] 1951 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1952 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1953 } 1954 w.WriteHeader(tc.ResponseCode) 1955 w.Write(tc.Response) 1956 })) 1957 defer server.Close() 1958 1959 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1960 defer r.Body.Close() 1961 t.Error("Unexpected data API call") 1962 w.WriteHeader(404) 1963 w.Write([]byte(`{"message":"Not found"}`)) 1964 })) 1965 defer dataServer.Close() 1966 1967 client, err := mockClient(server, dataServer) 1968 if err != nil { 1969 t.Fatal(err) 1970 } 1971 var res *BasicResponse 1972 for i, tc := range testCases { 1973 currentTestIdx = i 1974 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1975 switch tc.TestMethod.(type) { 1976 case *PushMessageCall: 1977 res, err = client.PushMessage(toUserIDs[0], tc.Messages...).WithRetryKey(msgUUIDs).Do() 1978 case *MulticastCall: 1979 res, err = client.Multicast(toUserIDs, tc.Messages...).WithRetryKey(msgUUIDs).Do() 1980 case *NarrowcastCall: 1981 res, err = client.Narrowcast(tc.Messages...).WithRetryKey(msgUUIDs).Do() 1982 case *BroadcastMessageCall: 1983 res, err = client.BroadcastMessage(tc.Messages...).WithRetryKey(msgUUIDs).Do() 1984 } 1985 if tc.Want.Error != nil { 1986 if !reflect.DeepEqual(err, tc.Want.Error) { 1987 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1988 } 1989 } else { 1990 if err != nil { 1991 t.Error(err) 1992 } 1993 } 1994 if tc.Want.Response != nil { 1995 if !reflect.DeepEqual(res, tc.Want.Response) { 1996 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1997 } 1998 } 1999 }) 2000 } 2001 } 2002 2003 func BenchmarkReplyMessages(b *testing.B) { 2004 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2005 defer r.Body.Close() 2006 w.Write([]byte("{}")) 2007 })) 2008 defer server.Close() 2009 2010 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2011 defer r.Body.Close() 2012 b.Error("Unexpected data API call") 2013 w.WriteHeader(404) 2014 w.Write([]byte(`{"message":"Not found"}`)) 2015 })) 2016 defer dataServer.Close() 2017 2018 client, err := mockClient(server, dataServer) 2019 if err != nil { 2020 b.Fatal(err) 2021 } 2022 b.ResetTimer() 2023 for i := 0; i < b.N; i++ { 2024 client.ReplyMessage("nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", NewTextMessage("Hello, world")).Do() 2025 } 2026 } 2027 2028 func BenchmarkMulticast(b *testing.B) { 2029 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2030 defer r.Body.Close() 2031 w.Write([]byte("{}")) 2032 })) 2033 defer server.Close() 2034 2035 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2036 defer r.Body.Close() 2037 b.Error("Unexpected data API call") 2038 w.WriteHeader(404) 2039 w.Write([]byte(`{"message":"Not found"}`)) 2040 })) 2041 defer dataServer.Close() 2042 2043 client, err := mockClient(server, dataServer) 2044 if err != nil { 2045 b.Fatal(err) 2046 } 2047 b.ResetTimer() 2048 for i := 0; i < b.N; i++ { 2049 client.Multicast([]string{"U0cc15697597f61dd8b01cea8b027050e", "U38ecbecfade326557b6971140741a4a6"}, NewTextMessage("Hello, world")).Do() 2050 } 2051 } 2052 2053 func BenchmarkBroadcastMessage(b *testing.B) { 2054 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2055 defer r.Body.Close() 2056 w.Write([]byte("{}")) 2057 })) 2058 defer server.Close() 2059 2060 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2061 defer r.Body.Close() 2062 b.Error("Unexpected data API call") 2063 w.WriteHeader(404) 2064 w.Write([]byte(`{"message":"Not found"}`)) 2065 })) 2066 defer dataServer.Close() 2067 2068 client, err := mockClient(server, dataServer) 2069 if err != nil { 2070 b.Fatal(err) 2071 } 2072 b.ResetTimer() 2073 for i := 0; i < b.N; i++ { 2074 client.BroadcastMessage(NewTextMessage("Hello, world")).Do() 2075 } 2076 }