diff --git a/handlers/m3tech/handler.go b/handlers/m3tech/handler.go index 5262f63e8..f2fb715b4 100644 --- a/handlers/m3tech/handler.go +++ b/handlers/m3tech/handler.go @@ -69,19 +69,10 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") - if username == "" { - return nil, fmt.Errorf("no username set for M3 channel") - } - password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") - if password == "" { - return nil, fmt.Errorf("no password set for M3 channel") + if username == "" || password == "" { + return courier.ErrChannelConfig } // figure out if we need to send as unicode (encoding 7) @@ -91,8 +82,6 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour encoding = "7" } - // send our message - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), text, maxMsgLength) { // build our request params := url.Values{ @@ -114,17 +103,16 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour msgURL.RawQuery = params.Encode() req, err := http.NewRequest(http.MethodGet, msgURL.String(), nil) if err != nil { - return nil, err + return err } resp, _, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - break + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } - - // all went well, set ourselves to wired - status.SetStatus(courier.MsgStatusWired) } - return status, nil + return nil } diff --git a/handlers/m3tech/handler_test.go b/handlers/m3tech/handler_test.go index 12d84196f..2048b8fdb 100644 --- a/handlers/m3tech/handler_test.go +++ b/handlers/m3tech/handler_test.go @@ -1,12 +1,13 @@ package m3tech import ( - "net/http/httptest" + "net/url" "testing" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var testChannels = []courier.Channel{ @@ -47,52 +48,114 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), handleTestCases) } -// setSendURL takes care of setting the send_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL -} - var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", - MsgText: "Simple Message", MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - MockResponseBody: `[{"Response": "0"}]`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{ - "MobileNo": "250788383383", - "SMS": "Simple Message", - "SMSChannel": "0", - "AuthKey": "m3-Tech", - "HandsetPort": "0", - "MsgHeader": "2020", - "Telco": "0", - "SMSType": "0", - "UserId": "Username", - "Password": "Password", + MsgText: "Simple Message", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS*": { + httpx.NewMockResponse(200, nil, []byte(`[{"Response": "0"}]`)), + }, }, - SendPrep: setSendURL}, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{ + "MobileNo": {"250788383383"}, + "SMS": {"Simple Message"}, + "SMSChannel": {"0"}, + "AuthKey": {"m3-Tech"}, + "HandsetPort": {"0"}, + "MsgHeader": {"2020"}, + "MsgId": {"10"}, + "Telco": {"0"}, + "SMSType": {"0"}, + "UserId": {"Username"}, + "Password": {"Password"}, + }, + }}, + }, {Label: "Unicode Send", - MsgText: "☺", MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - MockResponseBody: `[{"Response": "0"}]`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{"SMS": "☺", "SMSType": "7"}, - SendPrep: setSendURL}, + MsgText: "☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS*": { + httpx.NewMockResponse(200, nil, []byte(`[{"Response": "0"}]`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{ + "SMS": {"☺"}, + "MobileNo": {"250788383383"}, + "SMSChannel": {"0"}, + "AuthKey": {"m3-Tech"}, + "HandsetPort": {"0"}, + "MsgHeader": {"2020"}, + "MsgId": {"10"}, + "Telco": {"0"}, + "SMSType": {"7"}, + "UserId": {"Username"}, + "Password": {"Password"}, + }, + }}, + }, {Label: "Smart Encoding", - MsgText: "Fancy “Smart” Quotes", MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - MockResponseBody: `[{"Response": "0"}]`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{"SMS": `Fancy "Smart" Quotes`, "SMSType": "0"}, - SendPrep: setSendURL}, + MsgText: "Fancy “Smart” Quotes", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS*": { + httpx.NewMockResponse(200, nil, []byte(`[{"Response": "0"}]`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{ + "SMS": {`Fancy "Smart" Quotes`}, + "MobileNo": {"250788383383"}, + "SMSChannel": {"0"}, + "AuthKey": {"m3-Tech"}, + "HandsetPort": {"0"}, + "MsgHeader": {"2020"}, + "MsgId": {"10"}, + "Telco": {"0"}, + "SMSType": {"0"}, + "UserId": {"Username"}, + "Password": {"Password"}, + }, + }}, + }, {Label: "Send Attachment", - MsgText: "My pic!", MsgURN: "tel:+250788383383", MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - ExpectedMsgStatus: "W", - MockResponseBody: `[{"Response": "0"}]`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{"SMS": "My pic!\nhttps://foo.bar/image.jpg", "SMSType": "0"}, - SendPrep: setSendURL}, + MsgText: "My pic!", + MsgURN: "tel:+250788383383", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS*": { + httpx.NewMockResponse(200, nil, []byte(`[{"Response": "0"}]`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{ + "SMS": {"My pic!\nhttps://foo.bar/image.jpg"}, + "MobileNo": {"250788383383"}, + "SMSChannel": {"0"}, + "AuthKey": {"m3-Tech"}, + "HandsetPort": {"0"}, + "MsgHeader": {"2020"}, + "MsgId": {"10"}, + "Telco": {"0"}, + "SMSType": {"0"}, + "UserId": {"Username"}, + "Password": {"Password"}, + }, + }}, + }, {Label: "Error Sending", - MsgText: "Error Sending", MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `[{"Response": "101"}]`, MockResponseStatus: 403, - SendPrep: setSendURL}, + MsgText: "Error Sending", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://secure.m3techservice.com/GenericServiceRestAPI/api/SendSMS*": { + httpx.NewMockResponse(403, nil, []byte(`[{"Response": "101"}]`)), + }, + }, + ExpectedError: courier.ErrResponseStatus, + }, } func TestOutgoing(t *testing.T) { diff --git a/handlers/macrokiosk/handler.go b/handlers/macrokiosk/handler.go index a60ad982c..4a51dec45 100644 --- a/handlers/macrokiosk/handler.go +++ b/handlers/macrokiosk/handler.go @@ -150,17 +150,12 @@ type mtPayload struct { } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") servID := msg.Channel().StringConfigForKey(configMacrokioskServiceID, "") senderID := msg.Channel().StringConfigForKey(configMacrokioskSenderID, "") if username == "" || password == "" || servID == "" || senderID == "" { - return nil, fmt.Errorf("missing username, password, serviceID or senderID for MK channel") + return courier.ErrChannelConfig } // figure out if we need to send as unicode (encoding 5) @@ -170,9 +165,8 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour encoding = "5" } - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), text, maxMsgLength) - for i, part := range parts { + for _, part := range parts { payload := &mtPayload{ From: senderID, ServID: servID, @@ -188,26 +182,24 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour // build our request req, err := http.NewRequest(http.MethodPost, sendURL, requestBody) if err != nil { - return nil, err + return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } externalID, err := jsonparser.GetString(respBody, "MsgID") if err != nil { - return status, fmt.Errorf("unable to parse response body from Macrokiosk") - } - - // set the external id if this is our first part - if i == 0 { - status.SetExternalID(externalID) + clog.Error(courier.ErrorResponseValueMissing("MsgID")) + } else { + res.AddExternalID(externalID) } } - status.SetStatus(courier.MsgStatusWired) - return status, nil + return nil } diff --git a/handlers/macrokiosk/handler_test.go b/handlers/macrokiosk/handler_test.go index 27a3814b7..a18db5708 100644 --- a/handlers/macrokiosk/handler_test.go +++ b/handlers/macrokiosk/handler_test.go @@ -1,13 +1,13 @@ package macrokiosk import ( - "net/http/httptest" "testing" "time" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var testChannels = []courier.Channel{ @@ -72,82 +72,103 @@ func TestIncoming(t *testing.T) { RunIncomingTestCases(t, testChannels, newHandler(), incomingTestCases) } -// setSendURL takes care of setting the send_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL -} - var outgoingTestCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"abc123"}, - MockResponseBody: `{ "MsgID":"abc123" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://www.etracker.cc/bulksms/send": { + httpx.NewMockResponse(200, nil, []byte(`{ "MsgID":"abc123" }`)), + }, }, - ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"Simple Message ☺","from":"macro","servid":"service-id","type":"5"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }, + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"Simple Message ☺","from":"macro","servid":"service-id","type":"5"}`, + }}, + ExpectedExtIDs: []string{"abc123"}, }, { - Label: "Long Send", - MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"abc123"}, - MockResponseBody: `{ "MsgID":"abc123" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", + Label: "Long Send", + MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://www.etracker.cc/bulksms/send": { + httpx.NewMockResponse(200, nil, []byte(`{ "MsgID":"abc123" }`)), + httpx.NewMockResponse(200, nil, []byte(`{ "MsgID":"abc123" }`)), + }, }, - ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"I need to keep adding more things to make it work","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }, + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say,","from":"macro","servid":"service-id","type":"0"}`, + }, + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }, + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"I need to keep adding more things to make it work","from":"macro","servid":"service-id","type":"0"}`, + }, + }, + ExpectedExtIDs: []string{"abc123", "abc123"}, }, { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"abc123"}, - MockResponseBody: `{ "MsgID":"abc123" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", + Label: "Send Attachment", + MsgText: "My pic!", + MsgURN: "tel:+250788383383", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://www.etracker.cc/bulksms/send": { + httpx.NewMockResponse(200, nil, []byte(`{ "MsgID":"abc123" }`)), + }, }, - ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"My pic!\nhttps://foo.bar/image.jpg","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }, + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"My pic!\nhttps://foo.bar/image.jpg","from":"macro","servid":"service-id","type":"0"}`, + }}, + ExpectedExtIDs: []string{"abc123"}, }, { - Label: "No External Id", - MsgText: "No External ID", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{ "missing":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedLogErrors: []*courier.ChannelError{courier.NewChannelError("", "", "unable to parse response body from Macrokiosk")}, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", + Label: "No External Id", + MsgText: "No External ID", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://www.etracker.cc/bulksms/send": { + httpx.NewMockResponse(200, nil, []byte(`{ "missing":"missing" }`)), + }, }, - ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"No External ID","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }, + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"No External ID","from":"macro","servid":"service-id","type":"0"}`, + }}, + ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("MsgID")}, }, { - Label: "Error Sending", - MsgText: "Error Message", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 401, - ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"Error Message","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL, + Label: "Error Sending", + MsgText: "Error Message", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://www.etracker.cc/bulksms/send": { + httpx.NewMockResponse(401, nil, []byte(`{ "error": "failed" }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"user":"Username","pass":"Password","to":"250788383383","text":"Error Message","from":"macro","servid":"service-id","type":"0"}`, + }}, + ExpectedError: courier.ErrResponseStatus, }, } diff --git a/handlers/mblox/handler.go b/handlers/mblox/handler.go index 1543f35c9..089109a5f 100644 --- a/handlers/mblox/handler.go +++ b/handlers/mblox/handler.go @@ -113,18 +113,12 @@ type mtPayload struct { } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") if username == "" || password == "" { - return nil, fmt.Errorf("Missing username or password for MB channel") + return courier.ErrChannelConfig } - - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { payload := &mtPayload{} @@ -139,25 +133,26 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour // build our request req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s/batches", sendURL, username), requestBody) if err != nil { - return nil, err + return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", password)) resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } externalID, err := jsonparser.GetString(respBody, "id") if err != nil { - return status, fmt.Errorf("unable to parse response body from MBlox") + clog.Error(courier.ErrorResponseValueMissing("id")) + } else { + res.AddExternalID(externalID) } - - status.SetStatus(courier.MsgStatusWired) - status.SetExternalID(externalID) } - return status, nil + return nil } diff --git a/handlers/mblox/handler_test.go b/handlers/mblox/handler_test.go index b72ee7637..123205b78 100644 --- a/handlers/mblox/handler_test.go +++ b/handlers/mblox/handler_test.go @@ -1,13 +1,13 @@ package mblox import ( - "net/http/httptest" "testing" "time" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var testChannels = []courier.Channel{ @@ -89,86 +89,108 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -// setSendURL takes care of setting the send_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL -} - var defaultSendTestCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - MockResponseBody: `{ "id":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer Password", + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mblox.com/xms/v1/Username/batches": { + httpx.NewMockResponse(200, nil, []byte(`{ "id":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"from":"2020","to":["250788383383"],"body":"Simple Message ☺","delivery_report":"per_recipient"}`, - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer Password", + }, + Body: `{"from":"2020","to":["250788383383"],"body":"Simple Message ☺","delivery_report":"per_recipient"}`, + }}, + ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, }, { - Label: "Long Send", - MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - MockResponseBody: `{ "id":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer Password", + Label: "Long Send", + MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mblox.com/xms/v1/Username/batches": { + httpx.NewMockResponse(200, nil, []byte(`{ "id":"OzYDlvf3SQVc" }`)), + httpx.NewMockResponse(200, nil, []byte(`{ "id":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"from":"2020","to":["250788383383"],"body":"I need to keep adding more things to make it work","delivery_report":"per_recipient"}`, - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer Password", + }, + Body: `{"from":"2020","to":["250788383383"],"body":"This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say,","delivery_report":"per_recipient"}`, + }, + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer Password", + }, + Body: `{"from":"2020","to":["250788383383"],"body":"I need to keep adding more things to make it work","delivery_report":"per_recipient"}`, + }, + }, + ExpectedExtIDs: []string{"OzYDlvf3SQVc", "OzYDlvf3SQVc"}, }, { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - ExpectedMsgStatus: "W", - MockResponseBody: `{ "id":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer Password", + Label: "Send Attachment", + MsgText: "My pic!", + MsgURN: "tel:+250788383383", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mblox.com/xms/v1/Username/batches": { + httpx.NewMockResponse(200, nil, []byte(`{ "id":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"from":"2020","to":["250788383383"],"body":"My pic!\nhttps://foo.bar/image.jpg","delivery_report":"per_recipient"}`, - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer Password", + }, + Body: `{"from":"2020","to":["250788383383"],"body":"My pic!\nhttps://foo.bar/image.jpg","delivery_report":"per_recipient"}`, + }}, + ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, }, { - Label: "No External Id", - MsgText: "No External ID", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{ "missing":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedLogErrors: []*courier.ChannelError{courier.NewChannelError("", "", "unable to parse response body from MBlox")}, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer Password", + Label: "No External Id", + MsgText: "No External ID", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mblox.com/xms/v1/Username/batches": { + httpx.NewMockResponse(200, nil, []byte(`{ "missing":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"from":"2020","to":["250788383383"],"body":"No External ID","delivery_report":"per_recipient"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer Password", + }, + Body: `{"from":"2020","to":["250788383383"],"body":"No External ID","delivery_report":"per_recipient"}`, + }}, + ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("id")}, }, { - Label: "Error Sending", - MsgText: "Error Message", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 401, - ExpectedRequestBody: `{"from":"2020","to":["250788383383"],"body":"Error Message","delivery_report":"per_recipient"}`, - SendPrep: setSendURL, + Label: "Error Sending", + MsgText: "Error Message", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mblox.com/xms/v1/Username/batches": { + httpx.NewMockResponse(401, nil, []byte(`{ "error": "failed" }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"from":"2020","to":["250788383383"],"body":"Error Message","delivery_report":"per_recipient"}`, + }}, + ExpectedError: courier.ErrResponseStatus, }, } diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 6e2bfcdfc..01127ed21 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -185,20 +185,12 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { - authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { - return nil, fmt.Errorf("missing config 'auth_token' for Messagebird channel") + return courier.ErrChannelConfig } user := msg.URN().Path() - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - // create base payload payload := &Message{ Recipients: []string{user}, @@ -224,9 +216,8 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, sendUrl, bytes.NewReader(jsonBody)) - if err != nil { - return nil, err + return err } req.Header.Set("Content-Type", "application/json") @@ -234,18 +225,20 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour req.Header.Set("Authorization", bearer) resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } - status.SetStatus(courier.MsgStatusWired) externalID, err := jsonparser.GetString(respBody, "id") if err != nil { - clog.Error(courier.ErrorResponseUnparseable("JSON")) - return status, nil + clog.Error(courier.ErrorResponseValueMissing("id")) + } else { + res.AddExternalID(externalID) } - status.SetExternalID(externalID) - return status, nil + + return nil } func verifyToken(tokenString string, secret string) (jwt.MapClaims, error) { diff --git a/handlers/messagebird/handler_test.go b/handlers/messagebird/handler_test.go index ace002b9a..b157c271f 100644 --- a/handlers/messagebird/handler_test.go +++ b/handlers/messagebird/handler_test.go @@ -2,7 +2,6 @@ package messagebird import ( "net/http" - "net/http/httptest" "testing" "time" @@ -10,6 +9,7 @@ import ( "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var testChannels = []courier.Channel{ @@ -187,109 +187,127 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", true), defaultReceiveTestCases) } -func setSmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - smsURL = s.URL -} - -func setMmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - mmsURL = s.URL -} - var defaultSendTestCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:188885551515", - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setSmsSendURL, + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/messages": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "Send with text and image", - MsgText: "Simple Message ☺", - MsgURN: "tel:188885551515", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺","mediaUrls":["https://foo.bar/image.jpg"]}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setMmsSendURL, + Label: "Send with text and image", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/mms": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺","mediaUrls":["https://foo.bar/image.jpg"]}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "Send with image only", - MsgURN: "tel:188885551515", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg"]}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setMmsSendURL, + Label: "Send with image only", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/mms": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg"]}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "Send with two images", - MsgURN: "tel:188885551515", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg", "image/jpeg:https://foo.bar/image2.jpg"}, - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg","https://foo.bar/image2.jpg"]}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setMmsSendURL, + Label: "Send with two images", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg", "image/jpeg:https://foo.bar/image2.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/mms": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg","https://foo.bar/image2.jpg"]}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "Send with video only", - MsgURN: "tel:188885551515", - MsgAttachments: []string{"video/mp4:https://foo.bar/movie.mp4"}, - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/movie.mp4"]}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setMmsSendURL, + Label: "Send with video only", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"video/mp4:https://foo.bar/movie.mp4"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/mms": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/movie.mp4"]}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "Send with pdf", - MsgURN: "tel:188885551515", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: validResponse, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/document.pdf"]}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, - SendPrep: setMmsSendURL, + Label: "Send with pdf", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/mms": { + httpx.NewMockResponse(200, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/document.pdf"]}`, + }}, + ExpectedExtIDs: []string{"efa6405d518d4c0c88cce11f7db775fb"}, }, { - Label: "500 on Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:188885551515", - MockResponseBody: validResponse, - MockResponseStatus: 500, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, - ExpectedMsgStatus: "E", - SendPrep: setSmsSendURL, + Label: "500 on Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/messages": { + httpx.NewMockResponse(500, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, + }}, + ExpectedError: courier.ErrConnectionFailed, }, { - Label: "404 on Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:188885551515", - MockResponseBody: validResponse, - MockResponseStatus: 404, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, - ExpectedMsgStatus: "E", - SendPrep: setSmsSendURL, + Label: "404 on Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MockResponses: map[string][]*httpx.MockResponse{ + "https://rest.messagebird.com/messages": { + httpx.NewMockResponse(404, nil, []byte(validResponse)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + Body: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, + }}, + ExpectedError: courier.ErrResponseStatus, }, } diff --git a/handlers/messangi/handler.go b/handlers/messangi/handler.go index 8e298f11c..a88f43d1f 100644 --- a/handlers/messangi/handler.go +++ b/handlers/messangi/handler.go @@ -60,32 +60,14 @@ type mtResponse struct { } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { publicKey := msg.Channel().StringConfigForKey(configPublicKey, "") - if publicKey == "" { - return nil, fmt.Errorf("no public_key set for MG channel") - } - privateKey := msg.Channel().StringConfigForKey(configPrivateKey, "") - if privateKey == "" { - return nil, fmt.Errorf("no private_key set for MG channel") - } - instanceId := msg.Channel().IntConfigForKey(configInstanceId, -1) - if instanceId == -1 { - return nil, fmt.Errorf("no instance_id set for MG channel") - } - carrierId := msg.Channel().IntConfigForKey(configCarrierId, -1) - if carrierId == -1 { - return nil, fmt.Errorf("no carrier_id set for MG channel") + if publicKey == "" || privateKey == "" || instanceId == -1 || carrierId == -1 { + return courier.ErrChannelConfig } - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { shortcode := strings.TrimPrefix(msg.Channel().Address(), "+") @@ -97,31 +79,28 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour req, err := http.NewRequest(http.MethodGet, fullURL, nil) if err != nil { - return nil, err + return err } resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } // parse our response as XML response := &mtResponse{} err = xml.Unmarshal(respBody, response) if err != nil { - clog.Error(courier.ErrorResponseUnparseable("XML")) - break + return courier.ErrResponseUnparseable } // we always get 204 on success - if response.Status == "OK" { - status.SetStatus(courier.MsgStatusWired) - } else { - status.SetStatus(courier.MsgStatusFailed) - clog.Error(courier.ErrorResponseValueUnexpected("status", "OK")) - break + if response.Status != "OK" { + return courier.ErrResponseStatus } } - return status, nil + return nil } diff --git a/handlers/messangi/handler_test.go b/handlers/messangi/handler_test.go index a490cf5f8..6492bb0d4 100644 --- a/handlers/messangi/handler_test.go +++ b/handlers/messangi/handler_test.go @@ -1,12 +1,12 @@ package messangi import ( - "net/http/httptest" "testing" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var testChannels = []courier.Channel{ @@ -42,57 +42,80 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL -} - var defaultSendTestCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+18765422035", - MockResponseBody: `sendMTOKCompleted`, - MockResponseStatus: 200, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:+18765422035", + MockResponses: map[string][]*httpx.MockResponse{ + "https://flow.messangi.me/mmc/rest/api/sendMT/*": { + httpx.NewMockResponse(200, nil, []byte(`sendMTOKCompleted`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/U2ltcGxlIE1lc3NhZ2Ug4pi6/my-public-key/f69bc6a924480d3ed82970d9679c4be90589bd3064add51c47e8bf50a211d55f", + }}, }, { - Label: "Long Send", - MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", - MsgURN: "tel:+18765422035", - MockResponseBody: `sendMTOKCompleted`, - MockResponseStatus: 200, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + Label: "Long Send", + MsgText: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", + MsgURN: "tel:+18765422035", + MockResponses: map[string][]*httpx.MockResponse{ + "https://flow.messangi.me/mmc/rest/api/sendMT/*": { + httpx.NewMockResponse(200, nil, []byte(`sendMTOKCompleted`)), + httpx.NewMockResponse(200, nil, []byte(`sendMTOKCompleted`)), + }, + }, + ExpectedRequests: []ExpectedRequest{ + { + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/VGhpcyBpcyBhIGxvbmdlciBtZXNzYWdlIHRoYW4gMTYwIGNoYXJhY3RlcnMgYW5kIHdpbGwgY2F1c2UgdXMgdG8gc3BsaXQgaXQgaW50byB0d28gc2VwYXJhdGUgcGFydHMsIGlzbid0IHRoYXQgcmlnaHQgYnV0IGl0IGlzIGV2ZW4gbG9uZ2VyIHRoYW4gYmVmb3JlIEkgc2F5LA/my-public-key/48c658e8db8635843ac3d3e497a81cf79cc0d75b8630dae03c6e7d93a749ab90", + }, + { + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/SSBuZWVkIHRvIGtlZXAgYWRkaW5nIG1vcmUgdGhpbmdzIHRvIG1ha2UgaXQgd29yaw/my-public-key/ba305915a6cf56c1255071655de42b4408071460317bb5bf3419bb9f865c5078", + }, + }, }, { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+18765422035", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `sendMTOKCompleted`, - MockResponseStatus: 200, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + Label: "Send Attachment", + MsgText: "My pic!", + MsgURN: "tel:+18765422035", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://flow.messangi.me/mmc/rest/api/sendMT/*": { + httpx.NewMockResponse(200, nil, []byte(`sendMTOKCompleted`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/TXkgcGljIQpodHRwczovL2Zvby5iYXIvaW1hZ2UuanBn/my-public-key/4babdf316c0b5c7b6b40855329b421b1da1b8e63690d59eb5c231049dc4067fd", + }}, }, { - Label: "Invalid Parameters", - MsgText: "Invalid Parameters", - MsgURN: "tel:+18765422035", - MockResponseBody: "", - MockResponseStatus: 404, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + Label: "Invalid Parameters", + MsgText: "Invalid Parameters", + MsgURN: "tel:+18765422035", + MockResponses: map[string][]*httpx.MockResponse{ + "https://flow.messangi.me/mmc/rest/api/sendMT/*": { + httpx.NewMockResponse(404, nil, []byte(``)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/SW52YWxpZCBQYXJhbWV0ZXJz/my-public-key/f3d2ea825cf61226925dee2db3c14b7fc00f3183f11809d2183d1e2dbd230df6", + }}, + ExpectedError: courier.ErrResponseStatus, }, { - Label: "Error Response", - MsgText: "Error Response", - MsgURN: "tel:+18765422035", - MockResponseBody: `sendMTERRORCompleted`, - MockResponseStatus: 200, - ExpectedMsgStatus: "F", - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueUnexpected("status", "OK")}, - SendPrep: setSendURL, + Label: "Error Response", + MsgText: "Error Response", + MsgURN: "tel:+18765422035", + MockResponses: map[string][]*httpx.MockResponse{ + "https://flow.messangi.me/mmc/rest/api/sendMT/*": { + httpx.NewMockResponse(200, nil, []byte(`sendMTERRORCompleted`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Path: "/mmc/rest/api/sendMT/7/2020/2/18765422035/RXJyb3IgUmVzcG9uc2U/my-public-key/27f4c67fa00848ea6029cc0b1797aae6d05e2970ecb6e44ca486b463b933e61a", + }}, + ExpectedError: courier.ErrResponseStatus, }, } diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index e2ee4c5ed..46e5ecdec 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "strings" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" "github.com/stretchr/testify/assert" ) @@ -354,49 +356,55 @@ func TestFacebookVerify(t *testing.T) { }) } -// setSendURL takes care of setting the send_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL - graphURL = s.URL -} - var facebookOutgoingTests = []OutgoingTestCase{ { - Label: "Text only chat message", - MsgText: "Simple Message", - MsgURN: "facebook:12345", - MsgOrigin: courier.MsgOriginChat, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Text only broadcast message", - MsgText: "Simple Message", - MsgURN: "facebook:12345", - MsgOrigin: courier.MsgOriginBroadcast, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Text only broadcast with opt-in auth token", - MsgText: "Simple Message", - MsgURN: "facebook:12345", - MsgURNAuth: "345678", - MsgOrigin: courier.MsgOriginBroadcast, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"notification_messages_token":"345678"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Text only chat message", + MsgText: "Simple Message", + MsgURN: "facebook:12345", + MsgOrigin: courier.MsgOriginChat, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Text only broadcast message", + MsgText: "Simple Message", + MsgURN: "facebook:12345", + MsgOrigin: courier.MsgOriginBroadcast, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Text only broadcast with opt-in auth token", + MsgText: "Simple Message", + MsgURN: "facebook:12345", + MsgURNAuth: "345678", + MsgOrigin: courier.MsgOriginBroadcast, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"notification_messages_token":"345678"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { Label: "Text only flow response", @@ -404,12 +412,16 @@ var facebookOutgoingTests = []OutgoingTestCase{ MsgURN: "facebook:12345", MsgOrigin: courier.MsgOriginFlow, MsgResponseToExternalID: "23526", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"RESPONSE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"RESPONSE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { Label: "Text only flow response using referal URN", @@ -417,126 +429,189 @@ var facebookOutgoingTests = []OutgoingTestCase{ MsgURN: "facebook:ref:67890", MsgOrigin: courier.MsgOriginFlow, MsgResponseToExternalID: "23526", - MockResponseBody: `{"message_id": "mid.133", "recipient_id": "12345"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"RESPONSE","recipient":{"user_ref":"67890"},"message":{"text":"Simple Message"}}`, - ExpectedContactURNs: map[string]bool{"facebook:12345": true, "ext:67890": true, "facebook:ref:67890": false}, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Quick replies on a broadcast message", - MsgText: "Are you happy?", - MsgURN: "facebook:12345", - MsgOrigin: courier.MsgOriginBroadcast, - MsgQuickReplies: []string{"Yes", "No"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Are you happy?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Message that exceeds max text length", - MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?", - MsgURN: "facebook:12345", - MsgQuickReplies: []string{"Yes", "No"}, - MsgTopic: "account", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"we exceed the max length?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133", "recipient_id": "12345"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"RESPONSE","recipient":{"user_ref":"67890"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedContactURNs: map[string]bool{"facebook:12345": true, "ext:67890": true, "facebook:ref:67890": false}, ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, }, { - Label: "Image attachment", - MsgURN: "facebook:12345", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Text, image attachment, quick replies and explicit message topic", - MsgText: "This is some text.", - MsgURN: "facebook:12345", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MsgQuickReplies: []string{"Yes", "No"}, - MsgTopic: "event", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is some text.","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Quick replies on a broadcast message", + MsgText: "Are you happy?", + MsgURN: "facebook:12345", + MsgOrigin: courier.MsgOriginBroadcast, + MsgQuickReplies: []string{"Yes", "No"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Are you happy?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Quick replies on a broadcast message", + MsgText: "Are you happy?", + MsgURN: "facebook:12345", + MsgOrigin: courier.MsgOriginBroadcast, + MsgQuickReplies: []string{"Yes", "No"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Are you happy?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Message that exceeds max text length", + MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?", + MsgURN: "facebook:12345", + MsgQuickReplies: []string{"Yes", "No"}, + MsgTopic: "account", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is a long message which spans more than one part, what will actually be sent in the end if"}}`, + }, + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"we exceed the max length?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }, + }, + ExpectedExtIDs: []string{"mid.133", "mid.133"}, }, { - Label: "Document attachment", - MsgURN: "facebook:12345", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"file","payload":{"url":"https://foo.bar/document.pdf","is_reusable":true}}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Image attachment", + MsgURN: "facebook:12345", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Text, image attachment, quick replies and explicit message topic", + MsgText: "This is some text.", + MsgURN: "facebook:12345", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgQuickReplies: []string{"Yes", "No"}, + MsgTopic: "event", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, + }, + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is some text.","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }, + }, + ExpectedExtIDs: []string{"mid.133", "mid.133"}, }, { - Label: "Opt-in request", - MsgURN: "facebook:12345", - MsgOptIn: &courier.OptInReference{ID: 3456, Name: "Joke Of The Day"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"template","payload":{"template_type":"notification_messages","title":"Joke Of The Day","payload":"3456"}}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, - }, - { - Label: "Response doesn't contain message id", - MsgText: "ID Error", - MsgURN: "facebook:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 200, - ExpectedMsgStatus: "E", - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response status code is non-200", - MsgText: "Error", - MsgURN: "facebook:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "facebook:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, - { - Label: "Response is channel specific error", - MsgText: "Error", - MsgURN: "facebook:12345", - MockResponseBody: `{ "error": {"message": "The image size is too large.","code": 36000 }}`, - MockResponseStatus: 400, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + Label: "Document attachment", + MsgURN: "facebook:12345", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"file","payload":{"url":"https://foo.bar/document.pdf","is_reusable":true}}}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Opt-in request", + MsgURN: "facebook:12345", + MsgOptIn: &courier.OptInReference{ID: 3456, Name: "Joke Of The Day"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"template","payload":{"template_type":"notification_messages","title":"Joke Of The Day","payload":"3456"}}}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, + }, + { + Label: "Response doesn't contain message id", + MsgText: "ID Error", + MsgURN: "facebook:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{ "is_error": true }`)), + }, + }, + ExpectedError: courier.ErrResponseUnexpected, + }, + { + Label: "Response status code is non-200", + MsgText: "Error", + MsgURN: "facebook:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(403, nil, []byte(`{ "is_error": true }`)), + }, + }, + ExpectedError: courier.ErrResponseStatus, + }, + { + Label: "Response is invalid JSON", + MsgText: "Error", + MsgURN: "facebook:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`bad json`)), + }, + }, + ExpectedError: courier.ErrResponseUnparseable, + }, + { + Label: "Response is channel specific error", + MsgText: "Error", + MsgURN: "facebook:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{ "error": {"message": "The image size is too large.","code": 36000 }}`)), + }, + }, + ExpectedError: courier.ErrFailedWithReason("36000", "The image size is too large."), }, } diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 8a556833e..903e4cc03 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -622,25 +622,20 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { if msg.Channel().ChannelType() == "FBA" || msg.Channel().ChannelType() == "IG" { - return h.sendFacebookInstagramMsg(ctx, msg, clog) + return h.sendFacebookInstagramMsg(ctx, msg, res, clog) } else if msg.Channel().ChannelType() == "WAC" { - return h.sendWhatsAppMsg(ctx, msg, clog) + return h.sendWhatsAppMsg(ctx, msg, res, clog) } - return nil, fmt.Errorf("unssuported channel type") + return fmt.Errorf("unssuported channel type") } -func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if accessToken == "" { - return nil, fmt.Errorf("missing access token") + return courier.ErrChannelConfig } isHuman := msg.Origin() == courier.MsgOriginChat || msg.Origin() == courier.MsgOriginTicket @@ -677,8 +672,6 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO query.Set("access_token", accessToken) msgURL.RawQuery = query.Encode() - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - // Send each text segment and attachment separately. We send attachments first as otherwise quick replies get // attached to attachment segments and are hidden when images load. for _, part := range handlers.SplitMsg(msg, handlers.SplitOptions{MaxTextLen: maxMsgLength}) { @@ -720,80 +713,75 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO req, err := http.NewRequest(http.MethodPost, msgURL.String(), bytes.NewReader(jsonBody)) if err != nil { - return nil, err + return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - _, respBody, _ := h.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus + } + respPayload := &messenger.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { - clog.Error(courier.ErrorResponseUnparseable("JSON")) - return status, nil + return courier.ErrResponseUnparseable } if respPayload.Error.Code != 0 { - clog.Error(courier.ErrorExternal(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message)) - return status, nil + return courier.ErrFailedWithReason(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message) } if respPayload.ExternalID == "" { - clog.Error(courier.ErrorResponseValueMissing("message_id")) - return status, nil + return courier.ErrResponseUnexpected } - // if this is our first message, record the external id - if part.IsFirst { - status.SetExternalID(respPayload.ExternalID) - if msg.URN().IsFacebookRef() { - recipientID := respPayload.RecipientID - if recipientID == "" { - clog.Error(courier.ErrorResponseValueMissing("recipient_id")) - return status, nil - } - - referralID := msg.URN().FacebookRef() + res.AddExternalID(respPayload.ExternalID) + if msg.URN().IsFacebookRef() { + recipientID := respPayload.RecipientID + if recipientID == "" { + return courier.ErrResponseUnexpected + } - realIDURN, err := urns.NewFacebookURN(recipientID) - if err != nil { - clog.RawError(errors.Errorf("unable to make facebook urn from %s", recipientID)) - } + referralID := msg.URN().FacebookRef() - contact, err := h.Backend().GetContact(ctx, msg.Channel(), msg.URN(), nil, "", clog) - if err != nil { - clog.RawError(errors.Errorf("unable to get contact for %s", msg.URN().String())) - } - realURN, err := h.Backend().AddURNtoContact(ctx, msg.Channel(), contact, realIDURN, nil) - if err != nil { - clog.RawError(errors.Errorf("unable to add real facebook URN %s to contact with uuid %s", realURN.String(), contact.UUID())) - } - referralIDExtURN, err := urns.NewURNFromParts(urns.ExternalScheme, referralID, "", "") - if err != nil { - clog.RawError(errors.Errorf("unable to make ext urn from %s", referralID)) - } - extURN, err := h.Backend().AddURNtoContact(ctx, msg.Channel(), contact, referralIDExtURN, nil) - if err != nil { - clog.RawError(errors.Errorf("unable to add URN %s to contact with uuid %s", extURN.String(), contact.UUID())) - } + realIDURN, err := urns.NewFacebookURN(recipientID) + if err != nil { + clog.RawError(errors.Errorf("unable to make facebook urn from %s", recipientID)) + } - referralFacebookURN, err := h.Backend().RemoveURNfromContact(ctx, msg.Channel(), contact, msg.URN()) - if err != nil { - clog.RawError(errors.Errorf("unable to remove referral facebook URN %s from contact with uuid %s", referralFacebookURN.String(), contact.UUID())) - } + contact, err := h.Backend().GetContact(ctx, msg.Channel(), msg.URN(), nil, "", clog) + if err != nil { + clog.RawError(errors.Errorf("unable to get contact for %s", msg.URN().String())) + } + realURN, err := h.Backend().AddURNtoContact(ctx, msg.Channel(), contact, realIDURN, nil) + if err != nil { + clog.RawError(errors.Errorf("unable to add real facebook URN %s to contact with uuid %s", realURN.String(), contact.UUID())) + } + referralIDExtURN, err := urns.NewURNFromParts(urns.ExternalScheme, referralID, "", "") + if err != nil { + clog.RawError(errors.Errorf("unable to make ext urn from %s", referralID)) + } + extURN, err := h.Backend().AddURNtoContact(ctx, msg.Channel(), contact, referralIDExtURN, nil) + if err != nil { + clog.RawError(errors.Errorf("unable to add URN %s to contact with uuid %s", extURN.String(), contact.UUID())) + } + referralFacebookURN, err := h.Backend().RemoveURNfromContact(ctx, msg.Channel(), contact, msg.URN()) + if err != nil { + clog.RawError(errors.Errorf("unable to remove referral facebook URN %s from contact with uuid %s", referralFacebookURN.String(), contact.UUID())) } } - - // this was wired successfully - status.SetStatus(courier.MsgStatusWired) } - return status, nil + return nil } -func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { // can't do anything without an access token accessToken := h.Server().Config().WhatsappAdminSystemUserToken @@ -801,8 +789,6 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog path, _ := url.Parse(fmt.Sprintf("/%s/messages", msg.Channel().Address())) wacPhoneURL := base.ResolveReference(path) - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - hasCaption := false msgParts := make([]string, 0) @@ -821,7 +807,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog // do we have a template? templating, err := whatsapp.GetTemplating(msg) if err != nil { - return nil, errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) + return errors.Wrapf(err, "unable to decode template: %s for channel: %s", string(msg.Metadata()), msg.Channel().UUID()) } if templating != nil { payload.Type = "template" @@ -840,6 +826,12 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog } else { if len(qrs) > 0 { payload.Type = "interactive" + if len(qrs) > 10 { + clog.Error(courier.NewChannelError("", "", "too many quick replies WAC supports only up to 10 quick replies")) + // limit to the first 10 + qrs = qrs[:10] + } + // We can use buttons if len(qrs) <= 3 { interactive := whatsapp.Interactive{Type: "button", Body: struct { @@ -884,8 +876,6 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog }} payload.Interactive = &interactive - } else { - return nil, fmt.Errorf("too many quick replies WAC supports only up to 10 quick replies") } } else { // this is still a msg part @@ -933,6 +923,12 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog } else { if len(qrs) > 0 { payload.Type = "interactive" + if len(qrs) > 10 { + clog.Error(courier.NewChannelError("", "", "too many quick replies WAC supports only up to 10 quick replies")) + // limit to the first 10 + qrs = qrs[:10] + } + // We can use buttons if len(qrs) <= 3 { interactive := whatsapp.Interactive{Type: "button", Body: struct { @@ -971,7 +967,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { - return nil, err + return err } document := whatsapp.Media{ Link: attURL, @@ -985,14 +981,11 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { - var zeroIndex bool - if i == 0 { - zeroIndex = true - } + payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} - err := h.requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := h.requestWAC(payloadAudio, accessToken, res, wacPhoneURL, clog) if err != nil { - return status, nil + return err } } else { interactive.Type = "button" @@ -1039,8 +1032,6 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog }} payload.Interactive = &interactive - } else { - return nil, fmt.Errorf("too many quick replies WAC supports only up to 10 quick replies") } } else { // this is still a msg part @@ -1054,24 +1045,19 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog } } - var zeroIndex bool - if i == 0 { - zeroIndex = true - } - - err := h.requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := h.requestWAC(payload, accessToken, res, wacPhoneURL, clog) if err != nil { - return status, err + return err } if hasCaption { break } } - return status, nil + return nil } -func (h *handler) requestWAC(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) error { +func (h *handler) requestWAC(payload whatsapp.SendRequest, accessToken string, res *courier.SendResult, wacPhoneURL *url.URL, clog *courier.ChannelLog) error { jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) @@ -1087,21 +1073,17 @@ func (h *handler) requestWAC(payload whatsapp.SendRequest, accessToken string, s respPayload := &whatsapp.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { - clog.Error(courier.ErrorResponseUnparseable("JSON")) - return nil + return courier.ErrResponseUnparseable } if respPayload.Error.Code != 0 { - clog.Error(courier.ErrorExternal(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message)) - return nil + return courier.ErrFailedWithReason(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message) } externalID := respPayload.Messages[0].ID - if zeroIndex && externalID != "" { - status.SetExternalID(externalID) + if externalID != "" { + res.AddExternalID(externalID) } - // this was wired successfully - status.SetStatus(courier.MsgStatusWired) return nil } diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 77a5afcd4..2d791158a 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "net/http/httptest" + "net/url" "strings" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" "github.com/stretchr/testify/assert" ) @@ -183,28 +185,36 @@ func TestInstagramIncoming(t *testing.T) { var instagramOutgoingTests = []OutgoingTestCase{ { - Label: "Text only chat message", - MsgText: "Simple Message", - MsgURN: "instagram:12345", - MsgOrigin: courier.MsgOriginChat, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Text only chat message", + MsgText: "Simple Message", + MsgURN: "instagram:12345", + MsgOrigin: courier.MsgOriginChat, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Text only broadcast message", - MsgText: "Simple Message", - MsgURN: "instagram:12345", - MsgOrigin: courier.MsgOriginBroadcast, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Text only broadcast message", + MsgText: "Simple Message", + MsgURN: "instagram:12345", + MsgOrigin: courier.MsgOriginBroadcast, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { Label: "Text only flow response", @@ -212,126 +222,172 @@ var instagramOutgoingTests = []OutgoingTestCase{ MsgURN: "instagram:12345", MsgOrigin: courier.MsgOriginFlow, MsgResponseToExternalID: "23526", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"RESPONSE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"RESPONSE","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Quick replies on a broadcast message", - MsgText: "Are you happy?", - MsgURN: "instagram:12345", - MsgOrigin: courier.MsgOriginBroadcast, - MsgQuickReplies: []string{"Yes", "No"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Are you happy?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Quick replies on a broadcast message", + MsgText: "Are you happy?", + MsgURN: "instagram:12345", + MsgOrigin: courier.MsgOriginBroadcast, + MsgQuickReplies: []string{"Yes", "No"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"text":"Are you happy?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Message that exceeds max text length", - MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?", - MsgURN: "instagram:12345", - MsgQuickReplies: []string{"Yes", "No"}, - MsgTopic: "account", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"we exceed the max length?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Message that exceeds max text length", + MsgText: "This is a long message which spans more than one part, what will actually be sent in the end if we exceed the max length?", + MsgURN: "instagram:12345", + MsgQuickReplies: []string{"Yes", "No"}, + MsgTopic: "account", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is a long message which spans more than one part, what will actually be sent in the end if"}}`, + }, + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"ACCOUNT_UPDATE","recipient":{"id":"12345"},"message":{"text":"we exceed the max length?","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }, + }, + ExpectedExtIDs: []string{"mid.133", "mid.133"}, }, { - Label: "Image attachment", - MsgURN: "instagram:12345", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Image attachment", + MsgURN: "instagram:12345", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Text, image attachment, quick replies and explicit message topic", - MsgText: "This is some text.", - MsgURN: "instagram:12345", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MsgQuickReplies: []string{"Yes", "No"}, - MsgTopic: "event", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is some text.","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Text, image attachment, quick replies and explicit message topic", + MsgText: "This is some text.", + MsgURN: "instagram:12345", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MsgQuickReplies: []string{"Yes", "No"}, + MsgTopic: "event", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"image","payload":{"url":"https://foo.bar/image.jpg","is_reusable":true}}}}`, + }, + { + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"CONFIRMED_EVENT_UPDATE","recipient":{"id":"12345"},"message":{"text":"This is some text.","quick_replies":[{"title":"Yes","payload":"Yes","content_type":"text"},{"title":"No","payload":"No","content_type":"text"}]}}`, + }, + }, + ExpectedExtIDs: []string{"mid.133", "mid.133"}, }, { - Label: "Explicit human agent tag", - MsgText: "Simple Message", - MsgURN: "instagram:12345", - MsgTopic: "agent", - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Explicit human agent tag", + MsgText: "Simple Message", + MsgURN: "instagram:12345", + MsgTopic: "agent", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"MESSAGE_TAG","tag":"HUMAN_AGENT","recipient":{"id":"12345"},"message":{"text":"Simple Message"}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Document attachment", - MsgURN: "instagram:12345", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{"message_id": "mid.133"}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"file","payload":{"url":"https://foo.bar/document.pdf","is_reusable":true}}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"mid.133"}, - SendPrep: setSendURL, + Label: "Document attachment", + MsgURN: "instagram:12345", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{"message_id": "mid.133"}`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Params: url.Values{"access_token": {"a123"}}, + Body: `{"messaging_type":"UPDATE","recipient":{"id":"12345"},"message":{"attachment":{"type":"file","payload":{"url":"https://foo.bar/document.pdf","is_reusable":true}}}}`, + }}, + ExpectedExtIDs: []string{"mid.133"}, }, { - Label: "Response doesn't contain message id", - MsgText: "ID Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 200, - ExpectedMsgStatus: "E", - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, + Label: "Response doesn't contain message id", + MsgText: "ID Error", + MsgURN: "instagram:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{ "is_error": true }`)), + }, + }, + ExpectedError: courier.ErrResponseUnexpected, }, { - Label: "Response status code is non-200", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, + Label: "Response status code is non-200", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(403, nil, []byte(`{ "is_error": true }`)), + }, + }, + ExpectedError: courier.ErrResponseStatus, }, { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + Label: "Response is invalid JSON", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`bad json`)), + }, + }, + ExpectedError: courier.ErrResponseUnparseable, }, { - Label: "Response is channel specific error", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "error": {"message": "The image size is too large.","code": 36000 }}`, - MockResponseStatus: 400, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + Label: "Response is channel specific error", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponses: map[string][]*httpx.MockResponse{ + "https://graph.facebook.com/v17.0/me/messages*": { + httpx.NewMockResponse(200, nil, []byte(`{ "error": {"message": "The image size is too large.","code": 36000 }}`)), + }, + }, + ExpectedError: courier.ErrFailedWithReason("36000", "The image size is too large."), }, } diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index 0d2a7cb17..ddc85566e 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -285,36 +285,38 @@ func TestWhatsAppIncoming(t *testing.T) { var whatsappOutgoingTests = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Plain Send", + MsgText: "Simple Message", + MsgURN: "whatsapp:250788123123", + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Simple Message","preview_url":false}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Unicode Send", - MsgText: "☺", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Unicode Send", + MsgText: "☺", + MsgURN: "whatsapp:250788123123", + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"☺","preview_url":false}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { Label: "Audio Send", @@ -331,177 +333,212 @@ var whatsappOutgoingTests = []OutgoingTestCase{ {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":"text","text":{"body":"audio caption","preview_url":false}}`}, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, }, { - Label: "Document Send", - MsgText: "document caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Document Send", + MsgText: "document caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Image Send", - MsgText: "image caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Image Send", + MsgText: "image caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Video Send", - MsgText: "video caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Video Send", + MsgText: "video caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}`, }, }, - ExpectedMsgStatus: "W", + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Template Send", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "params": {"body": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}, "language": "en_US"}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, + }}, + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Template Send, no variables", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "params": {}, "variables": [], "language": "en_US"}}`), + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(200, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"}}}`, + }}, + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Interactive Button Message Send", + MsgText: "Interactive Button Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"BUTTON1"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + 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":"BUTTON1"}}]}}}`, + }}, + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Interactive List Message Send", + MsgText: "Interactive List Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + 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"}, + }, + { + Label: "Interactive List Message Send, more than 10 QRs", + MsgText: "Interactive List Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4", "ROW5", "ROW6", "ROW7", "ROW8", "ROW9", "ROW10", "ROW11", "ROW12"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + 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"},{"id":"4","title":"ROW5"},{"id":"5","title":"ROW6"},{"id":"6","title":"ROW7"},{"id":"7","title":"ROW8"},{"id":"8","title":"ROW9"},{"id":"9","title":"ROW10"}]}]}}}`, + }}, ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, - }, - { - Label: "Template Send", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "params": {"body": [{"type":"text", "value":"Chef"}, {"type": "text" , "value": "tomorrow"}]}, "language": "en_US"}}`), - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - SendPrep: setSendURL, - }, - { - Label: "Template Send, no variables", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "params": {}, "variables": [], "language": "en_US"}}`), - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"}}}`, - SendPrep: setSendURL, - }, - { - Label: "Interactive Button Message Send", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"BUTTON1"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"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":"BUTTON1"}}]}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, - }, - { - Label: "Interactive List Message Send", - MsgText: "Interactive List Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"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"}]}]}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, - }, - { - Label: "Interactive List Message Send In Spanish", - MsgText: "Hola", - MsgURN: "whatsapp:250788123123", - MsgLocale: "spa", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"list","body":{"text":"Hola"},"action":{"button":"Menú","sections":[{"rows":[{"id":"0","title":"ROW1"},{"id":"1","title":"ROW2"},{"id":"2","title":"ROW3"},{"id":"3","title":"ROW4"}]}]}}}`, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, - }, - { - Label: "Interactive Button Message Send with image attachment", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + ExpectedLogErrors: []*courier.ChannelError{courier.NewChannelError("", "", "too many quick replies WAC supports only up to 10 quick replies")}, + }, + { + Label: "Interactive List Message Send In Spanish", + MsgText: "Hola", + MsgURN: "whatsapp:250788123123", + MsgLocale: "spa", + MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"list","body":{"text":"Hola"},"action":{"button":"Menú","sections":[{"rows":[{"id":"0","title":"ROW1"},{"id":"1","title":"ROW2"},{"id":"2","title":"ROW3"},{"id":"3","title":"ROW4"}]}]}}}`, + }}, + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Interactive Button Message Send with image attachment", + MsgText: "Interactive Button Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"BUTTON1"}, + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}]}}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Interactive Button Message Send with video attachment", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Interactive Button Message Send with video attachment", + MsgText: "Interactive Button Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"BUTTON1"}, + MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}]}}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { - Label: "Interactive Button Message Send with document attachment", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"BUTTON1"}, - MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Interactive Button Message Send with document attachment", + MsgText: "Interactive Button Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"BUTTON1"}, + MsgAttachments: []string{"document/pdf:https://foo.bar/document.pdf"}, + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, 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"}}]}}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, }, { Label: "Interactive Button Message Send with audio attachment", @@ -519,9 +556,7 @@ var whatsappOutgoingTests = []OutgoingTestCase{ {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":"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"}}]}}}`}, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, }, { Label: "Interactive List Message Send with attachment", @@ -539,46 +574,46 @@ var whatsappOutgoingTests = []OutgoingTestCase{ {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":"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"}]}]}}}`}, }, - - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8", "157b5e14568e8"}, }, { - Label: "Link Sending", - MsgText: "Link Sending https://link.com", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, + Label: "Link Sending", + MsgText: "Link Sending https://link.com", + MsgURN: "whatsapp:250788123123", + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + }, ExpectedRequests: []ExpectedRequest{ { Path: "/12345_ID/messages", Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Link Sending https://link.com","preview_url":true}}`, }, }, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"157b5e14568e8"}, - SendPrep: setSendURL, - }, - { - Label: "Error Bad JSON", - MsgText: "Error", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `bad json`, - MockResponseStatus: 403, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, - { - Label: "Error", - MsgText: "Error", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "error": {"message": "(#130429) Rate limit hit","code": 130429 }}`, - MockResponseStatus: 403, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorExternal("130429", "(#130429) Rate limit hit")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + ExpectedExtIDs: []string{"157b5e14568e8"}, + }, + { + Label: "Error Bad JSON", + MsgText: "Error", + MsgURN: "whatsapp:250788123123", + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(403, nil, []byte(`bad json`)), + }, + }, + ExpectedError: courier.ErrResponseUnparseable, + }, + { + Label: "Error", + MsgText: "Error", + MsgURN: "whatsapp:250788123123", + MockResponses: map[string][]*httpx.MockResponse{ + "*/12345_ID/messages": { + httpx.NewMockResponse(403, nil, []byte(`{ "error": {"message": "(#130429) Rate limit hit","code": 130429 }}`)), + }, + }, + ExpectedError: courier.ErrFailedWithReason("130429", "(#130429) Rate limit hit"), }, } diff --git a/handlers/mtarget/handler.go b/handlers/mtarget/handler.go index a03a90c1d..ed0b6a173 100644 --- a/handlers/mtarget/handler.go +++ b/handlers/mtarget/handler.go @@ -148,23 +148,12 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") - if username == "" { - return nil, fmt.Errorf("no username set for MT channel") - } - password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") - if password == "" { - return nil, fmt.Errorf("no password set for MT channel") + if username == "" || password == "" { + return courier.ErrChannelConfig } - // send our message - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { // build our request params := url.Values{ @@ -180,12 +169,14 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour msgURL.RawQuery = params.Encode() req, err := http.NewRequest(http.MethodPost, msgURL.String(), nil) if err != nil { - return nil, err + return err } resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } // parse our response for our status code and ticket (external id) @@ -202,14 +193,12 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour externalID, _ := jsonparser.GetString(respBody, "results", "[0]", "ticket") if code == "0" && externalID != "" { // all went well, set ourselves to wired - status.SetStatus(courier.MsgStatusWired) - status.SetExternalID(externalID) + res.AddExternalID(externalID) } else { - status.SetStatus(courier.MsgStatusFailed) - clog.RawError(fmt.Errorf("Error status code, failing permanently")) - break + reason, _ := jsonparser.GetString(respBody, "results", "[0]", "reason") + return courier.ErrFailedWithReason(code, reason) } } - return status, nil + return nil } diff --git a/handlers/mtarget/handler_test.go b/handlers/mtarget/handler_test.go index de5cc8cb0..6951afc73 100644 --- a/handlers/mtarget/handler_test.go +++ b/handlers/mtarget/handler_test.go @@ -1,13 +1,13 @@ package mtarget import ( - "net/http/httptest" "net/url" "testing" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var ( @@ -118,70 +118,72 @@ func TestIncoming(t *testing.T) { var outgoingCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"results":[{"code": "0", "ticket": "externalID"}]}`, - MockResponseStatus: 200, + Label: "Plain Send", + MsgText: "Simple Message", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api-public.mtarget.fr/api-sms.json*": { + httpx.NewMockResponse(200, nil, []byte(`{"results":[{"code": "0", "ticket": "externalID"}]}`)), + }, + }, ExpectedRequests: []ExpectedRequest{ {Params: url.Values{"msisdn": {"+250788383383"}, "msg": {"Simple Message"}, "username": {"Username"}, "password": {"Password"}, "serviceid": {"2020"}, "allowunicode": {"true"}}}, }, - ExpectedExtIDs: []string{"externalID"}, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + ExpectedExtIDs: []string{"externalID"}, }, { - Label: "Unicode Send", - MsgText: "☺", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"results":[{"code": "0", "ticket": "externalID"}]}`, - MockResponseStatus: 200, + Label: "Unicode Send", + MsgText: "☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api-public.mtarget.fr/api-sms.json*": { + httpx.NewMockResponse(200, nil, []byte(`{"results":[{"code": "0", "ticket": "externalID"}]}`)), + }, + }, ExpectedRequests: []ExpectedRequest{ {Params: url.Values{"msisdn": {"+250788383383"}, "msg": {"☺"}, "username": {"Username"}, "password": {"Password"}, "serviceid": {"2020"}, "allowunicode": {"true"}}}, }, - ExpectedExtIDs: []string{"externalID"}, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + ExpectedExtIDs: []string{"externalID"}, }, { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{"results":[{"code": "0", "ticket": "externalID"}]}`, - MockResponseStatus: 200, + Label: "Send Attachment", + MsgText: "My pic!", + MsgURN: "tel:+250788383383", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://api-public.mtarget.fr/api-sms.json*": { + httpx.NewMockResponse(200, nil, []byte(`{"results":[{"code": "0", "ticket": "externalID"}]}`)), + }, + }, ExpectedRequests: []ExpectedRequest{ {Params: url.Values{"msisdn": {"+250788383383"}, "msg": {"My pic!\nhttps://foo.bar/image.jpg"}, "username": {"Username"}, "password": {"Password"}, "serviceid": {"2020"}, "allowunicode": {"true"}}}, }, - ExpectedExtIDs: []string{"externalID"}, - ExpectedMsgStatus: "W", - SendPrep: setSendURL, + ExpectedExtIDs: []string{"externalID"}, }, { - Label: "Error Sending", - MsgText: "Error Sending", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"results":[{"code": "3", "ticket": "null"}]}`, - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + Label: "Error Sending", + MsgText: "Error Sending", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api-public.mtarget.fr/api-sms.json*": { + httpx.NewMockResponse(403, nil, []byte(`{"results":[{"code": "3", "reason": "FAILED", "ticket": "null"}]}`)), + }, + }, + ExpectedError: courier.ErrResponseStatus, }, { - Label: "Error Response", - MsgText: "Error Sending", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"results":[{"code": "3", "ticket": "null"}]}`, - MockResponseStatus: 200, - ExpectedMsgStatus: "F", - ExpectedLogErrors: []*courier.ChannelError{courier.NewChannelError("", "", "Error status code, failing permanently")}, - SendPrep: setSendURL, + Label: "Error Response", + MsgText: "Error Sending", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api-public.mtarget.fr/api-sms.json*": { + httpx.NewMockResponse(200, nil, []byte(`{"results":[{"code": "3", "reason": "FAILED", "ticket": "null"}]}`)), + }, + }, + ExpectedError: courier.ErrFailedWithReason("3", "FAILED"), }, } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - sendURL = s.URL -} - func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MT", "2020", "FR", map[string]any{ diff --git a/handlers/mtn/handler.go b/handlers/mtn/handler.go index 978e53868..db473e16e 100644 --- a/handlers/mtn/handler.go +++ b/handlers/mtn/handler.go @@ -121,22 +121,15 @@ type mtPayload struct { } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, res *courier.SendResult, clog *courier.ChannelLog) error { - // TODO convert functionality from legacy method below - return nil -} - -func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accessToken, err := h.getAccessToken(ctx, msg.Channel(), clog) if err != nil { - return nil, err + return courier.ErrChannelConfig } baseURL := msg.Channel().StringConfigForKey(configAPIHost, apiHostURL) cpAddress := msg.Channel().StringConfigForKey(configCPAddress, "") partSendURL, _ := url.Parse(fmt.Sprintf("%s/%s", baseURL, "v2/messages/sms/outbound")) - status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - mtMsg := &mtPayload{} mtMsg.From = strings.TrimPrefix(msg.Channel().Address(), "+") mtMsg.To = []string{strings.TrimPrefix(msg.URN().Path(), "+")} @@ -152,29 +145,27 @@ func (h *handler) SendLegacy(ctx context.Context, msg courier.MsgOut, clog *cour // build our request req, err := http.NewRequest(http.MethodPost, partSendURL.String(), requestBody) if err != nil { - return nil, err + return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, respBody, err := h.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil + if err != nil || resp.StatusCode/100 == 5 { + return courier.ErrConnectionFailed + } else if resp.StatusCode/100 != 2 { + return courier.ErrResponseStatus } externalID, err := jsonparser.GetString(respBody, "transactionId") if err != nil { clog.Error(courier.ErrorResponseValueMissing("transactionId")) - return status, nil + } else { + res.AddExternalID(externalID) } - // if this is our first message, record the external id - - status.SetExternalID(externalID) - status.SetStatus(courier.MsgStatusWired) - - return status, nil + return nil } func (h *handler) RedactValues(ch courier.Channel) []string { diff --git a/handlers/mtn/handler_test.go b/handlers/mtn/handler_test.go index ca56b2cf4..50e4958f4 100644 --- a/handlers/mtn/handler_test.go +++ b/handlers/mtn/handler_test.go @@ -1,13 +1,13 @@ package mtn import ( - "net/http/httptest" "testing" "time" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/httpx" ) var ( @@ -155,89 +155,102 @@ func TestIncoming(t *testing.T) { var outgoingCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - MockResponseBody: `{ "transactionId":"OzYDlvf3SQVc" }`, - MockResponseStatus: 201, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer ACCESS_TOKEN", + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mtn.com/v2/messages/sms/outbound": { + httpx.NewMockResponse(201, nil, []byte(`{ "transactionId":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Simple Message ☺","clientCorrelator":"10"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer ACCESS_TOKEN", + }, + Body: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Simple Message ☺","clientCorrelator":"10"}`, + }}, + ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, }, { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - MockResponseBody: `{ "transactionId":"OzYDlvf3SQVc" }`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer ACCESS_TOKEN", + Label: "Send Attachment", + MsgText: "My pic!", + MsgURN: "tel:+250788383383", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mtn.com/v2/messages/sms/outbound": { + httpx.NewMockResponse(201, nil, []byte(`{ "transactionId":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"My pic!\nhttps://foo.bar/image.jpg","clientCorrelator":"10"}`, - SendPrep: setSendURL, + + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer ACCESS_TOKEN", + }, + Body: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"My pic!\nhttps://foo.bar/image.jpg","clientCorrelator":"10"}`, + }}, + ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, }, { - Label: "No External Id", - MsgText: "No External ID", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{"statusCode":"0000"}`, - MockResponseStatus: 200, - ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("transactionId")}, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer ACCESS_TOKEN", + Label: "No External Id", + MsgText: "No External ID", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mtn.com/v2/messages/sms/outbound": { + httpx.NewMockResponse(200, nil, []byte(`{"statusCode":"0000"}`)), + }, }, - ExpectedRequestBody: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"No External ID","clientCorrelator":"10"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer ACCESS_TOKEN", + }, + Body: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"No External ID","clientCorrelator":"10"}`, + }}, + ExpectedLogErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("transactionId")}, }, { - Label: "Error Sending", - MsgText: "Error Message", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "E", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 401, - ExpectedRequestBody: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Error Message","clientCorrelator":"10"}`, - SendPrep: setSendURL, + Label: "Error Sending", + MsgText: "Error Message", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mtn.com/v2/messages/sms/outbound": { + httpx.NewMockResponse(401, nil, []byte(`{ "error": "failed" }`)), + }, + }, + ExpectedRequests: []ExpectedRequest{{ + Body: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Error Message","clientCorrelator":"10"}`, + }}, + ExpectedError: courier.ErrResponseStatus, }, } var cpAddressOutgoingCases = []OutgoingTestCase{ { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+250788383383", - ExpectedMsgStatus: "W", - ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, - MockResponseBody: `{ "transactionId":"OzYDlvf3SQVc" }`, - MockResponseStatus: 201, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Bearer ACCESS_TOKEN", + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:+250788383383", + MockResponses: map[string][]*httpx.MockResponse{ + "https://api.mtn.com/v2/messages/sms/outbound": { + httpx.NewMockResponse(201, nil, []byte(`{ "transactionId":"OzYDlvf3SQVc" }`)), + }, }, - ExpectedRequestBody: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Simple Message ☺","clientCorrelator":"10","cpAddress":"FOO"}`, - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{{ + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer ACCESS_TOKEN", + }, + Body: `{"senderAddress":"2020","receiverAddress":["250788383383"],"message":"Simple Message ☺","clientCorrelator":"10","cpAddress":"FOO"}`, + }}, + ExpectedExtIDs: []string{"OzYDlvf3SQVc"}, }, } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { - apiHostURL = s.URL -} - func setupBackend(mb *test.MockBackend) { // ensure there's a cached access token rc := mb.RedisPool().Get()