github.com/line/line-bot-sdk-go/v7@v7.21.0/linebot/webhook_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 "bytes" 19 "context" 20 "crypto/hmac" 21 "crypto/sha256" 22 "crypto/tls" 23 "encoding/base64" 24 "encoding/json" 25 "io" 26 "net/http" 27 "net/http/httptest" 28 "reflect" 29 "strconv" 30 "testing" 31 "time" 32 ) 33 34 var webhookTestRequestBody = `{ 35 "events": [ 36 { 37 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 38 "type": "message", 39 "mode": "active", 40 "timestamp": 1462629479859, 41 "source": { 42 "type": "user", 43 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 44 }, 45 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 46 "deliveryContext": { 47 "isRedelivery": false 48 }, 49 "message": { 50 "id": "325708", 51 "type": "text", 52 "text": "Hello, world" 53 } 54 }, 55 { 56 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 57 "type": "message", 58 "mode": "active", 59 "timestamp": 1462629479859, 60 "source": { 61 "type": "group", 62 "groupId": "u206d25c2ea6bd87c17655609a1c37cb8", 63 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 64 }, 65 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 66 "deliveryContext": { 67 "isRedelivery": true 68 }, 69 "message": { 70 "id": "325708", 71 "type": "text", 72 "text": "Hello, world" 73 } 74 }, 75 { 76 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 77 "type": "message", 78 "mode": "active", 79 "timestamp": 1462629479859, 80 "source": { 81 "type": "group", 82 "groupId": "u206d25c2ea6bd87c17655609a1c37cb8", 83 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 84 }, 85 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 86 "deliveryContext": { 87 "isRedelivery": false 88 }, 89 "message": { 90 "id": "325708", 91 "type": "text", 92 "text": "@openProfileUser Hello. @notOpenProfileUser Hello.", 93 "mention": { 94 "mentionees": [ 95 { 96 "index": 0, 97 "length": 16, 98 "userId": "U0047556f2e40dba2456887320ba7c76d", 99 "type": "user" 100 }, 101 { 102 "index": 24, 103 "length": 16, 104 "type": "user" 105 } 106 ] 107 } 108 } 109 }, 110 { 111 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 112 "type": "message", 113 "mode": "active", 114 "timestamp": 1462629479859, 115 "source": { 116 "type": "user", 117 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 118 }, 119 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 120 "deliveryContext": { 121 "isRedelivery": false 122 }, 123 "message": { 124 "id": "325708", 125 "type": "image" 126 } 127 }, 128 { 129 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTAA", 130 "type": "message", 131 "mode": "active", 132 "timestamp": 1462629479859, 133 "source": { 134 "type": "user", 135 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 136 }, 137 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 138 "deliveryContext": { 139 "isRedelivery": false 140 }, 141 "message": { 142 "type": "image", 143 "id": "354718705033693861", 144 "contentProvider": { 145 "type": "line" 146 }, 147 "imageSet": { 148 "id": "E005D41A7288F41B65593ED38FF6E9834B046AB36A37921A56BC236F13A91855", 149 "index": 2, 150 "total": 2 151 } 152 } 153 }, 154 { 155 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 156 "type": "message", 157 "mode": "active", 158 "timestamp": 1462629479859, 159 "source": { 160 "type": "user", 161 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 162 }, 163 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 164 "deliveryContext": { 165 "isRedelivery": false 166 }, 167 "message": { 168 "type": "video", 169 "id": "325708", 170 "duration": 60000, 171 "contentProvider": { 172 "type": "external", 173 "originalContentUrl": "https://example.com/original.mp4", 174 "previewImageUrl": "https://example.com/preview.jpg" 175 } 176 } 177 }, 178 { 179 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 180 "type": "message", 181 "mode": "active", 182 "timestamp": 1462629479859, 183 "source": { 184 "type": "user", 185 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 186 }, 187 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 188 "deliveryContext": { 189 "isRedelivery": false 190 }, 191 "message": { 192 "type": "audio", 193 "id": "325708", 194 "duration": 60000, 195 "contentProvider": { 196 "type": "line" 197 } 198 } 199 }, 200 { 201 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 202 "type": "message", 203 "mode": "active", 204 "timestamp": 1462629479859, 205 "source": { 206 "type": "user", 207 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 208 }, 209 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 210 "deliveryContext": { 211 "isRedelivery": false 212 }, 213 "message": { 214 "id": "325708", 215 "type": "file", 216 "fileName": "file.txt", 217 "fileSize": 2138 218 } 219 }, 220 { 221 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 222 "type": "message", 223 "mode": "active", 224 "timestamp": 1462629479859, 225 "source": { 226 "type": "user", 227 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 228 }, 229 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 230 "deliveryContext": { 231 "isRedelivery": false 232 }, 233 "message": { 234 "id": "325708", 235 "type": "location", 236 "title": "hello", 237 "address": "〒150-0002 東京都渋谷区渋谷2丁目21−1", 238 "latitude": 35.65910807942215, 239 "longitude": 139.70372892916203 240 } 241 }, 242 { 243 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 244 "type": "message", 245 "mode": "active", 246 "timestamp": 1462629479859, 247 "source": { 248 "type": "user", 249 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 250 }, 251 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 252 "deliveryContext": { 253 "isRedelivery": false 254 }, 255 "message": { 256 "id": "325708", 257 "type": "sticker", 258 "packageId": "1", 259 "stickerId": "1", 260 "stickerResourceType": "STATIC" 261 } 262 }, 263 { 264 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 265 "type": "message", 266 "mode": "active", 267 "timestamp": 1462629479859, 268 "source": { 269 "type": "user", 270 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 271 }, 272 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 273 "deliveryContext": { 274 "isRedelivery": false 275 }, 276 "message": { 277 "id": "325708", 278 "type": "sticker", 279 "packageId": "1", 280 "stickerId": "3", 281 "stickerResourceType": "ANIMATION_SOUND" 282 } 283 }, 284 { 285 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 286 "type": "message", 287 "mode": "active", 288 "timestamp": 1462629479859, 289 "source": { 290 "type": "user", 291 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 292 }, 293 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 294 "deliveryContext": { 295 "isRedelivery": false 296 }, 297 "message": { 298 "id": "3257088", 299 "type": "sticker", 300 "packageId": "20", 301 "stickerId": "3", 302 "stickerResourceType": "MESSAGE", 303 "keywords": ["cony","sally","Staring","hi","whatsup","line","howdy","HEY","Peeking","wave","peek","Hello","yo","greetings"] 304 } 305 }, 306 { 307 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 308 "type": "follow", 309 "mode": "active", 310 "timestamp": 1462629479859, 311 "source": { 312 "type": "user", 313 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 314 }, 315 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 316 "deliveryContext": { 317 "isRedelivery": false 318 } 319 }, 320 { 321 "type": "unfollow", 322 "mode": "active", 323 "timestamp": 1462629479859, 324 "source": { 325 "type": "user", 326 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 327 }, 328 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 329 "deliveryContext": { 330 "isRedelivery": false 331 } 332 }, 333 { 334 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 335 "type": "join", 336 "mode": "active", 337 "timestamp": 1462629479859, 338 "source": { 339 "type": "group", 340 "groupId": "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 341 }, 342 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 343 "deliveryContext": { 344 "isRedelivery": false 345 } 346 }, 347 { 348 "type": "leave", 349 "mode": "active", 350 "timestamp": 1462629479859, 351 "source": { 352 "type": "group", 353 "groupId": "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 354 }, 355 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 356 "deliveryContext": { 357 "isRedelivery": false 358 } 359 }, 360 { 361 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 362 "type": "postback", 363 "mode": "active", 364 "timestamp": 1462629479859, 365 "source": { 366 "type": "user", 367 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 368 }, 369 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 370 "deliveryContext": { 371 "isRedelivery": false 372 }, 373 "postback": { 374 "data": "action=buyItem&itemId=123123&color=red" 375 } 376 }, 377 { 378 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 379 "type": "postback", 380 "mode": "active", 381 "timestamp": 1462629479859, 382 "source": { 383 "type": "user", 384 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 385 }, 386 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 387 "deliveryContext": { 388 "isRedelivery": false 389 }, 390 "postback": { 391 "data": "action=sel&only=date", 392 "params": { 393 "date": "2017-09-03" 394 } 395 } 396 }, 397 { 398 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 399 "type": "postback", 400 "mode": "active", 401 "timestamp": 1462629479859, 402 "source": { 403 "type": "user", 404 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 405 }, 406 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 407 "deliveryContext": { 408 "isRedelivery": false 409 }, 410 "postback": { 411 "data": "action=sel&only=time", 412 "params": { 413 "time": "15:38" 414 } 415 } 416 }, 417 { 418 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 419 "type": "postback", 420 "mode": "active", 421 "timestamp": 1462629479859, 422 "source": { 423 "type": "user", 424 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 425 }, 426 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 427 "deliveryContext": { 428 "isRedelivery": false 429 }, 430 "postback": { 431 "data": "action=sel", 432 "params": { 433 "datetime": "2017-09-03T15:38" 434 } 435 } 436 }, 437 { 438 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 439 "type": "postback", 440 "mode": "active", 441 "timestamp": 1462629479859, 442 "source": { 443 "type": "user", 444 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 445 }, 446 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 447 "deliveryContext": { 448 "isRedelivery": false 449 }, 450 "postback": { 451 "data": "action=richmenu-changed-to-a", 452 "params": { 453 "newRichMenuAliasId": "richmenu-alias-a", 454 "status": "SUCCESS" 455 } 456 } 457 }, 458 { 459 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 460 "type": "beacon", 461 "mode": "active", 462 "timestamp": 1462629479859, 463 "source": { 464 "type": "user", 465 "userId": "U012345678901234567890123456789ab" 466 }, 467 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 468 "deliveryContext": { 469 "isRedelivery": false 470 }, 471 "beacon": { 472 "hwid":"374591320", 473 "type":"enter" 474 } 475 }, 476 { 477 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 478 "type": "beacon", 479 "mode": "active", 480 "timestamp": 1462629479859, 481 "source": { 482 "type": "user", 483 "userId": "U012345678901234567890123456789ab" 484 }, 485 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 486 "deliveryContext": { 487 "isRedelivery": false 488 }, 489 "beacon": { 490 "hwid":"374591320", 491 "type":"enter", 492 "dm":"1234567890abcdef" 493 } 494 }, 495 { 496 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 497 "type": "beacon", 498 "mode": "active", 499 "timestamp": 1462629479859, 500 "source": { 501 "type": "user", 502 "userId": "U012345678901234567890123456789ab" 503 }, 504 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 505 "deliveryContext": { 506 "isRedelivery": false 507 }, 508 "beacon": { 509 "hwid":"374591320", 510 "type":"stay", 511 "dm":"1234567890abcdef" 512 } 513 }, 514 { 515 "type": "accountLink", 516 "mode": "active", 517 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 518 "source": { 519 "userId": "U012345678901234567890123456789ab", 520 "type": "user" 521 }, 522 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 523 "deliveryContext": { 524 "isRedelivery": false 525 }, 526 "timestamp": 1462629479859, 527 "link": { 528 "result": "ok", 529 "nonce": "xxxxxxxxxxxxxxx" 530 } 531 }, 532 { 533 "replyToken": "0f3779fba3b349968c5d07db31eabf65", 534 "type": "memberJoined", 535 "mode": "active", 536 "timestamp": 1462629479859, 537 "source": { 538 "type": "group", 539 "groupId": "C4af498062901234567890123456789ab" 540 }, 541 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 542 "deliveryContext": { 543 "isRedelivery": false 544 }, 545 "joined": { 546 "members": [ 547 { 548 "type": "user", 549 "userId": "U4af498062901234567890123456789ab" 550 }, 551 { 552 "type": "user", 553 "userId": "U91eeaf62d901234567890123456789ab" 554 } 555 ] 556 } 557 }, 558 { 559 "type": "memberLeft", 560 "mode": "active", 561 "timestamp": 1462629479960, 562 "source": { 563 "type": "group", 564 "groupId": "C4af498062901234567890123456789ab" 565 }, 566 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 567 "deliveryContext": { 568 "isRedelivery": false 569 }, 570 "left": { 571 "members": [ 572 { 573 "type": "user", 574 "userId": "U4af498062901234567890123456789ab" 575 }, 576 { 577 "type": "user", 578 "userId": "U91eeaf62d901234567890123456789ab" 579 } 580 ] 581 } 582 }, 583 { 584 "type": "things", 585 "mode": "active", 586 "timestamp": 1462629479859, 587 "source": { 588 "type": "user", 589 "userId": "U91eeaf62d901234567890123456789ab" 590 }, 591 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 592 "deliveryContext": { 593 "isRedelivery": false 594 }, 595 "things": { 596 "deviceId": "t2c449c9d1...", 597 "type": "link" 598 } 599 }, 600 { 601 "type": "things", 602 "mode": "active", 603 "timestamp": 1462629479859, 604 "source": { 605 "type": "user", 606 "userId": "U91eeaf62d901234567890123456789ab" 607 }, 608 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 609 "deliveryContext": { 610 "isRedelivery": false 611 }, 612 "things": { 613 "deviceId": "t2c449c9d1...", 614 "type": "unlink" 615 } 616 }, 617 { 618 "type": "things", 619 "mode": "active", 620 "timestamp": 1462629479859, 621 "source": { 622 "type": "user", 623 "userId": "U91eeaf62d901234567890123456789ab" 624 }, 625 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 626 "deliveryContext": { 627 "isRedelivery": false 628 }, 629 "things": { 630 "deviceId": "t016b8dc6...", 631 "type": "scenarioResult", 632 "result": { 633 "scenarioId": "01DE9CH7H...", 634 "revision": 3, 635 "startTime": 1563511217095, 636 "endTime": 1563511217097, 637 "resultCode": "success", 638 "bleNotificationPayload": "AQ==", 639 "actionResults": [ 640 { 641 "type": "binary", 642 "data": "/w==" 643 } 644 ] 645 } 646 } 647 }, 648 { 649 "type":"things", 650 "mode": "active", 651 "replyToken":"f026a377...", 652 "source":{ 653 "userId":"U91eeaf62d901234567890123456789ab", 654 "type":"user" 655 }, 656 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 657 "deliveryContext": { 658 "isRedelivery": false 659 }, 660 "timestamp":1563511218376, 661 "things":{ 662 "deviceId":"t016b8d...", 663 "result":{ 664 "scenarioId":"01DE9CH7H...", 665 "revision":3, 666 "startTime":1563511217095, 667 "endTime":1563511217097, 668 "resultCode":"gatt_error", 669 "errorReason":"c.l.h.D: No characteristic is found for the given UUID.", 670 "actionResults":[ 671 672 ] 673 }, 674 "type":"scenarioResult" 675 } 676 }, 677 { 678 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 679 "type": "message", 680 "mode": "standby", 681 "timestamp": 1462629479859, 682 "source": { 683 "type": "user", 684 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 685 }, 686 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 687 "deliveryContext": { 688 "isRedelivery": false 689 }, 690 "message": { 691 "id": "325708", 692 "type": "text", 693 "text": "Stand by me" 694 } 695 }, 696 { 697 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 698 "type": "message", 699 "mode": "active", 700 "timestamp": 1462629479859, 701 "source": { 702 "type": "user", 703 "userId": "u206d25c2ea6bd87c17655609a1c37cb8" 704 }, 705 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 706 "deliveryContext": { 707 "isRedelivery": false 708 }, 709 "message": { 710 "id": "325708", 711 "type": "text", 712 "text": "Hello, world! (love)", 713 "emojis": [ 714 { 715 "index": 14, 716 "length": 6, 717 "productId": "5ac1bfd5040ab15980c9b435", 718 "emojiId": "001" 719 } 720 ] 721 } 722 }, 723 { 724 "type": "unsend", 725 "mode": "active", 726 "timestamp": 1462629479859, 727 "source": { 728 "type": "group", 729 "groupId": "Ca56f94637c...", 730 "userId": "U4af4980629..." 731 }, 732 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 733 "deliveryContext": { 734 "isRedelivery": false 735 }, 736 "unsend": { 737 "messageId": "325708" 738 } 739 }, 740 { 741 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 742 "type": "videoPlayComplete", 743 "timestamp": 1462629479859, 744 "mode": "active", 745 "source": { 746 "type": "user", 747 "userId": "U4af4980629..." 748 }, 749 "webhookEventId": "01FZ74ASS536FW97EX38NKCZQK", 750 "deliveryContext": { 751 "isRedelivery": false 752 }, 753 "videoPlayComplete": { 754 "trackingId": "track_id" 755 } 756 }, 757 { 758 "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 759 "type": "message", 760 "mode": "active", 761 "timestamp": 1462629479859, 762 "source": { 763 "type": "group", 764 "groupId": "Ca56f94637c...", 765 "userId": "U4af4980629..." 766 }, 767 "webhookEventId": "01FZ74A0TDDPYRVKNK77XKC3ZR", 768 "deliveryContext": { 769 "isRedelivery": false 770 }, 771 "message": { 772 "id": "444573844083572737", 773 "type": "text", 774 "text": "@All @example Good Morning!! (love)", 775 "emojis": [ 776 { 777 "index": 29, 778 "length": 6, 779 "productId": "5ac1bfd5040ab15980c9b435", 780 "emojiId": "001" 781 } 782 ], 783 "mention": { 784 "mentionees": [ 785 { 786 "index": 0, 787 "length": 4, 788 "type": "all" 789 }, 790 { 791 "index": 5, 792 "length": 8, 793 "userId": "U49585cd0d5...", 794 "type": "user" 795 } 796 ] 797 } 798 } 799 } 800 ] 801 } 802 ` 803 804 var webhookTestWantEvents = []*Event{ 805 { 806 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 807 Type: EventTypeMessage, 808 Mode: EventModeActive, 809 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 810 Source: &EventSource{ 811 Type: EventSourceTypeUser, 812 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 813 }, 814 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 815 DeliveryContext: DeliveryContext{ 816 IsRedelivery: false, 817 }, 818 Message: &TextMessage{ 819 ID: "325708", 820 Text: "Hello, world", 821 }, 822 }, 823 { 824 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 825 Type: EventTypeMessage, 826 Mode: EventModeActive, 827 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 828 Source: &EventSource{ 829 Type: EventSourceTypeGroup, 830 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 831 GroupID: "u206d25c2ea6bd87c17655609a1c37cb8", 832 }, 833 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 834 DeliveryContext: DeliveryContext{ 835 IsRedelivery: true, 836 }, 837 Message: &TextMessage{ 838 ID: "325708", 839 Text: "Hello, world", 840 }, 841 }, 842 { 843 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 844 Type: EventTypeMessage, 845 Mode: EventModeActive, 846 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 847 Source: &EventSource{ 848 Type: EventSourceTypeGroup, 849 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 850 GroupID: "u206d25c2ea6bd87c17655609a1c37cb8", 851 }, 852 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 853 DeliveryContext: DeliveryContext{ 854 IsRedelivery: false, 855 }, 856 Message: &TextMessage{ 857 ID: "325708", 858 Text: "@openProfileUser Hello. @notOpenProfileUser Hello.", 859 Mention: &Mention{ 860 Mentionees: []*Mentionee{ 861 { 862 Index: 0, 863 Length: 16, 864 Type: MentionedTargetTypeUser, 865 UserID: "U0047556f2e40dba2456887320ba7c76d", 866 }, 867 { 868 Index: 24, 869 Length: 16, 870 Type: MentionedTargetTypeUser, 871 }, 872 }, 873 }, 874 }, 875 }, 876 { 877 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 878 Type: EventTypeMessage, 879 Mode: EventModeActive, 880 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 881 Source: &EventSource{ 882 Type: EventSourceTypeUser, 883 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 884 }, 885 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 886 DeliveryContext: DeliveryContext{ 887 IsRedelivery: false, 888 }, 889 Message: &ImageMessage{ 890 ID: "325708", 891 }, 892 }, 893 { 894 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTAA", 895 Type: EventTypeMessage, 896 Mode: EventModeActive, 897 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 898 Source: &EventSource{ 899 Type: EventSourceTypeUser, 900 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 901 }, 902 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 903 DeliveryContext: DeliveryContext{ 904 IsRedelivery: false, 905 }, 906 Message: &ImageMessage{ 907 ID: "354718705033693861", 908 ContentProvider: &ContentProvider{ 909 Type: ContentProviderTypeLINE, 910 }, 911 ImageSet: &ImageSet{ 912 ID: "E005D41A7288F41B65593ED38FF6E9834B046AB36A37921A56BC236F13A91855", 913 Index: 2, 914 Total: 2, 915 }, 916 }, 917 }, 918 { 919 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 920 Type: EventTypeMessage, 921 Mode: EventModeActive, 922 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 923 Source: &EventSource{ 924 Type: EventSourceTypeUser, 925 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 926 }, 927 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 928 DeliveryContext: DeliveryContext{ 929 IsRedelivery: false, 930 }, 931 Message: &VideoMessage{ 932 ID: "325708", 933 ContentProvider: &ContentProvider{ 934 Type: ContentProviderTypeExternal, 935 OriginalContentURL: "https://example.com/original.mp4", 936 PreviewImageURL: "https://example.com/preview.jpg", 937 }, 938 Duration: 60000, 939 }, 940 }, 941 { 942 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 943 Type: EventTypeMessage, 944 Mode: EventModeActive, 945 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 946 Source: &EventSource{ 947 Type: EventSourceTypeUser, 948 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 949 }, 950 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 951 DeliveryContext: DeliveryContext{ 952 IsRedelivery: false, 953 }, 954 Message: &AudioMessage{ 955 ID: "325708", 956 ContentProvider: &ContentProvider{ 957 Type: ContentProviderTypeLINE, 958 }, 959 Duration: 60000, 960 }, 961 }, 962 { 963 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 964 Type: EventTypeMessage, 965 Mode: EventModeActive, 966 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 967 Source: &EventSource{ 968 Type: EventSourceTypeUser, 969 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 970 }, 971 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 972 DeliveryContext: DeliveryContext{ 973 IsRedelivery: false, 974 }, 975 Message: &FileMessage{ 976 ID: "325708", 977 FileName: "file.txt", 978 FileSize: 2138, 979 }, 980 }, 981 { 982 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 983 Type: EventTypeMessage, 984 Mode: EventModeActive, 985 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 986 Source: &EventSource{ 987 Type: EventSourceTypeUser, 988 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 989 }, 990 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 991 DeliveryContext: DeliveryContext{ 992 IsRedelivery: false, 993 }, 994 Message: &LocationMessage{ 995 ID: "325708", 996 Title: "hello", 997 Address: "〒150-0002 東京都渋谷区渋谷2丁目21−1", 998 Latitude: 35.65910807942215, 999 Longitude: 139.70372892916203, 1000 }, 1001 }, 1002 { 1003 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1004 Type: EventTypeMessage, 1005 Mode: EventModeActive, 1006 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1007 Source: &EventSource{ 1008 Type: EventSourceTypeUser, 1009 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1010 }, 1011 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1012 DeliveryContext: DeliveryContext{ 1013 IsRedelivery: false, 1014 }, 1015 Message: &StickerMessage{ 1016 ID: "325708", 1017 PackageID: "1", 1018 StickerID: "1", 1019 StickerResourceType: StickerResourceTypeStatic, 1020 }, 1021 }, 1022 { 1023 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1024 Type: EventTypeMessage, 1025 Mode: EventModeActive, 1026 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1027 Source: &EventSource{ 1028 Type: EventSourceTypeUser, 1029 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1030 }, 1031 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1032 DeliveryContext: DeliveryContext{ 1033 IsRedelivery: false, 1034 }, 1035 Message: &StickerMessage{ 1036 ID: "325708", 1037 PackageID: "1", 1038 StickerID: "3", 1039 StickerResourceType: StickerResourceTypeAnimationSound, 1040 }, 1041 }, 1042 { 1043 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1044 Type: EventTypeMessage, 1045 Mode: EventModeActive, 1046 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1047 Source: &EventSource{ 1048 Type: EventSourceTypeUser, 1049 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1050 }, 1051 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1052 DeliveryContext: DeliveryContext{ 1053 IsRedelivery: false, 1054 }, 1055 Message: &StickerMessage{ 1056 ID: "3257088", 1057 PackageID: "20", 1058 StickerID: "3", 1059 StickerResourceType: StickerResourceTypePerStickerText, 1060 Keywords: []string{"cony", "sally", "Staring", "hi", "whatsup", "line", "howdy", "HEY", "Peeking", "wave", "peek", "Hello", "yo", "greetings"}, 1061 }, 1062 }, 1063 { 1064 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1065 Type: EventTypeFollow, 1066 Mode: EventModeActive, 1067 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1068 Source: &EventSource{ 1069 Type: EventSourceTypeUser, 1070 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1071 }, 1072 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1073 DeliveryContext: DeliveryContext{ 1074 IsRedelivery: false, 1075 }, 1076 }, 1077 { 1078 Type: EventTypeUnfollow, 1079 Mode: EventModeActive, 1080 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1081 Source: &EventSource{ 1082 Type: EventSourceTypeUser, 1083 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1084 }, 1085 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1086 DeliveryContext: DeliveryContext{ 1087 IsRedelivery: false, 1088 }, 1089 }, 1090 { 1091 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1092 Type: EventTypeJoin, 1093 Mode: EventModeActive, 1094 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1095 Source: &EventSource{ 1096 Type: EventSourceTypeGroup, 1097 GroupID: "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 1098 }, 1099 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1100 DeliveryContext: DeliveryContext{ 1101 IsRedelivery: false, 1102 }, 1103 }, 1104 { 1105 Type: EventTypeLeave, 1106 Mode: EventModeActive, 1107 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1108 Source: &EventSource{ 1109 Type: EventSourceTypeGroup, 1110 GroupID: "cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 1111 }, 1112 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1113 DeliveryContext: DeliveryContext{ 1114 IsRedelivery: false, 1115 }, 1116 }, 1117 { 1118 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1119 Type: EventTypePostback, 1120 Mode: EventModeActive, 1121 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1122 Source: &EventSource{ 1123 Type: EventSourceTypeUser, 1124 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1125 }, 1126 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1127 DeliveryContext: DeliveryContext{ 1128 IsRedelivery: false, 1129 }, 1130 Postback: &Postback{ 1131 Data: "action=buyItem&itemId=123123&color=red", 1132 }, 1133 }, 1134 { 1135 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1136 Type: EventTypePostback, 1137 Mode: EventModeActive, 1138 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1139 Source: &EventSource{ 1140 Type: EventSourceTypeUser, 1141 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1142 }, 1143 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1144 DeliveryContext: DeliveryContext{ 1145 IsRedelivery: false, 1146 }, 1147 Postback: &Postback{ 1148 Data: "action=sel&only=date", 1149 Params: &Params{ 1150 Date: "2017-09-03", 1151 }, 1152 }, 1153 }, 1154 { 1155 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1156 Type: EventTypePostback, 1157 Mode: EventModeActive, 1158 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1159 Source: &EventSource{ 1160 Type: EventSourceTypeUser, 1161 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1162 }, 1163 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1164 DeliveryContext: DeliveryContext{ 1165 IsRedelivery: false, 1166 }, 1167 Postback: &Postback{ 1168 Data: "action=sel&only=time", 1169 Params: &Params{ 1170 Time: "15:38", 1171 }, 1172 }, 1173 }, 1174 { 1175 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1176 Type: EventTypePostback, 1177 Mode: EventModeActive, 1178 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1179 Source: &EventSource{ 1180 Type: EventSourceTypeUser, 1181 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1182 }, 1183 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1184 DeliveryContext: DeliveryContext{ 1185 IsRedelivery: false, 1186 }, 1187 Postback: &Postback{ 1188 Data: "action=sel", 1189 Params: &Params{ 1190 Datetime: "2017-09-03T15:38", 1191 }, 1192 }, 1193 }, 1194 { 1195 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1196 Type: EventTypePostback, 1197 Mode: EventModeActive, 1198 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1199 Source: &EventSource{ 1200 Type: EventSourceTypeUser, 1201 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1202 }, 1203 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1204 DeliveryContext: DeliveryContext{ 1205 IsRedelivery: false, 1206 }, 1207 Postback: &Postback{ 1208 Data: "action=richmenu-changed-to-a", 1209 Params: &Params{ 1210 NewRichMenuAliasID: "richmenu-alias-a", 1211 Status: "SUCCESS", 1212 }, 1213 }, 1214 }, 1215 { 1216 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1217 Type: EventTypeBeacon, 1218 Mode: EventModeActive, 1219 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1220 Source: &EventSource{ 1221 Type: EventSourceTypeUser, 1222 UserID: "U012345678901234567890123456789ab", 1223 }, 1224 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1225 DeliveryContext: DeliveryContext{ 1226 IsRedelivery: false, 1227 }, 1228 Beacon: &Beacon{ 1229 Hwid: "374591320", 1230 Type: BeaconEventTypeEnter, 1231 DeviceMessage: []byte{}, 1232 }, 1233 }, 1234 { 1235 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1236 Type: EventTypeBeacon, 1237 Mode: EventModeActive, 1238 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1239 Source: &EventSource{ 1240 Type: EventSourceTypeUser, 1241 UserID: "U012345678901234567890123456789ab", 1242 }, 1243 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1244 DeliveryContext: DeliveryContext{ 1245 IsRedelivery: false, 1246 }, 1247 Beacon: &Beacon{ 1248 Hwid: "374591320", 1249 Type: BeaconEventTypeEnter, 1250 DeviceMessage: []byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}, 1251 }, 1252 }, 1253 { 1254 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1255 Type: EventTypeBeacon, 1256 Mode: EventModeActive, 1257 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1258 Source: &EventSource{ 1259 Type: EventSourceTypeUser, 1260 UserID: "U012345678901234567890123456789ab", 1261 }, 1262 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1263 DeliveryContext: DeliveryContext{ 1264 IsRedelivery: false, 1265 }, 1266 Beacon: &Beacon{ 1267 Hwid: "374591320", 1268 Type: BeaconEventTypeStay, 1269 DeviceMessage: []byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}, 1270 }, 1271 }, 1272 { 1273 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1274 Type: EventTypeAccountLink, 1275 Mode: EventModeActive, 1276 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1277 Source: &EventSource{ 1278 Type: EventSourceTypeUser, 1279 UserID: "U012345678901234567890123456789ab", 1280 }, 1281 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1282 DeliveryContext: DeliveryContext{ 1283 IsRedelivery: false, 1284 }, 1285 AccountLink: &AccountLink{ 1286 Result: AccountLinkResultOK, 1287 Nonce: "xxxxxxxxxxxxxxx", 1288 }, 1289 }, 1290 { 1291 ReplyToken: "0f3779fba3b349968c5d07db31eabf65", 1292 Type: EventTypeMemberJoined, 1293 Mode: EventModeActive, 1294 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1295 Source: &EventSource{ 1296 Type: EventSourceTypeGroup, 1297 GroupID: "C4af498062901234567890123456789ab", 1298 }, 1299 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1300 DeliveryContext: DeliveryContext{ 1301 IsRedelivery: false, 1302 }, 1303 Members: []*EventSource{ 1304 { 1305 Type: EventSourceTypeUser, 1306 UserID: "U4af498062901234567890123456789ab", 1307 }, 1308 { 1309 Type: EventSourceTypeUser, 1310 UserID: "U91eeaf62d901234567890123456789ab", 1311 }, 1312 }, 1313 }, 1314 { 1315 Type: EventTypeMemberLeft, 1316 Mode: EventModeActive, 1317 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(960*time.Millisecond), time.UTC), 1318 Source: &EventSource{ 1319 Type: EventSourceTypeGroup, 1320 GroupID: "C4af498062901234567890123456789ab", 1321 }, 1322 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1323 DeliveryContext: DeliveryContext{ 1324 IsRedelivery: false, 1325 }, 1326 Members: []*EventSource{ 1327 { 1328 Type: EventSourceTypeUser, 1329 UserID: "U4af498062901234567890123456789ab", 1330 }, 1331 { 1332 Type: EventSourceTypeUser, 1333 UserID: "U91eeaf62d901234567890123456789ab", 1334 }, 1335 }, 1336 }, 1337 { 1338 Type: EventTypeThings, 1339 Mode: EventModeActive, 1340 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1341 Source: &EventSource{ 1342 Type: EventSourceTypeUser, 1343 UserID: "U91eeaf62d901234567890123456789ab", 1344 }, 1345 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1346 DeliveryContext: DeliveryContext{ 1347 IsRedelivery: false, 1348 }, 1349 Things: &Things{ 1350 DeviceID: `t2c449c9d1...`, 1351 Type: `link`, 1352 }, 1353 }, 1354 { 1355 Type: EventTypeThings, 1356 Mode: EventModeActive, 1357 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1358 Source: &EventSource{ 1359 Type: EventSourceTypeUser, 1360 UserID: "U91eeaf62d901234567890123456789ab", 1361 }, 1362 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1363 DeliveryContext: DeliveryContext{ 1364 IsRedelivery: false, 1365 }, 1366 Things: &Things{ 1367 DeviceID: `t2c449c9d1...`, 1368 Type: `unlink`, 1369 }, 1370 }, 1371 { 1372 Type: EventTypeThings, 1373 Mode: EventModeActive, 1374 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1375 Source: &EventSource{ 1376 Type: EventSourceTypeUser, 1377 UserID: "U91eeaf62d901234567890123456789ab", 1378 }, 1379 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1380 DeliveryContext: DeliveryContext{ 1381 IsRedelivery: false, 1382 }, 1383 Things: &Things{ 1384 DeviceID: "t016b8dc6...", 1385 Type: "scenarioResult", 1386 Result: &ThingsResult{ 1387 ScenarioID: "01DE9CH7H...", 1388 Revision: 3, 1389 StartTime: 1563511217095, 1390 EndTime: 1563511217097, 1391 ResultCode: ThingsResultCodeSuccess, 1392 BLENotificationPayload: []byte(`AQ==`), 1393 ActionResults: []*ThingsActionResult{ 1394 { 1395 Type: ThingsActionResultTypeBinary, 1396 Data: []byte(`/w==`), 1397 }, 1398 }, 1399 }, 1400 }, 1401 }, 1402 { 1403 Type: EventTypeThings, 1404 Mode: EventModeActive, 1405 ReplyToken: "f026a377...", 1406 Timestamp: time.Date(2019, time.July, 19, 4, 40, 18, int(376*time.Millisecond), time.UTC), 1407 Source: &EventSource{ 1408 Type: EventSourceTypeUser, 1409 UserID: "U91eeaf62d901234567890123456789ab", 1410 }, 1411 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1412 DeliveryContext: DeliveryContext{ 1413 IsRedelivery: false, 1414 }, 1415 Things: &Things{ 1416 DeviceID: "t016b8d...", 1417 Type: "scenarioResult", 1418 Result: &ThingsResult{ 1419 ScenarioID: "01DE9CH7H...", 1420 Revision: 3, 1421 StartTime: 1563511217095, 1422 EndTime: 1563511217097, 1423 ResultCode: ThingsResultCodeGattError, 1424 ErrorReason: "c.l.h.D: No characteristic is found for the given UUID.", 1425 ActionResults: []*ThingsActionResult{}, 1426 BLENotificationPayload: []byte{}, 1427 }, 1428 }, 1429 }, 1430 { 1431 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1432 Type: EventTypeMessage, 1433 Mode: EventModeStandby, 1434 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1435 Source: &EventSource{ 1436 Type: EventSourceTypeUser, 1437 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1438 }, 1439 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1440 DeliveryContext: DeliveryContext{ 1441 IsRedelivery: false, 1442 }, 1443 Message: &TextMessage{ 1444 ID: "325708", 1445 Text: "Stand by me", 1446 }, 1447 }, 1448 { 1449 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1450 Type: EventTypeMessage, 1451 Mode: EventModeActive, 1452 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1453 Source: &EventSource{ 1454 Type: EventSourceTypeUser, 1455 UserID: "u206d25c2ea6bd87c17655609a1c37cb8", 1456 }, 1457 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1458 DeliveryContext: DeliveryContext{ 1459 IsRedelivery: false, 1460 }, 1461 Message: &TextMessage{ 1462 ID: "325708", 1463 Text: "Hello, world! (love)", 1464 Emojis: []*Emoji{ 1465 {Index: 14, Length: 6, ProductID: "5ac1bfd5040ab15980c9b435", EmojiID: "001"}, 1466 }, 1467 }, 1468 }, 1469 { 1470 Type: EventTypeUnsend, 1471 Mode: EventModeActive, 1472 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1473 Source: &EventSource{ 1474 Type: EventSourceTypeGroup, 1475 GroupID: "Ca56f94637c...", 1476 UserID: "U4af4980629...", 1477 }, 1478 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1479 DeliveryContext: DeliveryContext{ 1480 IsRedelivery: false, 1481 }, 1482 Unsend: &Unsend{ 1483 MessageID: "325708", 1484 }, 1485 }, 1486 { 1487 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1488 Type: EventTypeVideoPlayComplete, 1489 Mode: EventModeActive, 1490 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1491 Source: &EventSource{ 1492 Type: EventSourceTypeUser, 1493 UserID: "U4af4980629...", 1494 }, 1495 WebhookEventID: "01FZ74ASS536FW97EX38NKCZQK", 1496 DeliveryContext: DeliveryContext{ 1497 IsRedelivery: false, 1498 }, 1499 VideoPlayComplete: &VideoPlayComplete{ 1500 TrackingID: "track_id", 1501 }, 1502 }, 1503 { 1504 ReplyToken: "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 1505 Type: EventTypeMessage, 1506 Mode: EventModeActive, 1507 Timestamp: time.Date(2016, time.May, 7, 13, 57, 59, int(859*time.Millisecond), time.UTC), 1508 Source: &EventSource{ 1509 Type: EventSourceTypeGroup, 1510 GroupID: "Ca56f94637c...", 1511 UserID: "U4af4980629...", 1512 }, 1513 WebhookEventID: "01FZ74A0TDDPYRVKNK77XKC3ZR", 1514 DeliveryContext: DeliveryContext{ 1515 IsRedelivery: false, 1516 }, 1517 Message: &TextMessage{ 1518 ID: "444573844083572737", 1519 Text: "@All @example Good Morning!! (love)", 1520 Emojis: []*Emoji{ 1521 {Index: 29, Length: 6, ProductID: "5ac1bfd5040ab15980c9b435", EmojiID: "001"}, 1522 }, 1523 Mention: &Mention{ 1524 Mentionees: []*Mentionee{ 1525 { 1526 Index: 0, 1527 Length: 4, 1528 Type: MentionedTargetTypeAll, 1529 }, 1530 { 1531 Index: 5, 1532 Length: 8, 1533 Type: MentionedTargetTypeUser, 1534 UserID: "U49585cd0d5...", 1535 }, 1536 }, 1537 }, 1538 }, 1539 }, 1540 } 1541 1542 func TestParseRequest(t *testing.T) { 1543 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1544 client, err := New("testsecret", "testtoken") 1545 if err != nil { 1546 t.Error(err) 1547 } 1548 gotEvents, err := client.ParseRequest(r) 1549 if err != nil { 1550 if err == ErrInvalidSignature { 1551 w.WriteHeader(400) 1552 } else { 1553 w.WriteHeader(500) 1554 t.Error(err) 1555 } 1556 return 1557 } 1558 if len(gotEvents) != len(webhookTestWantEvents) { 1559 t.Errorf("Event length %d; want %d", len(gotEvents), len(webhookTestWantEvents)) 1560 } 1561 for i, got := range gotEvents { 1562 want := webhookTestWantEvents[i] 1563 if !reflect.DeepEqual(got, want) { 1564 t.Errorf("Event %d %v; want %v", i, got, want) 1565 gota := got 1566 if !reflect.DeepEqual( 1567 gota.Things.Result.BLENotificationPayload, 1568 want.Things.Result.BLENotificationPayload, 1569 ) { 1570 t.Log("this") 1571 t.Log(gota.Things.Result.BLENotificationPayload == nil) 1572 t.Log(want.Things.Result.BLENotificationPayload == nil) 1573 } 1574 } 1575 } 1576 })) 1577 defer server.Close() 1578 httpClient := &http.Client{ 1579 Transport: &http.Transport{ 1580 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 1581 }, 1582 } 1583 1584 // invalid signature 1585 { 1586 body := []byte(webhookTestRequestBody) 1587 req, err := http.NewRequest("POST", server.URL, bytes.NewReader(body)) 1588 if err != nil { 1589 t.Fatal(err) 1590 } 1591 req.Header.Set("X-Line-Signature", "invalidSignature") 1592 res, err := httpClient.Do(req) 1593 if err != nil { 1594 t.Fatal(err) 1595 } 1596 if res.StatusCode != 400 { 1597 t.Errorf("StatusCode %d; want %d", res.StatusCode, 400) 1598 } 1599 } 1600 1601 // valid signature 1602 { 1603 body := []byte(webhookTestRequestBody) 1604 req, err := http.NewRequest("POST", server.URL, bytes.NewReader(body)) 1605 if err != nil { 1606 t.Fatal(err) 1607 } 1608 // generate signature 1609 mac := hmac.New(sha256.New, []byte("testsecret")) 1610 mac.Write(body) 1611 1612 req.Header.Set("X-Line-Signature", base64.StdEncoding.EncodeToString(mac.Sum(nil))) 1613 res, err := httpClient.Do(req) 1614 if err != nil { 1615 t.Fatal(err) 1616 } 1617 if res == nil { 1618 t.Fatal("response is nil") 1619 } 1620 if res.StatusCode != http.StatusOK { 1621 t.Errorf("status: %d", res.StatusCode) 1622 } 1623 } 1624 } 1625 1626 func TestEventMarshaling(t *testing.T) { 1627 testCases := &struct { 1628 Events []map[string]interface{} `json:"events"` 1629 }{} 1630 err := json.Unmarshal([]byte(webhookTestRequestBody), testCases) 1631 if err != nil { 1632 t.Fatal(err) 1633 } 1634 for i, want := range testCases.Events { 1635 t.Run(strconv.Itoa(i), func(t *testing.T) { 1636 gotJSON, err := json.Marshal(webhookTestWantEvents[i]) 1637 if err != nil { 1638 t.Error(err) 1639 return 1640 } 1641 got := map[string]interface{}{} 1642 err = json.Unmarshal(gotJSON, &got) 1643 if err != nil { 1644 t.Error(err) 1645 return 1646 } 1647 if !reflect.DeepEqual(got, want) { 1648 t.Errorf("Event marshal %v; want %v", got, want) 1649 } 1650 }) 1651 } 1652 } 1653 1654 func BenchmarkParseRequest(b *testing.B) { 1655 body := []byte(webhookTestRequestBody) 1656 client, err := New("testsecret", "testtoken") 1657 if err != nil { 1658 b.Fatal(err) 1659 } 1660 mac := hmac.New(sha256.New, []byte("testsecret")) 1661 mac.Write(body) 1662 sign := base64.StdEncoding.EncodeToString(mac.Sum(nil)) 1663 b.ResetTimer() 1664 1665 for i := 0; i < b.N; i++ { 1666 req, _ := http.NewRequest("POST", "", bytes.NewReader(body)) 1667 req.Header.Set("X-Line-Signature", sign) 1668 client.ParseRequest(req) 1669 } 1670 } 1671 1672 func TestGetWebhookInfo(t *testing.T) { 1673 type want struct { 1674 URLPath string 1675 RequestBody []byte 1676 Response *WebhookInfoResponse 1677 Error error 1678 } 1679 testCases := []struct { 1680 Label string 1681 ResponseCode int 1682 Response []byte 1683 Want want 1684 }{ 1685 { 1686 Label: "Success", 1687 ResponseCode: 200, 1688 Response: []byte(`{"endpoint":"https://example.herokuapp.com/test","active":true}`), 1689 Want: want{ 1690 URLPath: APIEndpointGetWebhookInfo, 1691 RequestBody: []byte(""), 1692 Response: &WebhookInfoResponse{ 1693 Endpoint: "https://example.herokuapp.com/test", 1694 Active: true, 1695 }, 1696 }, 1697 }, 1698 { 1699 Label: "Internal server error", 1700 ResponseCode: 500, 1701 Response: []byte("500 Internal server error"), 1702 Want: want{ 1703 URLPath: APIEndpointGetWebhookInfo, 1704 RequestBody: []byte(""), 1705 Error: &APIError{ 1706 Code: 500, 1707 }, 1708 }, 1709 }, 1710 { 1711 Label: "Invalid channelAccessToken error", 1712 ResponseCode: 401, 1713 Response: []byte(`{"message":"Authentication failed due to the following reason: invalid token. Confirm that the access token in the authorization header is valid."}`), 1714 Want: want{ 1715 URLPath: APIEndpointGetWebhookInfo, 1716 RequestBody: []byte(""), 1717 Error: &APIError{ 1718 Code: 401, 1719 Response: &ErrorResponse{ 1720 Message: "Authentication failed due to the following reason: invalid token. Confirm that the access token in the authorization header is valid.", 1721 }, 1722 }, 1723 }, 1724 }, 1725 } 1726 1727 var currentTestIdx int 1728 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1729 defer r.Body.Close() 1730 tc := testCases[currentTestIdx] 1731 if r.Method != http.MethodGet { 1732 t.Errorf("Method %s; want %s", r.Method, http.MethodGet) 1733 } 1734 if r.URL.Path != tc.Want.URLPath { 1735 t.Errorf("URLPath %s; want %s", r.URL.Path, tc.Want.URLPath) 1736 } 1737 body, err := io.ReadAll(r.Body) 1738 if err != nil { 1739 t.Fatal(err) 1740 } 1741 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1742 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1743 } 1744 w.WriteHeader(tc.ResponseCode) 1745 w.Write(tc.Response) 1746 })) 1747 defer server.Close() 1748 1749 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1750 defer r.Body.Close() 1751 t.Error("Unexpected Data API call") 1752 w.WriteHeader(404) 1753 w.Write([]byte(`{"message":"Not found"}`)) 1754 })) 1755 defer dataServer.Close() 1756 1757 client, err := mockClient(server, dataServer) 1758 if err != nil { 1759 t.Fatal(err) 1760 } 1761 for i, tc := range testCases { 1762 currentTestIdx = i 1763 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1764 res, err := client.GetWebhookInfo().Do() 1765 if tc.Want.Error != nil { 1766 if !reflect.DeepEqual(err, tc.Want.Error) { 1767 t.Errorf("Error %v; want %v", err, tc.Want.Error) 1768 } 1769 } else { 1770 if err != nil { 1771 t.Error(err) 1772 } 1773 } 1774 if !reflect.DeepEqual(res, tc.Want.Response) { 1775 t.Errorf("Response %v; want %v", res, tc.Want.Response) 1776 } 1777 }) 1778 } 1779 } 1780 1781 func TestGetWebhookInfoWithContext(t *testing.T) { 1782 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1783 defer r.Body.Close() 1784 time.Sleep(10 * time.Millisecond) 1785 w.Write([]byte("{}")) 1786 })) 1787 defer server.Close() 1788 1789 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1790 defer r.Body.Close() 1791 t.Error("Unexpected Data API call") 1792 w.WriteHeader(404) 1793 w.Write([]byte(`{"message":"Not found"}`)) 1794 })) 1795 defer dataServer.Close() 1796 1797 client, err := mockClient(server, dataServer) 1798 if err != nil { 1799 t.Fatal(err) 1800 } 1801 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 1802 defer cancel() 1803 _, err = client.GetWebhookInfo().WithContext(ctx).Do() 1804 expectCtxDeadlineExceed(ctx, err, t) 1805 } 1806 1807 func BenchmarkGetWebhookInfo(b *testing.B) { 1808 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1809 defer r.Body.Close() 1810 w.Write([]byte(`{"endpoint":"https://example.herokuapp.com/test","active":true}`)) 1811 })) 1812 defer server.Close() 1813 1814 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1815 defer r.Body.Close() 1816 b.Error("Unexpected Data API call") 1817 w.WriteHeader(404) 1818 w.Write([]byte(`{"message":"Not found"}`)) 1819 })) 1820 defer dataServer.Close() 1821 1822 client, err := mockClient(server, dataServer) 1823 if err != nil { 1824 b.Fatal(err) 1825 } 1826 b.ResetTimer() 1827 for i := 0; i < b.N; i++ { 1828 client.GetWebhookInfo().Do() 1829 } 1830 } 1831 1832 func TestTestWebhook(t *testing.T) { 1833 type want struct { 1834 URLPath string 1835 RequestBody []byte 1836 Response *TestWebhookResponse 1837 Error error 1838 } 1839 testCases := []struct { 1840 Label string 1841 ResponseCode int 1842 Response []byte 1843 Want want 1844 }{ 1845 { 1846 Label: "Success", 1847 ResponseCode: 200, 1848 Response: []byte(`{ 1849 "success": true, 1850 "timestamp": "2020-09-30T05:38:20.031Z", 1851 "statusCode": 200, 1852 "reason": "OK", 1853 "detail": "200" 1854 }`), 1855 Want: want{ 1856 URLPath: APIEndpointTestWebhook, 1857 RequestBody: []byte(""), 1858 Response: &TestWebhookResponse{ 1859 Success: true, 1860 Timestamp: time.Date(2020, time.September, 30, 05, 38, 20, int(31*time.Millisecond), time.UTC), 1861 StatusCode: 200, 1862 Reason: "OK", 1863 Detail: "200", 1864 }, 1865 }, 1866 }, 1867 { 1868 Label: "Failed with timeout", 1869 ResponseCode: 200, 1870 Response: []byte(`{ 1871 "success": false, 1872 "timestamp": "2023-09-12T14:00:10.015Z", 1873 "statusCode": 0, 1874 "reason": "REQUEST_TIMEOUT", 1875 "detail": "Request timeout: https://example.com/" 1876 }`), 1877 Want: want{ 1878 URLPath: APIEndpointTestWebhook, 1879 RequestBody: []byte(""), 1880 Response: &TestWebhookResponse{ 1881 Success: false, 1882 Timestamp: time.Date(2023, time.September, 12, 14, 00, 10, int(15*time.Millisecond), time.UTC), 1883 StatusCode: 0, 1884 Reason: "REQUEST_TIMEOUT", 1885 Detail: "Request timeout: https://example.com/", 1886 }, 1887 }, 1888 }, 1889 { 1890 Label: "Internal server error", 1891 ResponseCode: 500, 1892 Response: []byte("500 Internal server error"), 1893 Want: want{ 1894 URLPath: APIEndpointTestWebhook, 1895 RequestBody: []byte(""), 1896 Error: &APIError{ 1897 Code: 500, 1898 }, 1899 }, 1900 }, 1901 { 1902 Label: "Invalid channelAccessToken error", 1903 ResponseCode: 401, 1904 Response: []byte(`{"message":"Authentication failed due to the following reason: invalid token. Confirm that the access token in the authorization header is valid."}`), 1905 Want: want{ 1906 URLPath: APIEndpointTestWebhook, 1907 RequestBody: []byte(""), 1908 Error: &APIError{ 1909 Code: 401, 1910 Response: &ErrorResponse{ 1911 Message: "Authentication failed due to the following reason: invalid token. Confirm that the access token in the authorization header is valid.", 1912 }, 1913 }, 1914 }, 1915 }, 1916 } 1917 1918 var currentTestIdx int 1919 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1920 defer r.Body.Close() 1921 tc := testCases[currentTestIdx] 1922 if r.Method != http.MethodPost { 1923 t.Errorf("Method %s; want %s", r.Method, http.MethodPost) 1924 } 1925 if r.URL.Path != tc.Want.URLPath { 1926 t.Errorf("URLPath %s; want %s", r.URL.Path, tc.Want.URLPath) 1927 } 1928 body, err := io.ReadAll(r.Body) 1929 if err != nil { 1930 t.Fatal(err) 1931 } 1932 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 1933 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 1934 } 1935 w.WriteHeader(tc.ResponseCode) 1936 w.Write(tc.Response) 1937 })) 1938 defer server.Close() 1939 1940 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1941 defer r.Body.Close() 1942 t.Error("Unexpected Data API call") 1943 w.WriteHeader(404) 1944 w.Write([]byte(`{"message":"Not found"}`)) 1945 })) 1946 defer dataServer.Close() 1947 1948 client, err := mockClient(server, dataServer) 1949 if err != nil { 1950 t.Fatal(err) 1951 } 1952 for i, tc := range testCases { 1953 currentTestIdx = i 1954 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 1955 res, err := client.TestWebhook().Do() 1956 if tc.Want.Error != nil { 1957 if !reflect.DeepEqual(err, tc.Want.Error) { 1958 t.Errorf("Error %v; want %v", err, tc.Want.Error) 1959 } 1960 } else { 1961 if err != nil { 1962 t.Error(err) 1963 } 1964 } 1965 if !reflect.DeepEqual(res, tc.Want.Response) { 1966 t.Errorf("Response %v; want %v", res, tc.Want.Response) 1967 } 1968 }) 1969 } 1970 } 1971 1972 func TestTestWebhookWithContext(t *testing.T) { 1973 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1974 defer r.Body.Close() 1975 time.Sleep(10 * time.Millisecond) 1976 w.Write([]byte("{}")) 1977 })) 1978 defer server.Close() 1979 1980 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1981 defer r.Body.Close() 1982 t.Error("Unexpected Data API call") 1983 w.WriteHeader(404) 1984 w.Write([]byte(`{"message":"Not found"}`)) 1985 })) 1986 defer dataServer.Close() 1987 1988 client, err := mockClient(server, dataServer) 1989 if err != nil { 1990 t.Fatal(err) 1991 } 1992 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 1993 defer cancel() 1994 _, err = client.TestWebhook().WithContext(ctx).Do() 1995 expectCtxDeadlineExceed(ctx, err, t) 1996 } 1997 1998 func TestSetWebhookEndpointURL(t *testing.T) { 1999 type want struct { 2000 URLPath string 2001 RequestBody []byte 2002 Response *BasicResponse 2003 Error error 2004 } 2005 testCases := []struct { 2006 Label string 2007 Endpoint string 2008 ResponseCode int 2009 RequestID string 2010 Response []byte 2011 Want want 2012 }{ 2013 { 2014 Label: "Success", 2015 Endpoint: "https://example.com/abcdefghijklmn", 2016 ResponseCode: 200, 2017 RequestID: "f70dd685-499a-4231-a441-f24b8d4fba21", 2018 Response: []byte(`{}`), 2019 Want: want{ 2020 URLPath: APIEndpointSetWebhookEndpoint, 2021 RequestBody: []byte(`{"endpoint":"https://example.com/abcdefghijklmn"}` + "\n"), 2022 Response: &BasicResponse{ 2023 RequestID: "f70dd685-499a-4231-a441-f24b8d4fba21", 2024 }, 2025 }, 2026 }, 2027 { 2028 Label: "Internal server error", 2029 Endpoint: "https://example.com/abcdefghijklmn", 2030 ResponseCode: 500, 2031 RequestID: "f70dd685-499a-4231-a441-f24b8d4fba21", 2032 Response: []byte("500 Internal server error"), 2033 Want: want{ 2034 URLPath: APIEndpointSetWebhookEndpoint, 2035 RequestBody: []byte(`{"endpoint":"https://example.com/abcdefghijklmn"}` + "\n"), 2036 Error: &APIError{ 2037 Code: 500, 2038 }, 2039 }, 2040 }, 2041 { 2042 Label: "Invalid webhook URL error:not https", 2043 Endpoint: "http://example.com/not/https", 2044 ResponseCode: 400, 2045 RequestID: "f70dd685-499a-4231-a441-f24b8d4fba21", 2046 Response: []byte(`{"message":"Invalid webhook endpoint URL"}`), 2047 Want: want{ 2048 URLPath: APIEndpointSetWebhookEndpoint, 2049 RequestBody: []byte(`{"endpoint":"http://example.com/not/https"}` + "\n"), 2050 Error: &APIError{ 2051 Code: 400, 2052 Response: &ErrorResponse{ 2053 Message: "Invalid webhook endpoint URL", 2054 }, 2055 }, 2056 }, 2057 }, 2058 { 2059 Label: "Invalid webhook URL error:more 500 characters", 2060 Endpoint: "https://example.com/exceed/500/characters", 2061 ResponseCode: 500, 2062 RequestID: "f70dd685-499a-4231-a441-f24b8d4fba21", 2063 Response: []byte(`{"message":"The request body has 1 error(s)","details":[{"message":"Size must be between 0 and 500","property":"endpoint"}]}`), 2064 Want: want{ 2065 URLPath: APIEndpointSetWebhookEndpoint, 2066 RequestBody: []byte(`{"endpoint":"https://example.com/exceed/500/characters"}` + "\n"), 2067 Error: &APIError{ 2068 Code: 500, 2069 Response: &ErrorResponse{ 2070 Message: "The request body has 1 error(s)", 2071 Details: []errorResponseDetail{ 2072 { 2073 Message: "Size must be between 0 and 500", 2074 Property: "endpoint", 2075 }, 2076 }, 2077 }, 2078 }, 2079 }, 2080 }, 2081 } 2082 2083 var currentTestIdx int 2084 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2085 defer r.Body.Close() 2086 tc := testCases[currentTestIdx] 2087 if r.Method != http.MethodPut { 2088 t.Errorf("Method %s; want %s", r.Method, http.MethodPut) 2089 } 2090 if r.URL.Path != tc.Want.URLPath { 2091 t.Errorf("URLPath %s; want %s", r.URL.Path, tc.Want.URLPath) 2092 } 2093 body, err := io.ReadAll(r.Body) 2094 if err != nil { 2095 t.Fatal(err) 2096 } 2097 if !reflect.DeepEqual(body, tc.Want.RequestBody) { 2098 t.Errorf("RequestBody %s; want %s", body, tc.Want.RequestBody) 2099 } 2100 w.Header().Set("X-Line-Request-Id", tc.RequestID) 2101 w.WriteHeader(tc.ResponseCode) 2102 w.Write(tc.Response) 2103 })) 2104 defer server.Close() 2105 2106 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2107 defer r.Body.Close() 2108 t.Error("Unexpected Data API call") 2109 w.WriteHeader(404) 2110 w.Write([]byte(`{"message":"Not found"}`)) 2111 })) 2112 defer dataServer.Close() 2113 2114 client, err := mockClient(server, dataServer) 2115 if err != nil { 2116 t.Fatal(err) 2117 } 2118 for i, tc := range testCases { 2119 currentTestIdx = i 2120 t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) { 2121 res, err := client.SetWebhookEndpointURL(tc.Endpoint).Do() 2122 if tc.Want.Error != nil { 2123 if !reflect.DeepEqual(err, tc.Want.Error) { 2124 t.Errorf("Error %v; want %v", err, tc.Want.Error) 2125 } 2126 } else { 2127 if err != nil { 2128 t.Error(err) 2129 } 2130 } 2131 if !reflect.DeepEqual(res, tc.Want.Response) { 2132 t.Errorf("Response %v; want %v", res, tc.Want.Response) 2133 } 2134 }) 2135 } 2136 } 2137 2138 func TestSetWebhookEndpointURLWithContext(t *testing.T) { 2139 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2140 defer r.Body.Close() 2141 time.Sleep(10 * time.Millisecond) 2142 w.Write([]byte("{}")) 2143 })) 2144 defer server.Close() 2145 2146 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2147 defer r.Body.Close() 2148 t.Error("Unexpected Data API call") 2149 w.WriteHeader(404) 2150 w.Write([]byte(`{"message":"Not found"}`)) 2151 })) 2152 defer dataServer.Close() 2153 2154 client, err := mockClient(server, dataServer) 2155 if err != nil { 2156 t.Fatal(err) 2157 } 2158 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) 2159 defer cancel() 2160 _, err = client.SetWebhookEndpointURL("https://example.com/abcdefghijklmn").WithContext(ctx).Do() 2161 expectCtxDeadlineExceed(ctx, err, t) 2162 } 2163 2164 func BenchmarkSetWebhookEndpointURL(b *testing.B) { 2165 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2166 defer r.Body.Close() 2167 w.Write([]byte(`{}`)) 2168 })) 2169 defer server.Close() 2170 2171 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2172 defer r.Body.Close() 2173 b.Error("Unexpected Data API call") 2174 w.WriteHeader(404) 2175 w.Write([]byte(`{"message":"Not found"}`)) 2176 })) 2177 defer dataServer.Close() 2178 2179 client, err := mockClient(server, dataServer) 2180 if err != nil { 2181 b.Fatal(err) 2182 } 2183 b.ResetTimer() 2184 for i := 0; i < b.N; i++ { 2185 client.SetWebhookEndpointURL("https://example.com/abcdefghijklmn").Do() 2186 } 2187 } 2188 2189 func BenchmarkTestWebhook(b *testing.B) { 2190 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2191 defer r.Body.Close() 2192 w.Write([]byte("{}")) 2193 })) 2194 defer server.Close() 2195 2196 dataServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2197 defer r.Body.Close() 2198 b.Error("Unexpected data API call") 2199 w.WriteHeader(404) 2200 w.Write([]byte(`{"message":"Not found"}`)) 2201 })) 2202 defer dataServer.Close() 2203 2204 client, err := mockClient(server, dataServer) 2205 if err != nil { 2206 b.Fatal(err) 2207 } 2208 b.ResetTimer() 2209 for i := 0; i < b.N; i++ { 2210 client.TestWebhook().Do() 2211 } 2212 }