diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index 4001e917b..dc78e264e 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -160,6 +160,8 @@ func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Ch } else if msg.Type == "image" && msg.Image != nil { text = msg.Image.Caption mediaURL, err = h.resolveMediaURL(channel, msg.Image.ID, clog) + } else if msg.Type == "sticker" && msg.Sticker != nil { + mediaURL, err = h.resolveMediaURL(channel, msg.Sticker.ID, clog) } else if msg.Type == "video" && msg.Video != nil { text = msg.Video.Caption mediaURL, err = h.resolveMediaURL(channel, msg.Video.ID, clog) diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index 4eca75dea..86a26a08e 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -114,6 +114,20 @@ var testCasesD3C = []IncomingTestCase{ ExpectedAttachments: []string{"https://waba-v2.360dialog.io/whatsapp_business/attachments/?mid=id_image"}, ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), }, + { + Label: "Receive Valid Sticker Message", + URL: d3CReceiveURL, + Data: string(test.ReadFile("../meta/testdata/wac/stickerWAC.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp(""), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://waba-v2.360dialog.io/whatsapp_business/attachments/?mid=id_sticker"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + }, { Label: "Receive Valid Video Message", URL: d3CReceiveURL, @@ -272,6 +286,10 @@ func buildMockD3MediaService(testChannels []courier.Channel, testCases []Incomin fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_audio" } + if strings.HasSuffix(r.URL.Path, "id_sticker") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_sticker" + } + w.WriteHeader(http.StatusOK) w.Write([]byte(fmt.Sprintf(`{ "url": "%s" }`, fileURL))) })) @@ -352,7 +370,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ Label: "Audio Send", MsgText: "audio caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, + MsgAttachments: []string{"audio/mpeg:http://mock.com/1234/test.mp3"}, MockResponses: map[string][]*httpx.MockResponse{ "*/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -360,7 +378,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/1234/test.mp3"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"audio caption","preview_url":false}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -369,7 +387,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ Label: "Document Send", MsgText: "document caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MsgAttachments: []string{"application/pdf:http://mock.com/1234/test.pdf"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -378,7 +396,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"http://mock.com/1234/test.pdf","caption":"document caption","filename":"test.pdf"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -387,7 +405,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ Label: "Image Send", MsgText: "image caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -396,7 +414,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/1234/test.jpg","caption":"image caption"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -405,7 +423,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ Label: "Video Send", MsgText: "video caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MsgAttachments: []string{"video/mp4:http://mock.com/1234/test.mp4"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -414,7 +432,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"http://mock.com/1234/test.mp4","caption":"video caption"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -507,7 +525,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -517,7 +535,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"https://foo.bar/image.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"http://mock.com/1234/test.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -527,7 +545,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MsgAttachments: []string{"video/mp4:http://mock.com/1234/test.mp4"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -537,7 +555,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"https://foo.bar/video.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"http://mock.com/1234/test.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -547,7 +565,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"}, + MsgAttachments: []string{"document/pdf:http://mock.com/1234/test.pdf"}, MockResponses: map[string][]*httpx.MockResponse{ "https://waba-v2.360dialog.io/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -557,7 +575,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"https://foo.bar/document.pdf","filename":"document.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"http://mock.com/1234/test.pdf","filename":"test.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -567,7 +585,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"}, - MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, + MsgAttachments: []string{"audio/mp3:http://mock.com/1234/test.mp3"}, MockResponses: map[string][]*httpx.MockResponse{ "*/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -575,7 +593,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/1234/test.mp3"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"ROW1"}},{"type":"reply","reply":{"id":"1","title":"ROW2"}},{"type":"reply","reply":{"id":"2","title":"ROW3"}}]}}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -585,7 +603,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "Interactive List Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "*/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -593,7 +611,7 @@ var SendTestCasesD3C = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/1234/test.jpg"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"list","body":{"text":"Interactive List Msg"},"action":{"button":"Menu","sections":[{"rows":[{"id":"0","title":"ROW1"},{"id":"1","title":"ROW2"},{"id":"2","title":"ROW3"},{"id":"3","title":"ROW4"}]}]}}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -669,6 +687,30 @@ var SendTestCasesD3C = []OutgoingTestCase{ }, } +// setupMedia takes care of having the media files needed to our test server host +func setupMedia(mb *test.MockBackend) { + imageJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/1234/test.jpg", 1024*1024, 640, 480, 0, nil) + + audioM4A := test.NewMockMedia("test.m4a", "audio/mp4", "http://mock.com/2345/test.m4a", 1024*1024, 0, 0, 200, nil) + audioMP3 := test.NewMockMedia("test.mp3", "audio/mpeg", "http://mock.com/3456/test.mp3", 1024*1024, 0, 0, 200, []courier.Media{audioM4A}) + + thumbJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/4567/test.jpg", 1024*1024, 640, 480, 0, nil) + videoMP4 := test.NewMockMedia("test.mp4", "video/mp4", "http://mock.com/5678/test.mp4", 1024*1024, 0, 0, 1000, []courier.Media{thumbJPG}) + + videoMOV := test.NewMockMedia("test.mov", "video/quicktime", "http://mock.com/6789/test.mov", 100*1024*1024, 0, 0, 2000, nil) + + filePDF := test.NewMockMedia("test.pdf", "application/pdf", "http://mock.com/7890/test.pdf", 100*1024*1024, 0, 0, 0, nil) + + stickerWEBP := test.NewMockMedia("test.webp", "image/webp", "http://mock.com/8901/test.webp", 50*1024, 480, 480, 0, nil) + + mb.MockMedia(imageJPG) + mb.MockMedia(audioMP3) + mb.MockMedia(videoMP4) + mb.MockMedia(videoMOV) + mb.MockMedia(filePDF) + mb.MockMedia(stickerWEBP) +} + func TestOutgoing(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 @@ -679,5 +721,5 @@ func TestOutgoing(t *testing.T) { }) checkRedacted := []string{"the-auth-token"} - RunOutgoingTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, nil) + RunOutgoingTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, setupMedia) } diff --git a/handlers/media.go b/handlers/media.go index 136040584..f1a51bd53 100644 --- a/handlers/media.go +++ b/handlers/media.go @@ -20,8 +20,10 @@ const ( ) type MediaTypeSupport struct { - Types []string - MaxBytes int + Types []string + MaxBytes int + MaxWidth int + MaxHeight int } // Attachment is a resolved attachment @@ -84,7 +86,10 @@ func resolveAttachment(ctx context.Context, b courier.Backend, contentType, medi } mediaType, _ := parseContentType(media.ContentType()) - mediaSupport := support[mediaType] + mediaSupport, ok := support[MediaType(media.ContentType())] + if !ok { + mediaSupport = support[mediaType] + } // our candidates are the uploaded media and any alternates of the same media type candidates := append([]courier.Media{media}, filterMediaByType(media.Alternates(), mediaType)...) @@ -99,6 +104,11 @@ func resolveAttachment(ctx context.Context, b courier.Backend, contentType, medi candidates = filterMediaBySize(candidates, mediaSupport.MaxBytes) } + // narrow down the candidates to the ones that don't exceed our max dimensions + if mediaSupport.MaxWidth > 0 && mediaSupport.MaxHeight > 0 { + candidates = filterMediaByDimensions(candidates, mediaSupport.MaxWidth, mediaSupport.MaxHeight) + } + // if we have no candidates, we can't use this media if len(candidates) == 0 { return nil, nil @@ -144,6 +154,10 @@ func filterMediaBySize(in []courier.Media, maxBytes int) []courier.Media { return filterMedia(in, func(m courier.Media) bool { return m.Size() <= maxBytes }) } +func filterMediaByDimensions(in []courier.Media, maxWidth int, MaxHeight int) []courier.Media { + return filterMedia(in, func(m courier.Media) bool { return m.Width() <= maxWidth && m.Height() <= MaxHeight }) +} + func filterMedia(in []courier.Media, f func(courier.Media) bool) []courier.Media { filtered := make([]courier.Media, 0, len(in)) for _, m := range in { diff --git a/handlers/media_test.go b/handlers/media_test.go index 5fd132db6..84d8f3d13 100644 --- a/handlers/media_test.go +++ b/handlers/media_test.go @@ -139,6 +139,29 @@ func TestResolveAttachments(t *testing.T) { mediaSupport: map[handlers.MediaType]handlers.MediaTypeSupport{}, err: "invalid attachment format: http://mock.com/1234/test.jpg", }, + { // 14: resolveable uploaded image URL with matching dimensions + attachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, + mediaSupport: map[handlers.MediaType]handlers.MediaTypeSupport{handlers.MediaTypeImage: {Types: []string{"image/jpeg", "image/png"}, MaxWidth: 1000, MaxHeight: 1000}}, + allowURLOnly: true, + resolved: []*handlers.Attachment{ + {Type: handlers.MediaTypeImage, Name: "test.jpg", ContentType: "image/jpeg", URL: "http://mock.com/1234/test.jpg", Media: imageJPG, Thumbnail: nil}, + }, + }, + { // 15: resolveable uploaded image URL without matching dimensions + attachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, + mediaSupport: map[handlers.MediaType]handlers.MediaTypeSupport{handlers.MediaTypeImage: {Types: []string{"image/jpeg", "image/png"}, MaxWidth: 100, MaxHeight: 100}}, + allowURLOnly: true, + resolved: []*handlers.Attachment{}, + errors: []*courier.ChannelError{courier.ErrorMediaUnresolveable("image/jpeg")}, + }, + { // 16: resolveable uploaded image URL without matching dimensions by specific content type precendence + attachments: []string{"image/jpeg:http://mock.com/1234/test.jpg"}, + mediaSupport: map[handlers.MediaType]handlers.MediaTypeSupport{handlers.MediaTypeImage: {Types: []string{"image/jpeg", "image/png"}, MaxWidth: 100, MaxHeight: 100}, handlers.MediaType("image/jpeg"): {Types: []string{"image/jpeg", "image/png"}, MaxWidth: 1000, MaxHeight: 1000}}, + allowURLOnly: true, + resolved: []*handlers.Attachment{ + {Type: handlers.MediaTypeImage, Name: "test.jpg", ContentType: "image/jpeg", URL: "http://mock.com/1234/test.jpg", Media: imageJPG, Thumbnail: nil}, + }, + }, } for i, tc := range tcs { diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 4005b6010..27b157dfb 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -309,6 +309,8 @@ func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Ch } else if msg.Type == "image" && msg.Image != nil { text = msg.Image.Caption mediaURL, err = h.resolveMediaURL(msg.Image.ID, token, clog) + } else if msg.Type == "sticker" && msg.Sticker != nil { + mediaURL, err = h.resolveMediaURL(msg.Sticker.ID, token, clog) } else if msg.Type == "video" && msg.Video != nil { text = msg.Video.Caption mediaURL, err = h.resolveMediaURL(msg.Video.ID, token, clog) diff --git a/handlers/meta/testdata/wac/audio.json b/handlers/meta/testdata/wac/audio.json index f578e5fc9..40e5233b0 100644 --- a/handlers/meta/testdata/wac/audio.json +++ b/handlers/meta/testdata/wac/audio.json @@ -26,7 +26,7 @@ "audio": { "file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683", "id": "id_audio", - "mime_type": "image/jpeg", + "mime_type": "audio/mpeg", "sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db", "caption": "Check out my new phone!" }, diff --git a/handlers/meta/testdata/wac/stickerWAC.json b/handlers/meta/testdata/wac/stickerWAC.json new file mode 100644 index 000000000..481319f1e --- /dev/null +++ b/handlers/meta/testdata/wac/stickerWAC.json @@ -0,0 +1,42 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "8856996819413533", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "+250 788 123 200", + "phone_number_id": "12345" + }, + "contacts": [ + { + "profile": { + "name": "Kerry Fisher" + }, + "wa_id": "5678" + } + ], + "messages": [ + { + "from": "5678", + "id": "external_id", + "sticker": { + "file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683", + "id": "id_sticker", + "mime_type": "image/webp", + "sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db" + }, + "timestamp": "1454119029", + "type": "sticker" + } + ] + }, + "field": "messages" + } + ] + } + ] +} \ No newline at end of file diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index cef934d16..13b4e84f6 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -2,6 +2,10 @@ package meta import ( "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" "testing" "time" @@ -107,6 +111,21 @@ var whatsappIncomingTests = []IncomingTestCase{ ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), PrepRequest: addValidSignature, }, + { + Label: "Receive Valid Sticker Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/stickerWAC.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp(""), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Sticker"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, { Label: "Receive Valid Video Message", URL: whatappReceiveURL, @@ -277,7 +296,53 @@ var whatsappIncomingTests = []IncomingTestCase{ } func TestWhatsAppIncoming(t *testing.T) { - graphURL = createMockGraphAPI().URL + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + accessToken := r.Header.Get("Authorization") + defer r.Body.Close() + + // invalid auth token + if accessToken != "Bearer a123" && accessToken != "Bearer wac_admin_system_user_token" { + fmt.Printf("Access token: %s\n", accessToken) + http.Error(w, "invalid auth token", http.StatusForbidden) + return + } + + if strings.HasSuffix(r.URL.Path, "image") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Image"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "audio") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Audio"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "voice") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Voice"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "video") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Video"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "document") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Document"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "sticker") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Sticker"}`)) + return + } + + // valid token + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL"}`)) + + })) + graphURL = server.URL RunIncomingTestCases(t, whatsappTestChannels, newHandler("WAC", "Cloud API WhatsApp"), whatsappIncomingTests) } @@ -321,7 +386,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ Label: "Audio Send", MsgText: "audio caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, + MsgAttachments: []string{"audio/mpeg:http://mock.com/3456/test.mp3"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -329,7 +394,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/3456/test.mp3"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"audio caption","preview_url":false}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -338,7 +403,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ Label: "Document Send", MsgText: "document caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MsgAttachments: []string{"application/pdf:http://mock.com/3456/test.pdf"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -347,7 +412,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"http://mock.com/3456/test.pdf","caption":"document caption","filename":"test.pdf"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -356,7 +421,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ Label: "Image Send", MsgText: "image caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/3456/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -365,7 +430,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/3456/test.jpg","caption":"image caption"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -374,7 +439,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ Label: "Video Send", MsgText: "video caption", MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MsgAttachments: []string{"video/mp4:http://mock.com/3456/test.mp4"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -383,7 +448,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"http://mock.com/3456/test.mp4","caption":"video caption"}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -530,7 +595,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/3456/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -539,7 +604,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"https://foo.bar/image.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","image":{"link":"http://mock.com/3456/test.jpg"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -549,7 +614,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MsgAttachments: []string{"video/mp4:http://mock.com/3456/test.mp4"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -558,7 +623,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"https://foo.bar/video.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"video","video":{"link":"http://mock.com/3456/test.mp4"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -568,7 +633,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"}, + MsgAttachments: []string{"document/pdf:http://mock.com/3456/test.pdf"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -577,7 +642,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"https://foo.bar/document.pdf","filename":"document.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"document","document":{"link":"http://mock.com/3456/test.pdf","filename":"test.pdf"}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, }, }, ExpectedExtIDs: []string{"157b5e14568e8"}, @@ -587,7 +652,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "Interactive Button Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"}, - MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, + MsgAttachments: []string{"audio/mp3:http://mock.com/3456/test.mp3"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -595,7 +660,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"http://mock.com/3456/test.mp3"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"ROW1"}},{"type":"reply","reply":{"id":"1","title":"ROW2"}},{"type":"reply","reply":{"id":"2","title":"ROW3"}}]}}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -605,7 +670,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "Interactive List Msg", MsgURN: "whatsapp:250788123123", MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:http://mock.com/3456/test.jpg"}, MockResponses: map[string][]*httpx.MockResponse{ "*/12345_ID/messages": { httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), @@ -613,7 +678,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ }, }, ExpectedRequests: []ExpectedRequest{ - {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`}, + {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"http://mock.com/3456/test.jpg"}}`}, {Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"list","body":{"text":"Interactive List Msg"},"action":{"button":"Menu","sections":[{"rows":[{"id":"0","title":"ROW1"},{"id":"1","title":"ROW2"},{"id":"2","title":"ROW3"},{"id":"3","title":"ROW4"}]}]}}}`}, }, ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, @@ -670,6 +735,30 @@ var whatsappOutgoingTests = []OutgoingTestCase{ }, } +// setupMedia takes care of having the media files needed to our test server host +func setupMedia(mb *test.MockBackend) { + imageJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/1234/test.jpg", 1024*1024, 640, 480, 0, nil) + + audioM4A := test.NewMockMedia("test.m4a", "audio/mp4", "http://mock.com/2345/test.m4a", 1024*1024, 0, 0, 200, nil) + audioMP3 := test.NewMockMedia("test.mp3", "audio/mpeg", "http://mock.com/3456/test.mp3", 1024*1024, 0, 0, 200, []courier.Media{audioM4A}) + + thumbJPG := test.NewMockMedia("test.jpg", "image/jpeg", "http://mock.com/4567/test.jpg", 1024*1024, 640, 480, 0, nil) + videoMP4 := test.NewMockMedia("test.mp4", "video/mp4", "http://mock.com/5678/test.mp4", 1024*1024, 0, 0, 1000, []courier.Media{thumbJPG}) + + videoMOV := test.NewMockMedia("test.mov", "video/quicktime", "http://mock.com/6789/test.mov", 100*1024*1024, 0, 0, 2000, nil) + + filePDF := test.NewMockMedia("test.pdf", "application/pdf", "http://mock.com/7890/test.pdf", 100*1024*1024, 0, 0, 0, nil) + + stickerWEBP := test.NewMockMedia("test.webp", "image/webp", "http://mock.com/8901/test.webp", 50*1024, 480, 480, 0, nil) + + mb.MockMedia(imageJPG) + mb.MockMedia(audioMP3) + mb.MockMedia(videoMP4) + mb.MockMedia(videoMOV) + mb.MockMedia(filePDF) + mb.MockMedia(stickerWEBP) +} + func TestWhatsAppOutgoing(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 @@ -678,7 +767,7 @@ func TestWhatsAppOutgoing(t *testing.T) { checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} - RunOutgoingTestCases(t, channel, newHandler("WAC", "Cloud API WhatsApp"), whatsappOutgoingTests, checkRedacted, nil) + RunOutgoingTestCases(t, channel, newHandler("WAC", "Cloud API WhatsApp"), whatsappOutgoingTests, checkRedacted, setupMedia) } func TestWhatsAppDescribeURN(t *testing.T) { diff --git a/handlers/meta/whatsapp/api.go b/handlers/meta/whatsapp/api.go index 5a36d008b..bfeb7c1eb 100644 --- a/handlers/meta/whatsapp/api.go +++ b/handlers/meta/whatsapp/api.go @@ -23,6 +23,13 @@ type MOMedia struct { SHA256 string `json:"sha256"` } +type wacSticker struct { + Animated bool `json:"animated"` + ID string `json:"id"` + Mimetype string `json:"mime_type"` + SHA256 string `json:"sha256"` +} + type Change struct { Field string `json:"field"` Value struct { @@ -51,11 +58,12 @@ type Change struct { Text struct { Body string `json:"body"` } `json:"text"` - Image *MOMedia `json:"image"` - Audio *MOMedia `json:"audio"` - Video *MOMedia `json:"video"` - Document *MOMedia `json:"document"` - Voice *MOMedia `json:"voice"` + Image *MOMedia `json:"image"` + Audio *MOMedia `json:"audio"` + Video *MOMedia `json:"video"` + Document *MOMedia `json:"document"` + Voice *MOMedia `json:"voice"` + Sticker *wacSticker `json:"sticker"` Location *struct { Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"`