github.com/line/line-bot-sdk-go/v7@v7.21.0/linebot/validate_message_test.go (about) 1 // Copyright 2022 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 "io" 19 "net/http" 20 "net/http/httptest" 21 "reflect" 22 "strconv" 23 "testing" 24 ) 25 26 func TestValidatePushMessages(t *testing.T) { 27 type want struct { 28 RequestBody []byte 29 Response *BasicResponse 30 Error error 31 } 32 testCases := []struct { 33 Label string 34 Messages []SendingMessage 35 Response []byte 36 ResponseCode int 37 Want want 38 }{ 39 { 40 Label: "A text message", 41 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 42 ResponseCode: 200, 43 Response: []byte(`{}`), 44 Want: want{ 45 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 46 Response: &BasicResponse{}, 47 }, 48 }, 49 { 50 Label: "A image message", 51 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 52 ResponseCode: 200, 53 Response: []byte(`{}`), 54 Want: want{ 55 RequestBody: []byte(`{"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 56 Response: &BasicResponse{}, 57 }, 58 }, 59 { 60 Label: "A video message", 61 Messages: []SendingMessage{NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg")}, 62 ResponseCode: 200, 63 Response: []byte(`{}`), 64 Want: want{ 65 RequestBody: []byte(`{"messages":[{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 66 Response: &BasicResponse{}, 67 }, 68 }, 69 { 70 Label: "A audio message", 71 Messages: []SendingMessage{NewAudioMessage("https://example.com/original.m4a", 1000)}, 72 ResponseCode: 200, 73 Response: []byte(`{}`), 74 Want: want{ 75 RequestBody: []byte(`{"messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000}]}` + "\n"), 76 Response: &BasicResponse{}, 77 }, 78 }, 79 { 80 Label: "A location message", 81 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 82 ResponseCode: 200, 83 Response: []byte(`{}`), 84 Want: want{ 85 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 86 Response: &BasicResponse{}, 87 }, 88 }, 89 { 90 Label: "A sticker message", 91 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 92 ResponseCode: 200, 93 Response: []byte(`{}`), 94 Want: want{ 95 RequestBody: []byte(`{"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 96 Response: &BasicResponse{}, 97 }, 98 }, 99 { 100 Label: "A buttons template message", 101 Messages: []SendingMessage{ 102 NewTemplateMessage( 103 "this is a buttons template", 104 NewButtonsTemplate( 105 "https://example.com/bot/images/image.jpg", 106 "Menu", 107 "Please select", 108 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 109 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 110 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionCloseRichMenu, ""), 111 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 112 NewURIAction("View detail", "https://example.com/page/123"), 113 ), 114 ), 115 }, 116 ResponseCode: 200, 117 Response: []byte(`{}`), 118 Want: want{ 119 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":"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"), 120 Response: &BasicResponse{}, 121 }, 122 }, 123 { 124 Label: "A buttons template message with datetimepicker action", 125 Messages: []SendingMessage{ 126 NewTemplateMessage( 127 "this is a buttons template", 128 NewButtonsTemplate( 129 "https://example.com/bot/images/image.jpg", 130 "Menu", 131 "Please select a date, time or datetime", 132 NewDatetimePickerAction("Date", "action=sel&only=date", "date", "2017-09-01", "2017-09-03", ""), 133 NewDatetimePickerAction("Time", "action=sel&only=time", "time", "", "23:59", "00:00"), 134 NewDatetimePickerAction("DateTime", "action=sel", "datetime", "2017-09-01T12:00", "", ""), 135 ), 136 ), 137 }, 138 ResponseCode: 200, 139 Response: []byte(`{}`), 140 Want: want{ 141 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"), 142 Response: &BasicResponse{}, 143 }, 144 }, 145 { 146 Label: "A buttons template message without thumbnailImageURL", 147 Messages: []SendingMessage{ 148 NewTemplateMessage( 149 "this is a buttons template", 150 NewButtonsTemplate( 151 "", 152 "Menu", 153 "Please select", 154 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 155 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 156 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenRichMenu, ""), 157 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 158 NewURIAction("View detail", "https://example.com/page/123"), 159 ), 160 ), 161 }, 162 ResponseCode: 200, 163 Response: []byte(`{}`), 164 Want: want{ 165 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":"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"), 166 Response: &BasicResponse{}, 167 }, 168 }, 169 { 170 Label: "A buttons template message without title", 171 Messages: []SendingMessage{ 172 NewTemplateMessage( 173 "this is a buttons template", 174 NewButtonsTemplate( 175 "https://example.com/bot/images/image.jpg", 176 "", 177 "Please select", 178 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 179 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 180 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenVoice, ""), 181 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 182 NewURIAction("View detail", "https://example.com/page/123"), 183 ), 184 ), 185 }, 186 ResponseCode: 200, 187 Response: []byte(`{}`), 188 Want: want{ 189 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":"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"), 190 Response: &BasicResponse{}, 191 }, 192 }, 193 { 194 Label: "A buttons template message without title, with image options", 195 Messages: []SendingMessage{ 196 NewTemplateMessage( 197 "this is a buttons template", 198 NewButtonsTemplate( 199 "https://example.com/bot/images/image.jpg", 200 "", 201 "Please select", 202 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 203 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 204 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, ""), 205 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 206 NewURIAction("View detail", "https://example.com/page/123"), 207 ).WithImageOptions("rectangle", "cover", "#FFFFFF"), 208 ), 209 }, 210 ResponseCode: 200, 211 Response: []byte(`{}`), 212 Want: want{ 213 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":"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"), 214 Response: &BasicResponse{}, 215 }, 216 }, 217 { 218 Label: "A buttons template message without thumbnailImageURL and title", 219 Messages: []SendingMessage{ 220 NewTemplateMessage( 221 "this is a buttons template", 222 NewButtonsTemplate( 223 "", 224 "", 225 "Please select", 226 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 227 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 228 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 229 NewURIAction("View detail", "https://example.com/page/123"), 230 ), 231 ), 232 }, 233 ResponseCode: 200, 234 Response: []byte(`{}`), 235 Want: want{ 236 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":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/123"}]}}]}` + "\n"), 237 Response: &BasicResponse{}, 238 }, 239 }, 240 { 241 Label: "A confirm template message", 242 Messages: []SendingMessage{ 243 NewTemplateMessage( 244 "this is a confirm template", 245 NewConfirmTemplate( 246 "Are you sure?", 247 NewMessageAction("Yes", "yes"), 248 NewMessageAction("No", "no"), 249 ), 250 ), 251 }, 252 ResponseCode: 200, 253 Response: []byte(`{}`), 254 Want: want{ 255 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"), 256 Response: &BasicResponse{}, 257 }, 258 }, 259 { 260 Label: "A carousel template message", 261 Messages: []SendingMessage{ 262 NewTemplateMessage( 263 "this is a carousel template", 264 NewCarouselTemplate( 265 NewCarouselColumn( 266 "https://example.com/bot/images/item1.jpg", 267 "this is menu", 268 "description", 269 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 270 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 271 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 272 NewURIAction("View detail", "https://example.com/page/111"), 273 ), 274 ), 275 ), 276 }, 277 ResponseCode: 200, 278 Response: []byte(`{}`), 279 Want: want{ 280 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":"postback","label":"Buy","data":"action=buy\u0026itemid=123","inputOption":"openKeyboard","fillInText":"text"},{"type":"uri","label":"View detail","uri":"https://example.com/page/111"}]}]}}]}` + "\n"), 281 Response: &BasicResponse{}, 282 }, 283 }, 284 { 285 Label: "A carousel template message, with new image options", 286 Messages: []SendingMessage{ 287 NewTemplateMessage( 288 "this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor", 289 NewCarouselTemplate( 290 NewCarouselColumn( 291 "https://example.com/bot/images/item1.jpg", 292 "this is menu", 293 "description", 294 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 295 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 296 NewPostbackAction("Buy", "action=buy&itemid=123", "", "", InputOptionOpenKeyboard, "text"), 297 NewURIAction("View detail", "https://example.com/page/111"), 298 ).WithImageOptions("#FFFFFF"), 299 ).WithImageOptions("rectangle", "cover"), 300 ), 301 }, 302 ResponseCode: 200, 303 Response: []byte(`{}`), 304 Want: want{ 305 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":"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"), 306 Response: &BasicResponse{}, 307 }, 308 }, 309 { 310 Label: "A imagecarousel template message", 311 Messages: []SendingMessage{ 312 NewTemplateMessage( 313 "this is a image carousel template", 314 NewImageCarouselTemplate( 315 NewImageCarouselColumn( 316 "https://example.com/bot/images/item1.jpg", 317 NewURIAction("View detail", "https://example.com/page/111"), 318 ), 319 ), 320 ), 321 }, 322 ResponseCode: 200, 323 Response: []byte(`{}`), 324 Want: want{ 325 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"), 326 Response: &BasicResponse{}, 327 }, 328 }, 329 { 330 Label: "A imagemap message", 331 Messages: []SendingMessage{ 332 NewImagemapMessage( 333 "https://example.com/bot/images/rm001", 334 "this is an imagemap", 335 ImagemapBaseSize{1040, 1040}, 336 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 337 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 338 ), 339 }, 340 ResponseCode: 200, 341 Response: []byte(`{}`), 342 Want: want{ 343 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"), 344 Response: &BasicResponse{}, 345 }, 346 }, 347 { 348 Label: "A imagemap messages with video 1", 349 Messages: []SendingMessage{ 350 NewImagemapMessage( 351 "https://example.com/bot/images/rm001", 352 "this is an imagemap with video", 353 ImagemapBaseSize{1040, 1040}, 354 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 355 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 356 ).WithVideo(&ImagemapVideo{ 357 OriginalContentURL: "https://example.com/original.mp4", 358 PreviewImageURL: "https://example.com/preview.jpg", 359 Area: ImagemapArea{10, 10, 100, 200}, 360 }), 361 }, 362 ResponseCode: 200, 363 Response: []byte(`{}`), 364 Want: want{ 365 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"), 366 Response: &BasicResponse{}, 367 }, 368 }, 369 { 370 Label: "A imagemap messages with video 2", 371 Messages: []SendingMessage{ 372 NewImagemapMessage( 373 "https://example.com/bot/images/rm001", 374 "this is an imagemap with video and external link", 375 ImagemapBaseSize{1040, 1040}, 376 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 377 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 378 ).WithVideo(&ImagemapVideo{ 379 OriginalContentURL: "https://example.com/original.mp4", 380 PreviewImageURL: "https://example.com/preview.jpg", 381 Area: ImagemapArea{10, 10, 100, 200}, 382 ExternalLink: &ImagemapVideoExternalLink{ 383 LinkURI: "https://example.com/", 384 Label: "external link", 385 }, 386 }), 387 }, 388 ResponseCode: 200, 389 Response: []byte(`{}`), 390 Want: want{ 391 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"), 392 Response: &BasicResponse{}, 393 }, 394 }, 395 { 396 Label: "A flex message", 397 Messages: []SendingMessage{ 398 NewFlexMessage( 399 "this is a flex message", 400 &BubbleContainer{ 401 Type: FlexContainerTypeBubble, 402 Body: &BoxComponent{ 403 Type: FlexComponentTypeBox, 404 Layout: FlexBoxLayoutTypeVertical, 405 Contents: []FlexComponent{ 406 &TextComponent{ 407 Type: FlexComponentTypeText, 408 Text: "hello", 409 }, 410 &TextComponent{ 411 Type: FlexComponentTypeText, 412 Text: "world", 413 Flex: IntPtr(0), 414 }, 415 &SpacerComponent{ 416 Type: FlexComponentTypeSpacer, 417 }, 418 }, 419 }, 420 }, 421 ), 422 }, 423 ResponseCode: 200, 424 Response: []byte(`{}`), 425 Want: want{ 426 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"), 427 Response: &BasicResponse{}, 428 }, 429 }, 430 { 431 Label: "A text message with quick replies", 432 Messages: []SendingMessage{ 433 NewTextMessage( 434 "Select your favorite food category or send me your location!", 435 ).WithQuickReplies( 436 NewQuickReplyItems( 437 NewQuickReplyButton("https://example.com/sushi.png", NewMessageAction("Sushi", "Sushi")), 438 NewQuickReplyButton("https://example.com/tempura.png", NewMessageAction("Tempura", "Tempura")), 439 NewQuickReplyButton("", NewLocationAction("Send location")), 440 NewQuickReplyButton("", NewCameraAction("Open camera")), 441 NewQuickReplyButton("", NewURIAction("Phone order", "tel:09001234567")), 442 ), 443 ), 444 }, 445 ResponseCode: 200, 446 Response: []byte(`{}`), 447 Want: want{ 448 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"}},{"type":"action","action":{"type":"camera","label":"Open camera"}},{"type":"action","action":{"type":"uri","label":"Phone order","uri":"tel:09001234567"}}]}}]}` + "\n"), 449 Response: &BasicResponse{}, 450 }, 451 }, 452 { 453 Label: "Multiple messages", 454 Messages: []SendingMessage{NewTextMessage("Hello, world1"), NewTextMessage("Hello, world2")}, 455 ResponseCode: 200, 456 Response: []byte(`{}`), 457 Want: want{ 458 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world1"},{"type":"text","text":"Hello, world2"}]}` + "\n"), 459 Response: &BasicResponse{}, 460 }, 461 }, 462 { 463 Label: "Bad request", 464 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 465 ResponseCode: 400, 466 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"}]}`), 467 Want: want{ 468 RequestBody: []byte(`{"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 469 Error: &APIError{ 470 Code: 400, 471 Response: &ErrorResponse{ 472 Message: "Request body has 2 error(s).", 473 Details: []errorResponseDetail{ 474 { 475 Message: "may not be empty", 476 Property: "messages[0].text", 477 }, 478 { 479 Message: "may not be empty", 480 Property: "messages[1].text", 481 }, 482 }, 483 }, 484 }, 485 }, 486 }, 487 } 488 489 var currentTestIdx int 490 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 491 defer r.Body.Close() 492 tc := testCases[currentTestIdx] 493 if r.Method != http.MethodPost { 494 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 495 } 496 if r.URL.Path != APIEndpointValidatePushMessage { 497 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidatePushMessage) 498 } 499 body, err := io.ReadAll(r.Body) 500 if err != nil { 501 t.Fatal(err) 502 } 503 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 504 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 505 } 506 w.WriteHeader(tc.ResponseCode) 507 w.Write(tc.Response) 508 })) 509 defer server.Close() 510 511 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 512 defer r.Body.Close() 513 t.Error("Unexpected data API call") 514 w.WriteHeader(404) 515 w.Write([]byte(`{"message":"Not found"}`)) 516 })) 517 defer dataServer.Close() 518 519 client, err := mockClient(server, dataServer) 520 if err != nil { 521 t.Fatal(err) 522 } 523 for i, tc := range testCases { 524 currentTestIdx = i 525 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 526 res, err := client.ValidatePushMessage(tc.Messages...).Do() 527 if tc.Want.Error != nil { 528 if !reflect.DeepEqual(err, tc.Want.Error) { 529 t.Errorf("Error %v; want %v", err, tc.Want.Error) 530 } 531 } else { 532 if err != nil { 533 t.Error(err) 534 } 535 } 536 if tc.Want.Response != nil { 537 if !reflect.DeepEqual(res, tc.Want.Response) { 538 t.Errorf("Response %v; want %v", res, tc.Want.Response) 539 } 540 } 541 }) 542 } 543 } 544 545 func TestValidateReplyMessages(t *testing.T) { 546 type want struct { 547 RequestBody []byte 548 Response *BasicResponse 549 Error error 550 } 551 testCases := []struct { 552 Label string 553 Messages []SendingMessage 554 Response []byte 555 ResponseCode int 556 Want want 557 }{ 558 { 559 Label: "A text message", 560 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 561 ResponseCode: 200, 562 Response: []byte(`{}`), 563 Want: want{ 564 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 565 Response: &BasicResponse{}, 566 }, 567 }, 568 { 569 Label: "A location message", 570 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 571 ResponseCode: 200, 572 Response: []byte(`{}`), 573 Want: want{ 574 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 575 Response: &BasicResponse{}, 576 }, 577 }, 578 { 579 Label: "A image message", 580 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 581 ResponseCode: 200, 582 Response: []byte(`{}`), 583 Want: want{ 584 RequestBody: []byte(`{"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 585 Response: &BasicResponse{}, 586 }, 587 }, 588 { 589 Label: "A sticker message", 590 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 591 ResponseCode: 200, 592 Response: []byte(`{}`), 593 Want: want{ 594 RequestBody: []byte(`{"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 595 Response: &BasicResponse{}, 596 }, 597 }, 598 { 599 Label: "Bad request", 600 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 601 ResponseCode: 400, 602 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"}]}`), 603 Want: want{ 604 RequestBody: []byte(`{"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 605 Error: &APIError{ 606 Code: 400, 607 Response: &ErrorResponse{ 608 Message: "Request body has 2 error(s).", 609 Details: []errorResponseDetail{ 610 { 611 Message: "may not be empty", 612 Property: "messages[0].text", 613 }, 614 { 615 Message: "may not be empty", 616 Property: "messages[1].text", 617 }, 618 }, 619 }, 620 }, 621 }, 622 }, 623 } 624 625 var currentTestIdx int 626 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 627 defer r.Body.Close() 628 if r.Method != http.MethodPost { 629 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 630 } 631 if r.URL.Path != APIEndpointValidateReplyMessage { 632 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidateReplyMessage) 633 } 634 body, err := io.ReadAll(r.Body) 635 if err != nil { 636 t.Fatal(err) 637 } 638 tc := testCases[currentTestIdx] 639 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 640 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 641 } 642 w.WriteHeader(tc.ResponseCode) 643 w.Write(tc.Response) 644 })) 645 defer server.Close() 646 647 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 648 defer r.Body.Close() 649 t.Error("Unexpected data API call") 650 w.WriteHeader(404) 651 w.Write([]byte(`{"message":"Not found"}`)) 652 })) 653 defer dataServer.Close() 654 655 client, err := mockClient(server, dataServer) 656 if err != nil { 657 t.Fatal(err) 658 } 659 for i, tc := range testCases { 660 currentTestIdx = i 661 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 662 res, err := client.ValidateReplyMessage(tc.Messages...).Do() 663 if tc.Want.Error != nil { 664 if !reflect.DeepEqual(err, tc.Want.Error) { 665 t.Errorf("Error %v; want %v", err, tc.Want.Error) 666 } 667 } else { 668 if err != nil { 669 t.Error(err) 670 } 671 } 672 if tc.Want.Response != nil { 673 if !reflect.DeepEqual(res, tc.Want.Response) { 674 t.Errorf("Response %v; want %v", res, tc.Want.Response) 675 } 676 } 677 }) 678 } 679 } 680 681 func TestValidateMulticastMessages(t *testing.T) { 682 type want struct { 683 RequestBody []byte 684 Response *BasicResponse 685 Error error 686 } 687 testCases := []struct { 688 Label string 689 Messages []SendingMessage 690 Response []byte 691 ResponseCode int 692 Want want 693 }{ 694 { 695 Label: "A text message", 696 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 697 ResponseCode: 200, 698 Response: []byte(`{}`), 699 Want: want{ 700 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 701 Response: &BasicResponse{}, 702 }, 703 }, 704 { 705 Label: "A location message", 706 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 707 ResponseCode: 200, 708 Response: []byte(`{}`), 709 Want: want{ 710 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 711 Response: &BasicResponse{}, 712 }, 713 }, 714 { 715 Label: "A image message", 716 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 717 ResponseCode: 200, 718 Response: []byte(`{}`), 719 Want: want{ 720 RequestBody: []byte(`{"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 721 Response: &BasicResponse{}, 722 }, 723 }, 724 { 725 Label: "A sticker message", 726 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 727 ResponseCode: 200, 728 Response: []byte(`{}`), 729 Want: want{ 730 RequestBody: []byte(`{"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 731 Response: &BasicResponse{}, 732 }, 733 }, 734 { 735 Label: "Bad request", 736 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 737 ResponseCode: 400, 738 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"}]}`), 739 Want: want{ 740 RequestBody: []byte(`{"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 741 Error: &APIError{ 742 Code: 400, 743 Response: &ErrorResponse{ 744 Message: "Request body has 2 error(s).", 745 Details: []errorResponseDetail{ 746 { 747 Message: "may not be empty", 748 Property: "messages[0].text", 749 }, 750 { 751 Message: "may not be empty", 752 Property: "messages[1].text", 753 }, 754 }, 755 }, 756 }, 757 }, 758 }, 759 } 760 761 var currentTestIdx int 762 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 763 defer r.Body.Close() 764 if r.Method != http.MethodPost { 765 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 766 } 767 if r.URL.Path != APIEndpointValidateMulticastMessage { 768 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidateMulticastMessage) 769 } 770 body, err := io.ReadAll(r.Body) 771 if err != nil { 772 t.Fatal(err) 773 } 774 tc := testCases[currentTestIdx] 775 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 776 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 777 } 778 w.WriteHeader(tc.ResponseCode) 779 w.Write(tc.Response) 780 })) 781 defer server.Close() 782 783 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 784 defer r.Body.Close() 785 t.Error("Unexpected data API call") 786 w.WriteHeader(404) 787 w.Write([]byte(`{"message":"Not found"}`)) 788 })) 789 defer dataServer.Close() 790 791 client, err := mockClient(server, dataServer) 792 if err != nil { 793 t.Fatal(err) 794 } 795 for i, tc := range testCases { 796 currentTestIdx = i 797 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 798 res, err := client.ValidateMulticastMessage(tc.Messages...).Do() 799 if tc.Want.Error != nil { 800 if !reflect.DeepEqual(err, tc.Want.Error) { 801 t.Errorf("Error %v; want %v", err, tc.Want.Error) 802 } 803 } else { 804 if err != nil { 805 t.Error(err) 806 } 807 } 808 if tc.Want.Response != nil { 809 if !reflect.DeepEqual(res, tc.Want.Response) { 810 t.Errorf("Response %v; want %v", res, tc.Want.Response) 811 } 812 } 813 }) 814 } 815 } 816 817 func TestValidateBroadcastMessages(t *testing.T) { 818 type want struct { 819 RequestBody []byte 820 Response *BasicResponse 821 Error error 822 } 823 testCases := []struct { 824 Messages []SendingMessage 825 Response []byte 826 ResponseCode int 827 Want want 828 }{ 829 { 830 // A text message 831 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 832 ResponseCode: 200, 833 Response: []byte(`{}`), 834 Want: want{ 835 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 836 Response: &BasicResponse{}, 837 }, 838 }, 839 { 840 // A image message 841 Messages: []SendingMessage{NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg")}, 842 ResponseCode: 200, 843 Response: []byte(`{}`), 844 Want: want{ 845 RequestBody: []byte(`{"messages":[{"type":"image","originalContentUrl":"https://example.com/original.jpg","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 846 Response: &BasicResponse{}, 847 }, 848 }, 849 { 850 // A video message 851 Messages: []SendingMessage{NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg")}, 852 ResponseCode: 200, 853 Response: []byte(`{}`), 854 Want: want{ 855 RequestBody: []byte(`{"messages":[{"type":"video","originalContentUrl":"https://example.com/original.mp4","previewImageUrl":"https://example.com/preview.jpg"}]}` + "\n"), 856 Response: &BasicResponse{}, 857 }, 858 }, 859 { 860 // A audio message 861 Messages: []SendingMessage{NewAudioMessage("https://example.com/original.m4a", 1000)}, 862 ResponseCode: 200, 863 Response: []byte(`{}`), 864 Want: want{ 865 RequestBody: []byte(`{"messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000}]}` + "\n"), 866 Response: &BasicResponse{}, 867 }, 868 }, 869 { 870 // A location message 871 Messages: []SendingMessage{NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203)}, 872 ResponseCode: 200, 873 Response: []byte(`{}`), 874 Want: want{ 875 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203}]}` + "\n"), 876 Response: &BasicResponse{}, 877 }, 878 }, 879 { 880 // A sticker message 881 Messages: []SendingMessage{NewStickerMessage("1", "1")}, 882 ResponseCode: 200, 883 Response: []byte(`{}`), 884 Want: want{ 885 RequestBody: []byte(`{"messages":[{"type":"sticker","packageId":"1","stickerId":"1"}]}` + "\n"), 886 Response: &BasicResponse{}, 887 }, 888 }, 889 { 890 // A buttons template message 891 Messages: []SendingMessage{ 892 NewTemplateMessage( 893 "this is a buttons template", 894 NewButtonsTemplate( 895 "https://example.com/bot/images/image.jpg", 896 "Menu", 897 "Please select", 898 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 899 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 900 NewURIAction("View detail", "https://example.com/page/123"), 901 ), 902 ), 903 }, 904 ResponseCode: 200, 905 Response: []byte(`{}`), 906 Want: want{ 907 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"), 908 Response: &BasicResponse{}, 909 }, 910 }, 911 { 912 // A buttons template message with datetimepicker action 913 Messages: []SendingMessage{ 914 NewTemplateMessage( 915 "this is a buttons template", 916 NewButtonsTemplate( 917 "https://example.com/bot/images/image.jpg", 918 "Menu", 919 "Please select a date, time or datetime", 920 NewDatetimePickerAction("Date", "action=sel&only=date", "date", "2017-09-01", "2017-09-03", ""), 921 NewDatetimePickerAction("Time", "action=sel&only=time", "time", "", "23:59", "00:00"), 922 NewDatetimePickerAction("DateTime", "action=sel", "datetime", "2017-09-01T12:00", "", ""), 923 ), 924 ), 925 }, 926 ResponseCode: 200, 927 Response: []byte(`{}`), 928 Want: want{ 929 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"), 930 Response: &BasicResponse{}, 931 }, 932 }, 933 { 934 // A buttons template message without thumbnailImageURL 935 Messages: []SendingMessage{ 936 NewTemplateMessage( 937 "this is a buttons template", 938 NewButtonsTemplate( 939 "", 940 "Menu", 941 "Please select", 942 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 943 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 944 NewURIAction("View detail", "https://example.com/page/123"), 945 ), 946 ), 947 }, 948 ResponseCode: 200, 949 Response: []byte(`{}`), 950 Want: want{ 951 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"), 952 Response: &BasicResponse{}, 953 }, 954 }, 955 { 956 // A buttons template message without title 957 Messages: []SendingMessage{ 958 NewTemplateMessage( 959 "this is a buttons template", 960 NewButtonsTemplate( 961 "https://example.com/bot/images/image.jpg", 962 "", 963 "Please select", 964 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 965 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 966 NewURIAction("View detail", "https://example.com/page/123"), 967 ), 968 ), 969 }, 970 ResponseCode: 200, 971 Response: []byte(`{}`), 972 Want: want{ 973 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"), 974 Response: &BasicResponse{}, 975 }, 976 }, 977 { 978 // A buttons template message without title, with image options and default action 979 Messages: []SendingMessage{ 980 NewTemplateMessage( 981 "this is a buttons template", 982 NewButtonsTemplate( 983 "https://example.com/bot/images/image.jpg", 984 "", 985 "Please select", 986 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 987 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 988 NewURIAction("View detail", "https://example.com/page/123"), 989 ).WithImageOptions( 990 "rectangle", 991 "cover", 992 "#FFFFFF", 993 ).WithDefaultAction( 994 NewURIAction("View detail", "https://example.com/page/123"), 995 ), 996 ), 997 }, 998 ResponseCode: 200, 999 Response: []byte(`{}`), 1000 Want: want{ 1001 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"), 1002 Response: &BasicResponse{}, 1003 }, 1004 }, 1005 { 1006 // A buttons template message without thumbnailImageURL and title 1007 Messages: []SendingMessage{ 1008 NewTemplateMessage( 1009 "this is a buttons template", 1010 NewButtonsTemplate( 1011 "", 1012 "", 1013 "Please select", 1014 NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText", "", ""), 1015 NewPostbackAction("Buy", "action=buy&itemid=123", "text", "", "", ""), 1016 NewURIAction("View detail", "https://example.com/page/123"), 1017 ), 1018 ), 1019 }, 1020 ResponseCode: 200, 1021 Response: []byte(`{}`), 1022 Want: want{ 1023 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"), 1024 Response: &BasicResponse{}, 1025 }, 1026 }, 1027 { 1028 // A confirm template message 1029 Messages: []SendingMessage{ 1030 NewTemplateMessage( 1031 "this is a confirm template", 1032 NewConfirmTemplate( 1033 "Are you sure?", 1034 NewMessageAction("Yes", "yes"), 1035 NewMessageAction("No", "no"), 1036 ), 1037 ), 1038 }, 1039 ResponseCode: 200, 1040 Response: []byte(`{}`), 1041 Want: want{ 1042 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"), 1043 Response: &BasicResponse{}, 1044 }, 1045 }, 1046 { 1047 // A carousel template message 1048 Messages: []SendingMessage{ 1049 NewTemplateMessage( 1050 "this is a carousel template", 1051 NewCarouselTemplate( 1052 NewCarouselColumn( 1053 "https://example.com/bot/images/item1.jpg", 1054 "this is menu", 1055 "description", 1056 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 1057 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 1058 NewURIAction("View detail", "https://example.com/page/111"), 1059 ), 1060 ), 1061 ), 1062 }, 1063 ResponseCode: 200, 1064 Response: []byte(`{}`), 1065 Want: want{ 1066 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"), 1067 Response: &BasicResponse{}, 1068 }, 1069 }, 1070 { 1071 // A carousel template message, with new image options and default action 1072 Messages: []SendingMessage{ 1073 NewTemplateMessage( 1074 "this is a carousel template with imageAspectRatio, imageSize and imageBackgroundColor", 1075 NewCarouselTemplate( 1076 NewCarouselColumn( 1077 "https://example.com/bot/images/item1.jpg", 1078 "this is menu", 1079 "description", 1080 NewPostbackAction("Buy", "action=buy&itemid=111", "", "", "", ""), 1081 NewPostbackAction("Add to cart", "action=add&itemid=111", "", "", "", ""), 1082 NewURIAction("View detail", "https://example.com/page/111"), 1083 ).WithImageOptions( 1084 "#FFFFFF", 1085 ).WithDefaultAction( 1086 NewURIAction("View detail", "http://example.com/page/123"), 1087 ), 1088 ).WithImageOptions("rectangle", "cover"), 1089 ), 1090 }, 1091 ResponseCode: 200, 1092 Response: []byte(`{}`), 1093 Want: want{ 1094 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"), 1095 Response: &BasicResponse{}, 1096 }, 1097 }, 1098 { 1099 // A imagecarousel template message 1100 Messages: []SendingMessage{ 1101 NewTemplateMessage( 1102 "this is a image carousel template", 1103 NewImageCarouselTemplate( 1104 NewImageCarouselColumn( 1105 "https://example.com/bot/images/item1.jpg", 1106 NewURIAction("View detail", "https://example.com/page/111"), 1107 ), 1108 ), 1109 ), 1110 }, 1111 ResponseCode: 200, 1112 Response: []byte(`{}`), 1113 Want: want{ 1114 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"), 1115 Response: &BasicResponse{}, 1116 }, 1117 }, 1118 { 1119 // A imagemap message 1120 Messages: []SendingMessage{ 1121 NewImagemapMessage( 1122 "https://example.com/bot/images/rm001", 1123 "this is an imagemap", 1124 ImagemapBaseSize{1040, 1040}, 1125 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1126 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1127 ), 1128 }, 1129 ResponseCode: 200, 1130 Response: []byte(`{}`), 1131 Want: want{ 1132 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"), 1133 Response: &BasicResponse{}, 1134 }, 1135 }, 1136 { 1137 // A imagemap messages with video 1 1138 Messages: []SendingMessage{ 1139 NewImagemapMessage( 1140 "https://example.com/bot/images/rm001", 1141 "this is an imagemap with video", 1142 ImagemapBaseSize{1040, 1040}, 1143 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1144 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1145 ).WithVideo(&ImagemapVideo{ 1146 OriginalContentURL: "https://example.com/original.mp4", 1147 PreviewImageURL: "https://example.com/preview.jpg", 1148 Area: ImagemapArea{10, 10, 100, 200}, 1149 }), 1150 }, 1151 ResponseCode: 200, 1152 Response: []byte(`{}`), 1153 Want: want{ 1154 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"), 1155 Response: &BasicResponse{}, 1156 }, 1157 }, 1158 { 1159 // A imagemap messages with video 2 1160 Messages: []SendingMessage{ 1161 NewImagemapMessage( 1162 "https://example.com/bot/images/rm001", 1163 "this is an imagemap with video and external link", 1164 ImagemapBaseSize{1040, 1040}, 1165 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1166 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1167 ).WithVideo(&ImagemapVideo{ 1168 OriginalContentURL: "https://example.com/original.mp4", 1169 PreviewImageURL: "https://example.com/preview.jpg", 1170 Area: ImagemapArea{10, 10, 100, 200}, 1171 ExternalLink: &ImagemapVideoExternalLink{ 1172 LinkURI: "https://example.com/", 1173 Label: "external link", 1174 }, 1175 }), 1176 }, 1177 ResponseCode: 200, 1178 Response: []byte(`{}`), 1179 Want: want{ 1180 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"), 1181 Response: &BasicResponse{}, 1182 }, 1183 }, 1184 { 1185 // A flex message 1186 Messages: []SendingMessage{ 1187 NewFlexMessage( 1188 "this is a flex message", 1189 &BubbleContainer{ 1190 Type: FlexContainerTypeBubble, 1191 Body: &BoxComponent{ 1192 Type: FlexComponentTypeBox, 1193 Layout: FlexBoxLayoutTypeVertical, 1194 Contents: []FlexComponent{ 1195 &TextComponent{ 1196 Type: FlexComponentTypeText, 1197 Text: "hello", 1198 }, 1199 &TextComponent{ 1200 Type: FlexComponentTypeText, 1201 Text: "world", 1202 Flex: IntPtr(0), 1203 }, 1204 &SpacerComponent{ 1205 Type: FlexComponentTypeSpacer, 1206 }, 1207 }, 1208 }, 1209 }, 1210 ), 1211 }, 1212 ResponseCode: 200, 1213 Response: []byte(`{}`), 1214 Want: want{ 1215 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"), 1216 Response: &BasicResponse{}, 1217 }, 1218 }, 1219 { 1220 // A text message with quick replies 1221 Messages: []SendingMessage{ 1222 NewTextMessage( 1223 "Select your favorite food category or send me your location!", 1224 ).WithQuickReplies( 1225 NewQuickReplyItems( 1226 NewQuickReplyButton("https://example.com/sushi.png", NewMessageAction("Sushi", "Sushi")), 1227 NewQuickReplyButton("https://example.com/tempura.png", NewMessageAction("Tempura", "Tempura")), 1228 NewQuickReplyButton("", NewLocationAction("Send location")), 1229 ), 1230 ), 1231 }, 1232 ResponseCode: 200, 1233 Response: []byte(`{}`), 1234 Want: want{ 1235 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"), 1236 Response: &BasicResponse{}, 1237 }, 1238 }, 1239 { 1240 // Multiple messages 1241 Messages: []SendingMessage{NewTextMessage("Hello, world1"), NewTextMessage("Hello, world2")}, 1242 ResponseCode: 200, 1243 Response: []byte(`{}`), 1244 Want: want{ 1245 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world1"},{"type":"text","text":"Hello, world2"}]}` + "\n"), 1246 Response: &BasicResponse{}, 1247 }, 1248 }, 1249 // Single message with sender 1250 { 1251 Messages: []SendingMessage{ 1252 NewAudioMessage("https://example.com/original.m4a", 1000).WithSender( 1253 NewSender("Cony", "https://line.me/conyprof"), 1254 ), 1255 }, 1256 ResponseCode: 200, 1257 Response: []byte(`{}`), 1258 Want: want{ 1259 RequestBody: []byte(`{"messages":[{"type":"audio","originalContentUrl":"https://example.com/original.m4a","duration":1000,"sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1260 Response: &BasicResponse{}, 1261 }, 1262 }, 1263 // Single location message with sender 1264 { 1265 Messages: []SendingMessage{ 1266 NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203).WithSender( 1267 NewSender("Cony", "https://line.me/conyprof"), 1268 ), 1269 }, 1270 ResponseCode: 200, 1271 Response: []byte(`{}`), 1272 Want: want{ 1273 RequestBody: []byte(`{"messages":[{"type":"location","title":"title","address":"address","latitude":35.65910807942215,"longitude":139.70372892916203,"sender":{"name":"Cony","iconUrl":"https://line.me/conyprof"}}]}` + "\n"), 1274 Response: &BasicResponse{}, 1275 }, 1276 }, 1277 { 1278 // A imagemap message with sender 1279 Messages: []SendingMessage{ 1280 NewImagemapMessage( 1281 "https://example.com/bot/images/rm001", 1282 "this is an imagemap", 1283 ImagemapBaseSize{1040, 1040}, 1284 NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}), 1285 NewMessageImagemapAction("hello", "hello", ImagemapArea{520, 0, 520, 1040}), 1286 ).WithSender( 1287 NewSender("Cony", "https://line.me/conyprof"), 1288 ), 1289 }, 1290 ResponseCode: 200, 1291 Response: []byte(`{}`), 1292 Want: want{ 1293 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"), 1294 Response: &BasicResponse{}, 1295 }, 1296 }, 1297 { 1298 Messages: []SendingMessage{ 1299 NewTextMessage("Hello, I am Cony!!").WithSender( 1300 NewSender("Cony", "https://line.me/conyprof"), 1301 ), 1302 NewStickerMessage("1", "1").WithSender( 1303 NewSender("Cony", "https://line.me/conyprof"), 1304 ), 1305 NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg").WithSender( 1306 NewSender("Cony", "https://line.me/conyprof"), 1307 ), 1308 }, 1309 ResponseCode: 200, 1310 Response: []byte(`{}`), 1311 Want: want{ 1312 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"), 1313 Response: &BasicResponse{}, 1314 }, 1315 }, 1316 { 1317 // Multiple messages with sender 1318 Messages: []SendingMessage{ 1319 NewTextMessage("Hello, I am Cony!!").WithSender( 1320 NewSender("Cony", "https://line.me/conyprof"), 1321 ), 1322 NewStickerMessage("1", "1").WithSender( 1323 NewSender("Cony", "https://line.me/conyprof"), 1324 ), 1325 NewVideoMessage("https://example.com/original.mp4", "https://example.com/preview.jpg").WithSender( 1326 NewSender("Cony", "https://line.me/conyprof"), 1327 ), 1328 }, 1329 ResponseCode: 200, 1330 Response: []byte(`{}`), 1331 Want: want{ 1332 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"), 1333 Response: &BasicResponse{}, 1334 }, 1335 }, 1336 { 1337 // A text message with emojis 1338 Messages: []SendingMessage{ 1339 NewTextMessage("$ Look at this: $ It's a LINE emoji!").AddEmoji( 1340 NewEmoji(0, "5ac1bfd5040ab15980c9b435", "086")).AddEmoji( 1341 NewEmoji(14, "5ac1bfd5040ab15980c9b435", "001"), 1342 ), 1343 }, 1344 ResponseCode: 200, 1345 Response: []byte(`{}`), 1346 Want: want{ 1347 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"), 1348 Response: &BasicResponse{}, 1349 }, 1350 }, 1351 { 1352 // Bad request 1353 Messages: []SendingMessage{NewTextMessage(""), NewTextMessage("")}, 1354 ResponseCode: 400, 1355 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"}]}`), 1356 Want: want{ 1357 RequestBody: []byte(`{"messages":[{"type":"text","text":""},{"type":"text","text":""}]}` + "\n"), 1358 Error: &APIError{ 1359 Code: 400, 1360 Response: &ErrorResponse{ 1361 Message: "Request body has 2 error(s).", 1362 Details: []errorResponseDetail{ 1363 { 1364 Message: "may not be empty", 1365 Property: "messages[0].text", 1366 }, 1367 { 1368 Message: "may not be empty", 1369 Property: "messages[1].text", 1370 }, 1371 }, 1372 }, 1373 }, 1374 }, 1375 }, 1376 } 1377 1378 var currentTestIdx int 1379 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1380 defer r.Body.Close() 1381 tc := testCases[currentTestIdx] 1382 if r.Method != http.MethodPost { 1383 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1384 } 1385 if r.URL.Path != APIEndpointValidateBroadcastMessage { 1386 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidateBroadcastMessage) 1387 } 1388 body, err := io.ReadAll(r.Body) 1389 if err != nil { 1390 t.Fatal(err) 1391 } 1392 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1393 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1394 } 1395 w.WriteHeader(tc.ResponseCode) 1396 w.Write(tc.Response) 1397 })) 1398 defer server.Close() 1399 1400 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1401 defer r.Body.Close() 1402 t.Error("Unexpected data API call") 1403 w.WriteHeader(404) 1404 w.Write([]byte(`{"message":"Not found"}`)) 1405 })) 1406 defer dataServer.Close() 1407 1408 client, err := mockClient(server, dataServer) 1409 if err != nil { 1410 t.Fatal(err) 1411 } 1412 for i, tc := range testCases { 1413 currentTestIdx = i 1414 res, err := client.ValidateBroadcastMessage(tc.Messages...).Do() 1415 if tc.Want.Error != nil { 1416 if !reflect.DeepEqual(err, tc.Want.Error) { 1417 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1418 } 1419 } else { 1420 if err != nil { 1421 t.Error(err) 1422 } 1423 } 1424 if tc.Want.Response != nil { 1425 if !reflect.DeepEqual(res, tc.Want.Response) { 1426 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1427 } 1428 } 1429 } 1430 } 1431 1432 func TestValidateNarrowcastMessages(t *testing.T) { 1433 type want struct { 1434 RequestBody []byte 1435 Response *BasicResponse 1436 Error error 1437 } 1438 testCases := []struct { 1439 Label string 1440 Messages []SendingMessage 1441 RequestID string 1442 Response []byte 1443 ResponseCode int 1444 Want want 1445 }{ 1446 { 1447 Label: "A text message for Narrowcast Message", 1448 Messages: []SendingMessage{NewTextMessage("Hello, world")}, 1449 RequestID: "12222", 1450 Response: []byte(`{}`), 1451 ResponseCode: 202, 1452 Want: want{ 1453 RequestBody: []byte(`{"messages":[{"type":"text","text":"Hello, world"}]}` + "\n"), 1454 Response: &BasicResponse{RequestID: "12222"}, 1455 }, 1456 }, 1457 } 1458 1459 var currentTestIdx int 1460 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1461 defer r.Body.Close() 1462 if r.Method != http.MethodPost { 1463 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1464 } 1465 if r.URL.Path != APIEndpointValidateNarrowcastMessage { 1466 t.Errorf("URLPath %s; want %s", r.URL.Path, APIEndpointValidateNarrowcastMessage) 1467 } 1468 body, err := io.ReadAll(r.Body) 1469 if err != nil { 1470 t.Fatal(err) 1471 } 1472 tc := testCases[currentTestIdx] 1473 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1474 t.Errorf("RequestBody \n%s; want \n%s", body, tc.Want.RequestBody) 1475 } 1476 w.Header().Set("X-Line-Request-Id", tc.RequestID) 1477 w.WriteHeader(tc.ResponseCode) 1478 w.Write(tc.Response) 1479 })) 1480 defer server.Close() 1481 1482 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1483 defer r.Body.Close() 1484 t.Error("Unexpected data API call") 1485 w.WriteHeader(404) 1486 w.Write([]byte(`{"message":"Not found"}`)) 1487 })) 1488 defer dataServer.Close() 1489 1490 client, err := mockClient(server, dataServer) 1491 if err != nil { 1492 t.Fatal(err) 1493 } 1494 for i, tc := range testCases { 1495 currentTestIdx = i 1496 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1497 narrowCast := client.ValidateNarrowcastMessage(tc.Messages...) 1498 res, err := narrowCast.Do() 1499 if tc.Want.Error != nil { 1500 if !reflect.DeepEqual(err, tc.Want.Error) { 1501 t.Errorf("Error %d %v; want %v", i, err, tc.Want.Error) 1502 } 1503 } else { 1504 if err != nil { 1505 t.Error(err) 1506 } 1507 } 1508 if tc.Want.Response != nil { 1509 if !reflect.DeepEqual(res, tc.Want.Response) { 1510 t.Errorf("Response %d %v; want %v", i, res, tc.Want.Response) 1511 } 1512 } 1513 }) 1514 } 1515 }