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  }