From 3827cc7864ed438e5dd41a7224d6bcefb58aabc3 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:00:36 -0400 Subject: [PATCH 001/170] initial messagebird commit --- go.mod | 1 + go.sum | 2 + handlers/messagebird/messagebird.go | 262 +++++++++++++++++++++++ handlers/messagebird/messagebird_test.go | 133 ++++++++++++ 4 files changed, 398 insertions(+) create mode 100644 handlers/messagebird/messagebird.go create mode 100644 handlers/messagebird/messagebird_test.go diff --git a/go.mod b/go.mod index c46a3aa6c..e40871268 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/getsentry/raven-go v0.2.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index 24e02beae..bcc46eb4e 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go new file mode 100644 index 000000000..57fbc9fc9 --- /dev/null +++ b/handlers/messagebird/messagebird.go @@ -0,0 +1,262 @@ +package freshchat + +/* + * Handler for FreshChat + */ +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + + "encoding/json" + + "fmt" + + "net/http" + "strings" + "time" + "github.com/golang-jwt/jwt/v5" + "github.com/nyaruka/courier" + "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/urns" +) + +var ( + smsURL = "https://rest.messagebird.com/messages" + mmsURL = "https://rest.messagebird.com/mms" + signatureHeader = "Messagebird-Signature-Jwt" + maxRequestBodyBytes int64 = 1024 * 1024 + + // max for the body + maxMsgLength = 1000 +) + +func init() { + courier.RegisterHandler(newHandler("MBD", "Messagebird", true)) +} + +type handler struct { + handlers.BaseHandler + validateSignatures bool +} + +func newHandler(channelType courier.ChannelType, name string, validateSignatures bool) courier.ChannelHandler { + return &handler{handlers.NewBaseHandler(courier.ChannelType("MBD"), "Messagebird"), validateSignatures} +} + +// Initialize is called by the engine once everything is loaded +func (h *handler) Initialize(s courier.Server) error { + h.SetServer(s) + s.AddHandlerRoute(h, http.MethodPost, "receive", h.receiveMessage) + return nil +} +func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { + err := h.validateSignature(channel, r) + if err != nil { + return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + } + payload := &ReceivedMessage{} + err = handlers.DecodeAndValidateJSON(payload, r) + if err != nil { + return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + } + + // no message? ignore this + if payload.Body == "" && payload.MediaUrls == nil { + return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "Ignoring request, no message") + } + + // create our date from the timestamp + date := payload.CreatedDatetime + + // create our URN + urn, err := urns.NewTelURNForCountry(payload.Originator, "US") + if err != nil { + return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + } + text := payload.Body + + // build our msg + msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date).WithExternalID(payload.ID) + + // process any attached media + for i := 0; i < len(payload.MediaUrls); i++ { + mediaURL := payload.MediaUrls[i] + msg.WithAttachment(mediaURL) + } + // and finally write our message + return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) +} + +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { + + sendingNumber := msg.Channel().Address() + + authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") + if authToken == "" { + return nil, fmt.Errorf("missing config 'auth_token' for Messagebird channel") + } + + user := msg.URN().Path() + status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + + // create base payload + payload := &Message{ + Recipients: []string{user}, + Originator: sendingNumber, + } + // build message payload + + if len(msg.Text()) > 0 { + payload.Body = msg.Text() + } + sendUrl := "" + if len(msg.Attachments()) > 0 { + sendUrl = mmsURL + } else { + sendUrl = smsURL + } + for _, attachment := range msg.Attachments() { + mediaType, mediaURL := handlers.SplitAttachment(attachment) + switch strings.Split(mediaType, "/")[0] { + case "image": + payload.MediaUrls = append([]string{mediaURL}) + default: + clog.Error(fmt.Errorf("unknown media type: %s", mediaType)) + } + } + + jsonBody, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, sendUrl, bytes.NewReader(jsonBody)) + + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + var bearer = "AccessKey " + authToken + req.Header.Set("Authorization", bearer) + + resp, _, err := handlers.RequestHTTP(req, clog) + if err != nil || resp.StatusCode/100 != 2 { + return status, nil + } + + status.SetStatus(courier.MsgWired) + + return status, nil +} + +func CalculateSignature(appSecret string, body []byte) (string, error) { + var buffer bytes.Buffer + buffer.Write(body) + + // hash with SHA1 + mac := hmac.New(sha256.New, []byte(appSecret)) + mac.Write(buffer.Bytes()) + + return hex.EncodeToString(mac.Sum(nil)), nil +} + +func verifyToken(tokenString string, secret string) (string, error) { + // Parse the token with the provided secret to get the claims + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Validate the signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + // Return the secret used to sign the token + return []byte(secret), nil + }) + + if err != nil { + return "", err + } + + // Check if the token is valid + if token.Valid { + // Extract the "payload_hash" claim value + if claims, ok := token.Claims.(jwt.MapClaims); ok { + if payloadHash, ok := claims["payload_hash"].(string); ok { + return payloadHash, nil + } + } + } + + return "", fmt.Errorf("Invalid token or missing payload_hash claim") +} + + + +func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { + if !h.validateSignatures { + return nil + } + headerSignature := r.Header.Get(signatureHeader) + if headerSignature == "" { + return fmt.Errorf("missing request signature") + } + payloadHash, err := verifyToken(headerSignature, c.StringConfigForKey(courier.ConfigSecret, "")) + if err != nil { + return err + } + body, err := handlers.ReadBody(r, maxRequestBodyBytes) + if err != nil { + return fmt.Errorf("unable to read request body: %s", err) + } + key := c.StringConfigForKey(courier.ConfigSecret, "") + if key == "" { + return fmt.Errorf("missing config 'secret' for Messagebird channel") + } + + expectedSignature, err := CalculateSignature(key, body) + if err != nil { + return err + } + if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { + return fmt.Errorf("invalid request signature, expected: %s got: %s for body: '%s'", expectedSignature, payloadHash, string(body)) + } + return nil +} + + + + +type Message struct { + Recipients []string `json:"recipients"` + Originator string `json:"originator"` + Subject string `json:"subject,omitempty"` + Body string `json:"body,omitempty"` + MediaUrls []string `json:"mediaUrls,omitempty"` +} + +type ReceivedMessage struct { + Body string `json:"body"` + CreatedDatetime time.Time `json:"createdDatetime"` + Date string `json:"date"` + DateUtc string `json:"date_utc"` + FlowID string `json:"flowId"` + FlowRevisionID string `json:"flowRevisionId"` + ID string `json:"id"` + IncomingMessage string `json:"incomingMessage"` + InvocationID string `json:"invocationId"` + MediaContentTypes []string `json:"mediaContentTypes"` + MediaUrls []string `json:"mediaUrls"` + Message string `json:"message"` + MessageBirdRequestID string `json:"messageBirdRequestId"` + MessageID string `json:"message_id"` + Originator string `json:"originator"` + Payload string `json:"payload"` + ReceivedSMSDateTime time.Time `json:"receivedSMSDateTime"` + Receiver string `json:"receiver"` + Recipient string `json:"recipient"` + Reference string `json:"reference"` + Sender string `json:"sender"` + Subject string `json:"subject"` +} diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go new file mode 100644 index 000000000..9c64c8ca4 --- /dev/null +++ b/handlers/messagebird/messagebird_test.go @@ -0,0 +1,133 @@ +package freshchat + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/nyaruka/courier" + . "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/test" +) + +var testChannels = []courier.Channel{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "2020", "US", map[string]interface{}{ + "username": "18665551212", //sending number + "secret": "my_super_secret", // secret key to sign for sig + "auth_token": "authtoken", //API bearer token + }), +} + +const ( + + receiveURL = "/c/fc/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" + validSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.FrhoATZOt5G7teacfeP-r-PNaGuwE1GZcxZHO8w1No0` + validReceive = `{"body":"Test 3","createdDatetime":"2023-07-25T17:31:42+00:00","date":"1690273902","date_utc":"1690306302","flowId":"21303270-85f5-4661-997d-8f406dec1932","flowRevisionId":"ffacb840-3381-4737-8017-9c0819a01c53","id":"22e6af2f764143e0b3e86b34084cc925","incomingMessage":"Test 3","invocationId":"d9ea9694-22d5-48aa-9b90-7363a2849ec2","message":"Test 3","messageBirdRequestId":"201412c7-2b11-11ee-b1e4-4a1a51e3ad83","message_id":"587c623091d84f228a0eda05b50bc0d3","originator":"188885551515","payload":"Test 3","receivedSMSDateTime":"2023-07-25T17:31:42+00:00","receiver":"18005551515","recipient":"18005551515","reference":"1","sender":"188885551515"}` + invalidSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.V4_HH1ExRl625Vpl2bNDRGXK-OC8J70dRfNIVejBJDU` +) + +var sigtestCases = []ChannelHandleTestCase{ + { + Label: "Receive Valid w Signature", + Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": validSignature}, + URL: receiveURL, + Data: validReceive, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Message Accepted", + ExpectedMsgText: Sp("Test 3"), + ExpectedURN: "tel:188885551515", + ExpectedDate: time.Date(2019, 6, 21, 17, 43, 20, 866000000, time.UTC), + }, + { + Label: "Bad Signature", + Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": invalidSignature}, + URL: receiveURL, + Data: validReceive, + ExpectedRespStatus: 400, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unable to verify signature, crypto/rsa: verification error"}]}`, + }, +} + +var testCases = []ChannelHandleTestCase{ + { + Label: "Receive Valid w Sig", + Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": validSignature}, + URL: receiveURL, + Data: validReceive, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Message Accepted", + ExpectedMsgText: Sp("Test 3"), + ExpectedURN: "tel:188885551515", + ExpectedDate: time.Date(2019, 6, 21, 17, 43, 20, 866000000, time.UTC), + }, + { + Label: "Bad JSON", + Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": invalidSignature}, + URL: receiveURL, + Data: "empty", + ExpectedRespStatus: 400, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unable to parse request JSON: invalid character 'e' looking for beginning of value"}]}`, + }, +} + +func TestHandler(t *testing.T) { + RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", true), sigtestCases) + RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", false), testCases) +} + +func BenchmarkHandler(b *testing.B) { + RunChannelBenchmarks(b, testChannels, newHandler("FC", "FreshChat", false), testCases) +} + +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { + smsURL = s.URL +} + +var defaultSendTestCases = []ChannelSendTestCase{ + { + Label: "Plain Send", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, + ExpectedRequestBody: `{"messages":[{"message_parts":[{"text":{"content":"Simple Message ☺"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setSendURL, + }, + { + Label: "Send with text and image", + MsgText: "Simple Message ☺", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"image:https://foo.bar/image.jpg"}, + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, + ExpectedRequestBody: `{"messages":[{"message_parts":[{"text":{"content":"Simple Message ☺"}},{"image":{"url":"https://foo.bar/image.jpg"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setSendURL, + }, + { + Label: "Send with image only", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"image/jpg:https://foo.bar/image.jpg"}, + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, + ExpectedRequestBody: `{"messages":[{"message_parts":[{"image":{"url":"https://foo.bar/image.jpg"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setSendURL, + }, +} + +func TestSending(t *testing.T) { + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "2020", "US", map[string]interface{}{ + "username": "18665551212", //sending number + "secret": "my_super_secret", // secret key to sign for sig + "auth_token": "authtoken", + }) + RunChannelSendTestCases(t, defaultChannel, newHandler("MBD", "Messagebird", false), defaultSendTestCases, []string{"my_super_secret", "authtoken"}, nil) +} From a985bf99ffe037ebf15450b0e24cea4823f91015 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:23:32 -0400 Subject: [PATCH 002/170] tweaks --- cmd/courier/main.go | 1 + handlers/messagebird/messagebird.go | 23 +++++++++++------------ handlers/messagebird/messagebird_test.go | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 3ed69adb7..59386e7f7 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -43,6 +43,7 @@ import ( _ "github.com/nyaruka/courier/handlers/m3tech" _ "github.com/nyaruka/courier/handlers/macrokiosk" _ "github.com/nyaruka/courier/handlers/mblox" + _ "github.com/nyaruka/courier/handlers/messagebird" _ "github.com/nyaruka/courier/handlers/messangi" _ "github.com/nyaruka/courier/handlers/mtarget" _ "github.com/nyaruka/courier/handlers/nexmo" diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 57fbc9fc9..bc875090d 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -1,4 +1,4 @@ -package freshchat +package messagebird /* * Handler for FreshChat @@ -34,7 +34,7 @@ var ( ) func init() { - courier.RegisterHandler(newHandler("MBD", "Messagebird", true)) + courier.RegisterHandler(newHandler(true)) } type handler struct { @@ -42,7 +42,7 @@ type handler struct { validateSignatures bool } -func newHandler(channelType courier.ChannelType, name string, validateSignatures bool) courier.ChannelHandler { +func newHandler(validateSignatures bool) courier.ChannelHandler { return &handler{handlers.NewBaseHandler(courier.ChannelType("MBD"), "Messagebird"), validateSignatures} } @@ -154,14 +154,9 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } func CalculateSignature(appSecret string, body []byte) (string, error) { - var buffer bytes.Buffer - buffer.Write(body) + mac := sha256.Sum256(body) - // hash with SHA1 - mac := hmac.New(sha256.New, []byte(appSecret)) - mac.Write(buffer.Bytes()) - - return hex.EncodeToString(mac.Sum(nil)), nil + return hex.EncodeToString(mac[:]), nil } func verifyToken(tokenString string, secret string) (string, error) { @@ -202,7 +197,11 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { if headerSignature == "" { return fmt.Errorf("missing request signature") } - payloadHash, err := verifyToken(headerSignature, c.StringConfigForKey(courier.ConfigSecret, "")) + configsecret := c.StringConfigForKey(courier.ConfigSecret, "") + if configsecret == "" { + return fmt.Errorf("missing configsecret") + } + payloadHash, err := verifyToken(headerSignature, configsecret) if err != nil { return err } @@ -220,7 +219,7 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { return err } if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { - return fmt.Errorf("invalid request signature, expected: %s got: %s for body: '%s'", expectedSignature, payloadHash, string(body)) + return fmt.Errorf("invalid request signature, jwt was: %s signature expected: %s got: %s for body: '%s'", headerSignature, expectedSignature, payloadHash, string(body)) } return nil } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 9c64c8ca4..10c3269cd 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -1,4 +1,4 @@ -package freshchat +package messagebird import ( "net/http/httptest" @@ -71,12 +71,12 @@ var testCases = []ChannelHandleTestCase{ } func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", true), sigtestCases) - RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", false), testCases) + RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), sigtestCases) + RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", false), testCases) } func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler("FC", "FreshChat", false), testCases) + RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", false), testCases) } func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { From d37a9d0fa4297c48353ff17890bdc504d65d1aac Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:09:40 -0400 Subject: [PATCH 003/170] more fixes --- handlers/messagebird/messagebird.go | 58 ++++++++++++------------ handlers/messagebird/messagebird_test.go | 4 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index bc875090d..58965daaf 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -17,6 +17,7 @@ import ( "net/http" "strings" "time" + "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" @@ -34,7 +35,7 @@ var ( ) func init() { - courier.RegisterHandler(newHandler(true)) + courier.RegisterHandler(newHandler("MBD", "Messagebird", true)) } type handler struct { @@ -42,29 +43,32 @@ type handler struct { validateSignatures bool } -func newHandler(validateSignatures bool) courier.ChannelHandler { +func newHandler(channelType courier.ChannelType, name string, validateSignatures bool) courier.ChannelHandler { return &handler{handlers.NewBaseHandler(courier.ChannelType("MBD"), "Messagebird"), validateSignatures} } // Initialize is called by the engine once everything is loaded func (h *handler) Initialize(s courier.Server) error { h.SetServer(s) - s.AddHandlerRoute(h, http.MethodPost, "receive", h.receiveMessage) + s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, h.receiveMessage) return nil } + func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(channel, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } + payload := &ReceivedMessage{} + fmt.Printf("%+v", payload) err = handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } // no message? ignore this - if payload.Body == "" && payload.MediaUrls == nil { + if payload.Body == "" && !payload.Mms { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "Ignoring request, no message") } @@ -82,9 +86,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date).WithExternalID(payload.ID) // process any attached media + if payload.Mms { for i := 0; i < len(payload.MediaUrls); i++ { - mediaURL := payload.MediaUrls[i] - msg.WithAttachment(mediaURL) + msg.WithAttachment(payload.MediaUrls[i]) + println("this is the media url", payload.MediaUrls[i]) + } } // and finally write our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) @@ -124,7 +130,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann case "image": payload.MediaUrls = append([]string{mediaURL}) default: - clog.Error(fmt.Errorf("unknown media type: %s", mediaType)) + clog.Error(courier.ErrorMediaUnsupported(mediaType)) } } @@ -236,26 +242,20 @@ type Message struct { } type ReceivedMessage struct { - Body string `json:"body"` - CreatedDatetime time.Time `json:"createdDatetime"` - Date string `json:"date"` - DateUtc string `json:"date_utc"` - FlowID string `json:"flowId"` - FlowRevisionID string `json:"flowRevisionId"` - ID string `json:"id"` - IncomingMessage string `json:"incomingMessage"` - InvocationID string `json:"invocationId"` - MediaContentTypes []string `json:"mediaContentTypes"` - MediaUrls []string `json:"mediaUrls"` - Message string `json:"message"` - MessageBirdRequestID string `json:"messageBirdRequestId"` - MessageID string `json:"message_id"` - Originator string `json:"originator"` - Payload string `json:"payload"` - ReceivedSMSDateTime time.Time `json:"receivedSMSDateTime"` - Receiver string `json:"receiver"` - Recipient string `json:"recipient"` - Reference string `json:"reference"` - Sender string `json:"sender"` - Subject string `json:"subject"` + Receiver string `json:"receiver"` + Sender string `json:"sender"` + Message string `json:"message"` + Date int `json:"date"` + DateUtc int `json:"date_utc"` + Reference string `json:"reference"` + ID string `json:"id"` + MessageID string `json:"message_id"` + Recipient string `json:"recipient"` + Originator string `json:"originator"` + Body string `json:"body"` + CreatedDatetime time.Time `json:"createdDatetime"` + MediaUrls []string `json:"mediaUrls"` + MediaContentTypes []string `json:"mediaContentTypes"` + Subject string `json:"subject"` + Mms bool `json:"mms"` } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 10c3269cd..22f893b0f 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "2020", "US", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18665551212", "US", map[string]interface{}{ "username": "18665551212", //sending number "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", //API bearer token @@ -20,7 +20,7 @@ var testChannels = []courier.Channel{ const ( - receiveURL = "/c/fc/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" + receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" validSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.FrhoATZOt5G7teacfeP-r-PNaGuwE1GZcxZHO8w1No0` validReceive = `{"body":"Test 3","createdDatetime":"2023-07-25T17:31:42+00:00","date":"1690273902","date_utc":"1690306302","flowId":"21303270-85f5-4661-997d-8f406dec1932","flowRevisionId":"ffacb840-3381-4737-8017-9c0819a01c53","id":"22e6af2f764143e0b3e86b34084cc925","incomingMessage":"Test 3","invocationId":"d9ea9694-22d5-48aa-9b90-7363a2849ec2","message":"Test 3","messageBirdRequestId":"201412c7-2b11-11ee-b1e4-4a1a51e3ad83","message_id":"587c623091d84f228a0eda05b50bc0d3","originator":"188885551515","payload":"Test 3","receivedSMSDateTime":"2023-07-25T17:31:42+00:00","receiver":"18005551515","recipient":"18005551515","reference":"1","sender":"188885551515"}` invalidSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.V4_HH1ExRl625Vpl2bNDRGXK-OC8J70dRfNIVejBJDU` From afab4b6fb12d043bbed8cb5e188631e025a40e07 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 08:36:34 -0400 Subject: [PATCH 004/170] update tests --- handlers/messagebird/messagebird.go | 23 ++++--------- handlers/messagebird/messagebird_test.go | 42 +++++++++++++----------- 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 58965daaf..d61523878 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -61,7 +61,6 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } payload := &ReceivedMessage{} - fmt.Printf("%+v", payload) err = handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -98,7 +97,6 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - sendingNumber := msg.Channel().Address() authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { @@ -111,7 +109,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // create base payload payload := &Message{ Recipients: []string{user}, - Originator: sendingNumber, + Originator: msg.Channel().Address(), } // build message payload @@ -128,17 +126,18 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann mediaType, mediaURL := handlers.SplitAttachment(attachment) switch strings.Split(mediaType, "/")[0] { case "image": - payload.MediaUrls = append([]string{mediaURL}) + payload.MediaUrls = append(payload.MediaUrls, mediaURL) default: clog.Error(courier.ErrorMediaUnsupported(mediaType)) } } - + jsonBody, err := json.Marshal(payload) if err != nil { return nil, err } + req, err := http.NewRequest(http.MethodPost, sendUrl, bytes.NewReader(jsonBody)) if err != nil { @@ -159,12 +158,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func CalculateSignature(appSecret string, body []byte) (string, error) { - mac := sha256.Sum256(body) - - return hex.EncodeToString(mac[:]), nil -} - func verifyToken(tokenString string, secret string) (string, error) { // Parse the token with the provided secret to get the claims token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { @@ -220,12 +213,10 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { return fmt.Errorf("missing config 'secret' for Messagebird channel") } - expectedSignature, err := CalculateSignature(key, body) - if err != nil { - return err - } + preHashSignature := sha256.Sum256(body) + expectedSignature := hex.EncodeToString(preHashSignature[:]) if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { - return fmt.Errorf("invalid request signature, jwt was: %s signature expected: %s got: %s for body: '%s'", headerSignature, expectedSignature, payloadHash, string(body)) + return fmt.Errorf("invalid request signature, signature expected: %s got: %s for body: '%s'", expectedSignature, payloadHash, string(body)) } return nil } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 22f893b0f..abc70b232 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -11,8 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18665551212", "US", map[string]interface{}{ - "username": "18665551212", //sending number + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", //API bearer token }), @@ -21,9 +20,9 @@ var testChannels = []courier.Channel{ const ( receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" - validSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.FrhoATZOt5G7teacfeP-r-PNaGuwE1GZcxZHO8w1No0` - validReceive = `{"body":"Test 3","createdDatetime":"2023-07-25T17:31:42+00:00","date":"1690273902","date_utc":"1690306302","flowId":"21303270-85f5-4661-997d-8f406dec1932","flowRevisionId":"ffacb840-3381-4737-8017-9c0819a01c53","id":"22e6af2f764143e0b3e86b34084cc925","incomingMessage":"Test 3","invocationId":"d9ea9694-22d5-48aa-9b90-7363a2849ec2","message":"Test 3","messageBirdRequestId":"201412c7-2b11-11ee-b1e4-4a1a51e3ad83","message_id":"587c623091d84f228a0eda05b50bc0d3","originator":"188885551515","payload":"Test 3","receivedSMSDateTime":"2023-07-25T17:31:42+00:00","receiver":"18005551515","recipient":"18005551515","reference":"1","sender":"188885551515"}` - invalidSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNTEzOCwianRpIjoiZjRlZDgzN2UtYWM0Ni00ZWY3LThhYjItYWExY2YzMTE4MGJkIiwicGF5bG9hZF9oYXNoIjoiYWM3OTA3NTk3Mjc4ZDdjY2VkOTU0NmYyY2E3ZmFmYWFjNmY1MjU4YzQxN2VjYTkyNDkwNjVkZDM4NDU3M2RmYyJ9.V4_HH1ExRl625Vpl2bNDRGXK-OC8J70dRfNIVejBJDU` + validSignature = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNjMwNSwianRpIjoiZTkyY2YwNzktMzYyZC00ODEzLWFiNDAtYmJkZDkzOGJkYzZkIiwidXJsX2hhc2giOiI4NGNiMWEwOThlZTY2OGRmNTBmNzQ5Y2M2OThlZDBjZmIwN2FmMzllODBiZDgyZjIzNzFiNTY0NzViNTQ5N2EwIiwicGF5bG9hZF9oYXNoIjoiMjhjZTBiYTE5MDg3ZmE3ODgwZWMwOGQyYmFiMWM3ZDVmM2U2NWMzYjZhZTA5M2EwYjI2MTA4NDY3MTc4MDMzOSJ9.hR6TQQRkPLWFxCe0bcCWM0XdnTgNOlxUTcEzLWJuFkI" + validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` + invalidSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNjMwNSwianRpIjoiZTkyY2YwNzktMzYyZC00ODEzLWFiNDAtYmJkZDkzOGJkYzZkIiwidXJsX2hhc2giOiI4NGNiMWEwOThlZTY2OGRmNTBmNzQ5Y2M2OThlZDBjZmIwN2FmMzllODBiZDgyZjIzNzFiNTY0NzViNTQ5N2EwIiwicGF5bG9hZF9oYXNoIjoiMDdhZTVjNmE5NjE2MGFlYjJlMGRkOGIwZWEwNTYxZDM2NzRiNjRhNWE3NTFiNmUxNWM0MDQ1MmY1NjFjYjcyZSJ9.jUUzDg2-e8fH7sghmxNC1cuuxRq-qYQgezZ52hPLL1A` ) var sigtestCases = []ChannelHandleTestCase{ @@ -36,7 +35,7 @@ var sigtestCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", - ExpectedDate: time.Date(2019, 6, 21, 17, 43, 20, 866000000, time.UTC), + ExpectedDate: time.Date(2023, time.July, 26, 20, 49, 29, 0, time.Local), }, { Label: "Bad Signature", @@ -44,7 +43,7 @@ var sigtestCases = []ChannelHandleTestCase{ URL: receiveURL, Data: validReceive, ExpectedRespStatus: 400, - ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unable to verify signature, crypto/rsa: verification error"}]}`, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"invalid request signature, signature expected: 28ce0ba19087fa7880ec08d2bab1c7d5f3e65c3b6ae093a0b261084671780339 got: 07ae5c6a96160aeb2e0dd8b0ea0561d3674b64a5a751b6e15c40452f561cb72e for body: '{\"receiver\":\"18005551515\",\"sender\":\"188885551515\",\"message\":\"Test again\",\"date\":1690386569,\"date_utc\":1690418969,\"reference\":\"1\",\"id\":\"b6aae1b5dfb2427a8f7ea6a717ba31a9\",\"message_id\":\"3b53c137369242138120d6b0b2122607\",\"recipient\":\"18005551515\",\"originator\":\"188885551515\",\"body\":\"Test 3\",\"createdDatetime\":\"2023-07-27T00:49:29+00:00\",\"mms\":false}'"}]}`, }, } @@ -58,7 +57,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", - ExpectedDate: time.Date(2019, 6, 21, 17, 43, 20, 866000000, time.UTC), + ExpectedDate: time.Date(2023, time.July, 26, 20, 49, 29, 0, time.Local), }, { Label: "Bad JSON", @@ -79,10 +78,14 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", false), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { smsURL = s.URL } +func setMmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { + mmsURL = s.URL +} + var defaultSendTestCases = []ChannelSendTestCase{ { Label: "Plain Send", @@ -90,11 +93,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ MsgURN: "tel:188885551515", MockResponseBody: "", MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, - ExpectedRequestBody: `{"messages":[{"message_parts":[{"text":{"content":"Simple Message ☺"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","body":"Simple Message ☺"}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", - SendPrep: setSendURL, + SendPrep: setSmsSendURL, }, { Label: "Send with text and image", @@ -103,11 +106,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ MsgAttachments: []string{"image:https://foo.bar/image.jpg"}, MockResponseBody: "", MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, - ExpectedRequestBody: `{"messages":[{"message_parts":[{"text":{"content":"Simple Message ☺"}},{"image":{"url":"https://foo.bar/image.jpg"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","body":"Simple Message ☺","mediaUrls":["https://foo.bar/image.jpg"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", - SendPrep: setSendURL, + SendPrep: setMmsSendURL, }, { Label: "Send with image only", @@ -115,17 +118,16 @@ var defaultSendTestCases = []ChannelSendTestCase{ MsgAttachments: []string{"image/jpg:https://foo.bar/image.jpg"}, MockResponseBody: "", MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "Bearer enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, - ExpectedRequestBody: `{"messages":[{"message_parts":[{"image":{"url":"https://foo.bar/image.jpg"}}],"actor_id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606","actor_type":"agent"}],"channel_id":"0534f78-b6e9-4f79-8853-11cedfc1f35b","users":[{"id":"c8fddfaf-622a-4a0e-b060-4f3ccbeab606"}]}`, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", - SendPrep: setSendURL, + SendPrep: setMmsSendURL, }, } func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "2020", "US", map[string]interface{}{ - "username": "18665551212", //sending number + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", }) From 7f737b16393ce8a4266bcdf6f6c7fe47e2382e95 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 08:47:07 -0400 Subject: [PATCH 005/170] set time to utc --- handlers/messagebird/messagebird.go | 4 ++-- handlers/messagebird/messagebird_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index d61523878..bc4642e14 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -82,7 +82,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w text := payload.Body // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date).WithExternalID(payload.ID) + msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date.UTC()).WithExternalID(payload.ID) // process any attached media if payload.Mms { @@ -131,7 +131,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann clog.Error(courier.ErrorMediaUnsupported(mediaType)) } } - + jsonBody, err := json.Marshal(payload) if err != nil { return nil, err diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index abc70b232..3371a88ad 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -35,7 +35,7 @@ var sigtestCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", - ExpectedDate: time.Date(2023, time.July, 26, 20, 49, 29, 0, time.Local), + ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), }, { Label: "Bad Signature", @@ -57,7 +57,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", - ExpectedDate: time.Date(2023, time.July, 26, 20, 49, 29, 0, time.Local), + ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), }, { Label: "Bad JSON", From e99b311e9a25b4e6453eac1bdcaee7c84c969130 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:23:30 -0400 Subject: [PATCH 006/170] formatting, cleanup --- handlers/messagebird/messagebird.go | 52 +++++++++--------------- handlers/messagebird/messagebird_test.go | 9 ++-- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index bc4642e14..045036b8d 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -25,13 +25,10 @@ import ( ) var ( - smsURL = "https://rest.messagebird.com/messages" - mmsURL = "https://rest.messagebird.com/mms" - signatureHeader = "Messagebird-Signature-Jwt" + smsURL = "https://rest.messagebird.com/messages" + mmsURL = "https://rest.messagebird.com/mms" + signatureHeader = "Messagebird-Signature-Jwt" maxRequestBodyBytes int64 = 1024 * 1024 - - // max for the body - maxMsgLength = 1000 ) func init() { @@ -67,7 +64,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // no message? ignore this - if payload.Body == "" && !payload.Mms { + if payload.Body == "" && !payload.Mms { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "Ignoring request, no message") } @@ -86,10 +83,10 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // process any attached media if payload.Mms { - for i := 0; i < len(payload.MediaUrls); i++ { - msg.WithAttachment(payload.MediaUrls[i]) - println("this is the media url", payload.MediaUrls[i]) - } + for i := 0; i < len(payload.MediaUrls); i++ { + msg.WithAttachment(payload.MediaUrls[i]) + println("this is the media url", payload.MediaUrls[i]) + } } // and finally write our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) @@ -97,7 +94,6 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { return nil, fmt.Errorf("missing config 'auth_token' for Messagebird channel") @@ -137,7 +133,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, err } - req, err := http.NewRequest(http.MethodPost, sendUrl, bytes.NewReader(jsonBody)) if err != nil { @@ -158,10 +153,12 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func verifyToken(tokenString string, secret string) (string, error) { +func verifyToken(tokenString string, secret string) (jwt.MapClaims, error) { // Parse the token with the provided secret to get the claims token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // Validate the signing method + // We only allow HS256 + // ref: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } @@ -170,24 +167,18 @@ func verifyToken(tokenString string, secret string) (string, error) { }) if err != nil { - return "", err + return nil, err } // Check if the token is valid if token.Valid { - // Extract the "payload_hash" claim value - if claims, ok := token.Claims.(jwt.MapClaims); ok { - if payloadHash, ok := claims["payload_hash"].(string); ok { - return payloadHash, nil - } - } + tokenClaims := token.Claims.(jwt.MapClaims) + return tokenClaims, nil } - return "", fmt.Errorf("Invalid token or missing payload_hash claim") + return nil, fmt.Errorf("Invalid token or missing payload_hash claim") } - - func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { if !h.validateSignatures { return nil @@ -196,22 +187,20 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { if headerSignature == "" { return fmt.Errorf("missing request signature") } - configsecret := c.StringConfigForKey(courier.ConfigSecret, "") + configsecret := c.StringConfigForKey(courier.ConfigSecret, "") if configsecret == "" { return fmt.Errorf("missing configsecret") } - payloadHash, err := verifyToken(headerSignature, configsecret) + verifiedToken, err := verifyToken(headerSignature, configsecret) if err != nil { return err } + payloadHash := verifiedToken["payload_hash"].(string) + body, err := handlers.ReadBody(r, maxRequestBodyBytes) if err != nil { return fmt.Errorf("unable to read request body: %s", err) } - key := c.StringConfigForKey(courier.ConfigSecret, "") - if key == "" { - return fmt.Errorf("missing config 'secret' for Messagebird channel") - } preHashSignature := sha256.Sum256(body) expectedSignature := hex.EncodeToString(preHashSignature[:]) @@ -221,9 +210,6 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { return nil } - - - type Message struct { Recipients []string `json:"recipients"` Originator string `json:"originator"` diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 3371a88ad..0bfc85f29 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -12,13 +12,12 @@ import ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ - "secret": "my_super_secret", // secret key to sign for sig - "auth_token": "authtoken", //API bearer token + "secret": "my_super_secret", // secret key to sign for sig + "auth_token": "authtoken", //API bearer token }), } const ( - receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" validSignature = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNjMwNSwianRpIjoiZTkyY2YwNzktMzYyZC00ODEzLWFiNDAtYmJkZDkzOGJkYzZkIiwidXJsX2hhc2giOiI4NGNiMWEwOThlZTY2OGRmNTBmNzQ5Y2M2OThlZDBjZmIwN2FmMzllODBiZDgyZjIzNzFiNTY0NzViNTQ5N2EwIiwicGF5bG9hZF9oYXNoIjoiMjhjZTBiYTE5MDg3ZmE3ODgwZWMwOGQyYmFiMWM3ZDVmM2U2NWMzYjZhZTA5M2EwYjI2MTA4NDY3MTc4MDMzOSJ9.hR6TQQRkPLWFxCe0bcCWM0XdnTgNOlxUTcEzLWJuFkI" validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` @@ -128,8 +127,8 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ - "secret": "my_super_secret", // secret key to sign for sig - "auth_token": "authtoken", + "secret": "my_super_secret", // secret key to sign for sig + "auth_token": "authtoken", }) RunChannelSendTestCases(t, defaultChannel, newHandler("MBD", "Messagebird", false), defaultSendTestCases, []string{"my_super_secret", "authtoken"}, nil) } From 985e8ef4e032a0a603189a51a47d3ea4e9d846f9 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:52:37 -0400 Subject: [PATCH 007/170] Update handlers/messagebird/messagebird.go Co-authored-by: Chris Erickson <3322287+chris-erickson@users.noreply.github.com> --- handlers/messagebird/messagebird.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 045036b8d..35f7ac106 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -1,7 +1,7 @@ package messagebird /* - * Handler for FreshChat + * Handler for MessageBird */ import ( "bytes" From c766a8065d164421757361b6850a7946fbb4c038 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:54:38 -0400 Subject: [PATCH 008/170] tidy --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index ad7b6f916..acef1064c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/dghubble/oauth1 v0.7.2 github.com/evalphobia/logrus_sentry v0.8.2 github.com/go-chi/chi v4.1.2+incompatible + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/gomodule/redigo v1.8.9 github.com/gorilla/schema v1.2.0 github.com/jmoiron/sqlx v1.3.5 @@ -34,7 +35,6 @@ require ( github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/getsentry/raven-go v0.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect diff --git a/go.sum b/go.sum index 794503631..898a1d873 100644 --- a/go.sum +++ b/go.sum @@ -33,10 +33,10 @@ github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+j github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -118,8 +118,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -153,8 +151,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 1f9d643607a8164b1045b927710e135053a922c8 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:49:49 -0400 Subject: [PATCH 009/170] update tests and handler --- handlers/messagebird/messagebird.go | 28 ++++---- handlers/messagebird/messagebird_test.go | 90 ++++++++++++++++++------ 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 35f7ac106..e941acb3c 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -47,17 +47,16 @@ func newHandler(channelType courier.ChannelType, name string, validateSignatures // Initialize is called by the engine once everything is loaded func (h *handler) Initialize(s courier.Server) error { h.SetServer(s) - s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, h.receiveMessage) + s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) return nil } -func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *ReceivedMessage, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(channel, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } - payload := &ReceivedMessage{} err = handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -83,9 +82,8 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // process any attached media if payload.Mms { - for i := 0; i < len(payload.MediaUrls); i++ { - msg.WithAttachment(payload.MediaUrls[i]) - println("this is the media url", payload.MediaUrls[i]) + for i := 0; i < len(payload.MediaURLs); i++ { + msg.WithAttachment(payload.MediaURLs[i]) } } // and finally write our message @@ -122,7 +120,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann mediaType, mediaURL := handlers.SplitAttachment(attachment) switch strings.Split(mediaType, "/")[0] { case "image": - payload.MediaUrls = append(payload.MediaUrls, mediaURL) + payload.MediaURLs = append(payload.MediaURLs, mediaURL) default: clog.Error(courier.ErrorMediaUnsupported(mediaType)) } @@ -179,6 +177,11 @@ func verifyToken(tokenString string, secret string) (jwt.MapClaims, error) { return nil, fmt.Errorf("Invalid token or missing payload_hash claim") } +func calculateSignature(body []byte) string { + preHashSignature := sha256.Sum256(body) + return hex.EncodeToString(preHashSignature[:]) +} + func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { if !h.validateSignatures { return nil @@ -202,10 +205,9 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { return fmt.Errorf("unable to read request body: %s", err) } - preHashSignature := sha256.Sum256(body) - expectedSignature := hex.EncodeToString(preHashSignature[:]) + expectedSignature := calculateSignature(body) if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { - return fmt.Errorf("invalid request signature, signature expected: %s got: %s for body: '%s'", expectedSignature, payloadHash, string(body)) + return fmt.Errorf("invalid request signature, signature doesn't match expected signature for body.") } return nil } @@ -215,7 +217,7 @@ type Message struct { Originator string `json:"originator"` Subject string `json:"subject,omitempty"` Body string `json:"body,omitempty"` - MediaUrls []string `json:"mediaUrls,omitempty"` + MediaURLs []string `json:"mediaUrls,omitempty"` } type ReceivedMessage struct { @@ -223,7 +225,7 @@ type ReceivedMessage struct { Sender string `json:"sender"` Message string `json:"message"` Date int `json:"date"` - DateUtc int `json:"date_utc"` + DateUTC int `json:"date_utc"` Reference string `json:"reference"` ID string `json:"id"` MessageID string `json:"message_id"` @@ -231,7 +233,7 @@ type ReceivedMessage struct { Originator string `json:"originator"` Body string `json:"body"` CreatedDatetime time.Time `json:"createdDatetime"` - MediaUrls []string `json:"mediaUrls"` + MediaURLs []string `json:"mediaUrls"` MediaContentTypes []string `json:"mediaContentTypes"` Subject string `json:"subject"` Mms bool `json:"mms"` diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 0bfc85f29..e3ea2ec6d 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -1,13 +1,16 @@ package messagebird import ( - "net/http/httptest" - "testing" - "time" - + "crypto/sha256" + "encoding/hex" + "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "net/http" + "net/http/httptest" + "testing" + "time" ) var testChannels = []courier.Channel{ @@ -18,16 +21,63 @@ var testChannels = []courier.Channel{ } const ( - receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" - validSignature = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNjMwNSwianRpIjoiZTkyY2YwNzktMzYyZC00ODEzLWFiNDAtYmJkZDkzOGJkYzZkIiwidXJsX2hhc2giOiI4NGNiMWEwOThlZTY2OGRmNTBmNzQ5Y2M2OThlZDBjZmIwN2FmMzllODBiZDgyZjIzNzFiNTY0NzViNTQ5N2EwIiwicGF5bG9hZF9oYXNoIjoiMjhjZTBiYTE5MDg3ZmE3ODgwZWMwOGQyYmFiMWM3ZDVmM2U2NWMzYjZhZTA5M2EwYjI2MTA4NDY3MTc4MDMzOSJ9.hR6TQQRkPLWFxCe0bcCWM0XdnTgNOlxUTcEzLWJuFkI" - validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` - invalidSignature = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNZXNzYWdlQmlyZCIsIm5iZiI6MTY5MDMwNjMwNSwianRpIjoiZTkyY2YwNzktMzYyZC00ODEzLWFiNDAtYmJkZDkzOGJkYzZkIiwidXJsX2hhc2giOiI4NGNiMWEwOThlZTY2OGRmNTBmNzQ5Y2M2OThlZDBjZmIwN2FmMzllODBiZDgyZjIzNzFiNTY0NzViNTQ5N2EwIiwicGF5bG9hZF9oYXNoIjoiMDdhZTVjNmE5NjE2MGFlYjJlMGRkOGIwZWEwNTYxZDM2NzRiNjRhNWE3NTFiNmUxNWM0MDQ1MmY1NjFjYjcyZSJ9.jUUzDg2-e8fH7sghmxNC1cuuxRq-qYQgezZ52hPLL1A` + receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" + validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` + validSecret = "my_super_secret" + invalidSecret = "bad_secret" ) +func addValidSignature(r *http.Request) { + body, _ := ReadBody(r, maxRequestBodyBytes) + sig := calculateSignature(body) + t := jwt.NewWithClaims(jwt.SigningMethodHS256, + jwt.MapClaims{ + "iss": "MessageBird", + "nbf": 1690306305, + "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", + "payload_hash": sig, + }) + + signedJWT, _ := t.SignedString([]byte(validSecret)) + r.Header.Set(signatureHeader, signedJWT) +} + +func addInvalidSignature(r *http.Request) { + body, _ := ReadBody(r, maxRequestBodyBytes) + sig := calculateSignature(body) + t := jwt.NewWithClaims(jwt.SigningMethodHS256, + jwt.MapClaims{ + "iss": "MessageBird", + "nbf": 1690306305, + "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", + "payload_hash": sig, + }) + + signedJWT, _ := t.SignedString([]byte(invalidSecret)) + r.Header.Set("Messagebird-Signature-Jwt", signedJWT) +} + +func addInvalidBodyHash(r *http.Request) { + body, _ := ReadBody(r, maxRequestBodyBytes) + body = body + []byte("bad") + preHashSignature := sha256.Sum256(body) + sig := hex.EncodeToString(preHashSignature[:]) + t := jwt.NewWithClaims(jwt.SigningMethodHS256, + jwt.MapClaims{ + "iss": "MessageBird", + "nbf": 1690306305, + "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", + "payload_hash": sig, + }) + + signedJWT, _ := t.SignedString([]byte(validSecret)) + r.Header.Set("Messagebird-Signature-Jwt", signedJWT) +} + var sigtestCases = []ChannelHandleTestCase{ { Label: "Receive Valid w Signature", - Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": validSignature}, + Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, @@ -35,21 +85,20 @@ var sigtestCases = []ChannelHandleTestCase{ ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), + PrepRequest: addValidSignature, }, { - Label: "Bad Signature", - Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": invalidSignature}, + Label: "Bad JWT Signature", + Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: validReceive, ExpectedRespStatus: 400, - ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"invalid request signature, signature expected: 28ce0ba19087fa7880ec08d2bab1c7d5f3e65c3b6ae093a0b261084671780339 got: 07ae5c6a96160aeb2e0dd8b0ea0561d3674b64a5a751b6e15c40452f561cb72e for body: '{\"receiver\":\"18005551515\",\"sender\":\"188885551515\",\"message\":\"Test again\",\"date\":1690386569,\"date_utc\":1690418969,\"reference\":\"1\",\"id\":\"b6aae1b5dfb2427a8f7ea6a717ba31a9\",\"message_id\":\"3b53c137369242138120d6b0b2122607\",\"recipient\":\"18005551515\",\"originator\":\"188885551515\",\"body\":\"Test 3\",\"createdDatetime\":\"2023-07-27T00:49:29+00:00\",\"mms\":false}'"}]}`, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"token signature is invalid: signature is invalid"}]}`, + PrepRequest: addInvalidSignature, }, -} - -var testCases = []ChannelHandleTestCase{ { - Label: "Receive Valid w Sig", - Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": validSignature}, + Label: "Receive Valid w Signature but non-matching body hash", + Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, @@ -57,24 +106,25 @@ var testCases = []ChannelHandleTestCase{ ExpectedMsgText: Sp("Test 3"), ExpectedURN: "tel:188885551515", ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), + PrepRequest: addInvalidBodyHash, }, { Label: "Bad JSON", - Headers: map[string]string{"Content-Type": "application/json", "Messagebird-Signature-Jwt": invalidSignature}, + Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: "empty", ExpectedRespStatus: 400, ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unable to parse request JSON: invalid character 'e' looking for beginning of value"}]}`, + PrepRequest: addValidSignature, }, } func TestHandler(t *testing.T) { RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), sigtestCases) - RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", false), testCases) } func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", false), testCases) + RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", true), sigtestCases) } func setSmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { From 7759760539d998e3d7cbe5b3d6f25d5fbdf8e970 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:55:56 -0400 Subject: [PATCH 010/170] fix hash test --- handlers/messagebird/messagebird_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index e3ea2ec6d..2cc3155ac 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -59,7 +59,8 @@ func addInvalidSignature(r *http.Request) { func addInvalidBodyHash(r *http.Request) { body, _ := ReadBody(r, maxRequestBodyBytes) - body = body + []byte("bad") + bad_bytes := []byte("bad") + body = append(body, bad_bytes[:]...) preHashSignature := sha256.Sum256(body) sig := hex.EncodeToString(preHashSignature[:]) t := jwt.NewWithClaims(jwt.SigningMethodHS256, @@ -101,11 +102,8 @@ var sigtestCases = []ChannelHandleTestCase{ Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: validReceive, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Message Accepted", - ExpectedMsgText: Sp("Test 3"), - ExpectedURN: "tel:188885551515", - ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), + ExpectedRespStatus: 400, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"invalid request signature, signature doesn't match expected signature for body."}]}`, PrepRequest: addInvalidBodyHash, }, { From c5f622f1d4b9555b5969e8c143bdbceaf1237d41 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 14:21:59 -0400 Subject: [PATCH 011/170] fixes --- handlers/messagebird/messagebird.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index e941acb3c..b078f7d1a 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -57,13 +57,8 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } - err = handlers.DecodeAndValidateJSON(payload, r) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - // no message? ignore this - if payload.Body == "" && !payload.Mms { + if payload.Body == "" && !payload.MMS { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "Ignoring request, no message") } @@ -81,7 +76,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date.UTC()).WithExternalID(payload.ID) // process any attached media - if payload.Mms { + if payload.MMS { for i := 0; i < len(payload.MediaURLs); i++ { msg.WithAttachment(payload.MediaURLs[i]) } @@ -236,5 +231,5 @@ type ReceivedMessage struct { MediaURLs []string `json:"mediaUrls"` MediaContentTypes []string `json:"mediaContentTypes"` Subject string `json:"subject"` - Mms bool `json:"mms"` + MMS bool `json:"mms"` } From fa491843281cbbb88545ab00ed69af5868208e31 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 14:33:33 -0400 Subject: [PATCH 012/170] use db configured country for URN --- handlers/messagebird/messagebird.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index b078f7d1a..7d1406883 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -21,7 +21,6 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/gocommon/urns" ) var ( @@ -66,7 +65,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w date := payload.CreatedDatetime // create our URN - urn, err := urns.NewTelURNForCountry(payload.Originator, "US") + urn, err := handlers.StrictTelForCountry(payload.Originator, channel.Country()) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } From d2767265873ec82f1a6b18c5bd49f09dc7218b63 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:20:39 -0400 Subject: [PATCH 013/170] add additional attachment handlers --- handlers/messagebird/messagebird.go | 47 ++++++++++++++++-- handlers/messagebird/messagebird_test.go | 63 +++++++++++++++++++++--- 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 7d1406883..1be90e24d 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -15,7 +15,6 @@ import ( "fmt" "net/http" - "strings" "time" "github.com/golang-jwt/jwt/v5" @@ -76,8 +75,8 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // process any attached media if payload.MMS { - for i := 0; i < len(payload.MediaURLs); i++ { - msg.WithAttachment(payload.MediaURLs[i]) + for _, mediaURL := range payload.MediaURLs { + msg.WithAttachment(mediaURL) } } // and finally write our message @@ -112,8 +111,46 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } for _, attachment := range msg.Attachments() { mediaType, mediaURL := handlers.SplitAttachment(attachment) - switch strings.Split(mediaType, "/")[0] { - case "image": + switch mediaType { + // Supported media types + // https://developers.messagebird.com/api/mms-messaging/#media-attachments + case "audio/basic", + "audio/L24", + "audio/mp4", + "audio/mpeg", + "audio/ogg", + "audio/vorbis", + "audio/vnd.rn-realaudio", + "audio/vnd.wave", + "audio/3gpp", + "audio/3gpp2", + "audio/ac3", + "audio/webm", + "audio/amr-nb", + "audio/amr", + "video/mpeg", + "video/mp4", + "video/quicktime", + "video/webm", + "video/3gpp", + "video/3gpp2", + "video/3gpp-tt", + "video/H261", + "video/H263", + "video/H263-1998", + "video/H263-2000", + "video/H264", + "image/jpeg", + "image/jpg", + "image/gif", + "image/png", + "image/bmp", + "text/vcard", + "text/csv", + "text/rtf", + "text/richtext", + "text/calendar", + "application/pdf": payload.MediaURLs = append(payload.MediaURLs, mediaURL) default: clog.Error(courier.ErrorMediaUnsupported(mediaType)) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 2cc3155ac..69f0656df 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -21,10 +21,11 @@ var testChannels = []courier.Channel{ } const ( - receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" - validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` - validSecret = "my_super_secret" - invalidSecret = "bad_secret" + receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" + validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` + validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` + validSecret = "my_super_secret" + invalidSecret = "bad_secret" ) func addValidSignature(r *http.Request) { @@ -77,7 +78,7 @@ func addInvalidBodyHash(r *http.Request) { var sigtestCases = []ChannelHandleTestCase{ { - Label: "Receive Valid w Signature", + Label: "Receive Valid text w Signature", Headers: map[string]string{"Content-Type": "application/json"}, URL: receiveURL, Data: validReceive, @@ -88,6 +89,18 @@ var sigtestCases = []ChannelHandleTestCase{ ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), PrepRequest: addValidSignature, }, + { + Label: "Receive Valid w image w Signature", + Headers: map[string]string{"Content-Type": "application/json"}, + URL: receiveURL, + Data: validReceiveMMS, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Message Accepted", + ExpectedAttachments: []string{"https://foo.bar/image.jpg"}, + ExpectedURN: "tel:188885551515", + ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), + PrepRequest: addValidSignature, + }, { Label: "Bad JWT Signature", Headers: map[string]string{"Content-Type": "application/json"}, @@ -150,7 +163,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ Label: "Send with text and image", MsgText: "Simple Message ☺", MsgURN: "tel:188885551515", - MsgAttachments: []string{"image:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: "", MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, @@ -162,7 +175,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ { Label: "Send with image only", MsgURN: "tel:188885551515", - MsgAttachments: []string{"image/jpg:https://foo.bar/image.jpg"}, + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: "", MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, @@ -171,6 +184,42 @@ var defaultSendTestCases = []ChannelSendTestCase{ ExpectedExternalID: "", 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"}, + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg","https://foo.bar/image2.jpg"]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setMmsSendURL, + }, + { + Label: "Send with video only", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"video/mp4:https://foo.bar/movie.mp4"}, + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/movie.mp4"]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setMmsSendURL, + }, + { + Label: "Send with pdf", + MsgURN: "tel:188885551515", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponseBody: "", + MockResponseStatus: 200, + ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, + ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/document.pdf"]}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "", + SendPrep: setMmsSendURL, + }, } func TestSending(t *testing.T) { From 544cf01e37ca928538389e61f644641161c46650 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:37:41 -0400 Subject: [PATCH 014/170] added status, url checking, and new tests --- handlers/messagebird/messagebird.go | 181 ++++++++++++++--------- handlers/messagebird/messagebird_test.go | 48 +++--- 2 files changed, 135 insertions(+), 94 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 1be90e24d..b744dbee3 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -9,8 +9,8 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "encoding/json" + "strconv" "fmt" @@ -20,6 +20,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/urns" + "github.com/sirupsen/logrus" ) var ( @@ -27,6 +29,8 @@ var ( mmsURL = "https://rest.messagebird.com/mms" signatureHeader = "Messagebird-Signature-Jwt" maxRequestBodyBytes int64 = 1024 * 1024 + // error code messagebird returns when a contact has sent "stop" + errorStopped = 103 ) func init() { @@ -46,9 +50,57 @@ func newHandler(channelType courier.ChannelType, name string, validateSignatures func (h *handler) Initialize(s courier.Server) error { h.SetServer(s) s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) + s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, h.receiveStatus) return nil } +func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { + + // get our params + receivedStatus := &ReceivedStatus{} + err := handlers.DecodeAndValidateForm(receivedStatus, r) + if err != nil { + return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "no msg status, ignoring") + } + + msgStatus, found := statusMapping[receivedStatus.Status] + if !found { + return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status '%s', must be one of 'queued', 'failed', 'sent', 'delivered', or 'undelivered'", receivedStatus.Status)) + } + + // if the message id was passed explicitely, use that + var status courier.MsgStatus + if receivedStatus.Reference != "" { + msgID, err := strconv.ParseInt(receivedStatus.Reference, 10, 64) + if err != nil { + logrus.WithError(err).WithField("id", receivedStatus.Reference).Error("error converting Messagebird status id to integer") + } else { + status = h.Backend().NewMsgStatusForID(channel, courier.MsgID(msgID), msgStatus, clog) + } + } + + // if we have no status, then build it from the external (twilio) id + if status == nil { + status = h.Backend().NewMsgStatusForExternalID(channel, receivedStatus.ID, msgStatus, clog) + } + + if receivedStatus.StatusErrorCode == errorStopped { + urn, err := urns.NewTelURNForCountry(receivedStatus.Recipient, "") + if err != nil { + return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + } + // create a stop channel event + channelEvent := h.Backend().NewChannelEvent(channel, courier.StopContact, urn, clog) + err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) + if err != nil { + return nil, err + } + } + clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "EC_SUBSCRIBER_OPTEDOUT")) + + return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) +} + func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *ReceivedMessage, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(channel, r) if err != nil { @@ -97,6 +149,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload := &Message{ Recipients: []string{user}, Originator: msg.Channel().Address(), + Reference: msg.ID().String(), } // build message payload @@ -110,51 +163,8 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann sendUrl = smsURL } for _, attachment := range msg.Attachments() { - mediaType, mediaURL := handlers.SplitAttachment(attachment) - switch mediaType { - // Supported media types - // https://developers.messagebird.com/api/mms-messaging/#media-attachments - case "audio/basic", - "audio/L24", - "audio/mp4", - "audio/mpeg", - "audio/ogg", - "audio/vorbis", - "audio/vnd.rn-realaudio", - "audio/vnd.wave", - "audio/3gpp", - "audio/3gpp2", - "audio/ac3", - "audio/webm", - "audio/amr-nb", - "audio/amr", - "video/mpeg", - "video/mp4", - "video/quicktime", - "video/webm", - "video/3gpp", - "video/3gpp2", - "video/3gpp-tt", - "video/H261", - "video/H263", - "video/H263-1998", - "video/H263-2000", - "video/H264", - "image/jpeg", - "image/jpg", - "image/gif", - "image/png", - "image/bmp", - "text/vcard", - "text/csv", - "text/rtf", - "text/richtext", - "text/calendar", - "application/pdf": - payload.MediaURLs = append(payload.MediaURLs, mediaURL) - default: - clog.Error(courier.ErrorMediaUnsupported(mediaType)) - } + _, mediaURL := handlers.SplitAttachment(attachment) + payload.MediaURLs = append(payload.MediaURLs, mediaURL) } jsonBody, err := json.Marshal(payload) @@ -172,13 +182,18 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann var bearer = "AccessKey " + authToken req.Header.Set("Authorization", bearer) - resp, _, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := handlers.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } - status.SetStatus(courier.MsgWired) - + sendStatus := &ReceivedMessage{} + err = json.Unmarshal(respBody, sendStatus) + if err != nil { + clog.Error(courier.ErrorResponseUnparseable("JSON")) + return status, nil + } + status.SetExternalID(sendStatus.ID) return status, nil } @@ -229,43 +244,65 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { if err != nil { return err } - payloadHash := verifiedToken["payload_hash"].(string) + CalledURL := fmt.Sprintf("https://%s%s", c.CallbackDomain(h.Server().Config().Domain), r.URL.Path) + expectedURLHash := calculateSignature([]byte(CalledURL)) + URLHash := verifiedToken["url_hash"].(string) - body, err := handlers.ReadBody(r, maxRequestBodyBytes) - if err != nil { - return fmt.Errorf("unable to read request body: %s", err) + if !hmac.Equal([]byte(expectedURLHash), []byte(URLHash)) { + return fmt.Errorf("invalid request signature, signature doesn't match expected signature for URL.") } - expectedSignature := calculateSignature(body) - if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { - return fmt.Errorf("invalid request signature, signature doesn't match expected signature for body.") + if verifiedToken["payload_hash"] != nil { + payloadHash := verifiedToken["payload_hash"].(string) + + body, err := handlers.ReadBody(r, maxRequestBodyBytes) + if err != nil { + return fmt.Errorf("unable to read request body: %s", err) + } + + expectedSignature := calculateSignature(body) + if !hmac.Equal([]byte(expectedSignature), []byte(payloadHash)) { + return fmt.Errorf("invalid request signature, signature doesn't match expected signature for body.") + } } + return nil } type Message struct { Recipients []string `json:"recipients"` + Reference string `json:"reference,omitempty"` Originator string `json:"originator"` Subject string `json:"subject,omitempty"` Body string `json:"body,omitempty"` MediaURLs []string `json:"mediaUrls,omitempty"` } +type ReceivedStatus struct { + ID string + Reference string + Recipient string + Status string + StatusReason string + StatusDatetime time.Time + StatusErrorCode int +} + +var statusMapping = map[string]courier.MsgStatusValue{ + "scheduled": courier.MsgSent, + "delivery_failed": courier.MsgFailed, + "sent": courier.MsgSent, + "buffered": courier.MsgSent, + "delivered": courier.MsgDelivered, + "expired": courier.MsgFailed, +} + type ReceivedMessage struct { - Receiver string `json:"receiver"` - Sender string `json:"sender"` - Message string `json:"message"` - Date int `json:"date"` - DateUTC int `json:"date_utc"` - Reference string `json:"reference"` - ID string `json:"id"` - MessageID string `json:"message_id"` - Recipient string `json:"recipient"` - Originator string `json:"originator"` - Body string `json:"body"` - CreatedDatetime time.Time `json:"createdDatetime"` - MediaURLs []string `json:"mediaUrls"` - MediaContentTypes []string `json:"mediaContentTypes"` - Subject string `json:"subject"` - MMS bool `json:"mms"` + ID string `json:"id"` + Recipient string `json:"recipient"` + Originator string `json:"originator"` + Body string `json:"body"` + CreatedDatetime time.Time `json:"createdDatetime"` + MediaURLs []string `json:"mediaUrls"` + MMS bool `json:"mms"` } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 69f0656df..e48df0905 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -1,8 +1,6 @@ package messagebird import ( - "crypto/sha256" - "encoding/hex" "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" @@ -21,22 +19,25 @@ var testChannels = []courier.Channel{ } const ( - receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" + receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` validSecret = "my_super_secret" + validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+31207009850","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":31612345678,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` invalidSecret = "bad_secret" ) func addValidSignature(r *http.Request) { body, _ := ReadBody(r, maxRequestBodyBytes) - sig := calculateSignature(body) + bodysig := calculateSignature(body) + urlsig := calculateSignature([]byte("https://localhost" + r.URL.Path)) t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": "MessageBird", "nbf": 1690306305, "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", - "payload_hash": sig, + "payload_hash": bodysig, + "url_hash": urlsig, }) signedJWT, _ := t.SignedString([]byte(validSecret)) @@ -45,13 +46,15 @@ func addValidSignature(r *http.Request) { func addInvalidSignature(r *http.Request) { body, _ := ReadBody(r, maxRequestBodyBytes) - sig := calculateSignature(body) + bodysig := calculateSignature(body) + urlsig := calculateSignature([]byte("https://localhost" + r.URL.Path)) t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": "MessageBird", "nbf": 1690306305, "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", - "payload_hash": sig, + "payload_hash": bodysig, + "url_hash": urlsig, }) signedJWT, _ := t.SignedString([]byte(invalidSecret)) @@ -62,14 +65,15 @@ func addInvalidBodyHash(r *http.Request) { body, _ := ReadBody(r, maxRequestBodyBytes) bad_bytes := []byte("bad") body = append(body, bad_bytes[:]...) - preHashSignature := sha256.Sum256(body) - sig := hex.EncodeToString(preHashSignature[:]) + urlsig := calculateSignature([]byte("https://localhost" + r.URL.Path)) + bodysig := calculateSignature(body) t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": "MessageBird", "nbf": 1690306305, "jti": "e92cf079-362d-4813-ab40-bbdd938bdc6d", - "payload_hash": sig, + "payload_hash": bodysig, + "url_hash": urlsig, }) signedJWT, _ := t.SignedString([]byte(validSecret)) @@ -151,10 +155,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:188885551515", - MockResponseBody: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","body":"Simple Message ☺"}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setSmsSendURL, @@ -164,10 +168,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ MsgText: "Simple Message ☺", MsgURN: "tel:188885551515", MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","body":"Simple Message ☺","mediaUrls":["https://foo.bar/image.jpg"]}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺","mediaUrls":["https://foo.bar/image.jpg"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setMmsSendURL, @@ -176,10 +180,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ Label: "Send with image only", MsgURN: "tel:188885551515", MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg"]}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setMmsSendURL, @@ -188,10 +192,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg","https://foo.bar/image2.jpg"]}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/image.jpg","https://foo.bar/image2.jpg"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setMmsSendURL, @@ -200,10 +204,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ Label: "Send with video only", MsgURN: "tel:188885551515", MsgAttachments: []string{"video/mp4:https://foo.bar/movie.mp4"}, - MockResponseBody: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/movie.mp4"]}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/movie.mp4"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setMmsSendURL, @@ -212,10 +216,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ Label: "Send with pdf", MsgURN: "tel:188885551515", MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: "", + MockResponseBody: validResponse, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, - ExpectedRequestBody: `{"recipients":["188885551515"],"originator":"18005551212","mediaUrls":["https://foo.bar/document.pdf"]}`, + ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","mediaUrls":["https://foo.bar/document.pdf"]}`, ExpectedMsgStatus: "W", ExpectedExternalID: "", SendPrep: setMmsSendURL, From fae697558ed7d6c036fa5b45566eae7cb7eb8c45 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:48:04 -0400 Subject: [PATCH 015/170] add 400 and 500 tests --- handlers/messagebird/messagebird_test.go | 36 ++++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index e48df0905..f03303c29 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -160,7 +160,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ ExpectedHeaders: map[string]string{"Content-Type": "application/json", "Authorization": "AccessKey authtoken"}, ExpectedRequestBody: `{"recipients":["188885551515"],"reference":"10","originator":"18005551212","body":"Simple Message ☺"}`, ExpectedMsgStatus: "W", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setSmsSendURL, }, { @@ -173,7 +173,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setMmsSendURL, }, { @@ -185,7 +185,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setMmsSendURL, }, { @@ -197,7 +197,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setMmsSendURL, }, { @@ -209,7 +209,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setMmsSendURL, }, { @@ -221,9 +221,33 @@ var defaultSendTestCases = []ChannelSendTestCase{ 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", - ExpectedExternalID: "", + ExpectedExternalID: "efa6405d518d4c0c88cce11f7db775fb", SendPrep: setMmsSendURL, }, + { + 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", + ExpectedExternalID: "", + SendPrep: setSmsSendURL, + }, + { + 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", + ExpectedExternalID: "", + SendPrep: setSmsSendURL, + }, } func TestSending(t *testing.T) { From 911b6abe0da468337b8feaa8c354e7c07a17ad77 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:52:36 -0400 Subject: [PATCH 016/170] update for NewIncomingMsg change --- handlers/messagebird/messagebird.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index b744dbee3..34e4bf153 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -123,7 +123,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w text := payload.Body // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, text, clog).WithReceivedOn(date.UTC()).WithExternalID(payload.ID) + msg := h.Backend().NewIncomingMsg(channel, urn, text, payload.ID, clog).WithReceivedOn(date.UTC()) // process any attached media if payload.MMS { From a8a2dee7a1a17375f1f8d941306772866f35afe3 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:21:59 -0400 Subject: [PATCH 017/170] changed external id to parser --- handlers/messagebird/messagebird.go | 83 +++++++++++++++-------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 34e4bf153..04b88a61e 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -17,6 +17,7 @@ import ( "net/http" "time" + "github.com/buger/jsonparser" "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" @@ -33,6 +34,44 @@ var ( errorStopped = 103 ) +type Message struct { + Recipients []string `json:"recipients"` + Reference string `json:"reference,omitempty"` + Originator string `json:"originator"` + Subject string `json:"subject,omitempty"` + Body string `json:"body,omitempty"` + MediaURLs []string `json:"mediaUrls,omitempty"` +} + +type ReceivedStatus struct { + ID string + Reference string + Recipient string + Status string + StatusReason string + StatusDatetime time.Time + StatusErrorCode int +} + +var statusMapping = map[string]courier.MsgStatusValue{ + "scheduled": courier.MsgSent, + "delivery_failed": courier.MsgFailed, + "sent": courier.MsgSent, + "buffered": courier.MsgSent, + "delivered": courier.MsgDelivered, + "expired": courier.MsgFailed, +} + +type ReceivedMessage struct { + ID string `json:"id"` + Recipient string `json:"recipient"` + Originator string `json:"originator"` + Body string `json:"body"` + CreatedDatetime time.Time `json:"createdDatetime"` + MediaURLs []string `json:"mediaUrls"` + MMS bool `json:"mms"` +} + func init() { courier.RegisterHandler(newHandler("MBD", "Messagebird", true)) } @@ -187,13 +226,13 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } status.SetStatus(courier.MsgWired) - sendStatus := &ReceivedMessage{} - err = json.Unmarshal(respBody, sendStatus) + + externalID, err := jsonparser.GetString(respBody, "id") if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) return status, nil } - status.SetExternalID(sendStatus.ID) + status.SetExternalID(externalID) return status, nil } @@ -268,41 +307,3 @@ func (h *handler) validateSignature(c courier.Channel, r *http.Request) error { return nil } - -type Message struct { - Recipients []string `json:"recipients"` - Reference string `json:"reference,omitempty"` - Originator string `json:"originator"` - Subject string `json:"subject,omitempty"` - Body string `json:"body,omitempty"` - MediaURLs []string `json:"mediaUrls,omitempty"` -} - -type ReceivedStatus struct { - ID string - Reference string - Recipient string - Status string - StatusReason string - StatusDatetime time.Time - StatusErrorCode int -} - -var statusMapping = map[string]courier.MsgStatusValue{ - "scheduled": courier.MsgSent, - "delivery_failed": courier.MsgFailed, - "sent": courier.MsgSent, - "buffered": courier.MsgSent, - "delivered": courier.MsgDelivered, - "expired": courier.MsgFailed, -} - -type ReceivedMessage struct { - ID string `json:"id"` - Recipient string `json:"recipient"` - Originator string `json:"originator"` - Body string `json:"body"` - CreatedDatetime time.Time `json:"createdDatetime"` - MediaURLs []string `json:"mediaUrls"` - MMS bool `json:"mms"` -} From df8f4b881090e314f164022fa8f09882494cf161 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:32:12 -0400 Subject: [PATCH 018/170] move stopped log inside stopped event --- handlers/messagebird/messagebird.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 04b88a61e..54b1002d5 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -134,8 +134,8 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if err != nil { return nil, err } + clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "EC_SUBSCRIBER_OPTEDOUT")) } - clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "EC_SUBSCRIBER_OPTEDOUT")) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } From 0b9aa9acb989710b47607817e4efbac2bfb44955 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:24:48 -0400 Subject: [PATCH 019/170] jsonpayload for status --- handlers/messagebird/messagebird.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 54b1002d5..7b3337a05 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -89,18 +89,11 @@ func newHandler(channelType courier.ChannelType, name string, validateSignatures func (h *handler) Initialize(s courier.Server) error { h.SetServer(s) s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) - s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, h.receiveStatus) + s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, handlers.JSONPayload(h, h.receiveStatus)) return nil } -func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { - - // get our params - receivedStatus := &ReceivedStatus{} - err := handlers.DecodeAndValidateForm(receivedStatus, r) - if err != nil { - return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "no msg status, ignoring") - } +func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, receivedStatus *ReceivedStatus, clog *courier.ChannelLog) ([]courier.Event, error) { msgStatus, found := statusMapping[receivedStatus.Status] if !found { @@ -134,7 +127,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if err != nil { return nil, err } - clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "EC_SUBSCRIBER_OPTEDOUT")) + clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "Subscriber has sent 'stop'")) } return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) From 481498382677706cf60a04e4b52ea995a1a176d3 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:09:49 -0400 Subject: [PATCH 020/170] fix status --- handlers/messagebird/messagebird.go | 26 ++++++++++++++++-------- handlers/messagebird/messagebird_test.go | 9 ++++---- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 7b3337a05..5c5f1af19 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -44,13 +44,13 @@ type Message struct { } type ReceivedStatus struct { - ID string - Reference string - Recipient string - Status string - StatusReason string - StatusDatetime time.Time - StatusErrorCode int + ID string `schema:"id"` + Reference string `schema:"reference"` + Recipient string `schema:"recipient,required"` + Status string `schema:"status,required"` + StatusReason string `schema:"statusReason"` + StatusDatetime time.Time `schema:"statusDatetime"` + StatusErrorCode int `schema:"statusErrorCode"` } var statusMapping = map[string]courier.MsgStatusValue{ @@ -89,12 +89,20 @@ func newHandler(channelType courier.ChannelType, name string, validateSignatures func (h *handler) Initialize(s courier.Server) error { h.SetServer(s) s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) - s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, handlers.JSONPayload(h, h.receiveStatus)) + s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, h.receiveStatus) + return nil } -func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, receivedStatus *ReceivedStatus, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { + // get our params + receivedStatus := &ReceivedStatus{} + err := handlers.DecodeAndValidateForm(receivedStatus, r) + if err != nil { + return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "no msg status, ignoring") + } + fmt.Printf("receivedStatus: %+v\n", receivedStatus) msgStatus, found := statusMapping[receivedStatus.Status] if !found { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status '%s', must be one of 'queued', 'failed', 'sent', 'delivered', or 'undelivered'", receivedStatus.Status)) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index f03303c29..4b2f776af 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -22,6 +22,7 @@ const ( receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` + statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=17174484057&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" validSecret = "my_super_secret" validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+31207009850","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":31612345678,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` invalidSecret = "bad_secret" @@ -80,7 +81,7 @@ func addInvalidBodyHash(r *http.Request) { r.Header.Set("Messagebird-Signature-Jwt", signedJWT) } -var sigtestCases = []ChannelHandleTestCase{ +var defaultReceiveTestCases = []ChannelHandleTestCase{ { Label: "Receive Valid text w Signature", Headers: map[string]string{"Content-Type": "application/json"}, @@ -134,12 +135,12 @@ var sigtestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), sigtestCases) +func TestReceiving(t *testing.T) { + RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), defaultReceiveTestCases) } func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", true), sigtestCases) + RunChannelBenchmarks(b, testChannels, newHandler("MBD", "Messagebird", true), defaultReceiveTestCases) } func setSmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { From 825421c7fa88aeaab8f0f43a7cac48ccf1f06382 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:52:23 -0400 Subject: [PATCH 021/170] status tests --- handlers/messagebird/messagebird.go | 4 ++-- handlers/messagebird/messagebird_test.go | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 5c5f1af19..3de7c4fb5 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -102,7 +102,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if err != nil { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "no msg status, ignoring") } - fmt.Printf("receivedStatus: %+v\n", receivedStatus) + msgStatus, found := statusMapping[receivedStatus.Status] if !found { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status '%s', must be one of 'queued', 'failed', 'sent', 'delivered', or 'undelivered'", receivedStatus.Status)) @@ -119,7 +119,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } } - // if we have no status, then build it from the external (twilio) id + // if we have no status, then build it from the external (messagebird) id if status == nil { status = h.Backend().NewMsgStatusForExternalID(channel, receivedStatus.ID, msgStatus, clog) } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 4b2f776af..1646847d9 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -24,7 +24,7 @@ const ( validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=17174484057&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" validSecret = "my_super_secret" - validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+31207009850","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":31612345678,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` + validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+188885551515","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":18005551515,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` invalidSecret = "bad_secret" ) @@ -133,6 +133,18 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unable to parse request JSON: invalid character 'e' looking for beginning of value"}]}`, PrepRequest: addValidSignature, }, + { + Label: "Status Valid", + URL: statusBaseURL + "&status=sent", + ExpectedRespStatus: 200, + ExpectedMsgStatus: "S", + }, + { + Label: "Receive Invalid Status", + URL: statusBaseURL + "&status=expiryttd", + ExpectedRespStatus: 400, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"unknown status 'expiryttd', must be one of 'queued', 'failed', 'sent', 'delivered', or 'undelivered'"}]}`, + }, } func TestReceiving(t *testing.T) { From 4f41eef4c6edf5b408c45ea6ce23c9a562c491d2 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Sun, 30 Jul 2023 12:39:59 -0400 Subject: [PATCH 022/170] added stop tests --- handlers/messagebird/messagebird_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 1646847d9..e92044c3e 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -22,7 +22,7 @@ const ( receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` - statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=17174484057&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" + statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=188885551515&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" validSecret = "my_super_secret" validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+188885551515","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":18005551515,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` invalidSecret = "bad_secret" @@ -139,6 +139,15 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ ExpectedRespStatus: 200, ExpectedMsgStatus: "S", }, + { + Label: "Status- Stop Received", + URL: statusBaseURL + "&status=delivery_failed&statusErrorCode=103", + ExpectedRespStatus: 200, + ExpectedMsgStatus: "F", + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Subscriber has sent 'stop'")}, + ExpectedEvent: "stop_contact", + ExpectedURN: "tel:188885551515", + }, { Label: "Receive Invalid Status", URL: statusBaseURL + "&status=expiryttd", From cae2ec8c1bb9edbdde83a58fd1569c83f4166cfd Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Sun, 30 Jul 2023 15:01:52 -0400 Subject: [PATCH 023/170] more tests --- handlers/messagebird/messagebird_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index e92044c3e..cb35ab2a0 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -115,6 +115,14 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"token signature is invalid: signature is invalid"}]}`, PrepRequest: addInvalidSignature, }, + { + Label: "Missing JWT Signature Header", + Headers: map[string]string{"Content-Type": "application/json"}, + URL: receiveURL, + Data: validReceive, + ExpectedRespStatus: 400, + ExpectedBodyContains: `{"message":"Error","data":[{"type":"error","error":"missing request signature"}]}`, + }, { Label: "Receive Valid w Signature but non-matching body hash", Headers: map[string]string{"Content-Type": "application/json"}, From 852f75fbe86421038b5482e7ddf307c72e150ab2 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Tue, 1 Aug 2023 07:35:02 -0400 Subject: [PATCH 024/170] update stop message --- handlers/messagebird/messagebird.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 3de7c4fb5..e92761255 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -135,7 +135,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if err != nil { return nil, err } - clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "Subscriber has sent 'stop'")) + clog.Error(courier.ErrorExternal(fmt.Sprint(receivedStatus.StatusErrorCode), "Contact has sent 'stop'")) } return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) From 7f38dc8fa13bdac0ac9f991a3ec47aa8c114d0d8 Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Tue, 1 Aug 2023 07:40:17 -0400 Subject: [PATCH 025/170] update test --- handlers/messagebird/messagebird_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index cb35ab2a0..6b47d60fa 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -152,7 +152,7 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ URL: statusBaseURL + "&status=delivery_failed&statusErrorCode=103", ExpectedRespStatus: 200, ExpectedMsgStatus: "F", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Subscriber has sent 'stop'")}, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Contact has sent 'stop'")}, ExpectedEvent: "stop_contact", ExpectedURN: "tel:188885551515", }, From 812c2b835f53e285230c5555e67eb46feeb313bc Mon Sep 17 00:00:00 2001 From: Tyler Britten <1933680+tybritten@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:23:30 -0400 Subject: [PATCH 026/170] add shortcode date handling on received messages --- handlers/messagebird/messagebird.go | 25 +++++++++++++++------- handlers/messagebird/messagebird_test.go | 27 ++++++++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index e92761255..5fb30a682 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -63,13 +63,13 @@ var statusMapping = map[string]courier.MsgStatusValue{ } type ReceivedMessage struct { - ID string `json:"id"` - Recipient string `json:"recipient"` - Originator string `json:"originator"` - Body string `json:"body"` - CreatedDatetime time.Time `json:"createdDatetime"` - MediaURLs []string `json:"mediaUrls"` - MMS bool `json:"mms"` + ID string `json:"id"` + Recipient string `json:"recipient"` + Originator string `json:"originator"` + Body string `json:"body"` + CreatedDatetime string `json:"createdDatetime"` + MediaURLs []string `json:"mediaUrls"` + MMS bool `json:"mms"` } func init() { @@ -153,7 +153,16 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // create our date from the timestamp - date := payload.CreatedDatetime + standardDateLayout := "2006-01-02T15:04:05+00:00" + date, err := time.Parse(standardDateLayout, payload.CreatedDatetime) + if err != nil { + //try shortcode format + shortCodeDateLayout := "20060102150405" + date, err = time.Parse(shortCodeDateLayout, payload.CreatedDatetime) + if err != nil { + return nil, fmt.Errorf("unable to parse date '%s': %v", payload.CreatedDatetime, err) + } + } // create our URN urn, err := handlers.StrictTelForCountry(payload.Originator, channel.Country()) diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 6b47d60fa..05ee40783 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -19,13 +19,14 @@ var testChannels = []courier.Channel{ } const ( - receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" - validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` - validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` - statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=188885551515&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" - validSecret = "my_super_secret" - validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+188885551515","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":18005551515,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` - invalidSecret = "bad_secret" + receiveURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" + validReceive = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"2023-07-27T00:49:29+00:00","mms":false}` + validReceiveShortCode = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","body":"Test 3","createdDatetime":"20230727004929","mms":false}` + validReceiveMMS = `{"receiver":"18005551515","sender":"188885551515","message":"Test again","date":1690386569,"date_utc":1690418969,"reference":"1","id":"b6aae1b5dfb2427a8f7ea6a717ba31a9","message_id":"3b53c137369242138120d6b0b2122607","recipient":"18005551515","originator":"188885551515","mediaURLs":["https://foo.bar/image.jpg"],"createdDatetime":"2023-07-27T00:49:29+00:00","mms":true}` + statusBaseURL = "/c/mbd/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?datacoding=plain&id=b6aae1b5dfb2427a8f7ea6a717ba31a9&mccmnc=310010&messageLength=4&messagePartCount=1&ported=0&price%5Bamount%5D=0.000&price%5Bcurrency%5D=USD&recipient=188885551515&reference=26&statusDatetime=2023-07-28T17%3A57%3A12%2B00%3A00" + validSecret = "my_super_secret" + validResponse = `{"id":"efa6405d518d4c0c88cce11f7db775fb","href":"https://rest.messagebird.com/mms/efa6405d518d4c0c88cce11f7db775fb","direction":"mt","originator":"+188885551515","subject":"Great logo","body":"Hi! Please have a look at this very nice logo of this cool company.","reference":"the-customers-reference","mediaUrls":["https://www.messagebird.com/assets/images/og/messagebird.gif"],"scheduledDatetime":null,"createdDatetime":"2017-09-01T10:00:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":18005551515,"status":"sent","statusDatetime":"2017-09-01T10:00:00+00:00"}]}}` + invalidSecret = "bad_secret" ) func addValidSignature(r *http.Request) { @@ -94,6 +95,18 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), PrepRequest: addValidSignature, }, + { + Label: "Receive Valid text w shortcode date", + Headers: map[string]string{"Content-Type": "application/json"}, + URL: receiveURL, + Data: validReceiveShortCode, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Message Accepted", + ExpectedMsgText: Sp("Test 3"), + ExpectedURN: "tel:188885551515", + ExpectedDate: time.Date(2023, time.July, 27, 00, 49, 29, 0, time.UTC), + PrepRequest: addValidSignature, + }, { Label: "Receive Valid w image w Signature", Headers: map[string]string{"Content-Type": "application/json"}, From a89b14d80052c45d4dfa684b377d127174536457 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Aug 2023 12:07:49 -0500 Subject: [PATCH 027/170] Update deps --- go.mod | 18 +++++++++--------- go.sum | 40 ++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index dfd050f23..d3eefb07e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.44.305 + github.com/aws/aws-sdk-go v1.44.319 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 github.com/evalphobia/logrus_sentry v0.8.2 @@ -14,7 +14,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.37.0 + github.com/nyaruka/gocommon v1.38.1 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -35,7 +35,7 @@ require ( github.com/getsentry/raven-go v0.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.1 // indirect + github.com/go-playground/validator/v10 v10.15.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -45,14 +45,14 @@ require ( github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.0.0 // indirect - github.com/nyaruka/phonenumbers v1.1.7 // indirect + github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index adcd4365c..65038dd00 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.44.305 h1:fU/5lY3WyBjGU9fkmQYd8o4fZu+2RaOv/i+sPaJVvFg= -github.com/aws/aws-sdk-go v1.44.305/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= +github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -29,8 +29,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= +github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -68,14 +68,14 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.37.0 h1:1wCSJtdjpBQ4FXWQy5zXBSiHENGEpL6sTy5IN79+0Iw= -github.com/nyaruka/gocommon v1.37.0/go.mod h1:HaUQmWPrZfKS9MLnXKQj28zF4KlJrzFou+DGuqT7RbE= +github.com/nyaruka/gocommon v1.38.1 h1:1IWSVRoSYrVGUaZ7a3NtCwpqay6rT0yvveditG2Kvf4= +github.com/nyaruka/gocommon v1.38.1/go.mod h1:YTmYzdW0261MLyEBuSrJq/B6jpNJRVTN1ZvlEEgR3Zc= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= -github.com/nyaruka/phonenumbers v1.1.7 h1:5UUI9hE79Kk0dymSquXbMYB7IlNDNhvu2aNlJpm9et8= -github.com/nyaruka/phonenumbers v1.1.7/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= +github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= +github.com/nyaruka/phonenumbers v1.1.8/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= github.com/nyaruka/redisx v0.3.1 h1:vnq1tHQwDh+7oG9BANyEVkqGjacgu8wpPxKBOx/exiw= github.com/nyaruka/redisx v0.3.1/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -104,10 +104,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -116,10 +116,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -131,8 +129,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -142,8 +140,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -151,8 +149,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 3cc0b91ebdef6c20dd0c9cb8cf914702a66c4d2c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Aug 2023 12:09:27 -0500 Subject: [PATCH 028/170] Update to go 1.20 --- .github/workflows/ci.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20c12225d..0906abdce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: [push, pull_request] env: - go-version: "1.19.x" + go-version: "1.20.x" jobs: test: name: Test diff --git a/go.mod b/go.mod index d3eefb07e..d09c9d03d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nyaruka/courier -go 1.19 +go 1.20 require ( github.com/antchfx/xmlquery v1.3.17 From 7a8950b5bd3c7be778163a21f6c5fc78f52b459e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Aug 2023 12:17:01 -0500 Subject: [PATCH 029/170] Update CHANGELOG.md for v8.3.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130ee8a8a..51ff2125d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.0 (2023-08-09) +------------------------- + * Update to go 1.20 + * Update deps + * Add Messagebird channel type + v8.2.1 (2023-08-03) ------------------------- * Always save http_logs as [] rather than null From 304977a79745395444baccfa102f0241d2db0dff Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Aug 2023 13:45:34 -0500 Subject: [PATCH 030/170] Revert validator dep upgrade --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 87a31a126..104d2d655 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.38.1 + github.com/nyaruka/gocommon v1.38.2 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -36,7 +36,7 @@ require ( github.com/getsentry/raven-go v0.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.0 // indirect + github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect diff --git a/go.sum b/go.sum index d09e6312b..0de76afb2 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= -github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -70,8 +70,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.38.1 h1:1IWSVRoSYrVGUaZ7a3NtCwpqay6rT0yvveditG2Kvf4= -github.com/nyaruka/gocommon v1.38.1/go.mod h1:YTmYzdW0261MLyEBuSrJq/B6jpNJRVTN1ZvlEEgR3Zc= +github.com/nyaruka/gocommon v1.38.2 h1:67GwC2XRIaPZV5uSif0PkQ6XI327dL18v/GaZYu7SG0= +github.com/nyaruka/gocommon v1.38.2/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From adb84e4068edd7db9fe85d63816a29370b914ee4 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Aug 2023 13:55:18 -0500 Subject: [PATCH 031/170] Update CHANGELOG.md for v8.3.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ff2125d..1e786025d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.1 (2023-08-09) +------------------------- + * Revert validator dep upgrade + v8.3.0 (2023-08-09) ------------------------- * Update to go 1.20 From dddf875350bf8b9dd88c8ea5a8d293a2fb68b94a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Sun, 13 Aug 2023 09:47:32 -0500 Subject: [PATCH 032/170] Test with PostgreSQL 15 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0906abdce..283974b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: ports: - 6379:6379 postgres: - image: postgres:14-alpine + image: postgres:15-alpine env: POSTGRES_DB: courier_test POSTGRES_USER: courier_test From aaa9f5ee44677e4f16bec186c2bf25a5498326d1 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Wed, 16 Aug 2023 14:33:41 +0200 Subject: [PATCH 033/170] Fix retrieve media files for D3C --- handlers/dialog360/dialog360.go | 26 ++++++++++++++-- handlers/dialog360/dialog360_test.go | 46 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index 52377f320..efd7bb911 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" @@ -375,16 +376,35 @@ func resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.Chan return "", nil } + token := channel.StringConfigForKey(courier.ConfigAuthToken, "") + if token == "" { + return "", fmt.Errorf("missing token for D3C channel") + } + urlStr := channel.StringConfigForKey(courier.ConfigBaseURL, "") url, err := url.Parse(urlStr) if err != nil { return "", fmt.Errorf("invalid base url set for D3C channel: %s", err) } - mediaPath, _ := url.Parse("/whatsapp_business/attachments/") - mediaEndpoint := url.ResolveReference(mediaPath).String() + mediaPath, _ := url.Parse(mediaID) + mediaURL := url.ResolveReference(mediaPath).String() + + req, _ := http.NewRequest(http.MethodGet, mediaURL, nil) + req.Header.Set("User-Agent", utils.HTTPUserAgent) + req.Header.Set(d3AuthorizationKey, token) + + resp, respBody, err := handlers.RequestHTTP(req, clog) + if err != nil || resp.StatusCode/100 != 2 { + return "", fmt.Errorf("failed to request media URL for D3C channel: %s", err) + } + + fbFileURL, err := jsonparser.GetString(respBody, "url") + if err != nil { + return "", fmt.Errorf("missing url field in response for D3C media: %s", err) + } - fileURL := fmt.Sprintf("%s?mid=%s", mediaEndpoint, mediaID) + fileURL := strings.ReplaceAll(fbFileURL, "https://lookaside.fbsbx.com", urlStr) return fileURL, nil } diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index 23bfcb8a2..4368dd43a 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -3,7 +3,10 @@ package dialog360 import ( "context" "encoding/json" + "fmt" + "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -252,11 +255,54 @@ var testCasesD3C = []ChannelHandleTestCase{ }, } +func buildMockD3MediaService(testChannels []courier.Channel, testCases []ChannelHandleTestCase) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fileURL := "" + + if strings.HasSuffix(r.URL.Path, "id_voice") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_voice" + } + if strings.HasSuffix(r.URL.Path, "id_document") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_document" + } + if strings.HasSuffix(r.URL.Path, "id_image") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_image" + } + if strings.HasSuffix(r.URL.Path, "id_video") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_video" + } + if strings.HasSuffix(r.URL.Path, "id_audio") { + fileURL = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=id_audio" + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte(fmt.Sprintf(`{ "url": "%s" }`, fileURL))) + })) + testChannels[0].(*test.MockChannel).SetConfig("base_url", server.URL) + + // update our tests media urls + for _, tc := range testCases { + for i := range tc.ExpectedAttachments { + if !strings.HasPrefix(tc.ExpectedAttachments[i], "geo:") { + tc.ExpectedAttachments[i] = strings.ReplaceAll(tc.ExpectedAttachments[i], "https://waba-v2.360dialog.io", server.URL) + } + } + } + + return server +} + func TestHandler(t *testing.T) { + + d3MediaService := buildMockD3MediaService(testChannels, testCasesD3C) + defer d3MediaService.Close() + RunChannelTestCases(t, testChannels, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), testCasesD3C) } func BenchmarkHandler(b *testing.B) { + d3MediaService := buildMockD3MediaService(testChannels, testCasesD3C) + defer d3MediaService.Close() RunChannelBenchmarks(b, testChannels, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), testCasesD3C) } From 46b5fffa17bc249ebe8c83fe363e9c4fcd545f88 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 16 Aug 2023 08:54:15 -0500 Subject: [PATCH 034/170] Update CHANGELOG.md for v8.3.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e786025d..4e08c7901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.2 (2023-08-16) +------------------------- + * Fix retrieve media files for D3C + v8.3.1 (2023-08-09) ------------------------- * Revert validator dep upgrade From 208c8839011ca08068da3d625e16cc6c68093283 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 17 Aug 2023 13:32:38 +0200 Subject: [PATCH 035/170] Remove blackmyna, junebug, old zenvia channel types handlers --- cmd/courier/main.go | 3 - handlers/blackmyna/blackmyna.go | 153 ------------- handlers/blackmyna/blackmyna_test.go | 151 ------------- handlers/junebug/junebug.go | 219 ------------------- handlers/junebug/junebug_test.go | 312 --------------------------- handlers/zenviaold/zenviaold.go | 207 ------------------ handlers/zenviaold/zenviaold_test.go | 221 ------------------- 7 files changed, 1266 deletions(-) delete mode 100644 handlers/blackmyna/blackmyna.go delete mode 100644 handlers/blackmyna/blackmyna_test.go delete mode 100644 handlers/junebug/junebug.go delete mode 100644 handlers/junebug/junebug_test.go delete mode 100644 handlers/zenviaold/zenviaold.go delete mode 100644 handlers/zenviaold/zenviaold_test.go diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 3b15a6e25..18a1263b8 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -14,7 +14,6 @@ import ( _ "github.com/nyaruka/courier/handlers/africastalking" _ "github.com/nyaruka/courier/handlers/arabiacell" _ "github.com/nyaruka/courier/handlers/bandwidth" - _ "github.com/nyaruka/courier/handlers/blackmyna" _ "github.com/nyaruka/courier/handlers/bongolive" _ "github.com/nyaruka/courier/handlers/burstsms" _ "github.com/nyaruka/courier/handlers/clickatell" @@ -37,7 +36,6 @@ import ( _ "github.com/nyaruka/courier/handlers/infobip" _ "github.com/nyaruka/courier/handlers/jasmin" _ "github.com/nyaruka/courier/handlers/jiochat" - _ "github.com/nyaruka/courier/handlers/junebug" _ "github.com/nyaruka/courier/handlers/justcall" _ "github.com/nyaruka/courier/handlers/kaleyra" _ "github.com/nyaruka/courier/handlers/kannel" @@ -71,7 +69,6 @@ import ( _ "github.com/nyaruka/courier/handlers/whatsapp" _ "github.com/nyaruka/courier/handlers/yo" _ "github.com/nyaruka/courier/handlers/zenvia" - _ "github.com/nyaruka/courier/handlers/zenviaold" // load available backends _ "github.com/nyaruka/courier/backends/rapidpro" diff --git a/handlers/blackmyna/blackmyna.go b/handlers/blackmyna/blackmyna.go deleted file mode 100644 index bc2211453..000000000 --- a/handlers/blackmyna/blackmyna.go +++ /dev/null @@ -1,153 +0,0 @@ -package blackmyna - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/buger/jsonparser" - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/gocommon/httpx" - "github.com/pkg/errors" -) - -var sendURL = "http://api.blackmyna.com/2/smsmessaging/outbound" - -type handler struct { - handlers.BaseHandler -} - -func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandler(courier.ChannelType("BM"), "Blackmyna")} -} - -func init() { - courier.RegisterHandler(newHandler()) -} - -// Initialize is called by the engine once everything is loaded -func (h *handler) Initialize(s courier.Server) error { - h.SetServer(s) - s.AddHandlerRoute(h, http.MethodGet, "receive", courier.ChannelLogTypeMsgReceive, h.receiveMessage) - s.AddHandlerRoute(h, http.MethodGet, "status", courier.ChannelLogTypeMsgStatus, h.StatusMessage) - return nil -} - -type moForm struct { - To string `validate:"required" name:"to"` - Text string `validate:"required" name:"text"` - From string `validate:"required" name:"from"` -} - -// receiveMessage is our HTTP handler function for incoming messages -func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { - // get our params - form := &moForm{} - err := handlers.DecodeAndValidateForm(form, r) - if err != nil { - return nil, err - } - - // create our URN - urn, err := handlers.StrictTelForCountry(form.From, channel.Country()) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, "", clog) - - // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) -} - -type statusForm struct { - ID string `validate:"required" name:"id"` - Status int `validate:"required" name:"status"` -} - -var statusMapping = map[int]courier.MsgStatusValue{ - 1: courier.MsgDelivered, - 2: courier.MsgFailed, - 8: courier.MsgSent, - 16: courier.MsgFailed, -} - -// StatusMessage is our HTTP handler function for status updates -func (h *handler) StatusMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { - // get our params - form := &statusForm{} - err := handlers.DecodeAndValidateForm(form, r) - if err != nil { - return nil, err - } - - msgStatus, found := statusMapping[form.Status] - if !found { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status '%d', must be one of 1, 2, 8 or 16", form.Status)) - } - - // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.ID, msgStatus, clog) - return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) -} - -// Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") - if username == "" { - return nil, fmt.Errorf("no username set for BM channel") - } - - password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") - if password == "" { - return nil, fmt.Errorf("no password set for BM channel") - } - - apiKey := msg.Channel().StringConfigForKey(courier.ConfigAPIKey, "") - if apiKey == "" { - return nil, fmt.Errorf("no API key set for BM channel") - } - - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) - - // build our request - form := url.Values{ - "address": []string{msg.URN().Path()}, - "senderaddress": []string{msg.Channel().Address()}, - "message": []string{handlers.GetTextAndAttachments(msg)}, - } - - req, err := http.NewRequest(http.MethodPost, sendURL, strings.NewReader(form.Encode())) - - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(username, password) - - resp, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil - } - - // get our external id - externalID, _ := jsonparser.GetString(respBody, "[0]", "id") - if externalID == "" { - return status, errors.Errorf("no external id returned in body") - } - - status.SetStatus(courier.MsgWired) - status.SetExternalID(externalID) - - return status, nil -} - -func (h *handler) RedactValues(ch courier.Channel) []string { - return []string{ - httpx.BasicAuth(ch.StringConfigForKey(courier.ConfigUsername, ""), ch.StringConfigForKey(courier.ConfigPassword, "")), - } -} diff --git a/handlers/blackmyna/blackmyna_test.go b/handlers/blackmyna/blackmyna_test.go deleted file mode 100644 index d0d666dc2..000000000 --- a/handlers/blackmyna/blackmyna_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package blackmyna - -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{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BM", "2020", "US", nil), -} - -const ( - receiveURL = "/c/bm/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" - statusURL = "/c/bm/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" -) - -var testCases = []ChannelHandleTestCase{ - { - Label: "Receive Valid", - URL: receiveURL + "?to=3344&smsc=ncell&from=%2B9779814641111&text=Msg", - ExpectedRespStatus: 200, - ExpectedBodyContains: "Message Accepted", - ExpectedMsgText: Sp("Msg"), - ExpectedURN: "tel:+9779814641111", - }, - { - Label: "Invalid URN", - URL: receiveURL + "?to=3344&smsc=ncell&from=MTN&text=Msg", - ExpectedRespStatus: 400, - ExpectedBodyContains: "phone number supplied is not a number", - }, - { - Label: "Receive Empty", - URL: receiveURL + "", - ExpectedRespStatus: 400, - ExpectedBodyContains: "field 'text' required", - }, - { - Label: "Receive Missing Text", - URL: receiveURL + "?to=3344&smsc=ncell&from=%2B9779814641111", - ExpectedRespStatus: 400, - ExpectedBodyContains: "field 'text' required", - }, - { - Label: "Status Invalid", - URL: statusURL + "?id=bmID&status=13", - ExpectedRespStatus: 400, - ExpectedBodyContains: "unknown status", - }, - { - Label: "Status Missing", - URL: statusURL + "?", - ExpectedRespStatus: 400, - ExpectedBodyContains: "field 'status' required", - }, - { - Label: "Valid Status", - URL: statusURL + "?id=bmID&status=2", - ExpectedRespStatus: 200, - ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, - }, -} - -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) -} - -func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler(), testCases) -} - -// setSend takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { - sendURL = s.URL -} - -var defaultSendTestCases = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `[{"id": "1002"}]`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Authorization": "Basic VXNlcm5hbWU6UGFzc3dvcmQ="}, - ExpectedPostParams: map[string]string{"message": "Simple Message", "address": "+250788383383", "senderaddress": "2020"}, - ExpectedMsgStatus: "W", - ExpectedExternalID: "1002", - SendPrep: setSendURL, - }, - { - Label: "Unicode Send", - MsgText: "☺", - MsgURN: "tel:+250788383383", - MockResponseBody: `[{"id": "1002"}]`, - MockResponseStatus: 200, - ExpectedPostParams: map[string]string{"message": "☺", "address": "+250788383383", "senderaddress": "2020"}, - ExpectedMsgStatus: "W", - ExpectedExternalID: "1002", - SendPrep: setSendURL, - }, - { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `[{ "id": "1002" }]`, - MockResponseStatus: 200, - ExpectedPostParams: map[string]string{"message": "My pic!\nhttps://foo.bar/image.jpg", "address": "+250788383383", "senderaddress": "2020"}, - ExpectedMsgStatus: "W", - ExpectedExternalID: "1002", - SendPrep: setSendURL, - }, - { - Label: "No External Id", - MsgText: "No External ID", - MsgURN: "tel:+250788383383", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 200, - ExpectedErrors: []*courier.ChannelError{courier.NewChannelError("", "", "no external id returned in body")}, - ExpectedPostParams: map[string]string{"message": `No External ID`, "address": "+250788383383", "senderaddress": "2020"}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, - { - Label: "Error Sending", - MsgText: "Error Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 401, - ExpectedPostParams: map[string]string{"message": `Error Message`, "address": "+250788383383", "senderaddress": "2020"}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BM", "2020", "US", - map[string]interface{}{ - courier.ConfigPassword: "Password", - courier.ConfigUsername: "Username", - courier.ConfigAPIKey: "KEY", - }) - - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) -} diff --git a/handlers/junebug/junebug.go b/handlers/junebug/junebug.go deleted file mode 100644 index d783f9b1d..000000000 --- a/handlers/junebug/junebug.go +++ /dev/null @@ -1,219 +0,0 @@ -package junebug - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/buger/jsonparser" - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/gocommon/httpx" -) - -var ( - maxMsgLength = 1530 -) - -func init() { - courier.RegisterHandler(newHandler()) -} - -type handler struct { - handlers.BaseHandler -} - -func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandler(courier.ChannelType("JN"), "Junebug")} -} - -// Initialize is called by the engine once everything is loaded -func (h *handler) Initialize(s courier.Server) error { - h.SetServer(s) - s.AddHandlerRoute(h, http.MethodPost, "event", courier.ChannelLogTypeEventReceive, handlers.JSONPayload(h, h.receiveEvent)) - s.AddHandlerRoute(h, http.MethodPost, "inbound", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) - return nil -} - -// { -// "from": "+27123456789", -// "timestamp": "2017-01-01 00:00:00.00", -// "content": "content", -// "to": "to-addr", -// "reply_to": null, -// "message_id": "message-id" -// } -type moPayload struct { - From string `json:"from" validate:"required"` - Timestamp string `json:"timestamp" validate:"required"` - Content string `json:"content"` - To string `json:"to" validate:"required"` - ReplyTo string `json:"reply_to"` - MessageID string `json:"message_id" validate:"required"` -} - -// receiveMessage is our HTTP handler function for incoming messages -func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http.ResponseWriter, r *http.Request, payload *moPayload, clog *courier.ChannelLog) ([]courier.Event, error) { - // check authentication - secret := c.StringConfigForKey(courier.ConfigSecret, "") - if secret != "" { - authorization := r.Header.Get("Authorization") - if authorization != fmt.Sprintf("Token %s", secret) { - return nil, courier.WriteAndLogUnauthorized(w, r, c, fmt.Errorf("invalid Authorization header")) - } - } - - // parse our date - date, err := time.Parse("2006-01-02 15:04:05", payload.Timestamp) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, c, w, r, fmt.Errorf("unable to parse date: %s", payload.Timestamp)) - } - - urn, err := handlers.StrictTelForCountry(payload.From, c.Country()) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, c, w, r, err) - } - - msg := h.Backend().NewIncomingMsg(c, urn, payload.Content, payload.MessageID, clog).WithReceivedOn(date.UTC()) - - // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) -} - -// { -// 'event_type': 'submitted', -// 'message_id': 'message-id', -// 'timestamp': '2017-01-01 00:00:00+0000', -// } -type eventPayload struct { - EventType string `json:"event_type" validate:"required"` - MessageID string `json:"message_id" validate:"required"` -} - -var statusMapping = map[string]courier.MsgStatusValue{ - "submitted": courier.MsgSent, - "delivery_pending": courier.MsgWired, - "delivery_succeeded": courier.MsgDelivered, - "delivery_failed": courier.MsgFailed, - "rejected": courier.MsgFailed, -} - -// receiveEvent is our HTTP handler function for incoming events -func (h *handler) receiveEvent(ctx context.Context, c courier.Channel, w http.ResponseWriter, r *http.Request, payload *eventPayload, clog *courier.ChannelLog) ([]courier.Event, error) { - // check authentication - secret := c.StringConfigForKey(courier.ConfigSecret, "") - if secret != "" { - authorization := r.Header.Get("Authorization") - if authorization != fmt.Sprintf("Token %s", secret) { - return nil, courier.WriteAndLogUnauthorized(w, r, c, fmt.Errorf("invalid Authorization header")) - } - } - - // look up our status - msgStatus, found := statusMapping[payload.EventType] - if !found { - return nil, handlers.WriteAndLogRequestIgnored(ctx, h, c, w, r, "ignoring unknown event_type") - } - - // ignore pending, same status we are already in - if msgStatus == courier.MsgWired { - return nil, handlers.WriteAndLogRequestIgnored(ctx, h, c, w, r, "ignoring existing pending status") - } - - status := h.Backend().NewMsgStatusForExternalID(c, payload.MessageID, msgStatus, clog) - return handlers.WriteMsgStatusAndResponse(ctx, h, c, status, w, r) -} - -// { -// "event_url": "https://callback.com/event", -// "content": "hello world", -// "from": "2020", -// "to": "+250788383383", -// "event_auth_token": "secret", -// } -type mtPayload struct { - EventURL string `json:"event_url"` - Content string `json:"content"` - From string `json:"from"` - To string `json:"to"` - EventAuthToken string `json:"event_auth_token,omitempty"` -} - -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - sendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, "") - if sendURL == "" { - return nil, fmt.Errorf("No send_url set for JN channel") - } - - username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") - password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") - if username == "" || password == "" { - return nil, fmt.Errorf("Missing username or password for JN channel") - } - - secret := msg.Channel().StringConfigForKey(courier.ConfigSecret, "") - - callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) - eventURL := fmt.Sprintf("https://%s/c/jn/%s/event", callbackDomain, msg.Channel().UUID()) - - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) - for i, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { - payload := mtPayload{ - EventURL: eventURL, - Content: part, - From: msg.Channel().Address(), - To: msg.URN().Path(), - } - - if secret != "" { - payload.EventAuthToken = secret - } - - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } - - req, err := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(jsonBody)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - req.SetBasicAuth(username, password) - - resp, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil - } - - externalID, err := jsonparser.GetString(respBody, "result", "message_id") - if err != nil { - clog.Error(courier.ErrorResponseValueMissing("message_id")) - return status, nil - } - - // if this is our first message, record the external id - if i == 0 { - status.SetExternalID(externalID) - } - } - - // this was wired successfully - status.SetStatus(courier.MsgWired) - return status, nil -} - -func (h *handler) RedactValues(ch courier.Channel) []string { - vals := []string{ - httpx.BasicAuth(ch.StringConfigForKey(courier.ConfigUsername, ""), ch.StringConfigForKey(courier.ConfigPassword, "")), - } - secret := ch.StringConfigForKey(courier.ConfigSecret, "") - if secret != "" { - vals = append(vals, secret) - } - return vals -} diff --git a/handlers/junebug/junebug_test.go b/handlers/junebug/junebug_test.go deleted file mode 100644 index 6664ad2b4..000000000 --- a/handlers/junebug/junebug_test.go +++ /dev/null @@ -1,312 +0,0 @@ -package junebug - -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 testChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JN", "2020", "US", map[string]interface{}{ - "username": "user1", - "password": "pass1", - "send_url": "https://foo.bar/", -}) - -var authenticatedTestChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JN", "2020", "US", map[string]interface{}{ - "username": "user1", - "password": "pass1", - "send_url": "https://foo.bar/", - "secret": "sesame", -}) - -var ( - inboundURL = "/c/jn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/inbound" - validMsg = `{ - "from": "+250788383383", - "timestamp": "2017-01-01 01:02:03.05", - "content": "hello world", - "to": "2020", - "message_id": "external-id" - } - ` - - invalidURN = `{ - "from": "MTN", - "timestamp": "2017-01-01 01:02:03.05", - "content": "hello world", - "to": "2020", - "message_id": "external-id" - } - ` - - invalidTimestamp = `{ - "from": "+250788383383", - "timestamp": "20170101T01:02:03.05", - "content": "hello world", - "to": "2020", - "message_id": "external-id" - } - ` - - missingMessageID = `{ - "from": "+250788383383", - "timestamp": "2017-01-01 01:02:03.05", - "content": "hello world", - "to": "2020" - } - ` - - eventURL = "/c/jn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/event" - - pendingEvent = `{ - "event_type": "delivery_pending", - "message_id": "xx12345" - }` - - sentEvent = `{ - "event_type": "submitted", - "message_id": "xx12345" - }` - - deliveredEvent = `{ - "event_type": "delivery_succeeded", - "message_id": "xx12345" - }` - - failedEvent = `{ - "event_type": "rejected", - "message_id": "xx12345" - }` - - unknownEvent = `{ - "event_type": "unknown", - "message_id": "xx12345" - }` - - missingEventType = `{ - "message_id": "xx12345" - }` -) - -var testCases = []ChannelHandleTestCase{ - { - Label: "Receive Valid Message", - URL: inboundURL, - Data: validMsg, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedMsgText: Sp("hello world"), - ExpectedURN: "tel:+250788383383", - ExpectedDate: time.Date(2017, 01, 01, 1, 2, 3, 50000000, time.UTC), - }, - { - Label: "Invalid URN", - URL: inboundURL, - Data: invalidURN, - ExpectedRespStatus: 400, - ExpectedBodyContains: "phone number supplied is not a number", - }, - { - Label: "Invalid Timestamp", - URL: inboundURL, - Data: invalidTimestamp, - ExpectedRespStatus: 400, - ExpectedBodyContains: "unable to parse date", - }, - { - Label: "Missing Message ID", - URL: inboundURL, - Data: missingMessageID, - ExpectedRespStatus: 400, - ExpectedBodyContains: "'MessageID' failed on the 'required'", - }, - { - Label: "Receive Pending Event", - URL: eventURL, - Data: pendingEvent, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Ignored", - }, - { - Label: "Receive Sent Event", - URL: eventURL, - Data: sentEvent, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedExternalID: "xx12345", - ExpectedMsgStatus: "S", - }, - { - Label: "Receive Delivered Event", - URL: eventURL, - Data: deliveredEvent, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedExternalID: "xx12345", - ExpectedMsgStatus: "D", - }, - { - Label: "Receive Failed Event", - URL: eventURL, - Data: failedEvent, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedExternalID: "xx12345", - ExpectedMsgStatus: "F", - }, - { - Label: "Receive Unknown Event", - URL: eventURL, - Data: unknownEvent, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Ignored", - }, - { - Label: "Receive Invalid JSON", - URL: eventURL, - Data: "not json", - ExpectedRespStatus: 400, - ExpectedBodyContains: "Error", - }, - { - Label: "Receive Missing Event Type", - URL: eventURL, - Data: missingEventType, - ExpectedRespStatus: 400, - ExpectedBodyContains: "Error", - }, -} - -var authenticatedTestCases = []ChannelHandleTestCase{ - { - Label: "Receive Valid Message", - URL: inboundURL, - Data: validMsg, - Headers: map[string]string{"Authorization": "Token sesame"}, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedMsgText: Sp("hello world"), - ExpectedURN: "tel:+250788383383", - ExpectedDate: time.Date(2017, 01, 01, 1, 2, 3, 50000000, time.UTC), - }, - { - Label: "Invalid Incoming Authorization", - URL: inboundURL, - Data: validMsg, - Headers: map[string]string{"Authorization": "Token foo"}, - ExpectedRespStatus: 401, - ExpectedBodyContains: "Unauthorized", - }, - - { - Label: "Receive Sent Event", - URL: eventURL, - Data: sentEvent, - Headers: map[string]string{"Authorization": "Token sesame"}, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedExternalID: "xx12345", - ExpectedMsgStatus: "S", - }, - { - Label: "Invalid Incoming Authorization", - URL: eventURL, - Data: sentEvent, - Headers: map[string]string{"Authorization": "Token foo"}, - ExpectedRespStatus: 401, - ExpectedBodyContains: "Unauthorized", - }, -} - -func TestHandler(t *testing.T) { - RunChannelTestCases(t, []courier.Channel{testChannel}, newHandler(), testCases) - RunChannelTestCases(t, []courier.Channel{authenticatedTestChannel}, newHandler(), authenticatedTestCases) -} - -func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, []courier.Channel{testChannel}, newHandler(), testCases) -} - -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { - c.(*test.MockChannel).SetConfig("send_url", s.URL) -} - -var sendTestCases = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"result":{"message_id":"externalID"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Authorization": "Basic dXNlcjE6cGFzczE="}, - ExpectedRequestBody: `{"event_url":"https://localhost/c/jn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/event","content":"Simple Message","from":"2020","to":"+250788383383"}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "externalID", - SendPrep: setSendURL, - }, - { - Label: "Send Attachement", - MsgText: "My pic!", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MsgURN: "tel:+250788383383", - MockResponseBody: `{"result":{"message_id":"externalID"}}`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"event_url":"https://localhost/c/jn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/event","content":"My pic!\nhttps://foo.bar/image.jpg","from":"2020","to":"+250788383383"}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "externalID", - SendPrep: setSendURL, - }, - { - Label: "Invalid JSON Response", - MsgText: "Error Sending", - MsgURN: "tel:+250788383383", - MockResponseStatus: 200, - MockResponseBody: "not json", - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Missing External ID", - MsgText: "Error Sending", - MsgURN: "tel:+250788383383", - MockResponseStatus: 200, - MockResponseBody: "{}", - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Error Sending", - MsgText: "Error Sending", - MsgURN: "tel:+250788383383", - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -var authenticatedSendTestCases = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"result":{"message_id":"externalID"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{"Authorization": "Basic dXNlcjE6cGFzczE="}, - ExpectedRequestBody: `{"event_url":"https://localhost/c/jn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/event","content":"Simple Message","from":"2020","to":"+250788383383","event_auth_token":"sesame"}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "externalID", - SendPrep: setSendURL, - }, -} - -func TestSending(t *testing.T) { - RunChannelSendTestCases(t, testChannel, newHandler(), sendTestCases, []string{httpx.BasicAuth("user1", "pass1"), "sesame"}, nil) - RunChannelSendTestCases(t, authenticatedTestChannel, newHandler(), authenticatedSendTestCases, []string{httpx.BasicAuth("user1", "pass1"), "sesame"}, nil) -} diff --git a/handlers/zenviaold/zenviaold.go b/handlers/zenviaold/zenviaold.go deleted file mode 100644 index ef1de04b2..000000000 --- a/handlers/zenviaold/zenviaold.go +++ /dev/null @@ -1,207 +0,0 @@ -package zenvia_old - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - "time" - - "github.com/buger/jsonparser" - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/gocommon/httpx" - "github.com/pkg/errors" -) - -var ( - maxMsgLength = 1152 - sendURL = "https://api-rest.zenvia.com/services/send-sms" -) - -func init() { - courier.RegisterHandler(newHandler()) -} - -type handler struct { - handlers.BaseHandler -} - -func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandler(courier.ChannelType("ZV"), "Zenvia")} -} - -// Initialize is called by the engine once everything is loaded -func (h *handler) Initialize(s courier.Server) error { - h.SetServer(s) - s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, handlers.JSONPayload(h, h.receiveMessage)) - s.AddHandlerRoute(h, http.MethodPost, "status", courier.ChannelLogTypeMsgStatus, handlers.JSONPayload(h, h.receiveStatus)) - return nil -} - -// { -// "callbackMoRequest": { -// "id": "20690090", -// "mobile": "555191951711", -// "shortCode": "40001", -// "account": "zenvia.envio", -// "body": "Content of reply SMS", -// "received": "2014-08-26T12:27:08.488-03:00", -// "correlatedMessageSmsId": "hs765939061" -// } -// } -type moPayload struct { - CallbackMORequest struct { - ID string `json:"id" validate:"required" ` - From string `json:"mobile" validate:"required" ` - Text string `json:"body"` - Date string `json:"received" validate:"required" ` - ExternalID string `json:"correlatedMessageSmsId"` - } `json:"callbackMoRequest"` -} - -// { -// "callbackMtRequest": { -// "status": "03", -// "statusMessage": "Delivered", -// "statusDetail": "120", -// "statusDetailMessage": "Message received by mobile", -// "id": "hs765939216", -// "received": "2014-08-26T12:55:48.593-03:00", -// "mobileOperatorName": "Claro" -// } -// } -type statusPayload struct { - CallbackMTRequest struct { - StatusCode string `json:"status" validate:"required"` - ID string `json:"id" validate:"required" ` - } -} - -// { -// "sendSmsRequest": { -// "to": "555199999999", -// "schedule": "2014-08-22T14:55:00", -// "msg": "Test message.", -// "callbackOption": "NONE", -// "id": "002", -// "aggregateId": "1111" -// } -// } -type mtPayload struct { - SendSMSRequest struct { - To string `json:"to"` - Schedule string `json:"schedule"` - Msg string `json:"msg"` - CallbackOption string `json:"callbackOption"` - ID string `json:"id"` - AggregateID string `json:"aggregateId"` - } `json:"sendSmsRequest"` -} - -var statusMapping = map[string]courier.MsgStatusValue{ - "00": courier.MsgSent, - "01": courier.MsgSent, - "02": courier.MsgSent, - "03": courier.MsgDelivered, - "04": courier.MsgErrored, - "05": courier.MsgErrored, - "06": courier.MsgErrored, - "07": courier.MsgErrored, - "08": courier.MsgErrored, - "09": courier.MsgErrored, - "10": courier.MsgErrored, -} - -// receiveMessage is our HTTP handler function for incoming messages -func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *moPayload, clog *courier.ChannelLog) ([]courier.Event, error) { - // create our date from the timestamp - // 2017-05-03T06:04:45.345-03:00 - date, err := time.Parse("2006-01-02T15:04:05.000-07:00", payload.CallbackMORequest.Date) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("invalid date format: %s", payload.CallbackMORequest.Date)) - } - - // create our URN - urn, err := handlers.StrictTelForCountry(payload.CallbackMORequest.From, channel.Country()) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, payload.CallbackMORequest.Text, payload.CallbackMORequest.ID, clog).WithReceivedOn(date.UTC()) - // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) -} - -// receiveStatus is our HTTP handler function for status updates -func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *statusPayload, clog *courier.ChannelLog) ([]courier.Event, error) { - msgStatus, found := statusMapping[payload.CallbackMTRequest.StatusCode] - if !found { - msgStatus = courier.MsgErrored - } - - // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.CallbackMTRequest.ID, msgStatus, clog) - return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) - -} - -// Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") - if username == "" { - return nil, fmt.Errorf("no username set for ZV channel") - } - - password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") - if password == "" { - return nil, fmt.Errorf("no password set for ZV channel") - } - - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) - parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) - for _, part := range parts { - zvMsg := mtPayload{} - zvMsg.SendSMSRequest.To = strings.TrimLeft(msg.URN().Path(), "+") - zvMsg.SendSMSRequest.Msg = part - zvMsg.SendSMSRequest.ID = msg.ID().String() - zvMsg.SendSMSRequest.CallbackOption = "FINAL" - - requestBody := new(bytes.Buffer) - json.NewEncoder(requestBody).Encode(zvMsg) - - // build our request - req, err := http.NewRequest(http.MethodPost, sendURL, requestBody) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - req.SetBasicAuth(username, password) - - resp, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return status, nil - } - - // was this request successful? - responseMsgStatus, _ := jsonparser.GetString(respBody, "sendSmsResponse", "statusCode") - msgStatus, found := statusMapping[responseMsgStatus] - if msgStatus == courier.MsgErrored || !found { - clog.RawError(errors.Errorf("received non-success response: '%s'", responseMsgStatus)) - return status, nil - } - - status.SetStatus(courier.MsgWired) - } - return status, nil -} - -func (h *handler) RedactValues(ch courier.Channel) []string { - return []string{ - httpx.BasicAuth(ch.StringConfigForKey(courier.ConfigUsername, ""), ch.StringConfigForKey(courier.ConfigPassword, "")), - } -} diff --git a/handlers/zenviaold/zenviaold_test.go b/handlers/zenviaold/zenviaold_test.go deleted file mode 100644 index 0cd62fed1..000000000 --- a/handlers/zenviaold/zenviaold_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package zenvia_old - -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{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZV", "2020", "BR", map[string]interface{}{"username": "zv-username", "password": "zv-password"}), -} - -var ( - receiveURL = "/c/zv/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" - statusURL = "/c/zv/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" - - notJSON = "empty" -) - -var wrongJSONSchema = `{}` - -var validWithMoreFieldsStatus = `{ - "callbackMtRequest": { - "status": "03", - "statusMessage": "Delivered", - "statusDetail": "120", - "statusDetailMessage": "Message received by mobile", - "id": "hs765939216", - "received": "2014-08-26T12:55:48.593-03:00", - "mobileOperatorName": "Claro" - } -}` - -var validStatus = `{ - "callbackMtRequest": { - "status": "03", - "id": "hs765939216" - } -}` - -var unknownStatus = `{ - "callbackMtRequest": { - "status": "038", - "id": "hs765939216" - } -}` - -var missingFieldsStatus = `{ - "callbackMtRequest": { - "status": "", - "id": "hs765939216" - } -}` - -var validReceive = `{ - "callbackMoRequest": { - "id": "20690090", - "mobile": "254791541111", - "shortCode": "40001", - "account": "zenvia.envio", - "body": "Msg", - "received": "2017-05-03T03:04:45.123-03:00", - "correlatedMessageSmsId": "hs765939061" - } -}` - -var invalidURN = `{ - "callbackMoRequest": { - "id": "20690090", - "mobile": "MTN", - "shortCode": "40001", - "account": "zenvia.envio", - "body": "Msg", - "received": "2017-05-03T03:04:45.123-03:00", - "correlatedMessageSmsId": "hs765939061" - } -}` - -var invalidDateReceive = `{ - "callbackMoRequest": { - "id": "20690090", - "mobile": "254791541111", - "shortCode": "40001", - "account": "zenvia.envio", - "body": "Msg", - "received": "yesterday?", - "correlatedMessageSmsId": "hs765939061" - } -}` - -var missingFieldsReceive = `{ - "callbackMoRequest": { - "id": "", - "mobile": "254791541111", - "shortCode": "40001", - "account": "zenvia.envio", - "body": "Msg", - "received": "2017-05-03T03:04:45.123-03:00", - "correlatedMessageSmsId": "hs765939061" - } -}` - -var testCases = []ChannelHandleTestCase{ - {Label: "Receive Valid", URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "Message Accepted", - ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+254791541111", ExpectedDate: time.Date(2017, 5, 3, 06, 04, 45, 123000000, time.UTC)}, - - {Label: "Invalid URN", URL: receiveURL, Data: invalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, - {Label: "Not JSON body", URL: receiveURL, Data: notJSON, ExpectedRespStatus: 400, ExpectedBodyContains: "unable to parse request JSON"}, - {Label: "Wrong JSON schema", URL: receiveURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, - {Label: "Missing field", URL: receiveURL, Data: missingFieldsReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "validation for 'ID' failed on the 'required'"}, - {Label: "Bad Date", URL: receiveURL, Data: invalidDateReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid date format"}, - - {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `Accepted`, ExpectedMsgStatus: "D"}, - {Label: "Valid Status with more fields", URL: statusURL, Data: validWithMoreFieldsStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `Accepted`, ExpectedMsgStatus: "D"}, - {Label: "Unkown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgStatus: "E"}, - {Label: "Not JSON body", URL: statusURL, Data: notJSON, ExpectedRespStatus: 400, ExpectedBodyContains: "unable to parse request JSON"}, - {Label: "Wrong JSON schema", URL: statusURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, - {Label: "Missing field", URL: statusURL, Data: missingFieldsStatus, ExpectedRespStatus: 400, ExpectedBodyContains: "validation for 'StatusCode' failed on the 'required'"}, -} - -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) -} - -func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler(), testCases) -} - -// setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { - sendURL = s.URL -} - -var defaultSendTestCases = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message ☺", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"sendSmsResponse":{"statusCode":"00","statusDescription":"Ok","detailCode":"000","detailDescription":"Message Sent"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", - }, - ExpectedRequestBody: `{"sendSmsRequest":{"to":"250788383383","schedule":"","msg":"Simple Message ☺","callbackOption":"FINAL","id":"10","aggregateId":""}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "", - 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:+250788383383", - ExpectedMsgStatus: "W", - ExpectedExternalID: "", - MockResponseBody: `{"sendSmsResponse":{"statusCode":"00","statusDescription":"Ok","detailCode":"000","detailDescription":"Message Sent"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", - }, - ExpectedRequestBody: `{"sendSmsRequest":{"to":"250788383383","schedule":"","msg":"I need to keep adding more things to make it work","callbackOption":"FINAL","id":"10","aggregateId":""}}`, - SendPrep: setSendURL, - }, - { - Label: "Send Attachment", - MsgText: "My pic!", - MsgURN: "tel:+250788383383", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{"sendSmsResponse":{"statusCode":"00","statusDescription":"Ok","detailCode":"000","detailDescription":"Message Sent"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", - }, - ExpectedRequestBody: `{"sendSmsRequest":{"to":"250788383383","schedule":"","msg":"My pic!\nhttps://foo.bar/image.jpg","callbackOption":"FINAL","id":"10","aggregateId":""}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "", - SendPrep: setSendURL, - }, - { - Label: "No External ID", - MsgText: "No External ID", - MsgURN: "tel:+250788383383", - MockResponseBody: `{"sendSmsResponse" :{"statusCode" :"05","statusDescription" :"Blocked","detailCode":"140","detailDescription":"Mobile number not covered"}}`, - MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", - }, - ExpectedRequestBody: `{"sendSmsRequest":{"to":"250788383383","schedule":"","msg":"No External ID","callbackOption":"FINAL","id":"10","aggregateId":""}}`, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.NewChannelError("", "", "received non-success response: '05'")}, - SendPrep: setSendURL}, - { - Label: "Error Sending", - MsgText: "Error Message", - MsgURN: "tel:+250788383383", - MockResponseBody: `{ "error": "failed" }`, - MockResponseStatus: 401, - ExpectedRequestBody: `{"sendSmsRequest":{"to":"250788383383","schedule":"","msg":"Error Message","callbackOption":"FINAL","id":"10","aggregateId":""}}`, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -func TestSending(t *testing.T) { - maxMsgLength = 160 - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZV", "2020", "BR", map[string]interface{}{"username": "zv-username", "password": "zv-password"}) - - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("zv-username", "zv-password")}, nil) -} From 3e74d42ec9644c8e4e1b1d25be423622d35e1249 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 17 Aug 2023 13:57:29 +0200 Subject: [PATCH 036/170] Remove TT registration --- handlers/twitter/twitter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/handlers/twitter/twitter.go b/handlers/twitter/twitter.go index cf8c4de1a..8d77d1685 100644 --- a/handlers/twitter/twitter.go +++ b/handlers/twitter/twitter.go @@ -45,7 +45,6 @@ const ( func init() { courier.RegisterHandler(newHandler("TWT", "Twitter Activity")) - courier.RegisterHandler(newHandler("TT", "Twitter")) } type handler struct { From 383d107c996c1df0d3cc23e3316a8b78bd7bf488 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 17 Aug 2023 09:14:06 -0500 Subject: [PATCH 037/170] Update CHANGELOG.md for v8.3.3 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e08c7901..fea8a8542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.3 (2023-08-17) +------------------------- + * Remove Legacy Twitter (TT) type registration + * Remove Blackmyna, Junebug, old Zenvia channel type handlers + v8.3.2 (2023-08-16) ------------------------- * Fix retrieve media files for D3C From c36dc1b7c8b73767c8e8481433c879262b8d4ff8 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 24 Aug 2023 13:31:44 +0200 Subject: [PATCH 038/170] Update channel type to save external ID for MO messages if we can, so we can dedupe by that --- handlers/dart/dart.go | 9 +++++---- handlers/dart/dart_test.go | 6 ++++-- handlers/freshchat/freshchat.go | 2 +- handlers/freshchat/freshchat_test.go | 1 + handlers/highconnection/highconnection.go | 9 ++++++++- handlers/highconnection/highconnection_test.go | 6 ++++-- handlers/mtarget/mtarget.go | 3 ++- handlers/mtarget/mtarget_test.go | 4 ++-- handlers/nexmo/nexmo.go | 2 +- handlers/nexmo/nexmo_test.go | 2 ++ handlers/start/start.go | 2 +- handlers/start/start_test.go | 3 +++ 12 files changed, 34 insertions(+), 15 deletions(-) diff --git a/handlers/dart/dart.go b/handlers/dart/dart.go index 4c23c0e68..13ff2139f 100644 --- a/handlers/dart/dart.go +++ b/handlers/dart/dart.go @@ -56,9 +56,10 @@ func (h *handler) Initialize(s courier.Server) error { } type moForm struct { - Message string `name:"message"` - Original string `name:"original"` - SendTo string `name:"sendto"` + Message string `name:"message"` + Original string `name:"original"` + SendTo string `name:"sendto"` + ExternalID string `name:"messageid"` } // receiveMessage is our HTTP handler function for incoming messages @@ -83,7 +84,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, "", clog) + msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.ExternalID, clog) // and finally queue our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) diff --git a/handlers/dart/dart_test.go b/handlers/dart/dart_test.go index 13723bedd..b7647b3e8 100644 --- a/handlers/dart/dart_test.go +++ b/handlers/dart/dart_test.go @@ -21,19 +21,21 @@ const ( var daTestCases = []ChannelHandleTestCase{ { Label: "Receive Valid", - URL: receiveURL + "?userid=testusr&password=test&original=6289881134560&sendto=2020&message=Msg", + URL: receiveURL + "?userid=testusr&password=test&original=6289881134560&sendto=2020&message=Msg&messageid=foo", ExpectedRespStatus: 200, ExpectedBodyContains: "000", ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+6289881134560", + ExpectedExternalID: "foo", }, { Label: "Receive Valid", - URL: receiveURL + "?userid=testusr&password=test&original=cmp-oodddqddwdwdcd&sendto=2020&message=Msg", + URL: receiveURL + "?userid=testusr&password=test&original=cmp-oodddqddwdwdcd&sendto=2020&message=Msg&messageid=bar", ExpectedRespStatus: 200, ExpectedBodyContains: "000", ExpectedMsgText: Sp("Msg"), ExpectedURN: "ext:cmp-oodddqddwdwdcd", + ExpectedExternalID: "bar", }, { Label: "Receive Invalid", diff --git a/handlers/freshchat/freshchat.go b/handlers/freshchat/freshchat.go index 8fa2b1d9a..35e9566c9 100644 --- a/handlers/freshchat/freshchat.go +++ b/handlers/freshchat/freshchat.go @@ -86,7 +86,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } } // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, text, "", clog).WithReceivedOn(date) + msg := h.Backend().NewIncomingMsg(channel, urn, text, payload.Data.Message.ID, clog).WithReceivedOn(date) //add image if mediaURL != "" { diff --git a/handlers/freshchat/freshchat_test.go b/handlers/freshchat/freshchat_test.go index 904abb5cf..9a0d954ce 100644 --- a/handlers/freshchat/freshchat_test.go +++ b/handlers/freshchat/freshchat_test.go @@ -39,6 +39,7 @@ var sigtestCases = []ChannelHandleTestCase{ ExpectedMsgText: Sp("Test 2"), ExpectedURN: "freshchat:c8fddfaf-622a-4a0e-b060-4f3ccbeab606/882f3926-b292-414b-a411-96380db373cd", ExpectedDate: time.Date(2019, 6, 21, 17, 43, 20, 866000000, time.UTC), + ExpectedExternalID: "7a454fde-c720-4c97-a61d-0ffe70449eb6", }, { Label: "Bad Signature", diff --git a/handlers/highconnection/highconnection.go b/handlers/highconnection/highconnection.go index b1a9304dc..e5bbc0af3 100644 --- a/handlers/highconnection/highconnection.go +++ b/handlers/highconnection/highconnection.go @@ -6,6 +6,7 @@ import ( "mime" "net/http" "net/url" + "strconv" "time" "github.com/nyaruka/courier" @@ -38,6 +39,7 @@ func (h *handler) Initialize(s courier.Server) error { } type moForm struct { + ID int64 `name:"ID"` To string `name:"TO" validate:"required"` From string `name:"FROM" validate:"required"` Message string `name:"MESSAGE"` @@ -72,8 +74,13 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w text = mime.BEncoding.Encode("ISO-8859-1", text) text, _ = new(mime.WordDecoder).DecodeHeader(text) + msgID := "" + if form.ID != 0 { + msgID = strconv.FormatInt(form.ID, 10) + } + // build our Message - msg := h.Backend().NewIncomingMsg(channel, urn, text, "", clog).WithReceivedOn(date.UTC()) + msg := h.Backend().NewIncomingMsg(channel, urn, text, msgID, clog).WithReceivedOn(date.UTC()) // and finally write our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) diff --git a/handlers/highconnection/highconnection_test.go b/handlers/highconnection/highconnection_test.go index 2be804227..5c4fde5f4 100644 --- a/handlers/highconnection/highconnection_test.go +++ b/handlers/highconnection/highconnection_test.go @@ -23,22 +23,24 @@ var testCases = []ChannelHandleTestCase{ { Label: "Receive Valid Message", URL: receiveURL, - Data: "FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14%3A26%3A06", + Data: "FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14%3A26%3A06&ID=123456", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("Hello World"), ExpectedURN: "tel:+33610346460", ExpectedDate: time.Date(2015, 04, 02, 14, 26, 06, 0, time.UTC), + ExpectedExternalID: "123456", }, { Label: "Receive Valid Message with accents", URL: receiveURL, - Data: "FROM=+33610346460&TO=5151&MESSAGE=je+suis+tr%E8s+satisfait+&RECEPTION_DATE=2015-04-02T14%3A26%3A06", + Data: "FROM=+33610346460&TO=5151&MESSAGE=je+suis+tr%E8s+satisfait+&RECEPTION_DATE=2015-04-02T14%3A26%3A06&ID=123123", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("je suis très satisfait "), ExpectedURN: "tel:+33610346460", ExpectedDate: time.Date(2015, 04, 02, 14, 26, 06, 0, time.UTC), + ExpectedExternalID: "123123", }, { Label: "Invalid URN", diff --git a/handlers/mtarget/mtarget.go b/handlers/mtarget/mtarget.go index 3e2a42dc3..659e6fb1f 100644 --- a/handlers/mtarget/mtarget.go +++ b/handlers/mtarget/mtarget.go @@ -61,6 +61,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp text := r.Form.Get("Content") from := r.Form.Get("Msisdn") keyword := r.Form.Get("Keyword") + msgID := r.Form.Get("MsgId") if from == "" { return nil, handlers.WriteAndLogRequestError(ctx, h, c, w, r, fmt.Errorf("missing required field 'Msisdn'")) @@ -142,7 +143,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp } // otherwise, create and write the message - msg := h.Backend().NewIncomingMsg(c, urn, text, "", clog).WithReceivedOn(time.Now().UTC()) + msg := h.Backend().NewIncomingMsg(c, urn, text, msgID, clog).WithReceivedOn(time.Now().UTC()) return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/mtarget_test.go index 045531dd8..acf13827b 100644 --- a/handlers/mtarget/mtarget_test.go +++ b/handlers/mtarget/mtarget_test.go @@ -12,7 +12,7 @@ import ( var ( receiveURL = "/c/mt/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" - receiveValidMessage = "Msisdn=+923161909799&Content=hello+world&Keyword=Default" + receiveValidMessage = "Msisdn=+923161909799&Content=hello+world&Keyword=Default&MsgId=foo" receiveInvalidURN = "Msisdn=MTN&Content=hello+world&Keyword=Default" receiveStop = "Msisdn=+923161909799&Content=Stop&Keyword=Stop" receiveMissingFrom = "Content=hello&Keyword=Default" @@ -33,7 +33,7 @@ var testChannels = []courier.Channel{ var handleTestCases = []ChannelHandleTestCase{ {Label: "Receive Valid Message", URL: receiveURL, Data: receiveValidMessage, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedMsgText: Sp("hello world"), ExpectedURN: "tel:+923161909799"}, + ExpectedMsgText: Sp("hello world"), ExpectedURN: "tel:+923161909799", ExpectedExternalID: "foo"}, {Label: "Invalid URN", URL: receiveURL, Data: receiveInvalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, {Label: "Receive Stop", URL: receiveURL, Data: receiveStop, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedURN: "tel:+923161909799", ExpectedEvent: courier.StopContact}, diff --git a/handlers/nexmo/nexmo.go b/handlers/nexmo/nexmo.go index ad77ff4b2..e4a88c3d9 100644 --- a/handlers/nexmo/nexmo.go +++ b/handlers/nexmo/nexmo.go @@ -165,7 +165,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // create and write the message - msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, "", clog) + msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, form.MessageID, clog) return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } diff --git a/handlers/nexmo/nexmo_test.go b/handlers/nexmo/nexmo_test.go index 6bffde3a4..4982e5d76 100644 --- a/handlers/nexmo/nexmo_test.go +++ b/handlers/nexmo/nexmo_test.go @@ -26,6 +26,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("Join"), ExpectedURN: "tel:+2349067554729", + ExpectedExternalID: "external1", }, { Label: "Invalid URN", @@ -41,6 +42,7 @@ var testCases = []ChannelHandleTestCase{ Data: "to=2020&msisdn=2349067554729&text=Join&messageId=external1", ExpectedMsgText: Sp("Join"), ExpectedURN: "tel:+2349067554729", + ExpectedExternalID: "external1", }, { Label: "Receive URL check", diff --git a/handlers/start/start.go b/handlers/start/start.go index 6a0a13e4c..95e3e35c6 100644 --- a/handlers/start/start.go +++ b/handlers/start/start.go @@ -82,7 +82,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w date := time.Unix(ts, 0).UTC() // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, payload.Body.Text, "", clog).WithReceivedOn(date) + msg := h.Backend().NewIncomingMsg(channel, urn, payload.Body.Text, payload.Service.RequestID, clog).WithReceivedOn(date) // and write it return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) diff --git a/handlers/start/start_test.go b/handlers/start/start_test.go index 746e7f052..e39444c75 100644 --- a/handlers/start/start_test.go +++ b/handlers/start/start_test.go @@ -82,6 +82,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedMsgText: Sp("Hello World"), ExpectedURN: "tel:+250788123123", ExpectedDate: time.Date(2015, 12, 18, 15, 02, 54, 0, time.UTC), + ExpectedExternalID: "msg1", }, { Label: "Receive Valid Encoded", @@ -92,6 +93,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedMsgText: Sp("Кохання"), ExpectedURN: "tel:+380501529999", ExpectedDate: time.Date(2015, 12, 18, 15, 02, 54, 0, time.UTC), + ExpectedExternalID: "43473486", }, { Label: "Receive Valid with empty Text", @@ -101,6 +103,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp(""), ExpectedURN: "tel:+250788123123", + ExpectedExternalID: "msg1", }, { Label: "Receive Valid missing body", From 58dce5deaa8bb5820b2ac40067b72c6b65ff8fa2 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 24 Aug 2023 16:25:49 +0200 Subject: [PATCH 039/170] Adjust field name --- handlers/dart/dart.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/handlers/dart/dart.go b/handlers/dart/dart.go index 13ff2139f..0ae09b4ee 100644 --- a/handlers/dart/dart.go +++ b/handlers/dart/dart.go @@ -56,10 +56,10 @@ func (h *handler) Initialize(s courier.Server) error { } type moForm struct { - Message string `name:"message"` - Original string `name:"original"` - SendTo string `name:"sendto"` - ExternalID string `name:"messageid"` + Message string `name:"message"` + Original string `name:"original"` + SendTo string `name:"sendto"` + MessageID string `name:"messageid"` } // receiveMessage is our HTTP handler function for incoming messages @@ -84,7 +84,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // build our msg - msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.ExternalID, clog) + msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.MessageID, clog) // and finally queue our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) From 410424ac9f331d0e4b1818d34336ba0def6b6bf7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 24 Aug 2023 09:43:28 -0500 Subject: [PATCH 040/170] Update CHANGELOG.md for v8.3.4 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fea8a8542..7f4ef5555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.4 (2023-08-24) +------------------------- + * Update channel type to save external ID for MO messages if we can, so we can dedupe by that + * Test with PostgreSQL 15 + v8.3.3 (2023-08-17) ------------------------- * Remove Legacy Twitter (TT) type registration From 7d3b9e3ad35828100b1ad9e4c28b88fd8216eb48 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 29 Aug 2023 09:56:56 -0500 Subject: [PATCH 041/170] Move sql strings only used by tests into test module --- backends/rapidpro/backend_test.go | 47 +++++++++++++++++++++++++++++++ backends/rapidpro/msg.go | 47 ------------------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index cf1110b37..9d2d8120c 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1544,3 +1544,50 @@ func checkMsgExists(b *backend, status courier.MsgStatus) (err error) { } return err } + +const sqlSelectMsg = ` +SELECT + org_id, + direction, + text, + attachments, + quick_replies, + msg_count, + error_count, + failed_reason, + high_priority, + status, + visibility, + external_id, + channel_id, + contact_id, + contact_urn_id, + created_on, + modified_on, + next_attempt, + queued_on, + sent_on, + log_uuids +FROM + msgs_msg +WHERE + id = $1` + +const selectChannelSQL = ` +SELECT + org_id, + ch.id as id, + ch.uuid as uuid, + ch.name as name, + channel_type, schemes, + address, role, + ch.country as country, + ch.config as config, + org.config as org_config, + org.is_anon as org_is_anon +FROM + channels_channel ch + JOIN orgs_org org on ch.org_id = org.id +WHERE + ch.id = $1 +` diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index e3eff0ae2..adf2b81a8 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -181,53 +181,6 @@ func writeMsgToDB(ctx context.Context, b *backend, m *DBMsg, clog *courier.Chann return nil } -const sqlSelectMsg = ` -SELECT - org_id, - direction, - text, - attachments, - quick_replies, - msg_count, - error_count, - failed_reason, - high_priority, - status, - visibility, - external_id, - channel_id, - contact_id, - contact_urn_id, - created_on, - modified_on, - next_attempt, - queued_on, - sent_on, - log_uuids -FROM - msgs_msg -WHERE - id = $1` - -const selectChannelSQL = ` -SELECT - org_id, - ch.id as id, - ch.uuid as uuid, - ch.name as name, - channel_type, schemes, - address, role, - ch.country as country, - ch.config as config, - org.config as org_config, - org.is_anon as org_is_anon -FROM - channels_channel ch - JOIN orgs_org org on ch.org_id = org.id -WHERE - ch.id = $1 -` - //----------------------------------------------------------------------------- // Msg flusher for flushing failed writes //----------------------------------------------------------------------------- From 4413d7b270d84de2baf37d079b5c9ac502e98fbf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 29 Aug 2023 10:06:32 -0500 Subject: [PATCH 042/170] Update to latest gocommon --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 104d2d655..d7c172185 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.38.2 + github.com/nyaruka/gocommon v1.39.1 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 0de76afb2..567ca51fa 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.38.2 h1:67GwC2XRIaPZV5uSif0PkQ6XI327dL18v/GaZYu7SG0= -github.com/nyaruka/gocommon v1.38.2/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= +github.com/nyaruka/gocommon v1.39.1 h1:C1LM6y0iGe3oTJPknb+FcNyz8yl4+CdsvRD36wAai38= +github.com/nyaruka/gocommon v1.39.1/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 81c1931c1132b1cf40ad3cb75f949628e9205e58 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 29 Aug 2023 11:41:38 -0500 Subject: [PATCH 043/170] Rework writing status updates so that updates by external id also use the batcher --- backends/rapidpro/backend.go | 18 +++---- backends/rapidpro/backend_test.go | 14 +---- backends/rapidpro/status.go | 90 +++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 701fc1ba7..fb0518dbc 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -292,8 +292,9 @@ func (b *backend) NewMsgStatusForExternalID(channel courier.Channel, externalID // WriteMsgStatus writes the passed in MsgStatus to our store func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) error { - timeout, cancel := context.WithTimeout(ctx, backendTimeout) - defer cancel() + if status.ID() == courier.NilMsgID && status.ExternalID() == "" { + return errors.New("message status with no id or external id") + } if status.HasUpdatedURN() { err := b.updateContactURN(ctx, status) @@ -301,16 +302,6 @@ func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) return errors.Wrap(err, "error updating contact URN") } } - // if we have an ID, we can have our batch commit for us - if status.ID() != courier.NilMsgID { - b.statusWriter.Queue(status.(*DBMsgStatus)) - } else { - // otherwise, write normally (synchronously) - err := writeMsgStatus(timeout, b, status) - if err != nil { - return err - } - } // if we have an id and are marking an outgoing msg as errored, then clear our sent flag if status.ID() != courier.NilMsgID && status.Status() == courier.MsgErrored { @@ -320,6 +311,9 @@ func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) } } + // queue the status to written by the batch writer + b.statusWriter.Queue(status.(*DBMsgStatus)) + return nil } diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 9d2d8120c..1a50e4726 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -606,19 +606,9 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.Equal(courier.MsgDelivered, m.Status_) ts.NotNil(m.SentOn_) - // no such external id for outgoing message - status := ts.b.NewMsgStatusForExternalID(channel, "ext2", courier.MsgSent, clog6) - err := ts.b.WriteMsgStatus(ctx, status) - ts.Error(err) - - // no such external id - status = ts.b.NewMsgStatusForExternalID(channel, "ext3", courier.MsgSent, clog6) - err = ts.b.WriteMsgStatus(ctx, status) - ts.Error(err) - // reset our status to sent - status = ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgSent, clog6) - err = ts.b.WriteMsgStatus(ctx, status) + status := ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgSent, clog6) + err := ts.b.WriteMsgStatus(ctx, status) ts.NoError(err) time.Sleep(time.Second) diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 0b4c5259a..cc5d0690d 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -3,7 +3,6 @@ package rapidpro import ( "context" "encoding/json" - "errors" "fmt" "log" "os" @@ -17,6 +16,7 @@ import ( "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/syncx" "github.com/nyaruka/gocommon/urns" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -37,24 +37,6 @@ func newMsgStatus(channel courier.Channel, id courier.MsgID, externalID string, } } -// writeMsgStatus writes the passed in status to the database, queueing it to our spool in case the database is down -func writeMsgStatus(ctx context.Context, b *backend, status courier.MsgStatus) error { - dbStatus := status.(*DBMsgStatus) - - err := writeMsgStatusToDB(ctx, b, dbStatus) - - if err == courier.ErrMsgNotFound { - return err - } - - // failed writing, write to our spool instead - if err != nil { - err = courier.WriteToSpool(b.config.SpoolDir, "statuses", dbStatus) - } - - return err -} - // the craziness below lets us update our status to 'F' and schedule retries without knowing anything about the message const sqlUpdateMsgByID = ` UPDATE msgs_msg SET @@ -315,7 +297,34 @@ func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWr } func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuses []*DBMsgStatus) { - for _, batch := range utils.ChunkSlice(statuses, 1000) { + log := logrus.WithField("comp", "status writer") + + // get the statuses which have external ID instead of a message ID + missingID := make([]*DBMsgStatus, 0, 500) + for _, s := range statuses { + if s.ID_ == courier.NilMsgID { + missingID = append(missingID, s) + } + } + + // try to resolve channel ID + external ID to message IDs + for _, batch := range utils.ChunkSlice(missingID, 1000) { + if err := resolveStatusMsgIDs(ctx, db, batch); err != nil { + log.WithError(err).Error("error resolving msg ids") + } + } + + resolved := make([]*DBMsgStatus, 0, 500) + + for _, s := range statuses { + if s.ID_ != courier.NilMsgID { + resolved = append(resolved, s) + } else { + log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) + } + } + + for _, batch := range utils.ChunkSlice(resolved, 1000) { err := dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, batch) // if we received an error, try again one at a time (in case it is one value hanging us up) @@ -323,7 +332,7 @@ func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuse for _, s := range batch { err = dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, []*DBMsgStatus{s}) if err != nil { - log := logrus.WithField("comp", "status writer").WithField("msg_id", s.ID()) + log := log.WithField("msg_id", s.ID()) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() @@ -341,3 +350,42 @@ func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuse } } } + +const sqlResolveStatusMsgIDs = ` +SELECT id, channel_id, external_id + FROM msgs_msg + WHERE (channel_id, external_id) IN (VALUES(CAST(:channel_id AS int), :external_id))` + +// resolveStatusMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel/external ID pair +// found for a status, that status will be left with a nil msg ID. +func resolveStatusMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStatus) error { + sql, params, err := dbutil.BulkSQL(db, sqlResolveStatusMsgIDs, statuses) + if err != nil { + return err + } + + rows, err := db.QueryContext(ctx, sql, params...) + if err != nil { + return err + } + defer rows.Close() + + var msgID courier.MsgID + var channelID courier.ChannelID + var externalID string + + for rows.Next() { + if err := rows.Scan(&msgID, &channelID, &externalID); err != nil { + return errors.Wrap(err, "error scanning rows") + } + + // find the status with this channel ID and external ID and update its msg ID + for _, s := range statuses { + if s.ChannelID_ == channelID && s.ExternalID_ == externalID { + s.ID_ = msgID + } + } + } + + return rows.Err() +} From d69691b5118456695b4e7aed65b6a0f5df8bb21d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 10:13:21 -0500 Subject: [PATCH 044/170] Use lookup map in resolveStatusMsgIDs --- backends/rapidpro/status.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index cc5d0690d..6366fb462 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -359,6 +359,16 @@ SELECT id, channel_id, external_id // resolveStatusMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel/external ID pair // found for a status, that status will be left with a nil msg ID. func resolveStatusMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStatus) error { + // create a mapping of channel id + external id -> status + type ext struct { + channelID courier.ChannelID + externalID string + } + statusesByExt := make(map[ext]*DBMsgStatus, len(statuses)) + for _, s := range statuses { + statusesByExt[ext{s.ChannelID_, s.ExternalID_}] = s + } + sql, params, err := dbutil.BulkSQL(db, sqlResolveStatusMsgIDs, statuses) if err != nil { return err @@ -380,11 +390,8 @@ func resolveStatusMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStat } // find the status with this channel ID and external ID and update its msg ID - for _, s := range statuses { - if s.ChannelID_ == channelID && s.ExternalID_ == externalID { - s.ID_ = msgID - } - } + s := statusesByExt[ext{channelID, externalID}] + s.ID_ = msgID } return rows.Err() From ebeca40a5a9dc3b75fa2ff775470d33b0471ece8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 10:21:54 -0500 Subject: [PATCH 045/170] Update CHANGELOG.md for v8.3.5 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4ef5555..f3fa91c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.5 (2023-08-30) +------------------------- + * Rework writing status updates so that updates by external id also use the batcher + v8.3.4 (2023-08-24) ------------------------- * Update channel type to save external ID for MO messages if we can, so we can dedupe by that From 403e4fbeaa7f0b4907c16cd79f9b4bc77befa895 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 11:19:15 -0500 Subject: [PATCH 046/170] Rework writing msg statuses to always use id resolving --- backends/rapidpro/backend_test.go | 54 --------- backends/rapidpro/status.go | 178 ++++++++-------------------- handlers/dialog360/dialog360.go | 7 -- handlers/facebook/facebook.go | 7 -- handlers/facebookapp/facebookapp.go | 14 --- handlers/infobip/infobip.go | 5 - handlers/responses.go | 4 - handlers/whatsapp/whatsapp.go | 7 -- msg.go | 7 -- 9 files changed, 49 insertions(+), 234 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 1a50e4726..2cc81eeb1 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -2,7 +2,6 @@ package rapidpro import ( "context" - "database/sql" "encoding/base64" "encoding/json" "fmt" @@ -169,36 +168,6 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { ts.Equal("", msg.FlowUUID()) } -func (ts *BackendTestSuite) TestCheckMsgExists() { - knChannel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, knChannel, nil) - - // check with invalid message id - err := checkMsgExists(ts.b, ts.b.NewMsgStatusForID(knChannel, -1, courier.MsgStatusValue("S"), clog)) - ts.Equal(err, courier.ErrMsgNotFound) - - // check with valid message id - err = checkMsgExists(ts.b, ts.b.NewMsgStatusForID(knChannel, 10000, courier.MsgStatusValue("S"), clog)) - ts.Nil(err) - - // only outgoing messages are matched - err = checkMsgExists(ts.b, ts.b.NewMsgStatusForID(knChannel, 10002, courier.MsgStatusValue("S"), clog)) - ts.Equal(err, courier.ErrMsgNotFound) - - // check with invalid external id - err = checkMsgExists(ts.b, ts.b.NewMsgStatusForExternalID(knChannel, "ext-invalid", courier.MsgStatusValue("S"), clog)) - ts.Equal(err, courier.ErrMsgNotFound) - - // only outgoing messages are matched - err = checkMsgExists(ts.b, ts.b.NewMsgStatusForExternalID(knChannel, "ext2", courier.MsgStatusValue("S"), clog)) - ts.Equal(err, courier.ErrMsgNotFound) - - // check with valid external id - status := ts.b.NewMsgStatusForExternalID(knChannel, "ext1", courier.MsgStatusValue("S"), clog) - err = checkMsgExists(ts.b, status) - ts.Nil(err) -} - func (ts *BackendTestSuite) TestDeleteMsgWithExternalID() { knChannel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") @@ -1512,29 +1481,6 @@ func readMsgFromDB(b *backend, id courier.MsgID) *DBMsg { return m } -const selectMsgIDForID = ` -SELECT m."id" FROM "msgs_msg" m INNER JOIN "channels_channel" c ON (m."channel_id" = c."id") WHERE (m."id" = $1 AND c."uuid" = $2 AND m."direction" = 'O')` - -const selectMsgIDForExternalID = ` -SELECT m."id" FROM "msgs_msg" m INNER JOIN "channels_channel" c ON (m."channel_id" = c."id") WHERE (m."external_id" = $1 AND c."uuid" = $2 AND m."direction" = 'O')` - -func checkMsgExists(b *backend, status courier.MsgStatus) (err error) { - var id int64 - - if status.ID() != courier.NilMsgID { - err = b.db.QueryRow(selectMsgIDForID, status.ID(), status.ChannelUUID()).Scan(&id) - } else if status.ExternalID() != "" { - err = b.db.QueryRow(selectMsgIDForExternalID, status.ExternalID(), status.ChannelUUID()).Scan(&id) - } else { - return fmt.Errorf("no id or external id for status update") - } - - if err == sql.ErrNoRows { - return courier.ErrMsgNotFound - } - return err -} - const sqlSelectMsg = ` SELECT org_id, diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 6366fb462..e69b8b882 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "log" "os" "strconv" "sync" @@ -106,114 +105,18 @@ WHERE msgs_msg.direction = 'O' ` -const sqlUpdateMsgByExternalID = ` -UPDATE msgs_msg SET - status = CASE - WHEN - :status = 'E' - THEN CASE - WHEN - error_count >= 2 OR status = 'F' - THEN - 'F' - ELSE - 'E' - END - ELSE - :status - END, - error_count = CASE - WHEN - :status = 'E' - THEN - error_count + 1 - ELSE - error_count - END, - next_attempt = CASE - WHEN - :status = 'E' - THEN - NOW() + (5 * (error_count+1) * interval '1 minutes') - ELSE - next_attempt - END, - failed_reason = CASE - WHEN - error_count >= 2 - THEN - 'E' - ELSE - failed_reason - END, - sent_on = CASE - WHEN - :status IN ('W', 'S', 'D') - THEN - COALESCE(sent_on, NOW()) - ELSE - NULL - END, - modified_on = :modified_on, - log_uuids = array_append(log_uuids, :log_uuid) -WHERE - msgs_msg.id = (SELECT msgs_msg.id FROM msgs_msg WHERE msgs_msg.external_id = :external_id AND msgs_msg.channel_id = :channel_id AND msgs_msg.direction = 'O' LIMIT 1) -RETURNING - msgs_msg.id -` - -// writeMsgStatusToDB writes the passed in msg status to our db -func writeMsgStatusToDB(ctx context.Context, b *backend, status *DBMsgStatus) error { - if status.ID() == courier.NilMsgID && status.ExternalID() == "" { - return fmt.Errorf("attempt to update msg status without id or external id") - } - - var rows *sqlx.Rows - var err error - - if status.ID() != courier.NilMsgID { - err = dbutil.BulkQuery(context.Background(), b.db, sqlUpdateMsgByID, []*DBMsgStatus{status}) - return err - } - - rows, err = b.db.NamedQueryContext(ctx, sqlUpdateMsgByExternalID, status) - if err != nil { - return err - } - defer rows.Close() - - // scan and read the id of the msg that was updated - if rows.Next() { - rows.Scan(&status.ID_) - } else { - return courier.ErrMsgNotFound - } - - return nil -} - func (b *backend) flushStatusFile(filename string, contents []byte) error { + ctx := context.Background() status := &DBMsgStatus{} err := json.Unmarshal(contents, status) if err != nil { - log.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) + logrus.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) os.Rename(filename, fmt.Sprintf("%s.error", filename)) return nil } // try to flush to our db - err = writeMsgStatusToDB(context.Background(), b, status) - - // not finding the message is ok for status updates - if err == courier.ErrMsgNotFound { - return nil - } - - // Ignore wrong status update for incoming messages - if err == courier.ErrWrongIncomingMsgStatus { - return nil - } - + _, err = writeMsgStatusesToDB(ctx, b.db, []*DBMsgStatus{status}) return err } @@ -296,9 +199,44 @@ func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWr } } +// tries to write all the message statuses to the database and spools those that fail func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuses []*DBMsgStatus) { log := logrus.WithField("comp", "status writer") + for _, batch := range utils.ChunkSlice(statuses, 1000) { + unresolved, err := writeMsgStatusesToDB(ctx, db, batch) + + // if we received an error, try again one at a time (in case it is one value hanging us up) + if err != nil { + for _, s := range batch { + _, err = writeMsgStatusesToDB(ctx, db, []*DBMsgStatus{s}) + if err != nil { + log := log.WithField("msg_id", s.ID()) + + if qerr := dbutil.AsQueryError(err); qerr != nil { + query, params := qerr.Query() + log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + } + + log.WithError(err).Error("error writing msg status") + + err := courier.WriteToSpool(spoolDir, "statuses", s) + if err != nil { + log.WithError(err).Error("error writing status to spool") // just have to log and move on + } + } + } + } else { + for _, s := range unresolved { + log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) + } + } + } +} + +// writes a batch of msg statuses to the database - messages that can't be resolved are returned and aren't considered +// an error +func writeMsgStatusesToDB(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStatus) ([]*DBMsgStatus, error) { // get the statuses which have external ID instead of a message ID missingID := make([]*DBMsgStatus, 0, 500) for _, s := range statuses { @@ -308,47 +246,29 @@ func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuse } // try to resolve channel ID + external ID to message IDs - for _, batch := range utils.ChunkSlice(missingID, 1000) { - if err := resolveStatusMsgIDs(ctx, db, batch); err != nil { - log.WithError(err).Error("error resolving msg ids") + if len(missingID) > 0 { + if err := resolveStatusMsgIDs(ctx, db, missingID); err != nil { + return nil, err } } - resolved := make([]*DBMsgStatus, 0, 500) + resolved := make([]*DBMsgStatus, 0, len(statuses)) + unresolved := make([]*DBMsgStatus, 0, len(statuses)) for _, s := range statuses { if s.ID_ != courier.NilMsgID { resolved = append(resolved, s) } else { - log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) + unresolved = append(unresolved, s) } } - for _, batch := range utils.ChunkSlice(resolved, 1000) { - err := dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, batch) - - // if we received an error, try again one at a time (in case it is one value hanging us up) - if err != nil { - for _, s := range batch { - err = dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, []*DBMsgStatus{s}) - if err != nil { - log := log.WithField("msg_id", s.ID()) - - if qerr := dbutil.AsQueryError(err); qerr != nil { - query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) - } - - log.WithError(err).Error("error writing msg status") - - err = courier.WriteToSpool(spoolDir, "statuses", s) - if err != nil { - logrus.WithField("comp", "status committer").WithError(err).Error("error writing status to spool") - } - } - } - } + err := dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, resolved) + if err != nil { + return nil, errors.Wrap(err, "error updating status") } + + return unresolved, nil } const sqlResolveStatusMsgIDs = ` diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index efd7bb911..26a0731d0 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -328,13 +328,6 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) err := h.Backend().WriteMsgStatus(ctx, event) - - // we don't know about this message, just tell them we ignored it - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData(fmt.Sprintf("message id: %s not found, ignored", status.ID))) - continue - } - if err != nil { return nil, nil, err } diff --git a/handlers/facebook/facebook.go b/handlers/facebook/facebook.go index 09bf1adc3..9eadd2dbb 100644 --- a/handlers/facebook/facebook.go +++ b/handlers/facebook/facebook.go @@ -411,13 +411,6 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w for _, mid := range msg.Delivery.MIDs { event := h.Backend().NewMsgStatusForExternalID(channel, mid, courier.MsgDelivered, clog) err := h.Backend().WriteMsgStatus(ctx, event) - - // we don't know about this message, just tell them we ignored it - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData("message not found, ignored")) - continue - } - if err != nil { return nil, err } diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 08d24fd05..9c2b827da 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -524,13 +524,6 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) err := h.Backend().WriteMsgStatus(ctx, event) - - // we don't know about this message, just tell them we ignored it - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData(fmt.Sprintf("message id: %s not found, ignored", status.ID))) - continue - } - if err != nil { return nil, nil, err } @@ -766,13 +759,6 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c for _, mid := range msg.Delivery.MIDs { event := h.Backend().NewMsgStatusForExternalID(channel, mid, courier.MsgDelivered, clog) err := h.Backend().WriteMsgStatus(ctx, event) - - // we don't know about this message, just tell them we ignored it - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData("message not found, ignored")) - continue - } - if err != nil { return nil, nil, err } diff --git a/handlers/infobip/infobip.go b/handlers/infobip/infobip.go index 75de6b126..08c06c13d 100644 --- a/handlers/infobip/infobip.go +++ b/handlers/infobip/infobip.go @@ -70,11 +70,6 @@ func (h *handler) statusMessage(ctx context.Context, channel courier.Channel, w // write our status status := h.Backend().NewMsgStatusForExternalID(channel, s.MessageID, msgStatus, clog) err := h.Backend().WriteMsgStatus(ctx, status) - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status update message id: %s, not found", s.MessageID))) - continue - } - if err != nil { return nil, err } diff --git a/handlers/responses.go b/handlers/responses.go index 2803f9e51..0a694e372 100644 --- a/handlers/responses.go +++ b/handlers/responses.go @@ -24,10 +24,6 @@ func WriteMsgsAndResponse(ctx context.Context, h courier.ChannelHandler, msgs [] // WriteMsgStatusAndResponse write the passed in status to our backend func WriteMsgStatusAndResponse(ctx context.Context, h courier.ChannelHandler, channel courier.Channel, status courier.MsgStatus, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { err := h.Server().Backend().WriteMsgStatus(ctx, status) - if err == courier.ErrMsgNotFound { - return nil, WriteAndLogRequestIgnored(ctx, h, channel, w, r, "msg not found, ignored") - } - if err != nil { return nil, err } diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index e4be241e9..b064828d2 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -271,13 +271,6 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) err := h.Backend().WriteMsgStatus(ctx, event) - - // we don't know about this message, just tell them we ignored it - if err == courier.ErrMsgNotFound { - data = append(data, courier.NewInfoData(fmt.Sprintf("message id: %s not found, ignored", status.ID))) - continue - } - if err != nil { return nil, err } diff --git a/msg.go b/msg.go index 7b81d6b44..3f91c0c12 100644 --- a/msg.go +++ b/msg.go @@ -3,7 +3,6 @@ package courier import ( "database/sql/driver" "encoding/json" - "errors" "strconv" "strings" "time" @@ -13,12 +12,6 @@ import ( "github.com/nyaruka/null/v2" ) -// ErrMsgNotFound is returned when trying to queue the status for a Msg that doesn't exit -var ErrMsgNotFound = errors.New("message not found") - -// ErrWrongIncomingMsgStatus use do ignore the status update if the DB raise this -var ErrWrongIncomingMsgStatus = errors.New("incoming messages can only be PENDING or HANDLED") - // MsgID is our typing of the db int type type MsgID null.Int64 From 4b723d65d89d6580ca8bde2976a2ba6a4953bffd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 13:19:37 -0500 Subject: [PATCH 047/170] Some renaming for clarity --- backend.go | 14 +-- backends/rapidpro/backend.go | 22 ++--- backends/rapidpro/backend_test.go | 88 +++++++++---------- backends/rapidpro/msg.go | 28 +++--- backends/rapidpro/status.go | 72 +++++++-------- handler.go | 4 +- handler_test.go | 6 +- handlers/africastalking/africastalking.go | 24 ++--- .../africastalking/africastalking_test.go | 4 +- handlers/arabiacell/arabiacell.go | 8 +- handlers/bandwidth/bandwidth.go | 16 ++-- handlers/bandwidth/bandwidth_test.go | 6 +- handlers/base.go | 2 +- handlers/bongolive/bongolive.go | 36 ++++---- handlers/bongolive/bongolive_test.go | 2 +- handlers/burstsms/burstsms.go | 18 ++-- handlers/clickatell/clickatell.go | 36 ++++---- handlers/clickatell/clickatell_test.go | 4 +- handlers/clickmobile/clickmobile.go | 6 +- handlers/clicksend/clicksend.go | 6 +- handlers/dart/dart.go | 16 ++-- handlers/dialog360/dialog360.go | 22 ++--- handlers/discord/discord.go | 16 ++-- handlers/discord/discord_test.go | 2 +- handlers/dmark/dmark.go | 20 ++--- handlers/dmark/dmark_test.go | 2 +- handlers/external/external.go | 16 ++-- handlers/external/external_test.go | 8 +- handlers/facebook/facebook.go | 10 +-- handlers/facebook/facebook_test.go | 2 +- handlers/facebookapp/facebookapp.go | 34 +++---- handlers/facebookapp/facebookapp_test.go | 2 +- handlers/firebase/firebase.go | 6 +- handlers/freshchat/freshchat.go | 6 +- handlers/generic.go | 4 +- handlers/globe/globe.go | 6 +- handlers/highconnection/highconnection.go | 28 +++--- .../highconnection/highconnection_test.go | 2 +- handlers/hormuud/hormuud.go | 6 +- handlers/i2sms/i2sms.go | 8 +- handlers/infobip/infobip.go | 22 ++--- handlers/infobip/infobip_test.go | 10 +-- handlers/jasmin/jasmin.go | 14 +-- handlers/jiochat/jiochat.go | 6 +- handlers/justcall/justcall.go | 18 ++-- handlers/kaleyra/kaleyra.go | 20 ++--- handlers/kannel/kannel.go | 24 ++--- handlers/kannel/kannel_test.go | 4 +- handlers/line/line.go | 6 +- handlers/m3tech/m3tech.go | 6 +- handlers/macrokiosk/macrokiosk.go | 18 ++-- handlers/macrokiosk/macrokiosk_test.go | 4 +- handlers/mblox/mblox.go | 22 ++--- handlers/mblox/mblox_test.go | 2 +- handlers/messagebird/messagebird.go | 26 +++--- handlers/messangi/messangi.go | 8 +- handlers/mtarget/mtarget.go | 22 ++--- handlers/mtn/mtn.go | 28 +++--- handlers/mtn/mtn_test.go | 6 +- handlers/nexmo/nexmo.go | 24 ++--- handlers/nexmo/nexmo_test.go | 10 +-- handlers/novo/novo.go | 8 +- handlers/playmobile/playmobile.go | 6 +- handlers/plivo/plivo.go | 20 ++--- handlers/plivo/plivo_test.go | 4 +- handlers/redrabbit/redrabbit.go | 6 +- handlers/responses.go | 6 +- handlers/rocketchat/rocketchat.go | 6 +- handlers/shaqodoon/shaqodoon.go | 6 +- handlers/slack/slack.go | 6 +- handlers/smscentral/smscentral.go | 6 +- handlers/start/start.go | 6 +- handlers/telegram/telegram.go | 16 ++-- handlers/telesom/telesom.go | 6 +- handlers/test.go | 4 +- handlers/thinq/thinq.go | 24 ++--- handlers/thinq/thinq_test.go | 2 +- handlers/twiml/twiml.go | 30 +++---- handlers/twiml/twiml_test.go | 42 ++++----- handlers/twitter/twitter.go | 6 +- handlers/viber/viber.go | 8 +- handlers/viber/viber_test.go | 2 +- handlers/vk/vk.go | 6 +- handlers/wavy/wavy.go | 38 ++++---- handlers/wavy/wavy_test.go | 4 +- handlers/wechat/wechat.go | 6 +- handlers/whatsapp/whatsapp.go | 22 ++--- handlers/yo/yo.go | 8 +- handlers/zenvia/zenvia.go | 22 ++--- log.go | 2 +- responses.go | 14 +-- sender.go | 12 +-- server.go | 2 +- status.go | 30 +++---- test/backend.go | 18 ++-- test/handler.go | 6 +- test/status.go | 6 +- 97 files changed, 667 insertions(+), 667 deletions(-) diff --git a/backend.go b/backend.go index 0a4da6906..44cbaedc0 100644 --- a/backend.go +++ b/backend.go @@ -47,14 +47,14 @@ type Backend interface { // WriteMsg writes the passed in message to our backend WriteMsg(context.Context, Msg, *ChannelLog) error - // NewMsgStatusForID creates a new Status object for the given message id - NewMsgStatusForID(Channel, MsgID, MsgStatusValue, *ChannelLog) MsgStatus + // NewStatusUpdate creates a new status update for the given message id + NewStatusUpdate(Channel, MsgID, MsgStatus, *ChannelLog) StatusUpdate - // NewMsgStatusForExternalID creates a new Status object for the given external id - NewMsgStatusForExternalID(Channel, string, MsgStatusValue, *ChannelLog) MsgStatus + // NewStatusUpdateByExternalID creates a new status update for the given external id + NewStatusUpdateByExternalID(Channel, string, MsgStatus, *ChannelLog) StatusUpdate - // WriteMsgStatus writes the passed in status update to our backend - WriteMsgStatus(context.Context, MsgStatus) error + // WriteStatusUpdate writes the passed in status update to our backend + WriteStatusUpdate(context.Context, StatusUpdate) error // NewChannelEvent creates a new channel event for the given channel and event type NewChannelEvent(Channel, ChannelEventType, urns.URN, *ChannelLog) ChannelEvent @@ -80,7 +80,7 @@ type Backend interface { // MarkOutgoingMsgComplete marks the passed in message as having been processed. Note this should be called even in the case // of errors during sending as it will manage the number of active workers per channel. The optional status parameter can be // used to determine any sort of deduping of msg sends - MarkOutgoingMsgComplete(context.Context, Msg, MsgStatus) + MarkOutgoingMsgComplete(context.Context, Msg, StatusUpdate) // SaveAttachment saves an attachment to backend storage SaveAttachment(context.Context, Channel, string, []byte, string) (string, error) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index fb0518dbc..19a6b3ee0 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -244,7 +244,7 @@ func (b *backend) ClearMsgSent(ctx context.Context, id courier.MsgID) error { } // MarkOutgoingMsgComplete marks the passed in message as having completed processing, freeing up a worker for that channel -func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, status courier.MsgStatus) { +func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, status courier.StatusUpdate) { rc := b.redisPool.Get() defer rc.Close() @@ -253,7 +253,7 @@ func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, queue.MarkComplete(rc, msgQueueName, dbMsg.workerToken) // mark as sent in redis as well if this was actually wired or sent - if status != nil && (status.Status() == courier.MsgSent || status.Status() == courier.MsgWired) { + if status != nil && (status.Status() == courier.MsgStatusSent || status.Status() == courier.MsgStatusWired) { dateKey := fmt.Sprintf(sentSetName, time.Now().UTC().Format("2006_01_02")) rc.Send("sadd", dateKey, msg.ID().String()) rc.Send("expire", dateKey, 60*60*24*2) @@ -281,17 +281,17 @@ func (b *backend) WriteMsg(ctx context.Context, m courier.Msg, clog *courier.Cha } // NewStatusUpdateForID creates a new Status object for the given message id -func (b *backend) NewMsgStatusForID(channel courier.Channel, id courier.MsgID, status courier.MsgStatusValue, clog *courier.ChannelLog) courier.MsgStatus { - return newMsgStatus(channel, id, "", status, clog) +func (b *backend) NewStatusUpdate(channel courier.Channel, id courier.MsgID, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { + return newStatusUpdate(channel, id, "", status, clog) } // NewStatusUpdateForID creates a new Status object for the given message id -func (b *backend) NewMsgStatusForExternalID(channel courier.Channel, externalID string, status courier.MsgStatusValue, clog *courier.ChannelLog) courier.MsgStatus { - return newMsgStatus(channel, courier.NilMsgID, externalID, status, clog) +func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { + return newStatusUpdate(channel, courier.NilMsgID, externalID, status, clog) } -// WriteMsgStatus writes the passed in MsgStatus to our store -func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) error { +// WriteStatusUpdate writes the passed in MsgStatus to our store +func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { if status.ID() == courier.NilMsgID && status.ExternalID() == "" { return errors.New("message status with no id or external id") } @@ -304,7 +304,7 @@ func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) } // if we have an id and are marking an outgoing msg as errored, then clear our sent flag - if status.ID() != courier.NilMsgID && status.Status() == courier.MsgErrored { + if status.ID() != courier.NilMsgID && status.Status() == courier.MsgStatusErrored { err := b.ClearMsgSent(ctx, status.ID()) if err != nil { logrus.WithError(err).WithField("msg", status.ID()).Error("error clearing sent flags") @@ -312,13 +312,13 @@ func (b *backend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) } // queue the status to written by the batch writer - b.statusWriter.Queue(status.(*DBMsgStatus)) + b.statusWriter.Queue(status.(*StatusUpdate)) return nil } // updateContactURN updates contact URN according to the old/new URNs from status -func (b *backend) updateContactURN(ctx context.Context, status courier.MsgStatus) error { +func (b *backend) updateContactURN(ctx context.Context, status courier.StatusUpdate) error { old, new := status.UpdatedURN() // retrieve channel diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 2cc81eeb1..ac060b788 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -467,22 +467,22 @@ func (ts *BackendTestSuite) TestMsgStatus() { channel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") now := time.Now().In(time.UTC) - updateStatusByID := func(id courier.MsgID, status courier.MsgStatusValue, newExtID string) *courier.ChannelLog { + updateStatusByID := func(id courier.MsgID, status courier.MsgStatus, newExtID string) *courier.ChannelLog { clog := courier.NewChannelLog(courier.ChannelLogTypeMsgStatus, channel, nil) - statusObj := ts.b.NewMsgStatusForID(channel, id, status, clog) + statusObj := ts.b.NewStatusUpdate(channel, id, status, clog) if newExtID != "" { statusObj.SetExternalID(newExtID) } - err := ts.b.WriteMsgStatus(ctx, statusObj) + err := ts.b.WriteStatusUpdate(ctx, statusObj) ts.NoError(err) time.Sleep(500 * time.Millisecond) // give committer time to write this return clog } - updateStatusByExtID := func(extID string, status courier.MsgStatusValue) *courier.ChannelLog { + updateStatusByExtID := func(extID string, status courier.MsgStatus) *courier.ChannelLog { clog := courier.NewChannelLog(courier.ChannelLogTypeMsgStatus, channel, nil) - statusObj := ts.b.NewMsgStatusForExternalID(channel, extID, status, clog) - err := ts.b.WriteMsgStatus(ctx, statusObj) + statusObj := ts.b.NewStatusUpdateByExternalID(channel, extID, status, clog) + err := ts.b.WriteStatusUpdate(ctx, statusObj) ts.NoError(err) time.Sleep(500 * time.Millisecond) // give committer time to write this return clog @@ -492,10 +492,10 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.b.db.MustExec(`UPDATE msgs_msg SET status = 'Q', sent_on = NULL WHERE id = $1`, 10001) // update to WIRED using id and provide new external ID - clog1 := updateStatusByID(10001, courier.MsgWired, "ext0") + clog1 := updateStatusByID(10001, courier.MsgStatusWired, "ext0") m := readMsgFromDB(ts.b, 10001) - ts.Equal(courier.MsgWired, m.Status_) + ts.Equal(courier.MsgStatusWired, m.Status_) ts.Equal(null.String("ext0"), m.ExternalID_) ts.True(m.ModifiedOn_.After(now)) ts.True(m.SentOn_.After(now)) @@ -505,37 +505,37 @@ func (ts *BackendTestSuite) TestMsgStatus() { sentOn := *m.SentOn_ // update to SENT using id - clog2 := updateStatusByID(10001, courier.MsgSent, "") + clog2 := updateStatusByID(10001, courier.MsgStatusSent, "") m = readMsgFromDB(ts.b, 10001) - ts.Equal(courier.MsgSent, m.Status_) + ts.Equal(courier.MsgStatusSent, m.Status_) ts.Equal(null.String("ext0"), m.ExternalID_) // no change ts.True(m.ModifiedOn_.After(now)) ts.True(m.SentOn_.Equal(sentOn)) // no change ts.Equal(pq.StringArray([]string{string(clog1.UUID()), string(clog2.UUID())}), m.LogUUIDs) // update to DELIVERED using id - clog3 := updateStatusByID(10001, courier.MsgDelivered, "") + clog3 := updateStatusByID(10001, courier.MsgStatusDelivered, "") m = readMsgFromDB(ts.b, 10001) - ts.Equal(m.Status_, courier.MsgDelivered) + ts.Equal(m.Status_, courier.MsgStatusDelivered) ts.True(m.ModifiedOn_.After(now)) ts.True(m.SentOn_.Equal(sentOn)) // no change ts.Equal(pq.StringArray([]string{string(clog1.UUID()), string(clog2.UUID()), string(clog3.UUID())}), m.LogUUIDs) // no change for incoming messages - updateStatusByID(10002, courier.MsgSent, "") + updateStatusByID(10002, courier.MsgStatusSent, "") m = readMsgFromDB(ts.b, 10002) - ts.Equal(courier.MsgPending, m.Status_) + ts.Equal(courier.MsgStatusPending, m.Status_) ts.Equal(m.ExternalID_, null.String("ext2")) ts.Equal(pq.StringArray(nil), m.LogUUIDs) // update to FAILED using external id - clog5 := updateStatusByExtID("ext1", courier.MsgFailed) + clog5 := updateStatusByExtID("ext1", courier.MsgStatusFailed) m = readMsgFromDB(ts.b, 10000) - ts.Equal(courier.MsgFailed, m.Status_) + ts.Equal(courier.MsgStatusFailed, m.Status_) ts.True(m.ModifiedOn_.After(now)) ts.Nil(m.SentOn_) ts.Equal(pq.StringArray([]string{string(clog5.UUID())}), m.LogUUIDs) @@ -544,20 +544,20 @@ func (ts *BackendTestSuite) TestMsgStatus() { time.Sleep(2 * time.Millisecond) // update to WIRED using external id - clog6 := updateStatusByExtID("ext1", courier.MsgWired) + clog6 := updateStatusByExtID("ext1", courier.MsgStatusWired) m = readMsgFromDB(ts.b, 10000) - ts.Equal(courier.MsgWired, m.Status_) + ts.Equal(courier.MsgStatusWired, m.Status_) ts.True(m.ModifiedOn_.After(now)) ts.True(m.SentOn_.After(now)) sentOn = *m.SentOn_ // update to SENT using external id - updateStatusByExtID("ext1", courier.MsgSent) + updateStatusByExtID("ext1", courier.MsgStatusSent) m = readMsgFromDB(ts.b, 10000) - ts.Equal(courier.MsgSent, m.Status_) + ts.Equal(courier.MsgStatusSent, m.Status_) ts.True(m.ModifiedOn_.After(now)) ts.True(m.SentOn_.Equal(sentOn)) // no change @@ -565,59 +565,59 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.b.db.MustExec(`UPDATE msgs_msg SET status = 'Q', sent_on = NULL WHERE id IN ($1, $2)`, 10002, 10001) // can skip WIRED and go straight to SENT or DELIVERED - updateStatusByExtID("ext1", courier.MsgSent) - updateStatusByID(10001, courier.MsgDelivered, "") + updateStatusByExtID("ext1", courier.MsgStatusSent) + updateStatusByID(10001, courier.MsgStatusDelivered, "") m = readMsgFromDB(ts.b, 10000) - ts.Equal(courier.MsgSent, m.Status_) + ts.Equal(courier.MsgStatusSent, m.Status_) ts.NotNil(m.SentOn_) m = readMsgFromDB(ts.b, 10001) - ts.Equal(courier.MsgDelivered, m.Status_) + ts.Equal(courier.MsgStatusDelivered, m.Status_) ts.NotNil(m.SentOn_) // reset our status to sent - status := ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgSent, clog6) - err := ts.b.WriteMsgStatus(ctx, status) + status := ts.b.NewStatusUpdateByExternalID(channel, "ext1", courier.MsgStatusSent, clog6) + err := ts.b.WriteStatusUpdate(ctx, status) ts.NoError(err) time.Sleep(time.Second) // error our msg now = time.Now().In(time.UTC) time.Sleep(2 * time.Millisecond) - status = ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgErrored, clog6) - err = ts.b.WriteMsgStatus(ctx, status) + status = ts.b.NewStatusUpdateByExternalID(channel, "ext1", courier.MsgStatusErrored, clog6) + err = ts.b.WriteStatusUpdate(ctx, status) ts.NoError(err) time.Sleep(time.Second) // give committer time to write this m = readMsgFromDB(ts.b, 10000) - ts.Equal(m.Status_, courier.MsgErrored) + ts.Equal(m.Status_, courier.MsgStatusErrored) ts.Equal(m.ErrorCount_, 1) ts.True(m.ModifiedOn_.After(now)) ts.True(m.NextAttempt_.After(now)) ts.Equal(null.NullString, m.FailedReason_) // second go - status = ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgErrored, clog6) - err = ts.b.WriteMsgStatus(ctx, status) + status = ts.b.NewStatusUpdateByExternalID(channel, "ext1", courier.MsgStatusErrored, clog6) + err = ts.b.WriteStatusUpdate(ctx, status) ts.NoError(err) time.Sleep(time.Second) // give committer time to write this m = readMsgFromDB(ts.b, 10000) - ts.Equal(m.Status_, courier.MsgErrored) + ts.Equal(m.Status_, courier.MsgStatusErrored) ts.Equal(m.ErrorCount_, 2) ts.Equal(null.NullString, m.FailedReason_) // third go - status = ts.b.NewMsgStatusForExternalID(channel, "ext1", courier.MsgErrored, clog6) - err = ts.b.WriteMsgStatus(ctx, status) + status = ts.b.NewStatusUpdateByExternalID(channel, "ext1", courier.MsgStatusErrored, clog6) + err = ts.b.WriteStatusUpdate(ctx, status) time.Sleep(time.Second) // give committer time to write this ts.NoError(err) m = readMsgFromDB(ts.b, 10000) - ts.Equal(m.Status_, courier.MsgFailed) + ts.Equal(m.Status_, courier.MsgStatusFailed) ts.Equal(m.ErrorCount_, 3) ts.Equal(null.String("E"), m.FailedReason_) @@ -629,10 +629,10 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) newURN, _ := urns.NewWhatsAppURN("5588776655") - status = ts.b.NewMsgStatusForID(channel, courier.MsgID(10000), courier.MsgSent, clog6) + status = ts.b.NewStatusUpdate(channel, courier.MsgID(10000), courier.MsgStatusSent, clog6) status.SetUpdatedURN(oldURN, newURN) - ts.NoError(ts.b.WriteMsgStatus(ctx, status)) + ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) contactURN, err := selectContactURN(tx, channel.OrgID_, newURN) @@ -650,10 +650,10 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) - status = ts.b.NewMsgStatusForID(channel, courier.MsgID(10007), courier.MsgSent, clog6) + status = ts.b.NewStatusUpdate(channel, courier.MsgID(10007), courier.MsgStatusSent, clog6) status.SetUpdatedURN(oldURN, newURN) - ts.NoError(ts.b.WriteMsgStatus(ctx, status)) + ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) newContactURN, _ := selectContactURN(tx, channel.OrgID_, newURN) @@ -672,10 +672,10 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) - status = ts.b.NewMsgStatusForID(channel, courier.MsgID(10007), courier.MsgSent, clog6) + status = ts.b.NewStatusUpdate(channel, courier.MsgID(10007), courier.MsgStatusSent, clog6) status.SetUpdatedURN(oldURN, newURN) - ts.NoError(ts.b.WriteMsgStatus(ctx, status)) + ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) oldContactURN, _ = selectContactURN(tx, channel.OrgID_, oldURN) @@ -819,7 +819,7 @@ func (ts *BackendTestSuite) TestOutgoingQueue() { ts.Equal(msg.Text(), "test message") // mark this message as dealt with - ts.b.MarkOutgoingMsgComplete(ctx, msg, ts.b.NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgWired, clog)) + ts.b.MarkOutgoingMsgComplete(ctx, msg, ts.b.NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusWired, clog)) // this message should now be marked as sent sent, err := ts.b.WasMsgSent(ctx, msg.ID()) @@ -838,7 +838,7 @@ func (ts *BackendTestSuite) TestOutgoingQueue() { ts.False(sent) // write an error for our original message - err = ts.b.WriteMsgStatus(ctx, ts.b.NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog)) + err = ts.b.WriteStatusUpdate(ctx, ts.b.NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog)) ts.NoError(err) // message should no longer be considered sent @@ -1082,7 +1082,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { ts.Equal(contactURN.ContactID, m.ContactID_) ts.Equal(contactURN.ID, m.ContactURNID_) ts.Equal(MsgIncoming, m.Direction_) - ts.Equal(courier.MsgPending, m.Status_) + ts.Equal(courier.MsgStatusPending, m.Status_) ts.False(m.HighPriority_) ts.Equal("ext123", m.ExternalID()) ts.Equal("test123", m.Text_) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index adf2b81a8..756d03a3e 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -110,7 +110,7 @@ func newMsg(direction MsgDirection, channel courier.Channel, urn urns.URN, text OrgID_: dbChannel.OrgID(), UUID_: courier.MsgUUID(uuids.New()), Direction_: direction, - Status_: courier.MsgPending, + Status_: courier.MsgStatusPending, Visibility_: MsgVisible, HighPriority_: false, Text_: text, @@ -281,19 +281,19 @@ func (b *backend) clearMsgSeen(rc redis.Conn, msg *DBMsg) { // DBMsg is our base struct to represent msgs both in our JSON and db representations type DBMsg struct { - OrgID_ OrgID `json:"org_id" db:"org_id"` - ID_ courier.MsgID `json:"id" db:"id"` - UUID_ courier.MsgUUID `json:"uuid" db:"uuid"` - Direction_ MsgDirection ` db:"direction"` - Status_ courier.MsgStatusValue ` db:"status"` - Visibility_ MsgVisibility ` db:"visibility"` - HighPriority_ bool `json:"high_priority" db:"high_priority"` - Text_ string `json:"text" db:"text"` - Attachments_ pq.StringArray `json:"attachments" db:"attachments"` - QuickReplies_ pq.StringArray `json:"quick_replies" db:"quick_replies"` - Locale_ null.String `json:"locale" db:"locale"` - ExternalID_ null.String ` db:"external_id"` - Metadata_ json.RawMessage `json:"metadata" db:"metadata"` + OrgID_ OrgID `json:"org_id" db:"org_id"` + ID_ courier.MsgID `json:"id" db:"id"` + UUID_ courier.MsgUUID `json:"uuid" db:"uuid"` + Direction_ MsgDirection ` db:"direction"` + Status_ courier.MsgStatus ` db:"status"` + Visibility_ MsgVisibility ` db:"visibility"` + HighPriority_ bool `json:"high_priority" db:"high_priority"` + Text_ string `json:"text" db:"text"` + Attachments_ pq.StringArray `json:"attachments" db:"attachments"` + QuickReplies_ pq.StringArray `json:"quick_replies" db:"quick_replies"` + Locale_ null.String `json:"locale" db:"locale"` + ExternalID_ null.String ` db:"external_id"` + Metadata_ json.RawMessage `json:"metadata" db:"metadata"` ChannelID_ courier.ChannelID ` db:"channel_id"` ContactID_ ContactID `json:"contact_id" db:"contact_id"` diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index e69b8b882..df1a82033 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -19,11 +19,11 @@ import ( "github.com/sirupsen/logrus" ) -// newMsgStatus creates a new DBMsgStatus for the passed in parameters -func newMsgStatus(channel courier.Channel, id courier.MsgID, externalID string, status courier.MsgStatusValue, clog *courier.ChannelLog) *DBMsgStatus { +// creates a new message status update +func newStatusUpdate(channel courier.Channel, id courier.MsgID, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) *StatusUpdate { dbChannel := channel.(*DBChannel) - return &DBMsgStatus{ + return &StatusUpdate{ ChannelUUID_: channel.UUID(), ChannelID_: dbChannel.ID(), ID_: id, @@ -107,7 +107,7 @@ WHERE func (b *backend) flushStatusFile(filename string, contents []byte) error { ctx := context.Background() - status := &DBMsgStatus{} + status := &StatusUpdate{} err := json.Unmarshal(contents, status) if err != nil { logrus.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) @@ -116,33 +116,33 @@ func (b *backend) flushStatusFile(filename string, contents []byte) error { } // try to flush to our db - _, err = writeMsgStatusesToDB(ctx, b.db, []*DBMsgStatus{status}) + _, err = writeStatusUpdatesToDB(ctx, b.db, []*StatusUpdate{status}) return err } //----------------------------------------------------------------------------- -// MsgStatusUpdate implementation +// StatusUpdate implementation //----------------------------------------------------------------------------- -// DBMsgStatus represents a status update on a message -type DBMsgStatus struct { +// StatusUpdate represents a status update on a message +type StatusUpdate struct { ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"` ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` ID_ courier.MsgID `json:"msg_id,omitempty" db:"msg_id"` OldURN_ urns.URN `json:"old_urn" db:"old_urn"` NewURN_ urns.URN `json:"new_urn" db:"new_urn"` ExternalID_ string `json:"external_id,omitempty" db:"external_id"` - Status_ courier.MsgStatusValue `json:"status" db:"status"` + Status_ courier.MsgStatus `json:"status" db:"status"` ModifiedOn_ time.Time `json:"modified_on" db:"modified_on"` LogUUID courier.ChannelLogUUID `json:"log_uuid" db:"log_uuid"` } -func (s *DBMsgStatus) EventID() int64 { return int64(s.ID_) } +func (s *StatusUpdate) EventID() int64 { return int64(s.ID_) } -func (s *DBMsgStatus) ChannelUUID() courier.ChannelUUID { return s.ChannelUUID_ } -func (s *DBMsgStatus) ID() courier.MsgID { return s.ID_ } +func (s *StatusUpdate) ChannelUUID() courier.ChannelUUID { return s.ChannelUUID_ } +func (s *StatusUpdate) ID() courier.MsgID { return s.ID_ } -func (s *DBMsgStatus) RowID() string { +func (s *StatusUpdate) RowID() string { if s.ID_ != courier.NilMsgID { return strconv.FormatInt(int64(s.ID_), 10) } else if s.ExternalID_ != "" { @@ -151,7 +151,7 @@ func (s *DBMsgStatus) RowID() string { return "" } -func (s *DBMsgStatus) SetUpdatedURN(old, new urns.URN) error { +func (s *StatusUpdate) SetUpdatedURN(old, new urns.URN) error { // check by nil URN if old == urns.NilURN || new == urns.NilURN { return errors.New("cannot update contact URN from/to nil URN") @@ -168,48 +168,48 @@ func (s *DBMsgStatus) SetUpdatedURN(old, new urns.URN) error { s.NewURN_ = new return nil } -func (s *DBMsgStatus) UpdatedURN() (urns.URN, urns.URN) { +func (s *StatusUpdate) UpdatedURN() (urns.URN, urns.URN) { return s.OldURN_, s.NewURN_ } -func (s *DBMsgStatus) HasUpdatedURN() bool { +func (s *StatusUpdate) HasUpdatedURN() bool { if s.OldURN_ != urns.NilURN && s.NewURN_ != urns.NilURN { return true } return false } -func (s *DBMsgStatus) ExternalID() string { return s.ExternalID_ } -func (s *DBMsgStatus) SetExternalID(id string) { s.ExternalID_ = id } +func (s *StatusUpdate) ExternalID() string { return s.ExternalID_ } +func (s *StatusUpdate) SetExternalID(id string) { s.ExternalID_ = id } -func (s *DBMsgStatus) Status() courier.MsgStatusValue { return s.Status_ } -func (s *DBMsgStatus) SetStatus(status courier.MsgStatusValue) { s.Status_ = status } +func (s *StatusUpdate) Status() courier.MsgStatus { return s.Status_ } +func (s *StatusUpdate) SetStatus(status courier.MsgStatus) { s.Status_ = status } type StatusWriter struct { - *syncx.Batcher[*DBMsgStatus] + *syncx.Batcher[*StatusUpdate] } func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWriter { return &StatusWriter{ - Batcher: syncx.NewBatcher[*DBMsgStatus](func(batch []*DBMsgStatus) { + Batcher: syncx.NewBatcher[*StatusUpdate](func(batch []*StatusUpdate) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - writeMsgStatuses(ctx, db, spoolDir, batch) + writeStatuseUpdates(ctx, db, spoolDir, batch) }, time.Millisecond*500, 1000, wg), } } // tries to write all the message statuses to the database and spools those that fail -func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuses []*DBMsgStatus) { +func writeStatuseUpdates(ctx context.Context, db *sqlx.DB, spoolDir string, statuses []*StatusUpdate) { log := logrus.WithField("comp", "status writer") for _, batch := range utils.ChunkSlice(statuses, 1000) { - unresolved, err := writeMsgStatusesToDB(ctx, db, batch) + unresolved, err := writeStatusUpdatesToDB(ctx, db, batch) // if we received an error, try again one at a time (in case it is one value hanging us up) if err != nil { for _, s := range batch { - _, err = writeMsgStatusesToDB(ctx, db, []*DBMsgStatus{s}) + _, err = writeStatusUpdatesToDB(ctx, db, []*StatusUpdate{s}) if err != nil { log := log.WithField("msg_id", s.ID()) @@ -234,11 +234,11 @@ func writeMsgStatuses(ctx context.Context, db *sqlx.DB, spoolDir string, statuse } } -// writes a batch of msg statuses to the database - messages that can't be resolved are returned and aren't considered -// an error -func writeMsgStatusesToDB(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStatus) ([]*DBMsgStatus, error) { +// writes a batch of msg status updates to the database - messages that can't be resolved are returned and aren't +// considered an error +func writeStatusUpdatesToDB(ctx context.Context, db *sqlx.DB, statuses []*StatusUpdate) ([]*StatusUpdate, error) { // get the statuses which have external ID instead of a message ID - missingID := make([]*DBMsgStatus, 0, 500) + missingID := make([]*StatusUpdate, 0, 500) for _, s := range statuses { if s.ID_ == courier.NilMsgID { missingID = append(missingID, s) @@ -247,13 +247,13 @@ func writeMsgStatusesToDB(ctx context.Context, db *sqlx.DB, statuses []*DBMsgSta // try to resolve channel ID + external ID to message IDs if len(missingID) > 0 { - if err := resolveStatusMsgIDs(ctx, db, missingID); err != nil { + if err := resolveStatusUpdateMsgIDs(ctx, db, missingID); err != nil { return nil, err } } - resolved := make([]*DBMsgStatus, 0, len(statuses)) - unresolved := make([]*DBMsgStatus, 0, len(statuses)) + resolved := make([]*StatusUpdate, 0, len(statuses)) + unresolved := make([]*StatusUpdate, 0, len(statuses)) for _, s := range statuses { if s.ID_ != courier.NilMsgID { @@ -276,15 +276,15 @@ SELECT id, channel_id, external_id FROM msgs_msg WHERE (channel_id, external_id) IN (VALUES(CAST(:channel_id AS int), :external_id))` -// resolveStatusMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel/external ID pair +// resolveStatusUpdateMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel id + external id pair // found for a status, that status will be left with a nil msg ID. -func resolveStatusMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*DBMsgStatus) error { +func resolveStatusUpdateMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*StatusUpdate) error { // create a mapping of channel id + external id -> status type ext struct { channelID courier.ChannelID externalID string } - statusesByExt := make(map[ext]*DBMsgStatus, len(statuses)) + statusesByExt := make(map[ext]*StatusUpdate, len(statuses)) for _, s := range statuses { statusesByExt[ext{s.ChannelID_, s.ExternalID_}] = s } diff --git a/handler.go b/handler.go index 732cb1d50..670ca7073 100644 --- a/handler.go +++ b/handler.go @@ -27,9 +27,9 @@ type ChannelHandler interface { UseChannelRouteUUID() bool RedactValues(Channel) []string GetChannel(context.Context, *http.Request) (Channel, error) - Send(context.Context, Msg, *ChannelLog) (MsgStatus, error) + Send(context.Context, Msg, *ChannelLog) (StatusUpdate, error) - WriteStatusSuccessResponse(context.Context, http.ResponseWriter, []MsgStatus) error + WriteStatusSuccessResponse(context.Context, http.ResponseWriter, []StatusUpdate) error WriteMsgSuccessResponse(context.Context, http.ResponseWriter, []Msg) error WriteRequestError(context.Context, http.ResponseWriter, error) error WriteRequestIgnored(context.Context, http.ResponseWriter, string) error diff --git a/handler_test.go b/handler_test.go index 1a0186088..d97af2703 100644 --- a/handler_test.go +++ b/handler_test.go @@ -53,7 +53,7 @@ func TestHandling(t *testing.T) { // message should have failed because we don't have a registered handler assert.Equal(1, len(mb.WrittenMsgStatuses())) assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].ID()) - assert.Equal(courier.MsgFailed, mb.WrittenMsgStatuses()[0].Status()) + assert.Equal(courier.MsgStatusFailed, mb.WrittenMsgStatuses()[0].Status()) assert.Equal(1, len(mb.WrittenChannelLogs())) mb.Reset() @@ -69,7 +69,7 @@ func TestHandling(t *testing.T) { assert.Len(mb.WrittenMsgStatuses(), 1) status := mb.WrittenMsgStatuses()[0] assert.Equal(msg.ID(), status.ID()) - assert.Equal(courier.MsgSent, status.Status()) + assert.Equal(courier.MsgStatusSent, status.Status()) assert.Len(mb.WrittenChannelLogs(), 1) clog := mb.WrittenChannelLogs()[0] @@ -90,7 +90,7 @@ func TestHandling(t *testing.T) { // message should be marked as wired assert.Equal(1, len(mb.WrittenMsgStatuses())) assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].ID()) - assert.Equal(courier.MsgWired, mb.WrittenMsgStatuses()[0].Status()) + assert.Equal(courier.MsgStatusWired, mb.WrittenMsgStatuses()[0].Status()) // try to receive a message instead resp, err := http.Get("http://localhost:8080/c/mck/e4bb1578-29da-4fa5-a214-9da19dd24230/receive") diff --git a/handlers/africastalking/africastalking.go b/handlers/africastalking/africastalking.go index 2799bfa60..b73a8e381 100644 --- a/handlers/africastalking/africastalking.go +++ b/handlers/africastalking/africastalking.go @@ -84,13 +84,13 @@ type statusForm struct { Status string `validate:"required" name:"status"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "Success": courier.MsgDelivered, - "Sent": courier.MsgSent, - "Buffered": courier.MsgSent, - "Rejected": courier.MsgFailed, - "Failed": courier.MsgFailed, - "Expired": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "Success": courier.MsgStatusDelivered, + "Sent": courier.MsgStatusSent, + "Buffered": courier.MsgStatusSent, + "Rejected": courier.MsgStatusFailed, + "Failed": courier.MsgStatusFailed, + "Expired": courier.MsgStatusFailed, } // receiveStatus is our HTTP handler function for status updates @@ -109,12 +109,12 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.ID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.ID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { isSharedStr := msg.Channel().ConfigForKey(configIsShared, false) isShared, _ := isSharedStr.(bool) @@ -128,7 +128,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no API key set for AT channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // build our request form := url.Values{ @@ -158,13 +158,13 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // was this request successful? msgStatus, _ := jsonparser.GetString(respBody, "SMSMessageData", "Recipients", "[0]", "status") if msgStatus != "Success" { - status.SetStatus(courier.MsgErrored) + status.SetStatus(courier.MsgStatusErrored) return status, nil } // grab the external id if we can externalID, _ := jsonparser.GetString(respBody, "SMSMessageData", "Recipients", "[0]", "messageId") - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) return status, nil diff --git a/handlers/africastalking/africastalking_test.go b/handlers/africastalking/africastalking_test.go index 744afb996..a32c0d3f1 100644 --- a/handlers/africastalking/africastalking_test.go +++ b/handlers/africastalking/africastalking_test.go @@ -90,7 +90,7 @@ var testCases = []ChannelHandleTestCase{ Data: "id=ATXid_dda018a640edfcc5d2ce455de3e4a6e7&status=Success", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Status Expired", @@ -98,7 +98,7 @@ var testCases = []ChannelHandleTestCase{ Data: "id=ATXid_dda018a640edfcc5d2ce455de3e4a6e7&status=Expired", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, } diff --git a/handlers/arabiacell/arabiacell.go b/handlers/arabiacell/arabiacell.go index 8cc220c12..96c052fb8 100644 --- a/handlers/arabiacell/arabiacell.go +++ b/handlers/arabiacell/arabiacell.go @@ -56,7 +56,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for AC channel") @@ -77,7 +77,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no charging_level set for AC channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { form := url.Values{ "userName": []string{username}, @@ -111,10 +111,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // we always get 204 on success if response.Code == "204" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(response.MessageID) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.Error(courier.ErrorResponseStatusCode()) break } diff --git a/handlers/bandwidth/bandwidth.go b/handlers/bandwidth/bandwidth.go index 1fec3375d..d84c8d435 100644 --- a/handlers/bandwidth/bandwidth.go +++ b/handlers/bandwidth/bandwidth.go @@ -113,10 +113,10 @@ type moStatusData struct { } `json:"message" validate:"required"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "message-sending": courier.MsgSent, - "message-delivered": courier.MsgDelivered, - "message-failed": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "message-sending": courier.MsgStatusSent, + "message-delivered": courier.MsgStatusDelivered, + "message-failed": courier.MsgStatusFailed, } // receiveMessage is our HTTP handler function for incoming messages @@ -153,7 +153,7 @@ func (h *handler) statusMessage(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, statusPayload.Message.ID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, statusPayload.Message.ID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -172,7 +172,7 @@ type mtResponse struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for BW channel") @@ -193,7 +193,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no application ID set for BW channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) msgParts := make([]string, 0) if msg.Text() != "" { @@ -239,7 +239,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) if response.ID == "" { clog.Error(courier.ErrorResponseValueMissing("id")) } else { diff --git a/handlers/bandwidth/bandwidth_test.go b/handlers/bandwidth/bandwidth_test.go index cb3ad5c86..5bf301725 100644 --- a/handlers/bandwidth/bandwidth_test.go +++ b/handlers/bandwidth/bandwidth_test.go @@ -217,7 +217,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusSent, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Status delivered", @@ -225,7 +225,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusDelivered, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Status delivered", @@ -234,7 +234,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("4432", "forbidden to country")}, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, } diff --git a/handlers/base.go b/handlers/base.go index e63a9fa86..f7eb81a4b 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -88,7 +88,7 @@ func (h *BaseHandler) GetChannel(ctx context.Context, r *http.Request) (courier. } // WriteStatusSuccessResponse writes a success response for the statuses -func (h *BaseHandler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.MsgStatus) error { +func (h *BaseHandler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { return courier.WriteStatusSuccess(w, statuses) } diff --git a/handlers/bongolive/bongolive.go b/handlers/bongolive/bongolive.go index 6df8767c7..b769a55b7 100644 --- a/handlers/bongolive/bongolive.go +++ b/handlers/bongolive/bongolive.go @@ -39,18 +39,18 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var statusMapping = map[int]courier.MsgStatusValue{ - 1: courier.MsgDelivered, - 2: courier.MsgSent, - 3: courier.MsgErrored, - 4: courier.MsgErrored, - 5: courier.MsgErrored, - 6: courier.MsgErrored, - 7: courier.MsgErrored, - 8: courier.MsgSent, - 9: courier.MsgErrored, - 10: courier.MsgErrored, - 11: courier.MsgErrored, +var statusMapping = map[int]courier.MsgStatus{ + 1: courier.MsgStatusDelivered, + 2: courier.MsgStatusSent, + 3: courier.MsgStatusErrored, + 4: courier.MsgStatusErrored, + 5: courier.MsgStatusErrored, + 6: courier.MsgStatusErrored, + 7: courier.MsgStatusErrored, + 8: courier.MsgStatusSent, + 9: courier.MsgStatusErrored, + 10: courier.MsgStatusErrored, + 11: courier.MsgStatusErrored, } type moForm struct { @@ -85,7 +85,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.DLRID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.DLRID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -109,7 +109,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr return writeBongoLiveResponse(w) } -func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.MsgStatus) error { +func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { return writeBongoLiveResponse(w) } @@ -126,7 +126,7 @@ func writeBongoLiveResponse(w http.ResponseWriter) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for %s channel", msg.Channel().ChannelType()) @@ -137,7 +137,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for %s channel", msg.Channel().ChannelType()) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { form := url.Values{ @@ -173,12 +173,12 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // was this request successful? msgStatus, _ := jsonparser.GetString(respBody, "results", "[0]", "status") if msgStatus != "0" { - status.SetStatus(courier.MsgErrored) + status.SetStatus(courier.MsgStatusErrored) return status, nil } // grab the external id if we can externalID, _ := jsonparser.GetString(respBody, "results", "[0]", "msgid") - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } diff --git a/handlers/bongolive/bongolive_test.go b/handlers/bongolive/bongolive_test.go index 251608aad..b5ac82de8 100644 --- a/handlers/bongolive/bongolive_test.go +++ b/handlers/bongolive/bongolive_test.go @@ -63,7 +63,7 @@ var testCases = []ChannelHandleTestCase{ Data: "msgtype=5&dlrid=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: "", - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Invalid Msg Type", diff --git a/handlers/burstsms/burstsms.go b/handlers/burstsms/burstsms.go index b33faf1d4..3f152f4fc 100644 --- a/handlers/burstsms/burstsms.go +++ b/handlers/burstsms/burstsms.go @@ -16,11 +16,11 @@ import ( var ( sendURL = "https://api.transmitsms.com/send-sms.json" maxMsgLength = 612 - statusMap = map[string]courier.MsgStatusValue{ - "delivered": courier.MsgDelivered, - "pending": courier.MsgSent, - "soft-bounce": courier.MsgErrored, - "hard-bounce": courier.MsgFailed, + statusMap = map[string]courier.MsgStatus{ + "delivered": courier.MsgStatusDelivered, + "pending": courier.MsgStatusSent, + "soft-bounce": courier.MsgStatusErrored, + "hard-bounce": courier.MsgStatusFailed, } ) @@ -57,7 +57,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for BS channel") @@ -68,7 +68,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for BS channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { form := url.Values{ "to": []string{strings.TrimLeft(msg.URN().Path(), "+")}, @@ -98,10 +98,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } if response.MessageID != 0 { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(fmt.Sprintf("%d", response.MessageID)) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.Error(courier.ErrorResponseValueMissing("message_id")) break } diff --git a/handlers/clickatell/clickatell.go b/handlers/clickatell/clickatell.go index 64c229acf..4a41290a4 100644 --- a/handlers/clickatell/clickatell.go +++ b/handlers/clickatell/clickatell.go @@ -47,20 +47,20 @@ type statusPayload struct { StatusCode int `name:"statusCode"` } -var statusMapping = map[int]courier.MsgStatusValue{ - 1: courier.MsgFailed, // incorrect msg id - 2: courier.MsgWired, // queued - 3: courier.MsgSent, // delivered to upstream gateway - 4: courier.MsgSent, // delivered to upstream gateway - 5: courier.MsgFailed, // error in message - 6: courier.MsgFailed, // terminated by user - 7: courier.MsgFailed, // error delivering - 8: courier.MsgWired, // msg received - 9: courier.MsgFailed, // error routing - 10: courier.MsgFailed, // expired - 11: courier.MsgWired, // delayed but queued - 12: courier.MsgFailed, // out of credit - 14: courier.MsgFailed, // too long +var statusMapping = map[int]courier.MsgStatus{ + 1: courier.MsgStatusFailed, // incorrect msg id + 2: courier.MsgStatusWired, // queued + 3: courier.MsgStatusSent, // delivered to upstream gateway + 4: courier.MsgStatusSent, // delivered to upstream gateway + 5: courier.MsgStatusFailed, // error in message + 6: courier.MsgStatusFailed, // terminated by user + 7: courier.MsgStatusFailed, // error delivering + 8: courier.MsgStatusWired, // msg received + 9: courier.MsgStatusFailed, // error routing + 10: courier.MsgStatusFailed, // expired + 11: courier.MsgStatusWired, // delayed but queued + 12: courier.MsgStatusFailed, // out of credit + 14: courier.MsgStatusFailed, // too long } // receiveStatus is our HTTP handler function for status updates @@ -77,7 +77,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.MessageID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.MessageID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -154,13 +154,13 @@ func decodeUTF16BE(b []byte) (string, error) { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(courier.ConfigAPIKey, "") if apiKey == "" { return nil, fmt.Errorf("no api_key set for CT channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { form := url.Values{ @@ -190,7 +190,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann if err != nil { clog.Error(courier.ErrorResponseValueMissing("apiMessageId")) } else { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } } diff --git a/handlers/clickatell/clickatell_test.go b/handlers/clickatell/clickatell_test.go index ac862d965..f7b497ad3 100644 --- a/handlers/clickatell/clickatell_test.go +++ b/handlers/clickatell/clickatell_test.go @@ -191,7 +191,7 @@ var testCases = []ChannelHandleTestCase{ Data: `{"messageId": "msg1", "statusCode": 5}`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, { Label: "Valid Delivered status report", @@ -199,7 +199,7 @@ var testCases = []ChannelHandleTestCase{ Data: `{"messageId": "msg1", "statusCode": 4}`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Unexpected status report", diff --git a/handlers/clickmobile/clickmobile.go b/handlers/clickmobile/clickmobile.go index f58df01fa..bf63da600 100644 --- a/handlers/clickmobile/clickmobile.go +++ b/handlers/clickmobile/clickmobile.go @@ -98,7 +98,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for CM channel") @@ -121,7 +121,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann cmSendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, sendURL) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { @@ -163,7 +163,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann responseCode, _ := jsonparser.GetString(respBody, "code") if responseCode == "000" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } else { clog.Error(courier.ErrorResponseValueUnexpected("code", "000")) } diff --git a/handlers/clicksend/clicksend.go b/handlers/clicksend/clicksend.go index 1461fba1c..77883d17e 100644 --- a/handlers/clicksend/clicksend.go +++ b/handlers/clicksend/clicksend.go @@ -61,7 +61,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("Missing 'username' config for CS channel") @@ -72,7 +72,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("Missing 'password' config for CS channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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{} @@ -113,7 +113,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } status.SetExternalID(id) - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/dart/dart.go b/handlers/dart/dart.go index 0ae09b4ee..8984166b1 100644 --- a/handlers/dart/dart.go +++ b/handlers/dart/dart.go @@ -112,13 +112,13 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("parsing failed: status '%s' is not an integer", form.Status)) } - msgStatus := courier.MsgSent + msgStatus := courier.MsgStatusSent if statusInt >= 10 && statusInt <= 12 { - msgStatus = courier.MsgDelivered + msgStatus = courier.MsgStatusDelivered } if statusInt > 20 { - msgStatus = courier.MsgFailed + msgStatus = courier.MsgStatusFailed } msgID, err := strconv.ParseInt(strings.Split(form.MessageID, ".")[0], 10, 64) @@ -127,12 +127,12 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForID(channel, courier.MsgID(msgID), msgStatus, clog) + status := h.Backend().NewStatusUpdate(channel, courier.MsgID(msgID), msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // DartMedia expects "000" from a message receive request -func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.MsgStatus) error { +func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { w.WriteHeader(200) _, err := fmt.Fprint(w, "000") return err @@ -146,7 +146,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for %s channel", msg.Channel().ChannelType()) @@ -157,7 +157,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for %s channel", msg.Channel().ChannelType()) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), h.maxLength) for i, part := range parts { form := url.Values{ @@ -197,7 +197,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index 26a0731d0..eed2bcea7 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -47,11 +47,11 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var waStatusMapping = map[string]courier.MsgStatusValue{ - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, - "read": courier.MsgDelivered, - "failed": courier.MsgFailed, +var waStatusMapping = map[string]courier.MsgStatus{ + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, } var waIgnoreStatuses = map[string]bool{ @@ -326,8 +326,8 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri clog.Error(courier.ErrorExternal(strconv.Itoa(statusError.Code), statusError.Title)) } - event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) - err := h.Backend().WriteMsgStatus(ctx, event) + event := h.Backend().NewStatusUpdateByExternalID(channel, status.ID, msgStatus, clog) + err := h.Backend().WriteStatusUpdate(ctx, event) if err != nil { return nil, nil, err } @@ -507,7 +507,7 @@ type wacMTResponse struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { conn := h.Backend().RedisPool().Get() defer conn.Close() @@ -525,7 +525,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } sendURL, _ := url.Parse("/messages") - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) hasCaption := false @@ -805,7 +805,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func requestD3C(payload wacMTPayload, accessToken string, status courier.MsgStatus, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func requestD3C(payload wacMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -838,7 +838,7 @@ func requestD3C(payload wacMTPayload, accessToken string, status courier.MsgStat status.SetExternalID(externalID) } // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/discord/discord.go b/handlers/discord/discord.go index befde7c58..a750d3331 100644 --- a/handlers/discord/discord.go +++ b/handlers/discord/discord.go @@ -117,10 +117,10 @@ type statusForm struct { ID int64 `name:"id" validate:"required"` } -var statusMappings = map[string]courier.MsgStatusValue{ - "failed": courier.MsgFailed, - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, +var statusMappings = map[string]courier.MsgStatus{ + "failed": courier.MsgStatusFailed, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, } // receiveStatus is our HTTP handler function for status updates @@ -138,12 +138,12 @@ func (h *handler) receiveStatus(ctx context.Context, statusString string, channe } // write our status - status := h.Backend().NewMsgStatusForID(channel, courier.MsgID(form.ID), msgStatus, clog) + status := h.Backend().NewStatusUpdate(channel, courier.MsgID(form.ID), msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { sendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, "") if sendURL == "" { return nil, fmt.Errorf("no send url set for DS channel") @@ -154,7 +154,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // sendBody := msg.Channel().StringConfigForKey(courier.ConfigSendBody, "") contentTypeHeader := jsonMimeTypeType - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) attachmentURLs := []string{} for _, attachment := range msg.Attachments() { _, attachmentURL := handlers.SplitAttachment(attachment) @@ -203,7 +203,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // If we don't have an error, set the message as wired and move on - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/discord/discord_test.go b/handlers/discord/discord_test.go index 3ce02df0a..a90796867 100644 --- a/handlers/discord/discord_test.go +++ b/handlers/discord/discord_test.go @@ -67,7 +67,7 @@ var testCases = []ChannelHandleTestCase{ Data: `id=12345`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Message Sent Handler Garbage", diff --git a/handlers/dmark/dmark.go b/handlers/dmark/dmark.go index aa15077b6..53d8d09df 100644 --- a/handlers/dmark/dmark.go +++ b/handlers/dmark/dmark.go @@ -78,12 +78,12 @@ type statusForm struct { Status string `validate:"required" name:"status"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "1": courier.MsgDelivered, - "2": courier.MsgErrored, - "4": courier.MsgSent, - "8": courier.MsgSent, - "16": courier.MsgErrored, +var statusMapping = map[string]courier.MsgStatus{ + "1": courier.MsgStatusDelivered, + "2": courier.MsgStatusErrored, + "4": courier.MsgStatusSent, + "8": courier.MsgStatusSent, + "16": courier.MsgStatusErrored, } // receiveStatus is our HTTP handler function for status updates @@ -101,12 +101,12 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.ID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.ID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // get our authentication token auth := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if auth == "" { @@ -116,7 +116,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) dlrURL := fmt.Sprintf("https://%s%s%s/status?id=%s&status=%%s", callbackDomain, "/c/dk/", msg.Channel().UUID(), msg.ID().String()) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) for i, part := range parts { form := url.Values{ @@ -152,7 +152,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/dmark/dmark_test.go b/handlers/dmark/dmark_test.go index 4ab893e66..65b2060c8 100644 --- a/handlers/dmark/dmark_test.go +++ b/handlers/dmark/dmark_test.go @@ -78,7 +78,7 @@ var testCases = []ChannelHandleTestCase{ Data: "id=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, } diff --git a/handlers/external/external.go b/handlers/external/external.go index 0ec48d03c..fe1967795 100644 --- a/handlers/external/external.go +++ b/handlers/external/external.go @@ -245,10 +245,10 @@ type statusForm struct { ID int64 `name:"id" validate:"required"` } -var statusMappings = map[string]courier.MsgStatusValue{ - "failed": courier.MsgFailed, - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, +var statusMappings = map[string]courier.MsgStatus{ + "failed": courier.MsgStatusFailed, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, } // receiveStatus is our HTTP handler function for status updates @@ -266,12 +266,12 @@ func (h *handler) receiveStatus(ctx context.Context, statusString string, channe } // write our status - status := h.Backend().NewMsgStatusForID(channel, courier.MsgID(form.ID), msgStatus, clog) + status := h.Backend().NewStatusUpdate(channel, courier.MsgID(form.ID), msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { channel := msg.Channel() sendURL := channel.StringConfigForKey(courier.ConfigSendURL, "") @@ -291,7 +291,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann contentTypeHeader = contentType } - status := h.Backend().NewMsgStatusForID(channel, msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(channel, msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(channel, handlers.GetTextAndAttachments(msg), sendMaxLength) for i, part := range parts { // build our request @@ -370,7 +370,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } if responseCheck == "" || strings.Contains(string(respBody), responseCheck) { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } else { clog.Error(courier.ErrorResponseUnexpected(responseCheck)) } diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index 12146607c..eb014ffe1 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -128,7 +128,7 @@ var handleTestCases = []ChannelHandleTestCase{ URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/failed/?id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, { Label: "Invalid Status", @@ -141,7 +141,7 @@ var handleTestCases = []ChannelHandleTestCase{ URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/sent/?id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Delivered Valid", @@ -149,7 +149,7 @@ var handleTestCases = []ChannelHandleTestCase{ Data: "nothing", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Delivered Valid Post", @@ -157,7 +157,7 @@ var handleTestCases = []ChannelHandleTestCase{ Data: "id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Stopped Event", diff --git a/handlers/facebook/facebook.go b/handlers/facebook/facebook.go index 9eadd2dbb..84f41ed0f 100644 --- a/handlers/facebook/facebook.go +++ b/handlers/facebook/facebook.go @@ -409,8 +409,8 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } else if msg.Delivery != nil { // this is a delivery report for _, mid := range msg.Delivery.MIDs { - event := h.Backend().NewMsgStatusForExternalID(channel, mid, courier.MsgDelivered, clog) - err := h.Backend().WriteMsgStatus(ctx, event) + event := h.Backend().NewStatusUpdateByExternalID(channel, mid, courier.MsgStatusDelivered, clog) + err := h.Backend().WriteStatusUpdate(ctx, event) if err != nil { return nil, err } @@ -471,7 +471,7 @@ type mtQuickReply struct { ContentType string `json:"content_type"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if accessToken == "" { @@ -503,7 +503,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann query.Set("access_token", accessToken) msgURL.RawQuery = query.Encode() - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) msgParts := make([]string, 0) if msg.Text() != "" { @@ -606,7 +606,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/facebook/facebook_test.go b/handlers/facebook/facebook_test.go index 62e7b6195..b1b4d634d 100644 --- a/handlers/facebook/facebook_test.go +++ b/handlers/facebook/facebook_test.go @@ -547,7 +547,7 @@ var testCases = []ChannelHandleTestCase{ Data: dlr, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", }, { diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 9c2b827da..7ba0afbe3 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -59,11 +59,11 @@ const ( payloadKey = "payload" ) -var waStatusMapping = map[string]courier.MsgStatusValue{ - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, - "read": courier.MsgDelivered, - "failed": courier.MsgFailed, +var waStatusMapping = map[string]courier.MsgStatus{ + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, } var waIgnoreStatuses = map[string]bool{ @@ -522,8 +522,8 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri clog.Error(courier.ErrorExternal(strconv.Itoa(statusError.Code), statusError.Title)) } - event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) - err := h.Backend().WriteMsgStatus(ctx, event) + event := h.Backend().NewStatusUpdateByExternalID(channel, status.ID, msgStatus, clog) + err := h.Backend().WriteStatusUpdate(ctx, event) if err != nil { return nil, nil, err } @@ -757,8 +757,8 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } else if msg.Delivery != nil { // this is a delivery report for _, mid := range msg.Delivery.MIDs { - event := h.Backend().NewMsgStatusForExternalID(channel, mid, courier.MsgDelivered, clog) - err := h.Backend().WriteMsgStatus(ctx, event) + event := h.Backend().NewStatusUpdateByExternalID(channel, mid, courier.MsgStatusDelivered, clog) + err := h.Backend().WriteStatusUpdate(ctx, event) if err != nil { return nil, nil, err } @@ -819,7 +819,7 @@ type mtQuickReply struct { ContentType string `json:"content_type"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { if msg.Channel().ChannelType() == "FBA" || msg.Channel().ChannelType() == "IG" { return h.sendFacebookInstagramMsg(ctx, msg, clog) } else if msg.Channel().ChannelType() == "WAC" { @@ -838,7 +838,7 @@ type fbaMTResponse struct { } `json:"error"` } -func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if accessToken == "" { @@ -877,7 +877,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, query.Set("access_token", accessToken) msgURL.RawQuery = query.Encode() - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) msgParts := make([]string, 0) if msg.Text() != "" { @@ -988,7 +988,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, } // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil @@ -1098,7 +1098,7 @@ type wacMTResponse struct { } `json:"error"` } -func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := h.Server().Config().WhatsappAdminSystemUserToken @@ -1106,7 +1106,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, path, _ := url.Parse(fmt.Sprintf("/%s/messages", msg.Channel().Address())) wacPhoneURL := base.ResolveReference(path) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) hasCaption := false @@ -1386,7 +1386,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, return status, nil } -func requestWAC(payload wacMTPayload, accessToken string, status courier.MsgStatus, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func requestWAC(payload wacMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -1419,7 +1419,7 @@ func requestWAC(payload wacMTPayload, accessToken string, status courier.MsgStat status.SetExternalID(externalID) } // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 7ec4f673e..cd8bd4168 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -181,7 +181,7 @@ var testCasesFBA = []ChannelHandleTestCase{ Data: string(test.ReadFile("./testdata/fba/dlr.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", PrepRequest: addValidSignature, }, diff --git a/handlers/firebase/firebase.go b/handlers/firebase/firebase.go index 1d954d51b..23abfd16b 100644 --- a/handlers/firebase/firebase.go +++ b/handlers/firebase/firebase.go @@ -134,7 +134,7 @@ type mtNotification struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { title := msg.Channel().StringConfigForKey(configTitle, "") if title == "" { return nil, fmt.Errorf("no FCM_TITLE set for FCM channel") @@ -153,7 +153,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann msgParts = handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for i, part := range msgParts { payload := mtPayload{} @@ -216,6 +216,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/freshchat/freshchat.go b/handlers/freshchat/freshchat.go index 35e9566c9..0b65f60e0 100644 --- a/handlers/freshchat/freshchat.go +++ b/handlers/freshchat/freshchat.go @@ -96,7 +96,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { agentID := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if agentID == "" { @@ -109,7 +109,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } user := strings.Split(msg.URN().Path(), "/") - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) url := apiURL + "/conversations" // create base payload @@ -167,7 +167,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/generic.go b/handlers/generic.go index 08f3556e7..987e051d1 100644 --- a/handlers/generic.go +++ b/handlers/generic.go @@ -34,7 +34,7 @@ func NewTelReceiveHandler(h courier.ChannelHandler, fromField string, bodyField } // NewExternalIDStatusHandler creates a new status handler given the passed in status map and fields -func NewExternalIDStatusHandler(h courier.ChannelHandler, statuses map[string]courier.MsgStatusValue, externalIDField string, statusField string) courier.ChannelHandleFunc { +func NewExternalIDStatusHandler(h courier.ChannelHandler, statuses map[string]courier.MsgStatus, externalIDField string, statusField string) courier.ChannelHandleFunc { return func(ctx context.Context, c courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { err := r.ParseForm() if err != nil { @@ -53,7 +53,7 @@ func NewExternalIDStatusHandler(h courier.ChannelHandler, statuses map[string]co } // create our status - status := h.Server().Backend().NewMsgStatusForExternalID(c, externalID, sValue, clog) + status := h.Server().Backend().NewStatusUpdateByExternalID(c, externalID, sValue, clog) return WriteMsgStatusAndResponse(ctx, h, c, status, w, r) } } diff --git a/handlers/globe/globe.go b/handlers/globe/globe.go index cb13ae83b..74b5d970c 100644 --- a/handlers/globe/globe.go +++ b/handlers/globe/globe.go @@ -120,7 +120,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { appID := msg.Channel().StringConfigForKey(configAppID, "") if appID == "" { return nil, fmt.Errorf("Missing 'app_id' config for GL channel") @@ -136,7 +136,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("Missing 'passphrase' config for GL channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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{} @@ -162,7 +162,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/highconnection/highconnection.go b/handlers/highconnection/highconnection.go index e5bbc0af3..5e5785b60 100644 --- a/handlers/highconnection/highconnection.go +++ b/handlers/highconnection/highconnection.go @@ -91,16 +91,16 @@ type statusForm struct { Status int `name:"status" validate:"required"` } -var statusMapping = map[int]courier.MsgStatusValue{ - 2: courier.MsgFailed, - 4: courier.MsgSent, - 6: courier.MsgDelivered, - 11: courier.MsgFailed, - 12: courier.MsgFailed, - 13: courier.MsgFailed, - 14: courier.MsgFailed, - 15: courier.MsgFailed, - 16: courier.MsgFailed, +var statusMapping = map[int]courier.MsgStatus{ + 2: courier.MsgStatusFailed, + 4: courier.MsgStatusSent, + 6: courier.MsgStatusDelivered, + 11: courier.MsgStatusFailed, + 12: courier.MsgStatusFailed, + 13: courier.MsgStatusFailed, + 14: courier.MsgStatusFailed, + 15: courier.MsgStatusFailed, + 16: courier.MsgStatusFailed, } // receiveStatus is our HTTP handler function for status updates @@ -117,12 +117,12 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForID(channel, courier.MsgID(form.RetID), msgStatus, clog) + status := h.Backend().NewStatusUpdate(channel, courier.MsgID(form.RetID), msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for HX channel") @@ -137,7 +137,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann statusURL := fmt.Sprintf("https://%s/c/hx/%s/status", callbackDomain, msg.Channel().UUID()) receiveURL := fmt.Sprintf("https://%s/c/hx/%s/receive", callbackDomain, msg.Channel().UUID()) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { @@ -166,7 +166,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } diff --git a/handlers/highconnection/highconnection_test.go b/handlers/highconnection/highconnection_test.go index 5c4fde5f4..b05a74360 100644 --- a/handlers/highconnection/highconnection_test.go +++ b/handlers/highconnection/highconnection_test.go @@ -74,7 +74,7 @@ var testCases = []ChannelHandleTestCase{ URL: statusURL + "?ret_id=12345&status=6", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, } diff --git a/handlers/hormuud/hormuud.go b/handlers/hormuud/hormuud.go index 1c30ba811..8d283b840 100644 --- a/handlers/hormuud/hormuud.go +++ b/handlers/hormuud/hormuud.go @@ -76,8 +76,8 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) token, err := h.FetchToken(ctx, msg.Channel(), msg, clog) if err != nil { @@ -112,7 +112,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) // try to get the message id out id, _ := jsonparser.GetString(respBody, "Data", "MessageID") diff --git a/handlers/i2sms/i2sms.go b/handlers/i2sms/i2sms.go index c04555c18..ee4d56193 100644 --- a/handlers/i2sms/i2sms.go +++ b/handlers/i2sms/i2sms.go @@ -84,7 +84,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for I2 channel") @@ -100,7 +100,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no channel_hash set for I2 channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { form := url.Values{ "action": []string{"send_single"}, @@ -132,10 +132,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // we always get 00 on success if response.ErrorCode == "00" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(response.Result.SessionID) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.Error(courier.ErrorResponseValueUnexpected("error_code", "00")) break } diff --git a/handlers/infobip/infobip.go b/handlers/infobip/infobip.go index 08c06c13d..f55556178 100644 --- a/handlers/infobip/infobip.go +++ b/handlers/infobip/infobip.go @@ -39,12 +39,12 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var statusMapping = map[string]courier.MsgStatusValue{ - "PENDING": courier.MsgSent, - "EXPIRED": courier.MsgSent, - "DELIVERED": courier.MsgDelivered, - "REJECTED": courier.MsgFailed, - "UNDELIVERABLE": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "PENDING": courier.MsgStatusSent, + "EXPIRED": courier.MsgStatusSent, + "DELIVERED": courier.MsgStatusDelivered, + "REJECTED": courier.MsgStatusFailed, + "UNDELIVERABLE": courier.MsgStatusFailed, } type statusPayload struct { @@ -68,8 +68,8 @@ func (h *handler) statusMessage(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, s.MessageID, msgStatus, clog) - err := h.Backend().WriteMsgStatus(ctx, status) + status := h.Backend().NewStatusUpdateByExternalID(channel, s.MessageID, msgStatus, clog) + err := h.Backend().WriteStatusUpdate(ctx, status) if err != nil { return nil, err } @@ -159,7 +159,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for IB channel") @@ -209,7 +209,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann req.Header.Set("Accept", "application/json") req.SetBasicAuth(username, password) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) resp, respBody, err := handlers.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { @@ -227,7 +227,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(externalID) } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/infobip/infobip_test.go b/handlers/infobip/infobip_test.go index 922c1ab1e..40f394e82 100644 --- a/handlers/infobip/infobip_test.go +++ b/handlers/infobip/infobip_test.go @@ -246,7 +246,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusDelivered, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Status rejected", @@ -254,7 +254,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusRejected, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, { Label: "Status undeliverable", @@ -262,7 +262,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusUndeliverable, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, }, { Label: "Status pending", @@ -270,7 +270,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusPending, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Status expired", @@ -278,7 +278,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatusExpired, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Status group name unexpected", diff --git a/handlers/jasmin/jasmin.go b/handlers/jasmin/jasmin.go index 2b80583a1..70f79a6ae 100644 --- a/handlers/jasmin/jasmin.go +++ b/handlers/jasmin/jasmin.go @@ -53,14 +53,14 @@ func (h *handler) receiveStatus(ctx context.Context, c courier.Channel, w http.R // should have either delivered or err reqStatus := courier.NilMsgStatus if form.Delivered == 1 { - reqStatus = courier.MsgDelivered + reqStatus = courier.MsgStatusDelivered } else if form.Err == 1 { - reqStatus = courier.MsgFailed + reqStatus = courier.MsgStatusFailed } else { return nil, handlers.WriteAndLogRequestError(ctx, h, c, w, r, fmt.Errorf("must have either dlvrd or err set to 1")) } - status := h.Backend().NewMsgStatusForExternalID(c, form.ID, reqStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(c, form.ID, reqStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, c, status, w, r) } @@ -104,7 +104,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr return writeJasminACK(w) } -func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.MsgStatus) error { +func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { return writeJasminACK(w) } @@ -119,7 +119,7 @@ func writeJasminACK(w http.ResponseWriter) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for JS channel") @@ -160,14 +160,14 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, err } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) resp, respBody, err := handlers.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) // try to read our external id out matches := idRegex.FindSubmatch(respBody) diff --git a/handlers/jiochat/jiochat.go b/handlers/jiochat/jiochat.go index 3785fc444..e97c42bbb 100644 --- a/handlers/jiochat/jiochat.go +++ b/handlers/jiochat/jiochat.go @@ -163,13 +163,13 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accessToken, err := h.getAccessToken(ctx, msg.Channel(), clog) if err != nil { return nil, err } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { jcMsg := &mtPayload{} @@ -191,7 +191,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/justcall/justcall.go b/handlers/justcall/justcall.go index 03e00e310..805d6041f 100644 --- a/handlers/justcall/justcall.go +++ b/handlers/justcall/justcall.go @@ -124,11 +124,11 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -var statusMapping = map[string]courier.MsgStatusValue{ - "delivered": courier.MsgDelivered, - "sent": courier.MsgSent, - "undelivered": courier.MsgErrored, - "failed": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "delivered": courier.MsgStatusDelivered, + "sent": courier.MsgStatusSent, + "undelivered": courier.MsgStatusErrored, + "failed": courier.MsgStatusFailed, } func (h *handler) statusMessage(ctx context.Context, c courier.Channel, w http.ResponseWriter, r *http.Request, payload *moPayload, clog *courier.ChannelLog) ([]courier.Event, error) { @@ -141,7 +141,7 @@ func (h *handler) statusMessage(ctx context.Context, c courier.Channel, w http.R return nil, handlers.WriteAndLogRequestError(ctx, h, c, w, r, fmt.Errorf("unknown status '%s', must be one of send, delivered, undelivered, failed", payload.Data.Status)) } // write our status - status := h.Backend().NewMsgStatusForExternalID(c, fmt.Sprint(payload.Data.MessageID), msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(c, fmt.Sprint(payload.Data.MessageID), msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, c, status, w, r) } @@ -153,7 +153,7 @@ type mtPayload struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(courier.ConfigAPIKey, "") if apiKey == "" { return nil, fmt.Errorf("no API key set for JCL channel") @@ -164,7 +164,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no API secret set for JCL channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) mediaURLs := make([]string, 0, 5) text := msg.Text() @@ -215,7 +215,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(fmt.Sprintf("%d", externalID)) } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/kaleyra/kaleyra.go b/handlers/kaleyra/kaleyra.go index 952065fdd..fc47b9ba3 100644 --- a/handlers/kaleyra/kaleyra.go +++ b/handlers/kaleyra/kaleyra.go @@ -103,11 +103,11 @@ func (h *handler) receiveMsg(ctx context.Context, channel courier.Channel, w htt return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -var statusMapping = map[string]courier.MsgStatusValue{ - "0": courier.MsgFailed, - "sent": courier.MsgWired, - "delivered": courier.MsgDelivered, - "read": courier.MsgDelivered, +var statusMapping = map[string]courier.MsgStatus{ + "0": courier.MsgStatusFailed, + "sent": courier.MsgStatusWired, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, } // receiveStatus is our HTTP handler function for outgoing messages statuses @@ -125,7 +125,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // msg not found? ignore this - status := h.Backend().NewMsgStatusForExternalID(channel, form.ID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.ID, msgStatus, clog) if status == nil { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, fmt.Sprintf("ignoring request, message %s not found", form.ID)) } @@ -135,7 +135,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accountSID := msg.Channel().StringConfigForKey(configAccountSID, "") apiKey := msg.Channel().StringConfigForKey(configApiKey, "") @@ -143,7 +143,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, errors.New("no account_sid or api_key config") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) sendURL := fmt.Sprintf("%s/v1/%s/messages", baseURL, accountSID) var kwaResp *http.Response @@ -223,7 +223,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } if kwaErr != nil || kwaResp.StatusCode/100 != 2 { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) return status, nil } @@ -233,7 +233,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(externalID) } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/kannel/kannel.go b/handlers/kannel/kannel.go index 1d45a94a5..b209aea18 100644 --- a/handlers/kannel/kannel.go +++ b/handlers/kannel/kannel.go @@ -80,12 +80,12 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -var statusMapping = map[int]courier.MsgStatusValue{ - 1: courier.MsgDelivered, - 2: courier.MsgErrored, - 4: courier.MsgSent, - 8: courier.MsgSent, - 16: courier.MsgErrored, +var statusMapping = map[int]courier.MsgStatus{ + 1: courier.MsgStatusDelivered, + 2: courier.MsgStatusErrored, + 4: courier.MsgStatusSent, + 8: courier.MsgStatusSent, + 16: courier.MsgStatusErrored, } type statusForm struct { @@ -108,17 +108,17 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // if we are ignoring delivery reports and this isn't failed then move on - if channel.BoolConfigForKey(configIgnoreSent, false) && msgStatus == courier.MsgSent { + if channel.BoolConfigForKey(configIgnoreSent, false) && msgStatus == courier.MsgStatusSent { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "ignoring sent report (message aready wired)") } // write our status - status := h.Backend().NewMsgStatusForID(channel, form.ID, msgStatus, clog) + status := h.Backend().NewStatusUpdate(channel, form.ID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for KN channel") @@ -206,14 +206,14 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann resp, _, err = handlers.RequestHTTPInsecure(req, clog) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) if err == nil && resp.StatusCode/100 == 2 { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } // kannel will respond with a 403 for non-routable numbers, fail permanently in these cases if resp != nil && resp.StatusCode == 403 { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) } return status, nil diff --git a/handlers/kannel/kannel_test.go b/handlers/kannel/kannel_test.go index 2a65f8ec1..9b3337fd2 100644 --- a/handlers/kannel/kannel_test.go +++ b/handlers/kannel/kannel_test.go @@ -82,7 +82,7 @@ var handleTestCases = []ChannelHandleTestCase{ URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/?id=12345&status=4", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, } @@ -103,7 +103,7 @@ var ignoreTestCases = []ChannelHandleTestCase{ URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/?id=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Ignore Status Wired", diff --git a/handlers/line/line.go b/handlers/line/line.go index 127889ef7..fe73f68c5 100644 --- a/handlers/line/line.go +++ b/handlers/line/line.go @@ -283,12 +283,12 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { return nil, fmt.Errorf("no auth token set for LN channel: %s", msg.Channel().UUID()) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // all msg parts in JSON var jsonMsgs []string @@ -401,7 +401,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/m3tech/m3tech.go b/handlers/m3tech/m3tech.go index 0079b2508..90bc1a435 100644 --- a/handlers/m3tech/m3tech.go +++ b/handlers/m3tech/m3tech.go @@ -69,7 +69,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for M3 channel") @@ -88,7 +88,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // send our message - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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{ @@ -119,7 +119,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // all went well, set ourselves to wired - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/macrokiosk/macrokiosk.go b/handlers/macrokiosk/macrokiosk.go index f2617a8ba..999a6e7e6 100644 --- a/handlers/macrokiosk/macrokiosk.go +++ b/handlers/macrokiosk/macrokiosk.go @@ -53,11 +53,11 @@ type statusForm struct { Status string `name:"status" validate:"required"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "ACCEPTED": courier.MsgSent, - "DELIVERED": courier.MsgDelivered, - "UNDELIVERED": courier.MsgFailed, - "PROCESSING": courier.MsgWired, +var statusMapping = map[string]courier.MsgStatus{ + "ACCEPTED": courier.MsgStatusSent, + "DELIVERED": courier.MsgStatusDelivered, + "UNDELIVERED": courier.MsgStatusFailed, + "PROCESSING": courier.MsgStatusWired, } // receiveStatus is our HTTP handler function for status updates @@ -73,7 +73,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, fmt.Sprintf("ignoring unknown status '%s'", form.Status)) } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.MsgID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.MsgID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -150,7 +150,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") servID := msg.Channel().StringConfigForKey(configMacrokioskServiceID, "") @@ -166,7 +166,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann encoding = "5" } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), text, maxMsgLength) for i, part := range parts { payload := &mtPayload{ @@ -204,6 +204,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(externalID) } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/macrokiosk/macrokiosk_test.go b/handlers/macrokiosk/macrokiosk_test.go index d534f50b9..27e373167 100644 --- a/handlers/macrokiosk/macrokiosk_test.go +++ b/handlers/macrokiosk/macrokiosk_test.go @@ -45,8 +45,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Invalid Params", URL: receiveURL, Data: invalidParamsReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "missing shortcode, longcode, from or msisdn parameters"}, {Label: "Invalid Address Params", URL: receiveURL, Data: invalidAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, - {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgSent}, - {Label: "Wired Status", URL: statusURL, Data: processingStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"W"`, ExpectedMsgStatus: courier.MsgWired}, + {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent}, + {Label: "Wired Status", URL: statusURL, Data: processingStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"W"`, ExpectedMsgStatus: courier.MsgStatusWired}, {Label: "Unknown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } diff --git a/handlers/mblox/mblox.go b/handlers/mblox/mblox.go index 86dd56458..20c6330f5 100644 --- a/handlers/mblox/mblox.go +++ b/handlers/mblox/mblox.go @@ -50,13 +50,13 @@ type eventPayload struct { ReceivedAt string `json:"received_at"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "Delivered": courier.MsgDelivered, - "Dispatched": courier.MsgSent, - "Aborted": courier.MsgFailed, - "Rejected": courier.MsgFailed, - "Failed": courier.MsgFailed, - "Expired": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "Delivered": courier.MsgStatusDelivered, + "Dispatched": courier.MsgStatusSent, + "Aborted": courier.MsgStatusFailed, + "Rejected": courier.MsgStatusFailed, + "Failed": courier.MsgStatusFailed, + "Expired": courier.MsgStatusFailed, } // receiveEvent is our HTTP handler function for incoming messages @@ -74,7 +74,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.BatchID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.BatchID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } else if payload.Type == "mo_text" { @@ -113,14 +113,14 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, 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") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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{} @@ -151,7 +151,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, fmt.Errorf("unable to parse response body from MBlox") } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } diff --git a/handlers/mblox/mblox_test.go b/handlers/mblox/mblox_test.go index bd1b21dc8..a603ee6d0 100644 --- a/handlers/mblox/mblox_test.go +++ b/handlers/mblox/mblox_test.go @@ -69,7 +69,7 @@ var testCases = []ChannelHandleTestCase{ {Label: "Receive Missing Params", URL: receiveURL, Data: missingParamsRecieve, ExpectedRespStatus: 400, ExpectedBodyContains: "missing one of 'id', 'from', 'to', 'body' or 'received_at' in request body"}, {Label: "Invalid URN", URL: receiveURL, Data: invalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, - {Label: "Status Valid", URL: receiveURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered}, + {Label: "Status Valid", URL: receiveURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered}, {Label: "Status Unknown", URL: receiveURL, Data: unknownStatus, ExpectedRespStatus: 400, ExpectedBodyContains: `unknown status 'INVALID'`}, {Label: "Status Missing Batch ID", URL: receiveURL, Data: missingBatchID, ExpectedRespStatus: 400, ExpectedBodyContains: "missing one of 'batch_id' or 'status' in request body"}, } diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 5fb30a682..0351cda8a 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -53,13 +53,13 @@ type ReceivedStatus struct { StatusErrorCode int `schema:"statusErrorCode"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "scheduled": courier.MsgSent, - "delivery_failed": courier.MsgFailed, - "sent": courier.MsgSent, - "buffered": courier.MsgSent, - "delivered": courier.MsgDelivered, - "expired": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "scheduled": courier.MsgStatusSent, + "delivery_failed": courier.MsgStatusFailed, + "sent": courier.MsgStatusSent, + "buffered": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "expired": courier.MsgStatusFailed, } type ReceivedMessage struct { @@ -109,19 +109,19 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // if the message id was passed explicitely, use that - var status courier.MsgStatus + var status courier.StatusUpdate if receivedStatus.Reference != "" { msgID, err := strconv.ParseInt(receivedStatus.Reference, 10, 64) if err != nil { logrus.WithError(err).WithField("id", receivedStatus.Reference).Error("error converting Messagebird status id to integer") } else { - status = h.Backend().NewMsgStatusForID(channel, courier.MsgID(msgID), msgStatus, clog) + status = h.Backend().NewStatusUpdate(channel, courier.MsgID(msgID), msgStatus, clog) } } // if we have no status, then build it from the external (messagebird) id if status == nil { - status = h.Backend().NewMsgStatusForExternalID(channel, receivedStatus.ID, msgStatus, clog) + status = h.Backend().NewStatusUpdateByExternalID(channel, receivedStatus.ID, msgStatus, clog) } if receivedStatus.StatusErrorCode == errorStopped { @@ -184,7 +184,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { @@ -192,7 +192,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } user := msg.URN().Path() - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // create base payload payload := &Message{ @@ -235,7 +235,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann if err != nil || resp.StatusCode/100 != 2 { return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) externalID, err := jsonparser.GetString(respBody, "id") if err != nil { diff --git a/handlers/messangi/messangi.go b/handlers/messangi/messangi.go index 1f67d392a..ff2724c28 100644 --- a/handlers/messangi/messangi.go +++ b/handlers/messangi/messangi.go @@ -60,7 +60,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { publicKey := msg.Channel().StringConfigForKey(configPublicKey, "") if publicKey == "" { return nil, fmt.Errorf("no public_key set for MG channel") @@ -81,7 +81,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no carrier_id set for MG channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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(), "+") @@ -111,9 +111,9 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // we always get 204 on success if response.Status == "OK" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.Error(courier.ErrorResponseValueUnexpected("status", "OK")) break } diff --git a/handlers/mtarget/mtarget.go b/handlers/mtarget/mtarget.go index 659e6fb1f..17dc50457 100644 --- a/handlers/mtarget/mtarget.go +++ b/handlers/mtarget/mtarget.go @@ -32,13 +32,13 @@ func newHandler() courier.ChannelHandler { return &handler{handlers.NewBaseHandler(courier.ChannelType("MT"), "Mtarget")} } -var statusMapping = map[string]courier.MsgStatusValue{ - "0": courier.MsgWired, - "1": courier.MsgWired, - "2": courier.MsgSent, - "3": courier.MsgDelivered, - "4": courier.MsgFailed, - "6": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "0": courier.MsgStatusWired, + "1": courier.MsgStatusWired, + "2": courier.MsgStatusSent, + "3": courier.MsgStatusDelivered, + "4": courier.MsgStatusFailed, + "6": courier.MsgStatusFailed, } // Initialize is called by the engine once everything is loaded @@ -148,7 +148,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for MT channel") @@ -160,7 +160,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // send our message - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + 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{ @@ -198,10 +198,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, _ := jsonparser.GetString(respBody, "results", "[0]", "ticket") if code == "0" && externalID != "" { // all went well, set ourselves to wired - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.RawError(fmt.Errorf("Error status code, failing permanently")) break } diff --git a/handlers/mtn/mtn.go b/handlers/mtn/mtn.go index b7fde7f60..32ab90ce0 100644 --- a/handlers/mtn/mtn.go +++ b/handlers/mtn/mtn.go @@ -49,17 +49,17 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var statusMapping = map[string]courier.MsgStatusValue{ - "DELIVRD": courier.MsgDelivered, - "DeliveredToTerminal": courier.MsgDelivered, - "DeliveryUncertain": courier.MsgSent, - "EXPIRED": courier.MsgFailed, - "DeliveryImpossible": courier.MsgErrored, - "DeliveredToNetwork": courier.MsgSent, +var statusMapping = map[string]courier.MsgStatus{ + "DELIVRD": courier.MsgStatusDelivered, + "DeliveredToTerminal": courier.MsgStatusDelivered, + "DeliveryUncertain": courier.MsgStatusSent, + "EXPIRED": courier.MsgStatusFailed, + "DeliveryImpossible": courier.MsgStatusErrored, + "DeliveredToNetwork": courier.MsgStatusSent, // no changes - "MessageWaiting": courier.MsgWired, - "DeliveryNotificationNotSupported": courier.MsgWired, + "MessageWaiting": courier.MsgStatusWired, + "DeliveryNotificationNotSupported": courier.MsgStatusWired, } type moPayload struct { @@ -102,12 +102,12 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h fmt.Errorf("unknown status '%s'", payload.DeliveryStatus)) } - if msgStatus == courier.MsgWired { + if msgStatus == courier.MsgStatusWired { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "no status changed, ignored") } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.TransactionID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.TransactionID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } } @@ -121,7 +121,7 @@ type mtPayload struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accessToken, err := h.getAccessToken(ctx, msg.Channel(), clog) if err != nil { return nil, err @@ -131,7 +131,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann cpAddress := msg.Channel().StringConfigForKey(configCPAddress, "") partSendURL, _ := url.Parse(fmt.Sprintf("%s/%s", baseURL, "v2/messages/sms/outbound")) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) mtMsg := &mtPayload{} mtMsg.From = strings.TrimPrefix(msg.Channel().Address(), "+") @@ -168,7 +168,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // if this is our first message, record the external id status.SetExternalID(externalID) - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/mtn/mtn_test.go b/handlers/mtn/mtn_test.go index d2d1cad9f..83e01446f 100644 --- a/handlers/mtn/mtn_test.go +++ b/handlers/mtn/mtn_test.go @@ -103,7 +103,7 @@ var testCases = []ChannelHandleTestCase{ Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "rrt-58503", }, { @@ -112,7 +112,7 @@ var testCases = []ChannelHandleTestCase{ Data: validDeliveredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "rrt-58503", }, { @@ -138,7 +138,7 @@ var testCases = []ChannelHandleTestCase{ Data: expiredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedExternalID: "rrt-58503", }, { diff --git a/handlers/nexmo/nexmo.go b/handlers/nexmo/nexmo.go index e4a88c3d9..7b722b9b4 100644 --- a/handlers/nexmo/nexmo.go +++ b/handlers/nexmo/nexmo.go @@ -109,14 +109,14 @@ type statusForm struct { ErrCode int `name:"err-code"` } -var statusMappings = map[string]courier.MsgStatusValue{ - "failed": courier.MsgFailed, - "expired": courier.MsgFailed, - "rejected": courier.MsgFailed, - "buffered": courier.MsgSent, - "accepted": courier.MsgSent, - "unknown": courier.MsgWired, - "delivered": courier.MsgDelivered, +var statusMappings = map[string]courier.MsgStatus{ + "failed": courier.MsgStatusFailed, + "expired": courier.MsgStatusFailed, + "rejected": courier.MsgStatusFailed, + "buffered": courier.MsgStatusSent, + "accepted": courier.MsgStatusSent, + "unknown": courier.MsgStatusWired, + "delivered": courier.MsgStatusDelivered, } // receiveStatus is our HTTP handler function for status updates @@ -137,7 +137,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w clog.Error(courier.ErrorExternal("dlr:"+strconv.Itoa(form.ErrCode), dlrErrorCodes[form.ErrCode])) } - status := h.Backend().NewMsgStatusForExternalID(channel, form.MessageID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.MessageID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -170,7 +170,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { nexmoAPIKey := msg.Channel().StringConfigForKey(configNexmoAPIKey, "") if nexmoAPIKey == "" { return nil, fmt.Errorf("no nexmo API key set for NX channel") @@ -191,7 +191,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann textType = "unicode" } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), text, maxMsgLength) for _, part := range parts { form := url.Values{ @@ -244,6 +244,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/nexmo/nexmo_test.go b/handlers/nexmo/nexmo_test.go index 4982e5d76..04b67466f 100644 --- a/handlers/nexmo/nexmo_test.go +++ b/handlers/nexmo/nexmo_test.go @@ -61,7 +61,7 @@ var testCases = []ChannelHandleTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=delivered&err-code=0", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "external1", }, { @@ -69,7 +69,7 @@ var testCases = []ChannelHandleTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=expired&err-code=0", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedExternalID: "external1", }, { @@ -77,7 +77,7 @@ var testCases = []ChannelHandleTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=failed&err-code=6", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedExternalID: "external1", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("dlr:6", "Anti-Spam Rejection")}, }, @@ -86,7 +86,7 @@ var testCases = []ChannelHandleTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=accepted", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, ExpectedExternalID: "external1", }, { @@ -94,7 +94,7 @@ var testCases = []ChannelHandleTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=buffered", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, ExpectedExternalID: "external1", }, { diff --git a/handlers/novo/novo.go b/handlers/novo/novo.go index 0257800a7..a9b65118a 100644 --- a/handlers/novo/novo.go +++ b/handlers/novo/novo.go @@ -78,7 +78,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { merchantID := msg.Channel().StringConfigForKey(configMerchantId, "") if merchantID == "" { return nil, fmt.Errorf("no merchant_id set for NV channel") @@ -89,7 +89,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no merchant_secret set for NV channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { from := strings.TrimPrefix(msg.Channel().Address(), "+") @@ -119,9 +119,9 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // we always get 204 on success if responseMsgStatus == "FINISHED" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } else { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) clog.RawError(fmt.Errorf("received invalid response")) break } diff --git a/handlers/playmobile/playmobile.go b/handlers/playmobile/playmobile.go index 6d63d6aa6..4b47bde29 100644 --- a/handlers/playmobile/playmobile.go +++ b/handlers/playmobile/playmobile.go @@ -146,7 +146,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(configUsername, "") if username == "" { return nil, fmt.Errorf("no username set for PM channel") @@ -167,7 +167,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no base url set for PM channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for i, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { payload := mtPayload{} @@ -202,7 +202,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/plivo/plivo.go b/handlers/plivo/plivo.go index 5499f4a15..d62a64e91 100644 --- a/handlers/plivo/plivo.go +++ b/handlers/plivo/plivo.go @@ -62,12 +62,12 @@ type statusForm struct { ParentMessageUUID string `name:"ParentMessageUUID"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "queued": courier.MsgWired, - "delivered": courier.MsgDelivered, - "undelivered": courier.MsgSent, - "sent": courier.MsgSent, - "rejected": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "queued": courier.MsgStatusWired, + "delivered": courier.MsgStatusDelivered, + "undelivered": courier.MsgStatusSent, + "sent": courier.MsgStatusSent, + "rejected": courier.MsgStatusFailed, } // receiveStatus is our HTTP handler function for status updates @@ -93,7 +93,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, externalID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, externalID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -136,7 +136,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authID := msg.Channel().StringConfigForKey(configPlivoAuthID, "") authToken := msg.Channel().StringConfigForKey(configPlivoAuthToken, "") plivoAppID := msg.Channel().StringConfigForKey(configPlivoAPPID, "") @@ -147,7 +147,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) statusURL := fmt.Sprintf("https://%s/c/pl/%s/status", callbackDomain, msg.Channel().UUID()) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for i, part := range parts { payload := &mtPayload{ @@ -186,7 +186,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/plivo/plivo_test.go b/handlers/plivo/plivo_test.go index 5d00c3515..8616d5e7f 100644 --- a/handlers/plivo/plivo_test.go +++ b/handlers/plivo/plivo_test.go @@ -36,8 +36,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Invalid Address Params", URL: receiveURL, Data: invalidAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, {Label: "Missing Params", URL: receiveURL, Data: missingParams, ExpectedRespStatus: 400, ExpectedBodyContains: "Field validation for 'To' failed"}, - {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered}, - {Label: "Sent Status", URL: statusURL, Data: validSentStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgSent}, + {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered}, + {Label: "Sent Status", URL: statusURL, Data: validSentStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent}, {Label: "Invalid Status Address", URL: statusURL, Data: invalidStatusAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, {Label: "Unkown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } diff --git a/handlers/redrabbit/redrabbit.go b/handlers/redrabbit/redrabbit.go index 59bfc586b..526d77817 100644 --- a/handlers/redrabbit/redrabbit.go +++ b/handlers/redrabbit/redrabbit.go @@ -36,7 +36,7 @@ func (h *handler) Initialize(s courier.Server) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") if username == "" || password == "" { @@ -44,7 +44,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } text := handlers.GetTextAndAttachments(msg) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) form := url.Values{ "LoginName": []string{username}, "Password": []string{password}, @@ -78,7 +78,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // all went well, set ourselves to wired - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/responses.go b/handlers/responses.go index 0a694e372..b53ad8f25 100644 --- a/handlers/responses.go +++ b/handlers/responses.go @@ -22,13 +22,13 @@ func WriteMsgsAndResponse(ctx context.Context, h courier.ChannelHandler, msgs [] } // WriteMsgStatusAndResponse write the passed in status to our backend -func WriteMsgStatusAndResponse(ctx context.Context, h courier.ChannelHandler, channel courier.Channel, status courier.MsgStatus, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { - err := h.Server().Backend().WriteMsgStatus(ctx, status) +func WriteMsgStatusAndResponse(ctx context.Context, h courier.ChannelHandler, channel courier.Channel, status courier.StatusUpdate, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { + err := h.Server().Backend().WriteStatusUpdate(ctx, status) if err != nil { return nil, err } - return []courier.Event{status}, h.WriteStatusSuccessResponse(ctx, w, []courier.MsgStatus{status}) + return []courier.Event{status}, h.WriteStatusSuccessResponse(ctx, w, []courier.StatusUpdate{status}) } // WriteAndLogRequestError logs the passed in error and writes the response to the response writer diff --git a/handlers/rocketchat/rocketchat.go b/handlers/rocketchat/rocketchat.go index 1b07fed1a..617cd805d 100644 --- a/handlers/rocketchat/rocketchat.go +++ b/handlers/rocketchat/rocketchat.go @@ -105,13 +105,13 @@ type mtPayload struct { Attachments []Attachment `json:"attachments,omitempty"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { baseURL := msg.Channel().StringConfigForKey(configBaseURL, "") secret := msg.Channel().StringConfigForKey(configSecret, "") botUsername := msg.Channel().StringConfigForKey(configBotUsername, "") // the status that will be written for this message - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) payload := &mtPayload{ UserURN: msg.URN().Path(), @@ -145,6 +145,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(msgID) } - status.SetStatus(courier.MsgSent) + status.SetStatus(courier.MsgStatusSent) return status, nil } diff --git a/handlers/shaqodoon/shaqodoon.go b/handlers/shaqodoon/shaqodoon.go index af8611192..96d8e0d0d 100644 --- a/handlers/shaqodoon/shaqodoon.go +++ b/handlers/shaqodoon/shaqodoon.go @@ -83,7 +83,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { sendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, "") if sendURL == "" { return nil, fmt.Errorf("missing send_url for SQ channel") @@ -113,13 +113,13 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) resp, _, err := handlers.RequestHTTPInsecure(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/slack/slack.go b/handlers/slack/slack.go index 51afb6c8d..1ad3af556 100644 --- a/handlers/slack/slack.go +++ b/handlers/slack/slack.go @@ -145,13 +145,13 @@ func (h *handler) resolveFile(ctx context.Context, channel courier.Channel, file return filePath, nil } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { botToken := msg.Channel().StringConfigForKey(configBotToken, "") if botToken == "" { return nil, fmt.Errorf("missing bot token for SL/slack channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, attachment := range msg.Attachments() { fileAttachment, err := parseAttachmentToFileParams(msg, attachment, clog) @@ -177,7 +177,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/smscentral/smscentral.go b/handlers/smscentral/smscentral.go index 1545401ec..67fa5f5e8 100644 --- a/handlers/smscentral/smscentral.go +++ b/handlers/smscentral/smscentral.go @@ -63,7 +63,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for SC channel") @@ -74,7 +74,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for SC channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // build our request form := url.Values{ @@ -95,7 +95,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/start/start.go b/handlers/start/start.go index 95e3e35c6..754954beb 100644 --- a/handlers/start/start.go +++ b/handlers/start/start.go @@ -121,7 +121,7 @@ type mtResponse struct { State string `xml:"state"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for ST channel: %s", msg.Channel().UUID()) @@ -132,7 +132,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for ST channel: %s", msg.Channel().UUID()) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for i, part := range parts { @@ -172,7 +172,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann response := &mtResponse{} err = xml.Unmarshal(respBody, response) if err == nil { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) if i == 0 { status.SetExternalID(response.ID) } diff --git a/handlers/telegram/telegram.go b/handlers/telegram/telegram.go index 82cf19cf5..af847fc69 100644 --- a/handlers/telegram/telegram.go +++ b/handlers/telegram/telegram.go @@ -178,7 +178,7 @@ func (h *handler) sendMsgPart(msg courier.Msg, token string, path string, form u } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { confAuth := msg.Channel().ConfigForKey(courier.ConfigAuthToken, "") authToken, isStr := confAuth.(string) if !isStr || authToken == "" { @@ -197,7 +197,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } // the status that will be written for this message - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // whether we encountered any errors sending any parts hasError := true @@ -223,7 +223,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendMessage", form, msgKeyBoard, clog) if botBlocked { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err @@ -249,7 +249,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendPhoto", form, attachmentKeyBoard, clog) if botBlocked { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err @@ -265,7 +265,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendVideo", form, attachmentKeyBoard, clog) if botBlocked { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err @@ -281,7 +281,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendAudio", form, attachmentKeyBoard, clog) if botBlocked { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err @@ -297,7 +297,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendDocument", form, attachmentKeyBoard, clog) if botBlocked { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err @@ -312,7 +312,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } if !hasError { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/telesom/telesom.go b/handlers/telesom/telesom.go index 7964e4a2c..a23a5836b 100644 --- a/handlers/telesom/telesom.go +++ b/handlers/telesom/telesom.go @@ -65,7 +65,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for TS channel") @@ -83,7 +83,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann tsSendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, sendURL) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { from := strings.TrimPrefix(msg.Channel().Address(), "+") @@ -120,7 +120,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } if strings.Contains(string(respBody), "Success") { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } else { clog.RawError(fmt.Errorf("Received invalid response content: %s", string(respBody))) } diff --git a/handlers/test.go b/handlers/test.go index 902851d36..0a2702821 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -47,7 +47,7 @@ type ChannelHandleTestCase struct { ExpectedURNAuth string ExpectedAttachments []string ExpectedDate time.Time - ExpectedMsgStatus courier.MsgStatusValue + ExpectedMsgStatus courier.MsgStatus ExpectedExternalID string ExpectedMsgID int64 ExpectedEvent courier.ChannelEventType @@ -281,7 +281,7 @@ type ChannelSendTestCase struct { ExpectedPostForm url.Values ExpectedRequestBody string ExpectedHeaders map[string]string - ExpectedMsgStatus courier.MsgStatusValue + ExpectedMsgStatus courier.MsgStatus ExpectedExternalID string ExpectedErrors []*courier.ChannelError ExpectedStopEvent bool diff --git a/handlers/thinq/thinq.go b/handlers/thinq/thinq.go index 032e27e25..e98fbcef5 100644 --- a/handlers/thinq/thinq.go +++ b/handlers/thinq/thinq.go @@ -99,13 +99,13 @@ type statusForm struct { Status string `validate:"required" name:"status"` } -var statusMapping = map[string]courier.MsgStatusValue{ - "DELIVRD": courier.MsgDelivered, - "EXPIRED": courier.MsgErrored, - "DELETED": courier.MsgFailed, - "UNDELIV": courier.MsgFailed, - "UNKNOWN": courier.MsgFailed, - "REJECTD": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "DELIVRD": courier.MsgStatusDelivered, + "EXPIRED": courier.MsgStatusErrored, + "DELETED": courier.MsgStatusFailed, + "UNDELIV": courier.MsgStatusFailed, + "UNKNOWN": courier.MsgStatusFailed, + "REJECTD": courier.MsgStatusFailed, } // receiveStatus is our HTTP handler function for status updates @@ -124,7 +124,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, form.GUID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, form.GUID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -135,7 +135,7 @@ type mtMessage struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accountID := msg.Channel().StringConfigForKey(configAccountID, "") if accountID == "" { return nil, fmt.Errorf("no account id set for TQ channel") @@ -151,7 +151,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no token set for TQ channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // we send attachments first so that text appears below for _, a := range msg.Attachments() { @@ -183,7 +183,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann clog.Error(courier.ErrorResponseValueMissing("guid")) return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } @@ -217,7 +217,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) status.SetExternalID(externalID) } } diff --git a/handlers/thinq/thinq_test.go b/handlers/thinq/thinq_test.go index 2fdc9bad9..120d20ba1 100644 --- a/handlers/thinq/thinq_test.go +++ b/handlers/thinq/thinq_test.go @@ -66,7 +66,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedRespStatus: 200, ExpectedExternalID: "1234", ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Status Invalid", diff --git a/handlers/twiml/twiml.go b/handlers/twiml/twiml.go index 440e649e1..7dbf79c22 100644 --- a/handlers/twiml/twiml.go +++ b/handlers/twiml/twiml.go @@ -102,13 +102,13 @@ type statusForm struct { To string } -var statusMapping = map[string]courier.MsgStatusValue{ - "queued": courier.MsgSent, - "failed": courier.MsgFailed, - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, - "read": courier.MsgDelivered, - "undelivered": courier.MsgFailed, +var statusMapping = map[string]courier.MsgStatus{ + "queued": courier.MsgStatusSent, + "failed": courier.MsgStatusFailed, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "undelivered": courier.MsgStatusFailed, } // receiveMessage is our HTTP handler function for incoming messages @@ -171,25 +171,25 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // if we are ignoring delivery reports and this isn't failed then move on - if channel.BoolConfigForKey(configIgnoreDLRs, false) && msgStatus != courier.MsgFailed { + if channel.BoolConfigForKey(configIgnoreDLRs, false) && msgStatus != courier.MsgStatusFailed { return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "ignoring non error delivery report") } // if the message id was passed explicitely, use that - var status courier.MsgStatus + var status courier.StatusUpdate idString := r.URL.Query().Get("id") if idString != "" { msgID, err := strconv.ParseInt(idString, 10, 64) if err != nil { logrus.WithError(err).WithField("id", idString).Error("error converting twilio callback id to integer") } else { - status = h.Backend().NewMsgStatusForID(channel, courier.MsgID(msgID), msgStatus, clog) + status = h.Backend().NewStatusUpdate(channel, courier.MsgID(msgID), msgStatus, clog) } } // if we have no status, then build it from the external (twilio) id if status == nil { - status = h.Backend().NewMsgStatusForExternalID(channel, form.MessageSID, msgStatus, clog) + status = h.Backend().NewStatusUpdateByExternalID(channel, form.MessageSID, msgStatus, clog) } errorCode, _ := strconv.ParseInt(form.ErrorCode, 10, 64) @@ -214,7 +214,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // build our callback URL callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) callbackURL := fmt.Sprintf("https://%s/c/%s/%s/status?id=%d&action=callback", callbackDomain, strings.ToLower(h.ChannelType().String()), msg.Channel().UUID(), msg.ID()) @@ -236,7 +236,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, errors.Wrap(err, "error resolving attachments") } - status := h.Backend().NewMsgStatusForID(channel, msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(channel, msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) for i, part := range parts { // build our request @@ -296,7 +296,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann errorCode, _ := jsonparser.GetInt(respBody, "code") if errorCode != 0 { if errorCode == errorStopped { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) // create a stop channel event channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) @@ -317,7 +317,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) // only save the first external id if i == 0 { diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index 6614bf9b2..60568b5e8 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -106,7 +106,7 @@ var testCases = []ChannelHandleTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvent: "stop_contact", ExpectedURN: "tel:+12028831111", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, @@ -116,13 +116,13 @@ var testCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: statusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: statusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status Valid", URL: statusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, - {Label: "Status Read", URL: statusURL, Data: statusRead, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status Read", URL: statusURL, Data: statusRead, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: statusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedMsgID: 12345, + {Label: "Status ID Valid", URL: statusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: statusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status ID Invalid", URL: statusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, } @@ -153,23 +153,23 @@ var tmsTestCases = []ChannelHandleTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvent: "stop_contact", ExpectedURN: "tel:+12028831111", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, }, - {Label: "Status TMS extra", URL: tmsStatusURL, Data: tmsStatusExtra, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgSent, + {Label: "Status TMS extra", URL: tmsStatusURL, Data: tmsStatusExtra, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent, ExpectedExternalID: "SM0b6e2697aae04182a9f5b5c7a8994c7f", PrepRequest: addValidSignature}, {Label: "Status No Params", URL: tmsStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring", PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: tmsStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: tmsStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status Valid", URL: tmsStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: tmsStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedMsgID: 12345, + {Label: "Status ID Valid", URL: tmsStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: tmsStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status ID Invalid", URL: tmsStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, } @@ -202,7 +202,7 @@ var twTestCases = []ChannelHandleTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvent: "stop_contact", ExpectedURN: "tel:+12028831111", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, @@ -212,11 +212,11 @@ var twTestCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: twStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: twStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status Valid", URL: twStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: twStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedMsgID: 12345, + {Label: "Status ID Valid", URL: twStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: twStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status ID Invalid", URL: twStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, } @@ -236,7 +236,7 @@ var swTestCases = []ChannelHandleTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvent: "stop_contact", ExpectedURN: "tel:+12028831111", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, @@ -244,9 +244,9 @@ var swTestCases = []ChannelHandleTestCase{ }, {Label: "Status No Params", URL: swStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring"}, {Label: "Status Invalid Status", URL: swStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'"}, - {Label: "Status Valid", URL: swStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, - {Label: "Status ID Valid", URL: swStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedMsgID: 12345}, - {Label: "Status ID Invalid", URL: swStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, + {Label: "Status Valid", URL: swStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, + {Label: "Status ID Valid", URL: swStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345}, + {Label: "Status ID Invalid", URL: swStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, } var waTestCases = []ChannelHandleTestCase{ @@ -269,11 +269,11 @@ var twaTestCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: twaStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: twaStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status Valid", URL: twaStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: twaStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedMsgID: 12345, + {Label: "Status ID Valid", URL: twaStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: twaStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + {Label: "Status ID Invalid", URL: twaStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, } diff --git a/handlers/twitter/twitter.go b/handlers/twitter/twitter.go index 8d77d1685..3fbea84be 100644 --- a/handlers/twitter/twitter.go +++ b/handlers/twitter/twitter.go @@ -248,7 +248,7 @@ type mtAttachment struct { } `json:"media"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(configAPIKey, "") apiSecret := msg.Channel().StringConfigForKey(configAPISecret, "") accessToken := msg.Channel().StringConfigForKey(configAccessToken, "") @@ -262,7 +262,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann token := oauth1.NewToken(accessToken, accessSecret) client := config.Client(ctx, token) - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // we build these as needed since our unit tests manipulate apiURL sendURL := sendDomain + "/1.1/direct_messages/events/new.json" @@ -349,7 +349,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann if err == nil { // this was wired successfully - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } } diff --git a/handlers/viber/viber.go b/handlers/viber/viber.go index 27108a981..3c85fef22 100644 --- a/handlers/viber/viber.go +++ b/handlers/viber/viber.go @@ -200,7 +200,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h case "failed": clog.SetType(courier.ChannelLogTypeMsgStatus) - msgStatus := h.Backend().NewMsgStatusForExternalID(channel, fmt.Sprintf("%d", payload.MessageToken), courier.MsgFailed, clog) + msgStatus := h.Backend().NewStatusUpdateByExternalID(channel, fmt.Sprintf("%d", payload.MessageToken), courier.MsgStatusFailed, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, msgStatus, w, r) case "delivered": @@ -349,13 +349,13 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { return nil, fmt.Errorf("missing auth token in config") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) // figure out whether we have a keyboard to send as well qrs := msg.QuickReplies() @@ -473,7 +473,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) keyboard = nil } return status, nil diff --git a/handlers/viber/viber_test.go b/handlers/viber/viber_test.go index b33715d25..fb663c7a6 100644 --- a/handlers/viber/viber_test.go +++ b/handlers/viber/viber_test.go @@ -506,7 +506,7 @@ var testCases = []ChannelHandleTestCase{ {Label: "Receive invalid Message Type", URL: receiveURL, Data: receiveInvalidMessageType, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown message type", PrepRequest: addValidSignature}, {Label: "Webhook validation", URL: receiveURL, Data: webhookCheck, ExpectedRespStatus: 200, ExpectedBodyContains: "webhook valid", PrepRequest: addValidSignature}, - {Label: "Failed Status Report", URL: receiveURL, Data: failedStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgFailed, PrepRequest: addValidSignature}, + {Label: "Failed Status Report", URL: receiveURL, Data: failedStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, PrepRequest: addValidSignature}, {Label: "Delivered Status Report", URL: receiveURL, Data: deliveredStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `Ignored`, PrepRequest: addValidSignature}, {Label: "Subcribe", URL: receiveURL, Data: validSubscribed, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvent: "new_conversation", ExpectedURN: "viber:01234567890A=", PrepRequest: addValidSignature}, {Label: "Subcribe Invalid URN", URL: receiveURL, Data: invalidURNSubscribed, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid viber id", PrepRequest: addValidSignature}, diff --git a/handlers/vk/vk.go b/handlers/vk/vk.go index 90b94f60e..c55cf264d 100644 --- a/handlers/vk/vk.go +++ b/handlers/vk/vk.go @@ -374,8 +374,8 @@ func takeFirstAttachmentUrl(payload moNewMessagePayload) string { return "" } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) params := buildApiBaseParams(msg.Channel()) params.Set(paramUserId, msg.URN().Path()) @@ -410,7 +410,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, errors.Errorf("no '%s' value in response", responseOutgoingMessageKey) } status.SetExternalID(strconv.FormatInt(externalMsgId, 10)) - status.SetStatus(courier.MsgSent) + status.SetStatus(courier.MsgStatusSent) return status, nil } diff --git a/handlers/wavy/wavy.go b/handlers/wavy/wavy.go index ec12c46ff..4ddefdc88 100644 --- a/handlers/wavy/wavy.go +++ b/handlers/wavy/wavy.go @@ -39,20 +39,20 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var statusMapping = map[int]courier.MsgStatusValue{ - 2: courier.MsgSent, - 4: courier.MsgDelivered, - 101: courier.MsgFailed, - 102: courier.MsgFailed, - 103: courier.MsgFailed, - 104: courier.MsgSent, - 201: courier.MsgFailed, - 202: courier.MsgFailed, - 203: courier.MsgFailed, - 204: courier.MsgFailed, - 205: courier.MsgFailed, - 207: courier.MsgFailed, - 301: courier.MsgErrored, +var statusMapping = map[int]courier.MsgStatus{ + 2: courier.MsgStatusSent, + 4: courier.MsgStatusDelivered, + 101: courier.MsgStatusFailed, + 102: courier.MsgStatusFailed, + 103: courier.MsgStatusFailed, + 104: courier.MsgStatusSent, + 201: courier.MsgStatusFailed, + 202: courier.MsgStatusFailed, + 203: courier.MsgStatusFailed, + 204: courier.MsgStatusFailed, + 205: courier.MsgStatusFailed, + 207: courier.MsgStatusFailed, + 301: courier.MsgStatusErrored, } type sentStatusPayload struct { @@ -68,7 +68,7 @@ func (h *handler) sentStatusMessage(ctx context.Context, channel courier.Channel } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.CollerationID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.CollerationID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -85,7 +85,7 @@ func (h *handler) deliveredStatusMessage(ctx context.Context, channel courier.Ch } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.CollerationID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.CollerationID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -120,7 +120,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for %s channel", msg.Channel().ChannelType()) @@ -131,7 +131,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no token set for %s channel", msg.Channel().ChannelType()) } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) payload := mtPayload{} payload.Destination = strings.TrimPrefix(msg.URN().Path(), "+") @@ -161,6 +161,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetExternalID(externalID) } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/handlers/wavy/wavy_test.go b/handlers/wavy/wavy_test.go index 70bb30263..6213dd3a3 100644 --- a/handlers/wavy/wavy_test.go +++ b/handlers/wavy/wavy_test.go @@ -106,7 +106,7 @@ var testCases = []ChannelHandleTestCase{ Data: validSentStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Status Update Accepted", - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, }, { Label: "Unknown Sent Status Valid", @@ -135,7 +135,7 @@ var testCases = []ChannelHandleTestCase{ Data: validDeliveredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Status Update Accepted", - ExpectedMsgStatus: courier.MsgDelivered, + ExpectedMsgStatus: courier.MsgStatusDelivered, }, { Label: "Unknown Delivered Status Valid", diff --git a/handlers/wechat/wechat.go b/handlers/wechat/wechat.go index 79308a8c9..c0731bd84 100644 --- a/handlers/wechat/wechat.go +++ b/handlers/wechat/wechat.go @@ -177,7 +177,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accessToken, err := h.getAccessToken(ctx, msg.Channel(), clog) if err != nil { return nil, err @@ -190,7 +190,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann partSendURL, _ := url.Parse(fmt.Sprintf("%s/%s", sendURL, "message/custom/send")) partSendURL.RawQuery = form.Encode() - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) for _, part := range parts { wcMsg := &mtPayload{} @@ -214,7 +214,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index b064828d2..679c4f1b1 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -269,8 +269,8 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w continue } - event := h.Backend().NewMsgStatusForExternalID(channel, status.ID, msgStatus, clog) - err := h.Backend().WriteMsgStatus(ctx, event) + event := h.Backend().NewStatusUpdateByExternalID(channel, status.ID, msgStatus, clog) + err := h.Backend().WriteStatusUpdate(ctx, event) if err != nil { return nil, err } @@ -318,12 +318,12 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, var _ courier.AttachmentRequestBuilder = (*handler)(nil) -var waStatusMapping = map[string]courier.MsgStatusValue{ - "sending": courier.MsgWired, - "sent": courier.MsgSent, - "delivered": courier.MsgDelivered, - "read": courier.MsgDelivered, - "failed": courier.MsgFailed, +var waStatusMapping = map[string]courier.MsgStatus{ + "sending": courier.MsgStatusWired, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, } var waIgnoreStatuses = map[string]bool{ @@ -492,7 +492,7 @@ type mtErrorPayload struct { const maxMsgLength = 4096 // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { conn := h.Backend().RedisPool().Get() defer conn.Close() @@ -509,7 +509,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } sendPath, _ := url.Parse("/v1/messages") - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) var wppID string @@ -544,7 +544,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann clog.RawError(err) } } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil diff --git a/handlers/yo/yo.go b/handlers/yo/yo.go index 9ea9ab280..7a24bd4c3 100644 --- a/handlers/yo/yo.go +++ b/handlers/yo/yo.go @@ -97,7 +97,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("no username set for YO channel") @@ -108,7 +108,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("no password set for YO channel") } - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) var err error for _, part := range handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) { @@ -140,7 +140,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // check whether we were blacklisted createMessage := responseQS["ybs_autocreate_message"] if len(createMessage) > 0 && strings.Contains(createMessage[0], "BLACKLISTED") { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) // create a stop channel event channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) @@ -155,7 +155,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // finally check that we were sent createStatus := responseQS["ybs_autocreate_status"] if len(createStatus) > 0 && createStatus[0] == "OK" { - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } } diff --git a/handlers/zenvia/zenvia.go b/handlers/zenvia/zenvia.go index bfe67b7b2..072dd8f25 100644 --- a/handlers/zenvia/zenvia.go +++ b/handlers/zenvia/zenvia.go @@ -129,12 +129,12 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, msgs, w, r, clog) } -var statusMapping = map[string]courier.MsgStatusValue{ - "REJECTED": courier.MsgFailed, - "NOT_DELIVERED": courier.MsgFailed, - "SENT": courier.MsgSent, - "DELIVERED": courier.MsgDelivered, - "READ": courier.MsgDelivered, +var statusMapping = map[string]courier.MsgStatus{ + "REJECTED": courier.MsgStatusFailed, + "NOT_DELIVERED": courier.MsgStatusFailed, + "SENT": courier.MsgStatusSent, + "DELIVERED": courier.MsgStatusDelivered, + "READ": courier.MsgStatusDelivered, } type statusPayload struct { @@ -155,11 +155,11 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w msgStatus, found := statusMapping[strings.ToUpper(payload.MessageStatus.Code)] if !found { - msgStatus = courier.MsgErrored + msgStatus = courier.MsgStatusErrored } // write our status - status := h.Backend().NewMsgStatusForExternalID(channel, payload.MessageID, msgStatus, clog) + status := h.Backend().NewStatusUpdateByExternalID(channel, payload.MessageID, msgStatus, clog) return handlers.WriteMsgStatusAndResponse(ctx, h, channel, status, w, r) } @@ -179,7 +179,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { channel := msg.Channel() token := channel.StringConfigForKey(courier.ConfigAPIKey, "") @@ -192,7 +192,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann To: strings.TrimLeft(msg.URN().Path(), "+"), } - status := h.Backend().NewMsgStatusForID(channel, msg.ID(), courier.MsgErrored, clog) + status := h.Backend().NewStatusUpdate(channel, msg.ID(), courier.MsgStatusErrored, clog) text := "" if channel.ChannelType() == "ZVW" { @@ -254,6 +254,6 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } status.SetExternalID(externalID) - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) return status, nil } diff --git a/log.go b/log.go index 773ec78fe..a89d3ccb3 100644 --- a/log.go +++ b/log.go @@ -8,7 +8,7 @@ import ( ) // LogMsgStatusReceived logs our that we received a new MsgStatus -func LogMsgStatusReceived(r *http.Request, status MsgStatus) { +func LogMsgStatusReceived(r *http.Request, status StatusUpdate) { log := logrus.WithFields(logrus.Fields{ "channel_uuid": status.ChannelUUID(), "url": r.Context().Value(contextRequestURL), diff --git a/responses.go b/responses.go index 94b8d6900..ad26a09eb 100644 --- a/responses.go +++ b/responses.go @@ -58,7 +58,7 @@ func WriteMsgSuccess(w http.ResponseWriter, msgs []Msg) error { } // WriteStatusSuccess writes a JSON response for the passed in status update indicating we handled it -func WriteStatusSuccess(w http.ResponseWriter, statuses []MsgStatus) error { +func WriteStatusSuccess(w http.ResponseWriter, statuses []StatusUpdate) error { data := []interface{}{} for _, status := range statuses { data = append(data, NewStatusData(status)) @@ -122,15 +122,15 @@ func NewEventReceiveData(event ChannelEvent) EventReceiveData { // StatusData is our response payload for a status update type StatusData struct { - Type string `json:"type"` - ChannelUUID ChannelUUID `json:"channel_uuid"` - Status MsgStatusValue `json:"status"` - MsgID MsgID `json:"msg_id,omitempty"` - ExternalID string `json:"external_id,omitempty"` + Type string `json:"type"` + ChannelUUID ChannelUUID `json:"channel_uuid"` + Status MsgStatus `json:"status"` + MsgID MsgID `json:"msg_id,omitempty"` + ExternalID string `json:"external_id,omitempty"` } // NewStatusData creates a new status data object for the passed in status -func NewStatusData(status MsgStatus) StatusData { +func NewStatusData(status StatusUpdate) StatusData { return StatusData{ "status", status.ChannelUUID(), diff --git a/sender.go b/sender.go index 715243ae7..27da570ed 100644 --- a/sender.go +++ b/sender.go @@ -187,7 +187,7 @@ func (w *Sender) sendMessage(msg Msg) { log.WithError(err).Error("error looking up msg was sent") } - var status MsgStatus + var status StatusUpdate var redactValues []string handler := server.GetHandler(msg.Channel()) if handler != nil { @@ -198,12 +198,12 @@ func (w *Sender) sendMessage(msg Msg) { if handler == nil { // if there's no handler, create a FAILED status for it - status = backend.NewMsgStatusForID(msg.Channel(), msg.ID(), MsgFailed, clog) + status = backend.NewStatusUpdate(msg.Channel(), msg.ID(), MsgStatusFailed, clog) log.Errorf("unable to find handler for channel type: %s", msg.Channel().ChannelType()) } else if sent { // if this message was already sent, create a WIRED status for it - status = backend.NewMsgStatusForID(msg.Channel(), msg.ID(), MsgWired, clog) + status = backend.NewStatusUpdate(msg.Channel(), msg.ID(), MsgStatusWired, clog) log.Warning("duplicate send, marking as wired") } else { @@ -222,12 +222,12 @@ func (w *Sender) sendMessage(msg Msg) { // possible for handlers to only return an error in which case we construct an error status if status == nil { - status = backend.NewMsgStatusForID(msg.Channel(), msg.ID(), MsgErrored, clog) + status = backend.NewStatusUpdate(msg.Channel(), msg.ID(), MsgStatusErrored, clog) } } // report to librato and log locally - if status.Status() == MsgErrored || status.Status() == MsgFailed { + if status.Status() == MsgStatusErrored || status.Status() == MsgStatusFailed { log.WithField("elapsed", duration).Warning("msg errored") analytics.Gauge(fmt.Sprintf("courier.msg_send_error_%s", msg.Channel().ChannelType()), secondDuration) } else { @@ -240,7 +240,7 @@ func (w *Sender) sendMessage(msg Msg) { writeCTX, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - err = backend.WriteMsgStatus(writeCTX, status) + err = backend.WriteStatusUpdate(writeCTX, status) if err != nil { log.WithError(err).Info("error writing msg status") } diff --git a/server.go b/server.go index 5d5e5cdb2..db8a74ca0 100644 --- a/server.go +++ b/server.go @@ -334,7 +334,7 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe clog.SetMsgID(e.ID()) analytics.Gauge(fmt.Sprintf("courier.msg_receive_%s", channel.ChannelType()), secondDuration) LogMsgReceived(r, e) - case MsgStatus: + case StatusUpdate: clog.SetMsgID(e.ID()) analytics.Gauge(fmt.Sprintf("courier.msg_status_%s", channel.ChannelType()), secondDuration) LogMsgStatusReceived(r, e) diff --git a/status.go b/status.go index f957f6677..47e299f35 100644 --- a/status.go +++ b/status.go @@ -2,27 +2,27 @@ package courier import "github.com/nyaruka/gocommon/urns" -// MsgStatusValue is the status of a message -type MsgStatusValue string +// MsgStatus is the status of a message +type MsgStatus string // Possible values for MsgStatus const ( - MsgPending MsgStatusValue = "P" - MsgQueued MsgStatusValue = "Q" - MsgSent MsgStatusValue = "S" - MsgWired MsgStatusValue = "W" - MsgErrored MsgStatusValue = "E" - MsgDelivered MsgStatusValue = "D" - MsgFailed MsgStatusValue = "F" - NilMsgStatus MsgStatusValue = "" + MsgStatusPending MsgStatus = "P" + MsgStatusQueued MsgStatus = "Q" + MsgStatusSent MsgStatus = "S" + MsgStatusWired MsgStatus = "W" + MsgStatusErrored MsgStatus = "E" + MsgStatusDelivered MsgStatus = "D" + MsgStatusFailed MsgStatus = "F" + NilMsgStatus MsgStatus = "" ) //----------------------------------------------------------------------------- -// MsgStatusUpdate Interface +// StatusUpdate Interface //----------------------------------------------------------------------------- -// MsgStatus represents a status update on a message -type MsgStatus interface { +// StatusUpdate represents a status update on a message +type StatusUpdate interface { EventID() int64 ChannelUUID() ChannelUUID @@ -35,6 +35,6 @@ type MsgStatus interface { ExternalID() string SetExternalID(string) - Status() MsgStatusValue - SetStatus(MsgStatusValue) + Status() MsgStatus + SetStatus(MsgStatus) } diff --git a/test/backend.go b/test/backend.go index 565259c6f..402326ead 100644 --- a/test/backend.go +++ b/test/backend.go @@ -43,7 +43,7 @@ type MockBackend struct { redisPool *redis.Pool writtenMsgs []courier.Msg - writtenMsgStatuses []courier.MsgStatus + writtenMsgStatuses []courier.StatusUpdate writtenChannelEvents []courier.ChannelEvent writtenChannelLogs []*courier.ChannelLog savedAttachments []*SavedAttachment @@ -167,7 +167,7 @@ func (mb *MockBackend) ClearMsgSent(ctx context.Context, id courier.MsgID) error } // MarkOutgoingMsgComplete marks the passed msg as having been dealt with -func (mb *MockBackend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, s courier.MsgStatus) { +func (mb *MockBackend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, s courier.StatusUpdate) { mb.mutex.Lock() defer mb.mutex.Unlock() @@ -214,8 +214,8 @@ func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courie return nil } -// NewMsgStatusForID creates a new Status object for the given message id -func (mb *MockBackend) NewMsgStatusForID(channel courier.Channel, id courier.MsgID, status courier.MsgStatusValue, clog *courier.ChannelLog) courier.MsgStatus { +// NewStatusUpdate creates a new Status object for the given message id +func (mb *MockBackend) NewStatusUpdate(channel courier.Channel, id courier.MsgID, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { return &mockMsgStatus{ channel: channel, id: id, @@ -224,8 +224,8 @@ func (mb *MockBackend) NewMsgStatusForID(channel courier.Channel, id courier.Msg } } -// NewMsgStatusForExternalID creates a new Status object for the given external id -func (mb *MockBackend) NewMsgStatusForExternalID(channel courier.Channel, externalID string, status courier.MsgStatusValue, clog *courier.ChannelLog) courier.MsgStatus { +// NewStatusUpdateByExternalID creates a new Status object for the given external id +func (mb *MockBackend) NewStatusUpdateByExternalID(channel courier.Channel, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { return &mockMsgStatus{ channel: channel, externalID: externalID, @@ -234,8 +234,8 @@ func (mb *MockBackend) NewMsgStatusForExternalID(channel courier.Channel, extern } } -// WriteMsgStatus writes the status update to our queue -func (mb *MockBackend) WriteMsgStatus(ctx context.Context, status courier.MsgStatus) error { +// WriteStatusUpdate writes the status update to our queue +func (mb *MockBackend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { mb.mutex.Lock() defer mb.mutex.Unlock() @@ -364,7 +364,7 @@ func (mb *MockBackend) RedisPool() *redis.Pool { //////////////////////////////////////////////////////////////////////////////// func (mb *MockBackend) WrittenMsgs() []courier.Msg { return mb.writtenMsgs } -func (mb *MockBackend) WrittenMsgStatuses() []courier.MsgStatus { return mb.writtenMsgStatuses } +func (mb *MockBackend) WrittenMsgStatuses() []courier.StatusUpdate { return mb.writtenMsgStatuses } func (mb *MockBackend) WrittenChannelEvents() []courier.ChannelEvent { return mb.writtenChannelEvents } func (mb *MockBackend) WrittenChannelLogs() []*courier.ChannelLog { return mb.writtenChannelLogs } func (mb *MockBackend) SavedAttachments() []*SavedAttachment { return mb.savedAttachments } diff --git a/test/handler.go b/test/handler.go index e3084bdf9..ef970f745 100644 --- a/test/handler.go +++ b/test/handler.go @@ -44,7 +44,7 @@ func (h *mockHandler) Initialize(s courier.Server) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *mockHandler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { +func (h *mockHandler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // log a request that contains a header value that should be redacted req, _ := httpx.NewRequest("GET", "http://mock.com/send", nil, map[string]string{"Authorization": "Token sesame"}) trace, _ := httpx.DoTrace(http.DefaultClient, req, nil, nil, 1024) @@ -53,10 +53,10 @@ func (h *mockHandler) Send(ctx context.Context, msg courier.Msg, clog *courier.C // log an error than contains a value that should be redacted clog.Error(courier.NewChannelError("seeds", "", "contains sesame seeds")) - return h.backend.NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgSent, clog), nil + return h.backend.NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusSent, clog), nil } -func (h *mockHandler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.MsgStatus) error { +func (h *mockHandler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { return courier.WriteStatusSuccess(w, statuses) } diff --git a/test/status.go b/test/status.go index 01fca36f2..ee1f195d4 100644 --- a/test/status.go +++ b/test/status.go @@ -13,7 +13,7 @@ type mockMsgStatus struct { oldURN urns.URN newURN urns.URN externalID string - status courier.MsgStatusValue + status courier.MsgStatus createdOn time.Time } @@ -39,5 +39,5 @@ func (m *mockMsgStatus) HasUpdatedURN() bool { func (m *mockMsgStatus) ExternalID() string { return m.externalID } func (m *mockMsgStatus) SetExternalID(id string) { m.externalID = id } -func (m *mockMsgStatus) Status() courier.MsgStatusValue { return m.status } -func (m *mockMsgStatus) SetStatus(status courier.MsgStatusValue) { m.status = status } +func (m *mockMsgStatus) Status() courier.MsgStatus { return m.status } +func (m *mockMsgStatus) SetStatus(status courier.MsgStatus) { m.status = status } From 45804f9726053d3eec6b398d9449b1761816aec7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 13:21:25 -0500 Subject: [PATCH 048/170] Update CHANGELOG.md for v8.3.6 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3fa91c52..20d4cdb4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.6 (2023-08-30) +------------------------- + * Rework writing msg statuses to always use id resolving + v8.3.5 (2023-08-30) ------------------------- * Rework writing status updates so that updates by external id also use the batcher From 63119559c928cff9014063f28f27fbfa4435fade Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 16:00:15 -0500 Subject: [PATCH 049/170] Sender deletion handled by mailroom task --- backend.go | 4 +- backends/rapidpro/backend.go | 35 ++++---- backends/rapidpro/backend_test.go | 122 ++++++++++++---------------- backends/rapidpro/task.go | 4 + handlers/facebookapp/facebookapp.go | 2 +- test/backend.go | 4 +- 6 files changed, 78 insertions(+), 93 deletions(-) diff --git a/backend.go b/backend.go index 44cbaedc0..4ec95af20 100644 --- a/backend.go +++ b/backend.go @@ -38,8 +38,8 @@ type Backend interface { // RemoveURNFromcontact removes a URN from the passed in contact RemoveURNfromContact(context context.Context, channel Channel, contact Contact, urn urns.URN) (urns.URN, error) - // DeleteMsgWithExternalID delete a message we receive an event that it should be deleted - DeleteMsgWithExternalID(ctx context.Context, channel Channel, externalID string) error + // DeleteMsgByExternalID deletes a message that has been deleted on the channel side + DeleteMsgByExternalID(ctx context.Context, channel Channel, externalID string) error // NewIncomingMsg creates a new message from the given params NewIncomingMsg(Channel, urns.URN, string, string, *ChannelLog) Msg diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 19a6b3ee0..99a21597c 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -120,25 +120,26 @@ func (b *backend) RemoveURNfromContact(ctx context.Context, c courier.Channel, c return urn, nil } -const updateMsgVisibilityDeletedBySender = ` -UPDATE - msgs_msg -SET - visibility = 'X', - text = '', - attachments = '{}' -WHERE - msgs_msg.id = (SELECT m."id" FROM "msgs_msg" m INNER JOIN "channels_channel" c ON (m."channel_id" = c."id") WHERE (c."uuid" = $1 AND m."external_id" = $2 AND m."direction" = 'I')) -RETURNING - msgs_msg.id -` +// DeleteMsgByExternalID resolves a message external id and quees a task to mailroom to delete it +func (b *backend) DeleteMsgByExternalID(ctx context.Context, channel courier.Channel, externalID string) error { + ch := channel.(*DBChannel) + row := b.db.QueryRowContext(ctx, `SELECT id, contact_id FROM msgs_msg WHERE channel_id = $1 AND external_id = $2 AND direction = 'I'`, ch.ID(), externalID) -// DeleteMsgWithExternalID delete a message we receive an event that it should be deleted -func (b *backend) DeleteMsgWithExternalID(ctx context.Context, channel courier.Channel, externalID string) error { - _, err := b.db.ExecContext(ctx, updateMsgVisibilityDeletedBySender, string(channel.UUID()), externalID) - if err != nil { - return err + var msgID courier.MsgID + var contactID ContactID + if err := row.Scan(&msgID, &contactID); err != nil && err != sql.ErrNoRows { + return errors.Wrap(err, "error querying deleted msg") } + + if msgID != courier.NilMsgID && contactID != NilContactID { + rc := b.redisPool.Get() + defer rc.Close() + + if err := queueMsgDeleted(rc, ch, msgID, contactID); err != nil { + return errors.Wrap(err, "error queuing message deleted task") + } + } + return nil } diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index ac060b788..7d7771b3a 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -74,11 +74,7 @@ func (ts *BackendTestSuite) SetupSuite() { } ts.b.db.MustExec(string(sql)) - // clear redis - r := ts.b.redisPool.Get() - defer r.Close() - _, err = r.Do("FLUSHDB") - ts.Require().NoError(err) + ts.clearRedis() } func (ts *BackendTestSuite) TearDownSuite() { @@ -90,6 +86,14 @@ func (ts *BackendTestSuite) TearDownSuite() { } } +func (ts *BackendTestSuite) clearRedis() { + // clear redis + r := ts.b.redisPool.Get() + defer r.Close() + _, err := r.Do("FLUSHDB") + ts.Require().NoError(err) +} + func (ts *BackendTestSuite) getChannel(cType string, cUUID string) *DBChannel { channelUUID := courier.ChannelUUID(cUUID) @@ -168,33 +172,27 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { ts.Equal("", msg.FlowUUID()) } -func (ts *BackendTestSuite) TestDeleteMsgWithExternalID() { +func (ts *BackendTestSuite) TestDeleteMsgByExternalID() { knChannel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") - ctx := context.Background() - // no error for invalid external ID - err := ts.b.DeleteMsgWithExternalID(ctx, knChannel, "ext-invalid") + ts.clearRedis() + + // noop for invalid external ID + err := ts.b.DeleteMsgByExternalID(ctx, knChannel, "ext-invalid") ts.Nil(err) - // cannot change out going messages - err = ts.b.DeleteMsgWithExternalID(ctx, knChannel, "ext1") + // noop for external ID of outgoing message + err = ts.b.DeleteMsgByExternalID(ctx, knChannel, "ext1") ts.Nil(err) - m := readMsgFromDB(ts.b, 10000) - ts.Equal(m.Text_, "test message") - ts.Equal(len(m.Attachments()), 0) - ts.Equal(m.Visibility_, MsgVisibility("V")) + ts.assertNoQueuedContactTask(ContactID(100)) - // for incoming messages mark them deleted by sender and readact their text and clear their attachments - err = ts.b.DeleteMsgWithExternalID(ctx, knChannel, "ext2") + // a valid external id becomes a queued task + err = ts.b.DeleteMsgByExternalID(ctx, knChannel, "ext2") ts.Nil(err) - m = readMsgFromDB(ts.b, 10002) - ts.Equal(m.Text_, "") - ts.Equal(len(m.Attachments()), 0) - ts.Equal(m.Visibility_, MsgVisibility("X")) - + ts.assertQueuedContactTask(ContactID(100), "msg_deleted", map[string]any{"org_id": float64(1), "msg_id": float64(10002)}) } func (ts *BackendTestSuite) TestContact() { @@ -1119,35 +1117,14 @@ func (ts *BackendTestSuite) TestWriteMsg() { err = writeMsgToDB(ctx, ts.b, msg, clog) ts.NoError(err) - // check that our mailroom queue has an item - rc := ts.b.redisPool.Get() - defer rc.Close() - rc.Do("DEL", "handler:1", "handler:active", fmt.Sprintf("c:1:%d", msg.ContactID_)) + ts.clearRedis() + // check that our mailroom queue has an item msg = ts.b.NewIncomingMsg(knChannel, urn, "hello 1 2 3", "", clog).(*DBMsg) err = writeMsgToDB(ctx, ts.b, msg, clog) ts.NoError(err) - count, err := redis.Int(rc.Do("ZCARD", "handler:1")) - ts.NoError(err) - ts.Equal(1, count) - - count, err = redis.Int(rc.Do("ZCARD", "handler:active")) - ts.NoError(err) - ts.Equal(1, count) - - count, err = redis.Int(rc.Do("LLEN", fmt.Sprintf("c:1:%d", msg.ContactID_))) - ts.NoError(err) - ts.Equal(1, count) - - data, err := redis.Bytes(rc.Do("LPOP", fmt.Sprintf("c:1:%d", contact.ID_))) - ts.NoError(err) - - var body map[string]interface{} - err = json.Unmarshal(data, &body) - ts.NoError(err) - ts.Equal("msg_event", body["type"]) - ts.Equal(map[string]interface{}{ + ts.assertQueuedContactTask(msg.ContactID_, "msg_event", map[string]any{ "contact_id": float64(contact.ID_), "org_id": float64(1), "channel_id": float64(10), @@ -1159,7 +1136,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { "text": msg.Text(), "attachments": nil, "new_contact": contact.IsNew_, - }, body["task"]) + }) } func (ts *BackendTestSuite) TestWriteMsgWithAttachments() { @@ -1279,9 +1256,7 @@ func (ts *BackendTestSuite) TestSessionTimeout() { func (ts *BackendTestSuite) TestMailroomEvents() { ctx := context.Background() - rc := ts.b.redisPool.Get() - defer rc.Close() - rc.Do("FLUSHDB") + ts.clearRedis() channel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) @@ -1305,26 +1280,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) - count, err := redis.Int(rc.Do("ZCARD", "handler:1")) - ts.NoError(err) - ts.Equal(1, count) - - count, err = redis.Int(rc.Do("ZCARD", "handler:active")) - ts.NoError(err) - ts.Equal(1, count) - - count, err = redis.Int(rc.Do("LLEN", fmt.Sprintf("c:1:%d", contact.ID_))) - ts.NoError(err) - ts.Equal(1, count) - - data, err := redis.Bytes(rc.Do("LPOP", fmt.Sprintf("c:1:%d", contact.ID_))) - ts.NoError(err) - - var body map[string]interface{} - err = json.Unmarshal(data, &body) - ts.NoError(err) - ts.Equal("referral", body["type"]) - ts.Equal(map[string]interface{}{ + ts.assertQueuedContactTask(contact.ID_, "referral", map[string]any{ "channel_id": float64(10), "contact_id": float64(contact.ID_), "extra": map[string]interface{}{"ref_id": "12345"}, @@ -1332,7 +1288,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { "occurred_on": "2020-08-05T13:30:00.123456789Z", "org_id": float64(1), "urn_id": float64(contact.URNID_), - }, body["task"]) + }) } func (ts *BackendTestSuite) TestResolveMedia() { @@ -1426,6 +1382,30 @@ func (ts *BackendTestSuite) TestResolveMedia() { assertredis.HLen(ts.T(), ts.b.redisPool, fmt.Sprintf("media-lookups:%s", time.Now().In(time.UTC).Format("2006-01-02")), 3) } +func (ts *BackendTestSuite) assertNoQueuedContactTask(contactID ContactID) { + assertredis.ZCard(ts.T(), ts.b.redisPool, "handler:1", 0) + assertredis.ZCard(ts.T(), ts.b.redisPool, "handler:active", 0) + assertredis.LLen(ts.T(), ts.b.redisPool, fmt.Sprintf("c:1:%d", contactID), 0) +} + +func (ts *BackendTestSuite) assertQueuedContactTask(contactID ContactID, expectedType string, expectedBody map[string]any) { + assertredis.ZCard(ts.T(), ts.b.redisPool, "handler:1", 1) + assertredis.ZCard(ts.T(), ts.b.redisPool, "handler:active", 1) + assertredis.LLen(ts.T(), ts.b.redisPool, fmt.Sprintf("c:1:%d", contactID), 1) + + rc := ts.b.redisPool.Get() + defer rc.Close() + + data, err := redis.Bytes(rc.Do("LPOP", fmt.Sprintf("c:1:%d", contactID))) + ts.NoError(err) + + var body map[string]any + err = json.Unmarshal(data, &body) + ts.NoError(err) + ts.Equal(expectedType, body["type"]) + ts.Equal(expectedBody, body["task"]) +} + func TestMsgSuite(t *testing.T) { suite.Run(t, new(BackendTestSuite)) } diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index 204338672..8bb3a13c6 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -81,6 +81,10 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { } } +func queueMsgDeleted(rc redis.Conn, ch *DBChannel, msgID courier.MsgID, contactID ContactID) error { + return queueMailroomTask(rc, "msg_deleted", ch.OrgID_, contactID, map[string]any{"org_id": ch.OrgID_, "msg_id": msgID}) +} + // queueMailroomTask queues the passed in task to mailroom. Mailroom processes both messages and // channel event tasks through the same ordered queue. func queueMailroomTask(rc redis.Conn, taskType string, orgID OrgID, contactID ContactID, body map[string]interface{}) (err error) { diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 7ba0afbe3..a916ef0f5 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -699,7 +699,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } if msg.Message.IsDeleted { - h.Backend().DeleteMsgWithExternalID(ctx, channel, msg.Message.MID) + h.Backend().DeleteMsgByExternalID(ctx, channel, msg.Message.MID) data = append(data, courier.NewInfoData("msg deleted")) continue } diff --git a/test/backend.go b/test/backend.go index 402326ead..c0a2734fa 100644 --- a/test/backend.go +++ b/test/backend.go @@ -90,8 +90,8 @@ func NewMockBackend() *MockBackend { } } -// DeleteMsgWithExternalID delete a message we receive an event that it should be deleted -func (mb *MockBackend) DeleteMsgWithExternalID(ctx context.Context, channel courier.Channel, externalID string) error { +// DeleteMsgByExternalID delete a message we receive an event that it should be deleted +func (mb *MockBackend) DeleteMsgByExternalID(ctx context.Context, channel courier.Channel, externalID string) error { return nil } From 77e2f6a9ec3a226e712b3669eb12de68bc287af9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 30 Aug 2023 18:02:28 -0500 Subject: [PATCH 050/170] Update CHANGELOG.md for v8.3.7 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d4cdb4e..8ac978d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.7 (2023-08-30) +------------------------- + * Sender deletion handled by mailroom task + v8.3.6 (2023-08-30) ------------------------- * Rework writing msg statuses to always use id resolving From 349912562ed7be0d93ce2520f30e99a76bc25e5d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 11:50:35 -0500 Subject: [PATCH 051/170] Update to latest gocommon --- backends/rapidpro/channel_log.go | 4 ++-- backends/rapidpro/status.go | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 2f5ac273e..25f72742f 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -120,7 +120,7 @@ func NewDBLogWriter(db *sqlx.DB, wg *sync.WaitGroup) *DBLogWriter { defer cancel() writeDBChannelLogs(ctx, db, batch) - }, time.Millisecond*500, 1000, wg), + }, 1000, time.Millisecond*500, 1000, wg), } } @@ -158,7 +158,7 @@ func NewStorageLogWriter(st storage.Storage, wg *sync.WaitGroup) *StorageLogWrit defer cancel() writeStorageChannelLogs(ctx, st, batch) - }, time.Millisecond*500, 1000, wg), + }, 1000, time.Millisecond*500, 1000, wg), } } diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index df1a82033..eb6060c54 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -195,7 +195,7 @@ func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWr defer cancel() writeStatuseUpdates(ctx, db, spoolDir, batch) - }, time.Millisecond*500, 1000, wg), + }, 1000, time.Millisecond*500, 1000, wg), } } diff --git a/go.mod b/go.mod index d7c172185..7f7f5ca8c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.44.319 + github.com/aws/aws-sdk-go v1.45.0 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 github.com/evalphobia/logrus_sentry v0.8.2 @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.39.1 + github.com/nyaruka/gocommon v1.40.0 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -50,7 +50,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect diff --git a/go.sum b/go.sum index 567ca51fa..b73c79a20 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= -github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.0 h1:qoVOQHuLacxJMO71T49KeE70zm+Tk3vtrl7XO4VUPZc= +github.com/aws/aws-sdk-go v1.45.0/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -70,8 +70,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.39.1 h1:C1LM6y0iGe3oTJPknb+FcNyz8yl4+CdsvRD36wAai38= -github.com/nyaruka/gocommon v1.39.1/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= +github.com/nyaruka/gocommon v1.40.0 h1:PvwLljY8rirgUu1ROwZM+gkTdNRbOnrGBFBf70OZt/8= +github.com/nyaruka/gocommon v1.40.0/go.mod h1:J+1hsRQ7GPJKGbU9IUTKIvP74ONG3iiM5y5S9dfqSq4= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -108,8 +108,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From abd94360c6255b2f143764632eeb9e98c02ec4c9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 12:07:53 -0500 Subject: [PATCH 052/170] Revert "Update to latest gocommon" This reverts commit 349912562ed7be0d93ce2520f30e99a76bc25e5d. --- backends/rapidpro/channel_log.go | 4 ++-- backends/rapidpro/status.go | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 25f72742f..2f5ac273e 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -120,7 +120,7 @@ func NewDBLogWriter(db *sqlx.DB, wg *sync.WaitGroup) *DBLogWriter { defer cancel() writeDBChannelLogs(ctx, db, batch) - }, 1000, time.Millisecond*500, 1000, wg), + }, time.Millisecond*500, 1000, wg), } } @@ -158,7 +158,7 @@ func NewStorageLogWriter(st storage.Storage, wg *sync.WaitGroup) *StorageLogWrit defer cancel() writeStorageChannelLogs(ctx, st, batch) - }, 1000, time.Millisecond*500, 1000, wg), + }, time.Millisecond*500, 1000, wg), } } diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index eb6060c54..df1a82033 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -195,7 +195,7 @@ func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWr defer cancel() writeStatuseUpdates(ctx, db, spoolDir, batch) - }, 1000, time.Millisecond*500, 1000, wg), + }, time.Millisecond*500, 1000, wg), } } diff --git a/go.mod b/go.mod index 7f7f5ca8c..d7c172185 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.45.0 + github.com/aws/aws-sdk-go v1.44.319 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 github.com/evalphobia/logrus_sentry v0.8.2 @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.40.0 + github.com/nyaruka/gocommon v1.39.1 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -50,7 +50,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect diff --git a/go.sum b/go.sum index b73c79a20..567ca51fa 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.45.0 h1:qoVOQHuLacxJMO71T49KeE70zm+Tk3vtrl7XO4VUPZc= -github.com/aws/aws-sdk-go v1.45.0/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= +github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -70,8 +70,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.40.0 h1:PvwLljY8rirgUu1ROwZM+gkTdNRbOnrGBFBf70OZt/8= -github.com/nyaruka/gocommon v1.40.0/go.mod h1:J+1hsRQ7GPJKGbU9IUTKIvP74ONG3iiM5y5S9dfqSq4= +github.com/nyaruka/gocommon v1.39.1 h1:C1LM6y0iGe3oTJPknb+FcNyz8yl4+CdsvRD36wAai38= +github.com/nyaruka/gocommon v1.39.1/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -108,8 +108,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 8becc096f6728a7cbbd2555a3ce0c8475a1d8bb4 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 12:10:05 -0500 Subject: [PATCH 053/170] More intuitive ordering of methods in backend.go --- backends/rapidpro/backend.go | 494 +++++++++++++++++------------------ 1 file changed, 242 insertions(+), 252 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 99a21597c..65abe665f 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -50,6 +50,247 @@ func init() { courier.RegisterBackend("rapidpro", newBackend) } +type backend struct { + config *courier.Config + + statusWriter *StatusWriter + dbLogWriter *DBLogWriter // unattached logs being written to the database + stLogWriter *StorageLogWriter // attached logs being written to storage + writerWG *sync.WaitGroup + + db *sqlx.DB + redisPool *redis.Pool + attachmentStorage storage.Storage + logStorage storage.Storage + + stopChan chan bool + waitGroup *sync.WaitGroup + + mediaCache *redisx.IntervalHash + mediaMutexes syncx.HashMutex + + seenMsgs *redisx.IntervalHash + seenExternalIDs *redisx.IntervalHash + + // both sqlx and redis provide wait stats which are cummulative that we need to convert into increments + dbWaitDuration time.Duration + dbWaitCount int64 + redisWaitDuration time.Duration + redisWaitCount int64 +} + +// NewBackend creates a new RapidPro backend +func newBackend(cfg *courier.Config) courier.Backend { + return &backend{ + config: cfg, + + stopChan: make(chan bool), + waitGroup: &sync.WaitGroup{}, + + writerWG: &sync.WaitGroup{}, + + mediaCache: redisx.NewIntervalHash("media-lookups", time.Hour*24, 2), + mediaMutexes: *syncx.NewHashMutex(8), + + seenMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), + seenExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), + } +} + +// Start starts our RapidPro backend, this tests our various connections and starts our spool flushers +func (b *backend) Start() error { + // parse and test our redis config + log := logrus.WithFields(logrus.Fields{ + "comp": "backend", + "state": "starting", + }) + log.Info("starting backend") + + // parse and test our db config + dbURL, err := url.Parse(b.config.DB) + if err != nil { + return fmt.Errorf("unable to parse DB URL '%s': %s", b.config.DB, err) + } + + if dbURL.Scheme != "postgres" { + return fmt.Errorf("invalid DB URL: '%s', only postgres is supported", b.config.DB) + } + + // build our db + db, err := sqlx.Open("postgres", b.config.DB) + if err != nil { + return fmt.Errorf("unable to open DB with config: '%s': %s", b.config.DB, err) + } + + // configure our pool + b.db = db + b.db.SetMaxIdleConns(4) + b.db.SetMaxOpenConns(16) + + // try connecting + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + err = b.db.PingContext(ctx) + cancel() + if err != nil { + log.WithError(err).Error("db not reachable") + } else { + log.Info("db ok") + } + + // parse and test our redis config + redisURL, err := url.Parse(b.config.Redis) + if err != nil { + return fmt.Errorf("unable to parse Redis URL '%s': %s", b.config.Redis, err) + } + + // create our pool + redisPool := &redis.Pool{ + Wait: true, // makes callers wait for a connection + MaxActive: 36, // only open this many concurrent connections at once + MaxIdle: 4, // only keep up to this many idle + IdleTimeout: 240 * time.Second, // how long to wait before reaping a connection + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial("tcp", redisURL.Host) + if err != nil { + return nil, err + } + + // send auth if required + if redisURL.User != nil { + pass, authRequired := redisURL.User.Password() + if authRequired { + if _, err := conn.Do("AUTH", pass); err != nil { + conn.Close() + return nil, err + } + } + } + + // switch to the right DB + _, err = conn.Do("SELECT", strings.TrimLeft(redisURL.Path, "/")) + return conn, err + }, + } + b.redisPool = redisPool + + // test our redis connection + conn := redisPool.Get() + defer conn.Close() + _, err = conn.Do("PING") + if err != nil { + log.WithError(err).Error("redis not reachable") + } else { + log.Info("redis ok") + } + + // start our dethrottler if we are going to be doing some sending + if b.config.MaxWorkers > 0 { + queue.StartDethrottler(redisPool, b.stopChan, b.waitGroup, msgQueueName) + } + + // create our storage (S3 or file system) + if b.config.AWSAccessKeyID != "" || b.config.AWSUseCredChain { + s3config := &storage.S3Options{ + AWSAccessKeyID: b.config.AWSAccessKeyID, + AWSSecretAccessKey: b.config.AWSSecretAccessKey, + Endpoint: b.config.S3Endpoint, + Region: b.config.S3Region, + DisableSSL: b.config.S3DisableSSL, + ForcePathStyle: b.config.S3ForcePathStyle, + MaxRetries: 3, + } + if b.config.AWSAccessKeyID != "" && !b.config.AWSUseCredChain { + s3config.AWSAccessKeyID = b.config.AWSAccessKeyID + s3config.AWSSecretAccessKey = b.config.AWSSecretAccessKey + } + s3Client, err := storage.NewS3Client(s3config) + if err != nil { + return err + } + b.attachmentStorage = storage.NewS3(s3Client, b.config.S3AttachmentsBucket, b.config.S3Region, s3.BucketCannedACLPublicRead, 32) + b.logStorage = storage.NewS3(s3Client, b.config.S3LogsBucket, b.config.S3Region, s3.BucketCannedACLPrivate, 32) + } else { + b.attachmentStorage = storage.NewFS(storageDir+"/attachments", 0766) + b.logStorage = storage.NewFS(storageDir+"/logs", 0766) + } + + // check our storages + if err := checkStorage(b.attachmentStorage); err != nil { + log.WithError(err).Error(b.attachmentStorage.Name() + " attachment storage not available") + } else { + log.Info(b.attachmentStorage.Name() + " attachment storage ok") + } + if err := checkStorage(b.logStorage); err != nil { + log.WithError(err).Error(b.logStorage.Name() + " log storage not available") + } else { + log.Info(b.logStorage.Name() + " log storage ok") + } + + // make sure our spool dirs are writable + err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "msgs") + if err == nil { + err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "statuses") + } + if err == nil { + err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "events") + } + if err != nil { + log.WithError(err).Error("spool directories not writable") + } else { + log.Info("spool directories ok") + } + + // create our batched writers and start them + b.statusWriter = NewStatusWriter(b.db, b.config.SpoolDir, b.writerWG) + b.statusWriter.Start() + + b.dbLogWriter = NewDBLogWriter(b.db, b.writerWG) + b.dbLogWriter.Start() + + b.stLogWriter = NewStorageLogWriter(b.logStorage, b.writerWG) + b.stLogWriter.Start() + + // register and start our spool flushers + courier.RegisterFlusher(path.Join(b.config.SpoolDir, "msgs"), b.flushMsgFile) + courier.RegisterFlusher(path.Join(b.config.SpoolDir, "statuses"), b.flushStatusFile) + courier.RegisterFlusher(path.Join(b.config.SpoolDir, "events"), b.flushChannelEventFile) + + logrus.WithFields(logrus.Fields{"comp": "backend", "state": "started"}).Info("backend started") + return nil +} + +// Stop stops our RapidPro backend, closing our db and redis connections +func (b *backend) Stop() error { + // close our stop channel + close(b.stopChan) + + // wait for our threads to exit + b.waitGroup.Wait() + return nil +} + +func (b *backend) Cleanup() error { + // stop our batched writers + if b.statusWriter != nil { + b.statusWriter.Stop() + } + if b.dbLogWriter != nil { + b.dbLogWriter.Stop() + } + if b.stLogWriter != nil { + b.stLogWriter.Stop() + } + + // wait for them to flush fully + b.writerWG.Wait() + + // close our db and redis pool + if b.db != nil { + b.db.Close() + } + return b.redisPool.Close() +} + // GetChannel returns the channel for the passed in type and UUID func (b *backend) GetChannel(ctx context.Context, ct courier.ChannelType, uuid courier.ChannelUUID) (courier.Channel, error) { timeout, cancel := context.WithTimeout(ctx, backendTimeout) @@ -100,20 +341,10 @@ func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contac return urn, nil } -const removeURNFromContact = ` -UPDATE - contacts_contacturn -SET - contact_id = NULL -WHERE - contact_id = $1 AND - identity = $2 -` - // RemoveURNFromcontact removes a URN from the passed in contact func (b *backend) RemoveURNfromContact(ctx context.Context, c courier.Channel, contact courier.Contact, urn urns.URN) (urns.URN, error) { dbContact := contact.(*DBContact) - _, err := b.db.ExecContext(ctx, removeURNFromContact, dbContact.ID_, urn.Identity().String()) + _, err := b.db.ExecContext(ctx, `UPDATE contacts_contacturn SET contact_id = NULL WHERE contact_id = $1 AND identity = $2`, dbContact.ID_, urn.Identity().String()) if err != nil { return urns.NilURN, err } @@ -627,252 +858,11 @@ func (b *backend) Status() string { return status.String() } -// Start starts our RapidPro backend, this tests our various connections and starts our spool flushers -func (b *backend) Start() error { - // parse and test our redis config - log := logrus.WithFields(logrus.Fields{ - "comp": "backend", - "state": "starting", - }) - log.Info("starting backend") - - // parse and test our db config - dbURL, err := url.Parse(b.config.DB) - if err != nil { - return fmt.Errorf("unable to parse DB URL '%s': %s", b.config.DB, err) - } - - if dbURL.Scheme != "postgres" { - return fmt.Errorf("invalid DB URL: '%s', only postgres is supported", b.config.DB) - } - - // build our db - db, err := sqlx.Open("postgres", b.config.DB) - if err != nil { - return fmt.Errorf("unable to open DB with config: '%s': %s", b.config.DB, err) - } - - // configure our pool - b.db = db - b.db.SetMaxIdleConns(4) - b.db.SetMaxOpenConns(16) - - // try connecting - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - err = b.db.PingContext(ctx) - cancel() - if err != nil { - log.WithError(err).Error("db not reachable") - } else { - log.Info("db ok") - } - - // parse and test our redis config - redisURL, err := url.Parse(b.config.Redis) - if err != nil { - return fmt.Errorf("unable to parse Redis URL '%s': %s", b.config.Redis, err) - } - - // create our pool - redisPool := &redis.Pool{ - Wait: true, // makes callers wait for a connection - MaxActive: 36, // only open this many concurrent connections at once - MaxIdle: 4, // only keep up to this many idle - IdleTimeout: 240 * time.Second, // how long to wait before reaping a connection - Dial: func() (redis.Conn, error) { - conn, err := redis.Dial("tcp", redisURL.Host) - if err != nil { - return nil, err - } - - // send auth if required - if redisURL.User != nil { - pass, authRequired := redisURL.User.Password() - if authRequired { - if _, err := conn.Do("AUTH", pass); err != nil { - conn.Close() - return nil, err - } - } - } - - // switch to the right DB - _, err = conn.Do("SELECT", strings.TrimLeft(redisURL.Path, "/")) - return conn, err - }, - } - b.redisPool = redisPool - - // test our redis connection - conn := redisPool.Get() - defer conn.Close() - _, err = conn.Do("PING") - if err != nil { - log.WithError(err).Error("redis not reachable") - } else { - log.Info("redis ok") - } - - // start our dethrottler if we are going to be doing some sending - if b.config.MaxWorkers > 0 { - queue.StartDethrottler(redisPool, b.stopChan, b.waitGroup, msgQueueName) - } - - // create our storage (S3 or file system) - if b.config.AWSAccessKeyID != "" || b.config.AWSUseCredChain { - s3config := &storage.S3Options{ - AWSAccessKeyID: b.config.AWSAccessKeyID, - AWSSecretAccessKey: b.config.AWSSecretAccessKey, - Endpoint: b.config.S3Endpoint, - Region: b.config.S3Region, - DisableSSL: b.config.S3DisableSSL, - ForcePathStyle: b.config.S3ForcePathStyle, - MaxRetries: 3, - } - if b.config.AWSAccessKeyID != "" && !b.config.AWSUseCredChain { - s3config.AWSAccessKeyID = b.config.AWSAccessKeyID - s3config.AWSSecretAccessKey = b.config.AWSSecretAccessKey - } - s3Client, err := storage.NewS3Client(s3config) - if err != nil { - return err - } - b.attachmentStorage = storage.NewS3(s3Client, b.config.S3AttachmentsBucket, b.config.S3Region, s3.BucketCannedACLPublicRead, 32) - b.logStorage = storage.NewS3(s3Client, b.config.S3LogsBucket, b.config.S3Region, s3.BucketCannedACLPrivate, 32) - } else { - b.attachmentStorage = storage.NewFS(storageDir+"/attachments", 0766) - b.logStorage = storage.NewFS(storageDir+"/logs", 0766) - } - - // check our storages - if err := checkStorage(b.attachmentStorage); err != nil { - log.WithError(err).Error(b.attachmentStorage.Name() + " attachment storage not available") - } else { - log.Info(b.attachmentStorage.Name() + " attachment storage ok") - } - if err := checkStorage(b.logStorage); err != nil { - log.WithError(err).Error(b.logStorage.Name() + " log storage not available") - } else { - log.Info(b.logStorage.Name() + " log storage ok") - } - - // make sure our spool dirs are writable - err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "msgs") - if err == nil { - err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "statuses") - } - if err == nil { - err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "events") - } - if err != nil { - log.WithError(err).Error("spool directories not writable") - } else { - log.Info("spool directories ok") - } - - // create our batched writers and start them - b.statusWriter = NewStatusWriter(b.db, b.config.SpoolDir, b.writerWG) - b.statusWriter.Start() - - b.dbLogWriter = NewDBLogWriter(b.db, b.writerWG) - b.dbLogWriter.Start() - - b.stLogWriter = NewStorageLogWriter(b.logStorage, b.writerWG) - b.stLogWriter.Start() - - // register and start our spool flushers - courier.RegisterFlusher(path.Join(b.config.SpoolDir, "msgs"), b.flushMsgFile) - courier.RegisterFlusher(path.Join(b.config.SpoolDir, "statuses"), b.flushStatusFile) - courier.RegisterFlusher(path.Join(b.config.SpoolDir, "events"), b.flushChannelEventFile) - - logrus.WithFields(logrus.Fields{"comp": "backend", "state": "started"}).Info("backend started") - return nil -} - -// Stop stops our RapidPro backend, closing our db and redis connections -func (b *backend) Stop() error { - // close our stop channel - close(b.stopChan) - - // wait for our threads to exit - b.waitGroup.Wait() - return nil -} - -func (b *backend) Cleanup() error { - // stop our batched writers - if b.statusWriter != nil { - b.statusWriter.Stop() - } - if b.dbLogWriter != nil { - b.dbLogWriter.Stop() - } - if b.stLogWriter != nil { - b.stLogWriter.Stop() - } - - // wait for them to flush fully - b.writerWG.Wait() - - // close our db and redis pool - if b.db != nil { - b.db.Close() - } - return b.redisPool.Close() -} - // RedisPool returns the redisPool for this backend func (b *backend) RedisPool() *redis.Pool { return b.redisPool } -// NewBackend creates a new RapidPro backend -func newBackend(cfg *courier.Config) courier.Backend { - return &backend{ - config: cfg, - - stopChan: make(chan bool), - waitGroup: &sync.WaitGroup{}, - - writerWG: &sync.WaitGroup{}, - - mediaCache: redisx.NewIntervalHash("media-lookups", time.Hour*24, 2), - mediaMutexes: *syncx.NewHashMutex(8), - - seenMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), - seenExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), - } -} - -type backend struct { - config *courier.Config - - statusWriter *StatusWriter - dbLogWriter *DBLogWriter // unattached logs being written to the database - stLogWriter *StorageLogWriter // attached logs being written to storage - writerWG *sync.WaitGroup - - db *sqlx.DB - redisPool *redis.Pool - attachmentStorage storage.Storage - logStorage storage.Storage - - stopChan chan bool - waitGroup *sync.WaitGroup - - mediaCache *redisx.IntervalHash - mediaMutexes syncx.HashMutex - - seenMsgs *redisx.IntervalHash - seenExternalIDs *redisx.IntervalHash - - // both sqlx and redis provide wait stats which are cummulative that we need to convert into increments - dbWaitDuration time.Duration - dbWaitCount int64 - redisWaitDuration time.Duration - redisWaitCount int64 -} - func checkStorage(s storage.Storage) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) err := s.Test(ctx) From 469c69919adc3ceb9bd31badd5be4ca011b4c09e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 12:26:23 -0500 Subject: [PATCH 054/170] Update to new batchers in gocommon --- backends/rapidpro/backend_test.go | 4 +-- backends/rapidpro/channel_log.go | 39 +++++++++++------------- backends/rapidpro/status.go | 50 +++++++++++++++---------------- go.mod | 6 ++-- go.sum | 10 ++++--- 5 files changed, 53 insertions(+), 56 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 7d7771b3a..51a18aaad 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -473,7 +473,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { } err := ts.b.WriteStatusUpdate(ctx, statusObj) ts.NoError(err) - time.Sleep(500 * time.Millisecond) // give committer time to write this + time.Sleep(600 * time.Millisecond) // give committer time to write this return clog } @@ -482,7 +482,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { statusObj := ts.b.NewStatusUpdateByExternalID(channel, extID, status, clog) err := ts.b.WriteStatusUpdate(ctx, statusObj) ts.NoError(err) - time.Sleep(500 * time.Millisecond) // give committer time to write this + time.Sleep(600 * time.Millisecond) // give committer time to write this return clog } diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 2f5ac273e..3d558a394 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -10,7 +10,6 @@ import ( "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" - "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" @@ -120,28 +119,26 @@ func NewDBLogWriter(db *sqlx.DB, wg *sync.WaitGroup) *DBLogWriter { defer cancel() writeDBChannelLogs(ctx, db, batch) - }, time.Millisecond*500, 1000, wg), + }, 1000, time.Millisecond*500, 1000, wg), } } -func writeDBChannelLogs(ctx context.Context, db *sqlx.DB, logs []*dbChannelLog) { - for _, batch := range utils.ChunkSlice(logs, 1000) { - err := dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, batch) +func writeDBChannelLogs(ctx context.Context, db *sqlx.DB, batch []*dbChannelLog) { + err := dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, batch) - // if we received an error, try again one at a time (in case it is one value hanging us up) - if err != nil { - for _, v := range batch { - err = dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, []*dbChannelLog{v}) - if err != nil { - log := logrus.WithField("comp", "log writer").WithField("log_uuid", v.UUID) + // if we received an error, try again one at a time (in case it is one value hanging us up) + if err != nil { + for _, v := range batch { + err = dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, []*dbChannelLog{v}) + if err != nil { + log := logrus.WithField("comp", "log writer").WithField("log_uuid", v.UUID) - if qerr := dbutil.AsQueryError(err); qerr != nil { - query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) - } - - log.WithError(err).Error("error writing channel log") + if qerr := dbutil.AsQueryError(err); qerr != nil { + query, params := qerr.Query() + log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) } + + log.WithError(err).Error("error writing channel log") } } } @@ -158,13 +155,13 @@ func NewStorageLogWriter(st storage.Storage, wg *sync.WaitGroup) *StorageLogWrit defer cancel() writeStorageChannelLogs(ctx, st, batch) - }, time.Millisecond*500, 1000, wg), + }, 1000, time.Millisecond*500, 1000, wg), } } -func writeStorageChannelLogs(ctx context.Context, st storage.Storage, logs []*stChannelLog) { - uploads := make([]*storage.Upload, len(logs)) - for i, l := range logs { +func writeStorageChannelLogs(ctx context.Context, st storage.Storage, batch []*stChannelLog) { + uploads := make([]*storage.Upload, len(batch)) + for i, l := range batch { uploads[i] = &storage.Upload{ Path: l.path(), ContentType: "application/json", diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index df1a82033..dc9efc83a 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -11,7 +11,6 @@ import ( "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" - "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/syncx" "github.com/nyaruka/gocommon/urns" @@ -195,41 +194,40 @@ func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWr defer cancel() writeStatuseUpdates(ctx, db, spoolDir, batch) - }, time.Millisecond*500, 1000, wg), + + }, 1000, time.Millisecond*500, 1000, wg), } } -// tries to write all the message statuses to the database and spools those that fail -func writeStatuseUpdates(ctx context.Context, db *sqlx.DB, spoolDir string, statuses []*StatusUpdate) { +// tries to write a batch of message statuses to the database and spools those that fail +func writeStatuseUpdates(ctx context.Context, db *sqlx.DB, spoolDir string, batch []*StatusUpdate) { log := logrus.WithField("comp", "status writer") - for _, batch := range utils.ChunkSlice(statuses, 1000) { - unresolved, err := writeStatusUpdatesToDB(ctx, db, batch) - - // if we received an error, try again one at a time (in case it is one value hanging us up) - if err != nil { - for _, s := range batch { - _, err = writeStatusUpdatesToDB(ctx, db, []*StatusUpdate{s}) - if err != nil { - log := log.WithField("msg_id", s.ID()) + unresolved, err := writeStatusUpdatesToDB(ctx, db, batch) - if qerr := dbutil.AsQueryError(err); qerr != nil { - query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) - } + // if we received an error, try again one at a time (in case it is one value hanging us up) + if err != nil { + for _, s := range batch { + _, err = writeStatusUpdatesToDB(ctx, db, []*StatusUpdate{s}) + if err != nil { + log := log.WithField("msg_id", s.ID()) + + if qerr := dbutil.AsQueryError(err); qerr != nil { + query, params := qerr.Query() + log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + } - log.WithError(err).Error("error writing msg status") + log.WithError(err).Error("error writing msg status") - err := courier.WriteToSpool(spoolDir, "statuses", s) - if err != nil { - log.WithError(err).Error("error writing status to spool") // just have to log and move on - } + err := courier.WriteToSpool(spoolDir, "statuses", s) + if err != nil { + log.WithError(err).Error("error writing status to spool") // just have to log and move on } } - } else { - for _, s := range unresolved { - log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) - } + } + } else { + for _, s := range unresolved { + log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) } } } diff --git a/go.mod b/go.mod index d7c172185..7f7f5ca8c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.44.319 + github.com/aws/aws-sdk-go v1.45.0 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 github.com/evalphobia/logrus_sentry v0.8.2 @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.39.1 + github.com/nyaruka/gocommon v1.40.0 github.com/nyaruka/null/v2 v2.0.3 github.com/nyaruka/redisx v0.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -50,7 +50,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect diff --git a/go.sum b/go.sum index 567ca51fa..38f6df0ca 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.0 h1:qoVOQHuLacxJMO71T49KeE70zm+Tk3vtrl7XO4VUPZc= +github.com/aws/aws-sdk-go v1.45.0/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -70,8 +72,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.39.1 h1:C1LM6y0iGe3oTJPknb+FcNyz8yl4+CdsvRD36wAai38= -github.com/nyaruka/gocommon v1.39.1/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= +github.com/nyaruka/gocommon v1.40.0 h1:PvwLljY8rirgUu1ROwZM+gkTdNRbOnrGBFBf70OZt/8= +github.com/nyaruka/gocommon v1.40.0/go.mod h1:J+1hsRQ7GPJKGbU9IUTKIvP74ONG3iiM5y5S9dfqSq4= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -108,8 +110,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= -golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 0e0e6d20a96c59c55ff68f3c0a8e3b3371af5175 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 14:11:16 -0500 Subject: [PATCH 055/170] For received messages without external id, de-dupe by hash of text+attachments instead of just text --- backends/rapidpro/backend.go | 11 +++++---- backends/rapidpro/backend_test.go | 7 ++++++ backends/rapidpro/msg.go | 41 +++++++++++++++++-------------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 65abe665f..4c02a874e 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -69,8 +69,9 @@ type backend struct { mediaCache *redisx.IntervalHash mediaMutexes syncx.HashMutex - seenMsgs *redisx.IntervalHash - seenExternalIDs *redisx.IntervalHash + // tracking of recent messages received to avoid creating duplicates + receivedExternalIDs *redisx.IntervalHash // using external id + receivedMsgs *redisx.IntervalHash // using content hash // both sqlx and redis provide wait stats which are cummulative that we need to convert into increments dbWaitDuration time.Duration @@ -92,8 +93,8 @@ func newBackend(cfg *courier.Config) courier.Backend { mediaCache: redisx.NewIntervalHash("media-lookups", time.Hour*24, 2), mediaMutexes: *syncx.NewHashMutex(8), - seenMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), - seenExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), + receivedMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), + receivedExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), } } @@ -385,7 +386,7 @@ func (b *backend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text str msg.WithReceivedOn(time.Now().UTC()) // check if this message could be a duplicate and if so use the original's UUID - if prevUUID := b.checkMsgSeen(msg); prevUUID != courier.NilMsgUUID { + if prevUUID := b.checkMsgAlreadyReceived(msg); prevUUID != courier.NilMsgUUID { msg.UUID_ = prevUUID msg.alreadyWritten = true } diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 51a18aaad..c2cda34b2 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -715,6 +715,13 @@ func (ts *BackendTestSuite) TestCheckForDuplicate() { msg1 := createAndWriteMsg(knChannel, urn, "ping", "") ts.False(msg1.alreadyWritten) + keys, err := redis.Strings(r.Do("KEYS", "seen-msgs:*")) + ts.NoError(err) + ts.Len(keys, 1) + assertredis.HGetAll(ts.T(), ts.b.redisPool, keys[0], map[string]string{ + "dbc126ed-66bc-4e28-b67b-81dc3327c95d|tel:+12065551215": string(msg1.UUID()) + "|fb826459f96c6e3ee563238d158a24702afbdd78", + }) + // trying again should lead to same UUID msg2 := createAndWriteMsg(knChannel, urn, "ping", "") ts.Equal(msg1.UUID(), msg2.UUID()) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 756d03a3e..04669d189 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -2,7 +2,9 @@ package rapidpro import ( "context" + "crypto/sha1" "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "log" @@ -96,7 +98,7 @@ func writeMsg(ctx context.Context, b *backend, msg courier.Msg, clog *courier.Ch } // mark this msg as having been seen - b.writeMsgSeen(m) + b.recordMsgReceived(m) return err } @@ -218,8 +220,8 @@ func (b *backend) flushMsgFile(filename string, contents []byte) error { // Deduping utility methods //----------------------------------------------------------------------------- -// checks to see if this message has already been seen and if so returns its UUID -func (b *backend) checkMsgSeen(msg *DBMsg) courier.MsgUUID { +// checks to see if this message has already been received and if so returns its UUID +func (b *backend) checkMsgAlreadyReceived(msg *DBMsg) courier.MsgUUID { rc := b.redisPool.Get() defer rc.Close() @@ -227,24 +229,20 @@ func (b *backend) checkMsgSeen(msg *DBMsg) courier.MsgUUID { if msg.ExternalID_ != "" { fingerprint := fmt.Sprintf("%s|%s|%s", msg.Channel().UUID(), msg.URN().Identity(), msg.ExternalID()) - uuid, _ := b.seenExternalIDs.Get(rc, fingerprint) - - if uuid != "" { + if uuid, _ := b.receivedExternalIDs.Get(rc, fingerprint); uuid != "" { return courier.MsgUUID(uuid) } } else { // otherwise de-dup based on text received from that channel+urn since last send fingerprint := fmt.Sprintf("%s|%s", msg.Channel().UUID(), msg.URN().Identity()) - uuidAndText, _ := b.seenMsgs.Get(rc, fingerprint) - - // if we have seen a message from this channel+urn check text too - if uuidAndText != "" { - prevText := uuidAndText[37:] + if uuidAndHash, _ := b.receivedMsgs.Get(rc, fingerprint); uuidAndHash != "" { + prevUUID := uuidAndHash[:36] + prevHash := uuidAndHash[37:] - // if it is the same, return the UUID - if prevText == msg.Text() { - return courier.MsgUUID(uuidAndText[:36]) + // if it is the same hash, return the UUID + if prevHash == msg.hash() { + return courier.MsgUUID(prevUUID) } } } @@ -252,19 +250,19 @@ func (b *backend) checkMsgSeen(msg *DBMsg) courier.MsgUUID { return courier.NilMsgUUID } -// writeMsgSeen records that the given message has been seen and written to the database -func (b *backend) writeMsgSeen(msg *DBMsg) { +// records that the given message has been received and written to the database +func (b *backend) recordMsgReceived(msg *DBMsg) { rc := b.redisPool.Get() defer rc.Close() if msg.ExternalID_ != "" { fingerprint := fmt.Sprintf("%s|%s|%s", msg.Channel().UUID(), msg.URN().Identity(), msg.ExternalID()) - b.seenExternalIDs.Set(rc, fingerprint, string(msg.UUID())) + b.receivedExternalIDs.Set(rc, fingerprint, string(msg.UUID())) } else { fingerprint := fmt.Sprintf("%s|%s", msg.Channel().UUID(), msg.URN().Identity()) - b.seenMsgs.Set(rc, fingerprint, fmt.Sprintf("%s|%s", msg.UUID(), msg.Text())) + b.receivedMsgs.Set(rc, fingerprint, fmt.Sprintf("%s|%s", msg.UUID(), msg.hash())) } } @@ -272,7 +270,7 @@ func (b *backend) writeMsgSeen(msg *DBMsg) { func (b *backend) clearMsgSeen(rc redis.Conn, msg *DBMsg) { fingerprint := fmt.Sprintf("%s|%s", msg.Channel().UUID(), msg.URN().Identity()) - b.seenMsgs.Remove(rc, fingerprint) + b.receivedMsgs.Remove(rc, fingerprint) } //----------------------------------------------------------------------------- @@ -381,6 +379,11 @@ func (m *DBMsg) Metadata() json.RawMessage { return m.Metadata_ } +func (m *DBMsg) hash() string { + hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) + return hex.EncodeToString(hash[:]) +} + // WithContactName can be used to set the contact name on a msg func (m *DBMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } From 02c1d0f702fd01d8c5491907e38218df1b368504 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 15:02:32 -0500 Subject: [PATCH 056/170] Update to latest redisx --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7f7f5ca8c..cd72dd51f 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.40.0 github.com/nyaruka/null/v2 v2.0.3 - github.com/nyaruka/redisx v0.3.1 + github.com/nyaruka/redisx v0.4.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 38f6df0ca..8e689dd93 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.44.319 h1:cwynvM8DBwWGzlINTZ6XLkGy5O99wZIS0197j3B61Fs= -github.com/aws/aws-sdk-go v1.44.319/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.45.0 h1:qoVOQHuLacxJMO71T49KeE70zm+Tk3vtrl7XO4VUPZc= github.com/aws/aws-sdk-go v1.45.0/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -80,8 +78,8 @@ github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= github.com/nyaruka/phonenumbers v1.1.8/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= -github.com/nyaruka/redisx v0.3.1 h1:vnq1tHQwDh+7oG9BANyEVkqGjacgu8wpPxKBOx/exiw= -github.com/nyaruka/redisx v0.3.1/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= +github.com/nyaruka/redisx v0.4.0 h1:DZpbNPjflOZzeR7OQArcwgKx4OTt1vpNOJjT+aBSe7U= +github.com/nyaruka/redisx v0.4.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= From 5ab4982a21ea521866a9ed7737599966b3200e51 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 15:07:47 -0500 Subject: [PATCH 057/170] Update CHANGELOG.md for v8.3.8 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac978d4d..5cfb4df04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.8 (2023-08-31) +------------------------- + * Update to latest redisx which fixes accuracy for sub-minute interval hashes + * Update to new batchers in gocommon which are more efficient + v8.3.7 (2023-08-30) ------------------------- * Sender deletion handled by mailroom task From 0519367138c968f0deda1ae29bc0b9eb8ca414dd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 15:10:51 -0500 Subject: [PATCH 058/170] Replace interface{} with any --- attachments_test.go | 2 +- backends/rapidpro/backend_test.go | 18 ++++----- backends/rapidpro/channel.go | 4 +- backends/rapidpro/channel_event.go | 4 +- backends/rapidpro/task.go | 18 ++++----- celery/celery.go | 12 +++--- channel.go | 4 +- channel_event.go | 4 +- handler_test.go | 4 +- .../africastalking/africastalking_test.go | 4 +- handlers/arabiacell/arabiacell_test.go | 2 +- handlers/bandwidth/bandwidth_test.go | 4 +- handlers/base_test.go | 4 +- handlers/bongolive/bongolive_test.go | 2 +- handlers/burstsms/burstsms_test.go | 2 +- handlers/clickatell/clickatell_test.go | 4 +- handlers/clickmobile/clickmobile_test.go | 2 +- handlers/clicksend/clicksend_test.go | 2 +- handlers/dart/dart_test.go | 2 +- handlers/dialog360/dialog360.go | 6 +-- handlers/dialog360/dialog360_test.go | 4 +- handlers/discord/discord_test.go | 2 +- handlers/dmark/dmark_test.go | 2 +- handlers/external/external.go | 2 +- handlers/external/external_test.go | 38 +++++++++---------- handlers/facebook/facebook.go | 8 ++-- handlers/facebook/facebook_test.go | 16 ++++---- handlers/facebookapp/facebookapp.go | 16 ++++---- handlers/facebookapp/facebookapp_test.go | 26 ++++++------- handlers/firebase/firebase_test.go | 4 +- handlers/freshchat/freshchat_test.go | 4 +- handlers/globe/globe_test.go | 2 +- .../highconnection/highconnection_test.go | 2 +- handlers/hormuud/hormuud_test.go | 2 +- handlers/i2sms/i2sms_test.go | 2 +- handlers/infobip/infobip.go | 2 +- handlers/infobip/infobip_test.go | 4 +- handlers/jasmin/jasmin_test.go | 2 +- handlers/jiochat/jiochat_test.go | 4 +- handlers/justcall/justcall_test.go | 4 +- handlers/kaleyra/kaleyra_test.go | 2 +- handlers/kannel/kannel_test.go | 6 +-- handlers/line/line_test.go | 4 +- handlers/m3tech/m3tech_test.go | 2 +- handlers/macrokiosk/macrokiosk_test.go | 2 +- handlers/mblox/mblox_test.go | 4 +- handlers/messagebird/messagebird.go | 2 +- handlers/messagebird/messagebird_test.go | 13 ++++--- handlers/messangi/messangi_test.go | 2 +- handlers/mtarget/mtarget.go | 2 +- handlers/mtarget/mtarget_test.go | 2 +- handlers/mtn/mtn_test.go | 6 +-- handlers/nexmo/nexmo_test.go | 2 +- handlers/novo/novo_test.go | 4 +- handlers/playmobile/playmobile_test.go | 4 +- handlers/plivo/plivo_test.go | 2 +- handlers/redrabbit/redrabbit_test.go | 2 +- handlers/rocketchat/rocketchat_test.go | 2 +- handlers/shaqodoon/shaqodoon_test.go | 2 +- handlers/slack/slack_test.go | 2 +- handlers/smscentral/smscentral_test.go | 4 +- handlers/start/start_test.go | 4 +- handlers/telegram/telegram_test.go | 4 +- handlers/telesom/telesom_test.go | 2 +- handlers/test.go | 2 +- handlers/thinq/thinq_test.go | 2 +- handlers/twiml/twiml_test.go | 28 +++++++------- handlers/twitter/twitter_test.go | 2 +- handlers/viber/keyboard.go | 6 +-- handlers/viber/keyboard_test.go | 18 ++++----- handlers/viber/viber.go | 2 +- handlers/viber/viber_test.go | 12 +++--- handlers/vk/vk_test.go | 2 +- handlers/wavy/wavy_test.go | 2 +- handlers/wechat/wechat_test.go | 4 +- handlers/whatsapp/whatsapp.go | 10 ++--- handlers/whatsapp/whatsapp_test.go | 14 +++---- handlers/yo/yo_test.go | 4 +- handlers/zenvia/zenvia_test.go | 10 ++--- responses.go | 32 ++++++++-------- server.go | 4 +- server_test.go | 2 +- spool.go | 2 +- test/channel.go | 14 +++---- test/channel_event.go | 6 +-- test/handler.go | 2 +- 86 files changed, 256 insertions(+), 255 deletions(-) diff --git a/attachments_test.go b/attachments_test.go index 2bca70fc0..111a96f46 100644 --- a/attachments_test.go +++ b/attachments_test.go @@ -37,7 +37,7 @@ func TestFetchAndStoreAttachment(t *testing.T) { ctx := context.Background() mb := test.NewMockBackend() - mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]interface{}{}) + mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) mb.AddChannel(mockChannel) clog := courier.NewChannelLogForAttachmentFetch(mockChannel, courier.MsgID(123), []string{"sesame"}) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 51a18aaad..484c28f81 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -732,7 +732,7 @@ func (ts *BackendTestSuite) TestCheckForDuplicate() { dbMsg.ChannelUUID_ = knChannel.UUID() dbMsg.Text_ = "test" - msgJSON, err := json.Marshal([]interface{}{dbMsg}) + msgJSON, err := json.Marshal([]any{dbMsg}) ts.NoError(err) err = queue.PushOntoQueue(r, msgQueueName, "dbc126ed-66bc-4e28-b67b-81dc3327c95d", 10, string(msgJSON), queue.HighPriority) ts.NoError(err) @@ -776,7 +776,7 @@ func (ts *BackendTestSuite) TestStatus() { ts.NotNil(dbMsg) // serialize our message - msgJSON, err := json.Marshal([]interface{}{dbMsg}) + msgJSON, err := json.Marshal([]any{dbMsg}) ts.NoError(err) err = queue.PushOntoQueue(r, msgQueueName, "dbc126ed-66bc-4e28-b67b-81dc3327c95d", 10, string(msgJSON), queue.HighPriority) @@ -797,7 +797,7 @@ func (ts *BackendTestSuite) TestOutgoingQueue() { ts.NotNil(dbMsg) // serialize our message - msgJSON, err := json.Marshal([]interface{}{dbMsg}) + msgJSON, err := json.Marshal([]any{dbMsg}) ts.NoError(err) err = queue.PushOntoQueue(r, msgQueueName, "dbc126ed-66bc-4e28-b67b-81dc3327c95d", 10, string(msgJSON), queue.HighPriority) @@ -970,7 +970,7 @@ func (ts *BackendTestSuite) TestWriteChanneLog() { assertdb.Query(ts.T(), ts.b.db, `SELECT count(*) FROM channels_channellog`).Returns(1) assertdb.Query(ts.T(), ts.b.db, `SELECT channel_id, http_logs->0->>'url' AS url, errors->0->>'message' AS err FROM channels_channellog`). - Columns(map[string]interface{}{"channel_id": int64(channel.ID()), "url": "https://api.messages.com/send.json", "err": "Unexpected response status code."}) + Columns(map[string]any{"channel_id": int64(channel.ID()), "url": "https://api.messages.com/send.json", "err": "Unexpected response status code."}) clog2 := courier.NewChannelLog(courier.ChannelLogTypeMsgSend, channel, nil) clog2.HTTP(trace) @@ -1222,7 +1222,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]interface{}{"ref_id": "12345"}).WithContactName("kermit frog") + event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}).WithContactName("kermit frog") err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) @@ -1234,7 +1234,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) - ts.Equal(map[string]interface{}{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) } @@ -1262,7 +1262,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]interface{}{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1276,14 +1276,14 @@ func (ts *BackendTestSuite) TestMailroomEvents() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) - ts.Equal(map[string]interface{}{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) ts.assertQueuedContactTask(contact.ID_, "referral", map[string]any{ "channel_id": float64(10), "contact_id": float64(contact.ID_), - "extra": map[string]interface{}{"ref_id": "12345"}, + "extra": map[string]any{"ref_id": "12345"}, "new_contact": contact.IsNew_, "occurred_on": "2020-08-05T13:30:00.123456789Z", "org_id": float64(1), diff --git a/backends/rapidpro/channel.go b/backends/rapidpro/channel.go index eeb2338f8..2dba1fe67 100644 --- a/backends/rapidpro/channel.go +++ b/backends/rapidpro/channel.go @@ -359,7 +359,7 @@ func (c *DBChannel) HasRole(role courier.ChannelRole) bool { } // ConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) ConfigForKey(key string, defaultValue interface{}) interface{} { +func (c *DBChannel) ConfigForKey(key string, defaultValue any) any { value, found := c.Config_[key] if !found { return defaultValue @@ -368,7 +368,7 @@ func (c *DBChannel) ConfigForKey(key string, defaultValue interface{}) interface } // OrgConfigForKey returns the org config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) OrgConfigForKey(key string, defaultValue interface{}) interface{} { +func (c *DBChannel) OrgConfigForKey(key string, defaultValue any) any { value, found := c.OrgConfig_[key] if !found { return defaultValue diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index d817dd420..198ba2582 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -183,7 +183,7 @@ func (e *DBChannelEvent) ChannelID() courier.ChannelID { return e.Channel func (e *DBChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } func (e *DBChannelEvent) ContactName() string { return e.ContactName_ } func (e *DBChannelEvent) URN() urns.URN { return e.URN_ } -func (e *DBChannelEvent) Extra() map[string]interface{} { return e.Extra_ } +func (e *DBChannelEvent) Extra() map[string]any { return e.Extra_ } func (e *DBChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } func (e *DBChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } func (e *DBChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } @@ -193,7 +193,7 @@ func (e *DBChannelEvent) WithContactName(name string) courier.ChannelEvent { e.ContactName_ = name return e } -func (e *DBChannelEvent) WithExtra(extra map[string]interface{}) courier.ChannelEvent { +func (e *DBChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { e.Extra_ = null.Map(extra) return e } diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index 8bb3a13c6..964b46168 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -34,7 +34,7 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { // queue to mailroom switch e.EventType() { case courier.StopContact: - body := map[string]interface{}{ + body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, "occurred_on": e.OccurredOn_, @@ -42,7 +42,7 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { return queueMailroomTask(rc, "stop_event", e.OrgID_, e.ContactID_, body) case courier.WelcomeMessage: - body := map[string]interface{}{ + body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, "urn_id": e.ContactURNID_, @@ -53,7 +53,7 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { return queueMailroomTask(rc, "welcome_message", e.OrgID_, e.ContactID_, body) case courier.Referral: - body := map[string]interface{}{ + body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, "urn_id": e.ContactURNID_, @@ -65,7 +65,7 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { return queueMailroomTask(rc, "referral", e.OrgID_, e.ContactID_, body) case courier.NewConversation: - body := map[string]interface{}{ + body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, "urn_id": e.ContactURNID_, @@ -87,7 +87,7 @@ func queueMsgDeleted(rc redis.Conn, ch *DBChannel, msgID courier.MsgID, contactI // queueMailroomTask queues the passed in task to mailroom. Mailroom processes both messages and // channel event tasks through the same ordered queue. -func queueMailroomTask(rc redis.Conn, taskType string, orgID OrgID, contactID ContactID, body map[string]interface{}) (err error) { +func queueMailroomTask(rc redis.Conn, taskType string, orgID OrgID, contactID ContactID, body map[string]any) (err error) { // create our event task eventJSON := jsonx.MustMarshal(mrTask{ Type: taskType, @@ -123,8 +123,8 @@ type mrContactTask struct { } type mrTask struct { - Type string `json:"type"` - OrgID OrgID `json:"org_id"` - Task interface{} `json:"task"` - QueuedOn time.Time `json:"queued_on"` + Type string `json:"type"` + OrgID OrgID `json:"org_id"` + Task any `json:"task"` + QueuedOn time.Time `json:"queued_on"` } diff --git a/celery/celery.go b/celery/celery.go index 90851aa65..597b148c9 100644 --- a/celery/celery.go +++ b/celery/celery.go @@ -44,7 +44,7 @@ func QueueEmptyTask(rc redis.Conn, queueName string, taskName string) error { task := Task{ Body: body, - Headers: map[string]interface{}{ + Headers: map[string]any{ "root_id": taskUUID, "id": taskUUID, "lang": "py", @@ -84,11 +84,11 @@ func QueueEmptyTask(rc redis.Conn, queueName string, taskName string) error { // Task is the outer struct for a celery task type Task struct { - Body string `json:"body"` - Headers map[string]interface{} `json:"headers"` - ContentType string `json:"content-type"` - Properties TaskProperties `json:"properties"` - ContentEncoding string `json:"content-encoding"` + Body string `json:"body"` + Headers map[string]any `json:"headers"` + ContentType string `json:"content-type"` + Properties TaskProperties `json:"properties"` + ContentEncoding string `json:"content-encoding"` } // TaskProperties is the struct for a task's properties diff --git a/channel.go b/channel.go index ab06b60b7..f549eec29 100644 --- a/channel.go +++ b/channel.go @@ -139,9 +139,9 @@ type Channel interface { // CallbackDomain returns the domain that should be used for any callbacks the channel registers CallbackDomain(fallbackDomain string) string - ConfigForKey(key string, defaultValue interface{}) interface{} + ConfigForKey(key string, defaultValue any) any StringConfigForKey(key string, defaultValue string) string BoolConfigForKey(key string, defaultValue bool) bool IntConfigForKey(key string, defaultValue int) int - OrgConfigForKey(key string, defaultValue interface{}) interface{} + OrgConfigForKey(key string, defaultValue any) any } diff --git a/channel_event.go b/channel_event.go index 3fc7a7144..bfa5a1f91 100644 --- a/channel_event.go +++ b/channel_event.go @@ -26,12 +26,12 @@ type ChannelEvent interface { ChannelUUID() ChannelUUID URN() urns.URN EventType() ChannelEventType - Extra() map[string]interface{} + Extra() map[string]any CreatedOn() time.Time OccurredOn() time.Time WithContactName(name string) ChannelEvent - WithExtra(extra map[string]interface{}) ChannelEvent + WithExtra(extra map[string]any) ChannelEvent WithOccurredOn(time.Time) ChannelEvent EventID() int64 diff --git a/handler_test.go b/handler_test.go index d97af2703..857adc562 100644 --- a/handler_test.go +++ b/handler_test.go @@ -40,8 +40,8 @@ func TestHandling(t *testing.T) { time.Sleep(100 * time.Millisecond) // create and add a new outgoing message - brokenChannel := test.NewMockChannel("53e5aafa-8155-449d-9009-fcb30d54bd26", "XX", "2020", "US", map[string]interface{}{}) - mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]interface{}{}) + brokenChannel := test.NewMockChannel("53e5aafa-8155-449d-9009-fcb30d54bd26", "XX", "2020", "US", map[string]any{}) + mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) mb.AddChannel(mockChannel) msg := test.NewMockMsg(courier.MsgID(101), courier.NilMsgUUID, brokenChannel, "tel:+250788383383", "test message") diff --git a/handlers/africastalking/africastalking_test.go b/handlers/africastalking/africastalking_test.go index a32c0d3f1..639ff2783 100644 --- a/handlers/africastalking/africastalking_test.go +++ b/handlers/africastalking/africastalking_test.go @@ -179,12 +179,12 @@ var sharedSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "Username", courier.ConfigAPIKey: "KEY", }) var sharedChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "Username", courier.ConfigAPIKey: "KEY", configIsShared: true, diff --git a/handlers/arabiacell/arabiacell_test.go b/handlers/arabiacell/arabiacell_test.go index 700ae9e41..379ec8dd2 100644 --- a/handlers/arabiacell/arabiacell_test.go +++ b/handlers/arabiacell/arabiacell_test.go @@ -106,7 +106,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configServiceID: "service1", diff --git a/handlers/bandwidth/bandwidth_test.go b/handlers/bandwidth/bandwidth_test.go index 5bf301725..58dd5b22d 100644 --- a/handlers/bandwidth/bandwidth_test.go +++ b/handlers/bandwidth/bandwidth_test.go @@ -14,7 +14,7 @@ import ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BW", "2020", "US", - map[string]interface{}{courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configAccountID: "accound-id", configApplicationID: "application-id"}), + map[string]any{courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configAccountID: "accound-id", configApplicationID: "application-id"}), } const ( @@ -321,7 +321,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BW", "2020", "US", - map[string]interface{}{courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configAccountID: "accound-id", configApplicationID: "application-id"}) + map[string]any{courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configAccountID: "accound-id", configApplicationID: "application-id"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1")}, nil) } diff --git a/handlers/base_test.go b/handlers/base_test.go index 6ab242775..b8cd79d4e 100644 --- a/handlers/base_test.go +++ b/handlers/base_test.go @@ -49,13 +49,13 @@ func TestSplitMsg(t *testing.T) { func TestSplitMsgByChannel(t *testing.T) { assert := assert.New(t) var channelWithMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", courier.ConfigMaxLength: 25, }) var channelWithoutMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", }) diff --git a/handlers/bongolive/bongolive_test.go b/handlers/bongolive/bongolive_test.go index b5ac82de8..68055ab48 100644 --- a/handlers/bongolive/bongolive_test.go +++ b/handlers/bongolive/bongolive_test.go @@ -146,7 +146,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BL", "2020", "KE", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", }) diff --git a/handlers/burstsms/burstsms_test.go b/handlers/burstsms/burstsms_test.go index 051515312..3282ddfd7 100644 --- a/handlers/burstsms/burstsms_test.go +++ b/handlers/burstsms/burstsms_test.go @@ -112,7 +112,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BS", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", }) diff --git a/handlers/clickatell/clickatell_test.go b/handlers/clickatell/clickatell_test.go index f7b497ad3..07b1c3dbd 100644 --- a/handlers/clickatell/clickatell_test.go +++ b/handlers/clickatell/clickatell_test.go @@ -79,7 +79,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CT", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigAPIKey: "API-KEY", }) @@ -88,7 +88,7 @@ func TestSending(t *testing.T) { var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CT", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigAPIKey: "12345", }), } diff --git a/handlers/clickmobile/clickmobile_test.go b/handlers/clickmobile/clickmobile_test.go index cff119ff4..9f932d720 100644 --- a/handlers/clickmobile/clickmobile_test.go +++ b/handlers/clickmobile/clickmobile_test.go @@ -199,7 +199,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CM", "2020", "MW", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", "app_id": "001-app", diff --git a/handlers/clicksend/clicksend_test.go b/handlers/clicksend/clicksend_test.go index 1cfe9a75c..b8f1742e1 100644 --- a/handlers/clicksend/clicksend_test.go +++ b/handlers/clicksend/clicksend_test.go @@ -169,7 +169,7 @@ var sendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "GL", "2020", "US", - map[string]interface{}{ + map[string]any{ "username": "Aladdin", "password": "open sesame", }, diff --git a/handlers/dart/dart_test.go b/handlers/dart/dart_test.go index b7647b3e8..a4c04ede0 100644 --- a/handlers/dart/dart_test.go +++ b/handlers/dart/dart_test.go @@ -168,7 +168,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultDAChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "DA", "2020", "ID", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "Username", courier.ConfigPassword: "Password", }) diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index eed2bcea7..f66c94c69 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -201,7 +201,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h } var events []courier.Event - var data []interface{} + var data []any events, data, err := h.processCloudWhatsAppPayload(ctx, channel, payload, w, r, clog) if err != nil { @@ -211,12 +211,12 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []interface{}, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) // the list of data we will return in our response - data := make([]interface{}, 0, 2) + data := make([]any, 0, 2) seenMsgIDs := make(map[string]bool) contactNames := make(map[string]string) diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index 4368dd43a..fb87917a7 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -23,7 +23,7 @@ var testChannels = []courier.Channel{ "D3C", "250788383383", "RW", - map[string]interface{}{ + map[string]any{ "auth_token": "the-auth-token", "base_url": "https://waba-v2.360dialog.io", }), @@ -606,7 +606,7 @@ func TestSending(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 - var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "D3C", "12345_ID", "", map[string]interface{}{ + var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "D3C", "12345_ID", "", map[string]any{ "auth_token": "the-auth-token", "base_url": "https://waba-v2.360dialog.io", }) diff --git a/handlers/discord/discord_test.go b/handlers/discord/discord_test.go index a90796867..ae721875b 100644 --- a/handlers/discord/discord_test.go +++ b/handlers/discord/discord_test.go @@ -19,7 +19,7 @@ func BenchmarkHandler(b *testing.B) { } var testChannels = []courier.Channel{ - test.NewMockChannel("bac782c2-7aeb-4389-92f5-97887744f573", "DS", "discord", "US", map[string]interface{}{courier.ConfigSendAuthorization: "sesame"}), + test.NewMockChannel("bac782c2-7aeb-4389-92f5-97887744f573", "DS", "discord", "US", map[string]any{courier.ConfigSendAuthorization: "sesame"}), } var testCases = []ChannelHandleTestCase{ diff --git a/handlers/dmark/dmark_test.go b/handlers/dmark/dmark_test.go index 65b2060c8..0a1580945 100644 --- a/handlers/dmark/dmark_test.go +++ b/handlers/dmark/dmark_test.go @@ -135,7 +135,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigAuthToken: "Authy", }) diff --git a/handlers/external/external.go b/handlers/external/external.go index fe1967795..1cc170a44 100644 --- a/handlers/external/external.go +++ b/handlers/external/external.go @@ -359,7 +359,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann req.Header.Set("Authorization", authorization) } - headers := channel.ConfigForKey(courier.ConfigSendHeaders, map[string]interface{}{}).(map[string]interface{}) + headers := channel.ConfigForKey(courier.ConfigSendHeaders, map[string]any{}).(map[string]any) for hKey, hValue := range headers { req.Header.Set(hKey, fmt.Sprint(hValue)) } diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index eb014ffe1..e8b1510c5 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -194,7 +194,7 @@ var handleTestCases = []ChannelHandleTestCase{ var testSOAPReceiveChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ configTextXPath: "//content", configFromXPath: "//source", configMOResponse: "0", @@ -236,7 +236,7 @@ var gmTestCases = []ChannelHandleTestCase{ var customChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ configMOFromField: "from_number", configMODateField: "timestamp", configMOTextField: "messageText", @@ -670,47 +670,47 @@ var nationalGetSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", courier.ConfigSendMethod: http.MethodGet}) var getSmartChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", configEncoding: encodingSmart, courier.ConfigSendMethod: http.MethodGet}) var postChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: "to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", courier.ConfigSendMethod: http.MethodPost}) var postChannelCustomContentType = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: "to={{to_no_plus}}&text={{text}}&from={{from_no_plus}}{{quick_replies}}", courier.ConfigContentType: "application/x-www-form-urlencoded; charset=utf-8", courier.ConfigSendMethod: http.MethodPost}) var postSmartChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: "to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", configEncoding: encodingSmart, courier.ConfigSendMethod: http.MethodPost}) var jsonChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: `{ "to":{{to}}, "text":{{text}}, "from":{{from}}, "quick_replies":{{quick_replies}} }`, courier.ConfigContentType: contentJSON, courier.ConfigSendMethod: http.MethodPost, - courier.ConfigSendHeaders: map[string]interface{}{"Authorization": "Token ABCDEF", "foo": "bar"}, + courier.ConfigSendHeaders: map[string]any{"Authorization": "Token ABCDEF", "foo": "bar"}, }) var xmlChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: `{{to}}{{text}}{{from}}{{quick_replies}}`, courier.ConfigContentType: contentXML, @@ -718,7 +718,7 @@ func TestSending(t *testing.T) { }) var xmlChannelWithResponseContent = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: `{{to}}{{text}}{{from}}{{quick_replies}}`, configMTResponseCheck: "0", @@ -738,35 +738,35 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, xmlChannelWithResponseContent, newHandler(), xmlSendWithResponseContentTestCases, nil, nil) var getChannel30IntLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "max_length": 30, "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", courier.ConfigSendMethod: http.MethodGet}) var getChannel30StrLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "max_length": "30", "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", courier.ConfigSendMethod: http.MethodGet}) var jsonChannel30IntLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", "max_length": 30, courier.ConfigSendBody: `{ "to":{{to}}, "text":{{text}}, "from":{{from}}, "quick_replies":{{quick_replies}} }`, courier.ConfigContentType: contentJSON, courier.ConfigSendMethod: http.MethodPost, - courier.ConfigSendHeaders: map[string]interface{}{"Authorization": "Token ABCDEF", "foo": "bar"}, + courier.ConfigSendHeaders: map[string]any{"Authorization": "Token ABCDEF", "foo": "bar"}, }) var xmlChannel30IntLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", "max_length": 30, courier.ConfigSendBody: `{{to}}{{text}}{{from}}{{quick_replies}}`, courier.ConfigContentType: contentXML, courier.ConfigSendMethod: http.MethodPost, - courier.ConfigSendHeaders: map[string]interface{}{"Authorization": "Token ABCDEF", "foo": "bar"}, + courier.ConfigSendHeaders: map[string]any{"Authorization": "Token ABCDEF", "foo": "bar"}, }) RunChannelSendTestCases(t, getChannel30IntLength, newHandler(), longSendTestCases, nil, nil) @@ -775,7 +775,7 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, xmlChannel30IntLength, newHandler(), xmlLongSendTestCases, nil, nil) var nationalChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", "use_national": true, courier.ConfigSendMethod: http.MethodGet}) @@ -783,7 +783,7 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, nationalChannel, newHandler(), nationalGetSendTestCases, nil, nil) var jsonChannelWithSendAuthorization = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", - map[string]interface{}{ + map[string]any{ "send_path": "", courier.ConfigSendBody: `{ "to":{{to}}, "text":{{text}}, "from":{{from}}, "quick_replies":{{quick_replies}} }`, courier.ConfigContentType: contentJSON, diff --git a/handlers/facebook/facebook.go b/handlers/facebook/facebook.go index 84f41ed0f..644ce92f0 100644 --- a/handlers/facebook/facebook.go +++ b/handlers/facebook/facebook.go @@ -229,7 +229,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w events := make([]courier.Event, 0, 2) // the list of data we will return in our response - data := make([]interface{}, 0, 2) + data := make([]any, 0, 2) seenMsgIDs := make(map[string]bool, 2) @@ -273,7 +273,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ referrerIDKey: msg.OptIn.Ref, } event = event.WithExtra(extra) @@ -295,7 +295,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -326,7 +326,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } diff --git a/handlers/facebook/facebook_test.go b/handlers/facebook/facebook_test.go index b1b4d634d..a81583327 100644 --- a/handlers/facebook/facebook_test.go +++ b/handlers/facebook/facebook_test.go @@ -18,7 +18,7 @@ import ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FB", "1234", "", - map[string]interface{}{courier.ConfigAuthToken: "a123", courier.ConfigSecret: "mysecret"}), + map[string]any{courier.ConfigAuthToken: "a123", courier.ConfigSecret: "mysecret"}), } const ( @@ -484,7 +484,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:ref:optin_user_ref", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, }, { Label: "Receive OptIn", @@ -495,7 +495,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, }, { Label: "Receive Get Started", @@ -506,7 +506,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started"}, }, { Label: "Receive Referral Postback", @@ -517,7 +517,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, }, { Label: "Receive Referral", @@ -528,7 +528,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, }, { Label: "Receive Referral", @@ -539,7 +539,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, }, { Label: "Receive DLR", @@ -860,7 +860,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FB", "2020", "US", map[string]interface{}{courier.ConfigAuthToken: "access_token"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FB", "2020", "US", map[string]any{courier.ConfigAuthToken: "access_token"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"access_token"}, nil) } diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index a916ef0f5..9c25ae533 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -389,7 +389,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } var events []courier.Event - var data []interface{} + var data []any if channel.ChannelType() == "FBA" || channel.ChannelType() == "IG" { events, data, err = h.processFacebookInstagramPayload(ctx, channel, payload, w, r, clog) @@ -405,12 +405,12 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []interface{}, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) // the list of data we will return in our response - data := make([]interface{}, 0, 2) + data := make([]any, 0, 2) token := h.Server().Config().WhatsappAdminSystemUserToken @@ -543,14 +543,14 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri return events, data, nil } -func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []interface{}, error) { +func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { var err error // the list of events we deal with events := make([]courier.Event, 0, 2) // the list of data we will return in our response - data := make([]interface{}, 0, 2) + data := make([]any, 0, 2) seenMsgIDs := make(map[string]bool, 2) @@ -609,7 +609,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ referrerIDKey: msg.OptIn.Ref, } event = event.WithExtra(extra) @@ -631,7 +631,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -662,7 +662,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]interface{}{ + extra := map[string]any{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index cd8bd4168..fc398f291 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -19,15 +19,15 @@ import ( ) var testChannelsFBA = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FBA", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), } var testChannelsIG = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "IG", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), } var testChannelsWAC = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), } var testCasesFBA = []ChannelHandleTestCase{ @@ -112,7 +112,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:ref:optin_user_ref", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, PrepRequest: addValidSignature, }, { @@ -124,7 +124,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, PrepRequest: addValidSignature, }, { @@ -136,7 +136,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started"}, PrepRequest: addValidSignature, }, { @@ -148,7 +148,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, PrepRequest: addValidSignature, }, { @@ -160,7 +160,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, PrepRequest: addValidSignature, }, { @@ -172,7 +172,7 @@ var testCasesFBA = []ChannelHandleTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, PrepRequest: addValidSignature, }, { @@ -320,7 +320,7 @@ var testCasesIG = []ChannelHandleTestCase{ ExpectedURN: "instagram:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]interface{}{"title": "icebreaker question", "payload": "get_started"}, + ExpectedEventExtra: map[string]any{"title": "icebreaker question", "payload": "get_started"}, PrepRequest: addValidSignature, }, { @@ -1524,9 +1524,9 @@ func TestSending(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 - var ChannelFBA = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FBA", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) - var ChannelIG = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IG", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) - var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WAC", "12345_ID", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) + var ChannelFBA = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) + var ChannelIG = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) + var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WAC", "12345_ID", "", map[string]any{courier.ConfigAuthToken: "a123"}) checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} diff --git a/handlers/firebase/firebase_test.go b/handlers/firebase/firebase_test.go index fe44a8fb2..b9594e547 100644 --- a/handlers/firebase/firebase_test.go +++ b/handlers/firebase/firebase_test.go @@ -31,12 +31,12 @@ Donec euismod dapibus ligula, sit amet hendrerit neque vulputate ac.` var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FCM", "1234", "", - map[string]interface{}{ + map[string]any{ configKey: "FCMKey", configTitle: "FCMTitle", }), test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FCM", "1234", "", - map[string]interface{}{ + map[string]any{ configKey: "FCMKey", configNotification: true, configTitle: "FCMTitle", diff --git a/handlers/freshchat/freshchat_test.go b/handlers/freshchat/freshchat_test.go index 9a0d954ce..8139f76e1 100644 --- a/handlers/freshchat/freshchat_test.go +++ b/handlers/freshchat/freshchat_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FC", "2020", "US", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FC", "2020", "US", map[string]any{ "username": "c8fddfaf-622a-4a0e-b060-4f3ccbeab606", //agent_id "secret": cert, // public_key for sig "auth_token": "authtoken", //API bearer token @@ -127,7 +127,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ } func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FC", "2020", "US", map[string]interface{}{ + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FC", "2020", "US", map[string]any{ "username": "c8fddfaf-622a-4a0e-b060-4f3ccbeab606", "secret": cert, "auth_token": "enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", diff --git a/handlers/globe/globe_test.go b/handlers/globe/globe_test.go index aeedf55b2..765e6f1c4 100644 --- a/handlers/globe/globe_test.go +++ b/handlers/globe/globe_test.go @@ -215,7 +215,7 @@ var sendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "GL", "2020", "US", - map[string]interface{}{ + map[string]any{ "app_id": "12345", "app_secret": "mysecret", "passphrase": "opensesame", diff --git a/handlers/highconnection/highconnection_test.go b/handlers/highconnection/highconnection_test.go index b05a74360..4bb8ed4cc 100644 --- a/handlers/highconnection/highconnection_test.go +++ b/handlers/highconnection/highconnection_test.go @@ -205,7 +205,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", }) diff --git a/handlers/hormuud/hormuud_test.go b/handlers/hormuud/hormuud_test.go index bd3ed63c7..39aeb82c1 100644 --- a/handlers/hormuud/hormuud_test.go +++ b/handlers/hormuud/hormuud_test.go @@ -143,7 +143,7 @@ func TestSending(t *testing.T) { tokenURL = server.URL + "?valid=true" var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HM", "2020", "US", - map[string]interface{}{ + map[string]any{ "username": "foo@bar.com", "password": "sesame", }, diff --git a/handlers/i2sms/i2sms_test.go b/handlers/i2sms/i2sms_test.go index ce8e9ae14..e367601f7 100644 --- a/handlers/i2sms/i2sms_test.go +++ b/handlers/i2sms/i2sms_test.go @@ -100,7 +100,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "I2", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configChannelHash: "hash123", diff --git a/handlers/infobip/infobip.go b/handlers/infobip/infobip.go index f55556178..2efd502f9 100644 --- a/handlers/infobip/infobip.go +++ b/handlers/infobip/infobip.go @@ -59,7 +59,7 @@ type ibStatus struct { // statusMessage is our HTTP handler function for status updates func (h *handler) statusMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *statusPayload, clog *courier.ChannelLog) ([]courier.Event, error) { - data := make([]interface{}, len(payload.Results)) + data := make([]any, len(payload.Results)) statuses := make([]courier.Event, len(payload.Results)) for _, s := range payload.Results { msgStatus, found := statusMapping[s.Status.GroupName] diff --git a/handlers/infobip/infobip_test.go b/handlers/infobip/infobip_test.go index 40f394e82..df234fd90 100644 --- a/handlers/infobip/infobip_test.go +++ b/handlers/infobip/infobip_test.go @@ -404,7 +404,7 @@ var transSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IB", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", }) @@ -412,7 +412,7 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) var transChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IB", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", configTransliteration: "COLOMBIAN", diff --git a/handlers/jasmin/jasmin_test.go b/handlers/jasmin/jasmin_test.go index 3bd11a6d9..2fd8f0db8 100644 --- a/handlers/jasmin/jasmin_test.go +++ b/handlers/jasmin/jasmin_test.go @@ -159,7 +159,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JS", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", }) diff --git a/handlers/jiochat/jiochat_test.go b/handlers/jiochat/jiochat_test.go index cb429d809..5c624627f 100644 --- a/handlers/jiochat/jiochat_test.go +++ b/handlers/jiochat/jiochat_test.go @@ -23,7 +23,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JC", "2020", "US", map[string]interface{}{configAppSecret: "secret123", configAppID: "app-id"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JC", "2020", "US", map[string]any{configAppSecret: "secret123", configAppID: "app-id"}), } var ( @@ -414,7 +414,7 @@ func setupBackend(mb *test.MockBackend) { func TestSending(t *testing.T) { maxMsgLength = 160 - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JC", "2020", "US", map[string]interface{}{configAppSecret: "secret123", configAppID: "app-id"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JC", "2020", "US", map[string]any{configAppSecret: "secret123", configAppID: "app-id"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) } diff --git a/handlers/justcall/justcall_test.go b/handlers/justcall/justcall_test.go index de13f08b8..f0b60e054 100644 --- a/handlers/justcall/justcall_test.go +++ b/handlers/justcall/justcall_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JCL", "2020", "US", map[string]interface{}{courier.ConfigAPIKey: "api_key", courier.ConfigSecret: "api_secret"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JCL", "2020", "US", map[string]any{courier.ConfigAPIKey: "api_key", courier.ConfigSecret: "api_secret"}), } var ( @@ -323,7 +323,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ } func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JCL", "2020", "US", map[string]interface{}{courier.ConfigAPIKey: "api_key", courier.ConfigSecret: "api_secret"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JCL", "2020", "US", map[string]any{courier.ConfigAPIKey: "api_key", courier.ConfigSecret: "api_secret"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"api_key", "api_secret"}, nil) } diff --git a/handlers/kaleyra/kaleyra_test.go b/handlers/kaleyra/kaleyra_test.go index 2311284a5..ce12f7942 100644 --- a/handlers/kaleyra/kaleyra_test.go +++ b/handlers/kaleyra/kaleyra_test.go @@ -20,7 +20,7 @@ const ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "KWA", "250788383383", "", - map[string]interface{}{ + map[string]any{ configAccountSID: "SID", configApiKey: "123456", }, diff --git a/handlers/kannel/kannel_test.go b/handlers/kannel/kannel_test.go index 9b3337fd2..08104c912 100644 --- a/handlers/kannel/kannel_test.go +++ b/handlers/kannel/kannel_test.go @@ -15,7 +15,7 @@ var testChannels = []courier.Channel{ } var ignoreChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", map[string]interface{}{"ignore_sent": true}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", map[string]any{"ignore_sent": true}), } var handleTestCases = []ChannelHandleTestCase{ @@ -240,12 +240,12 @@ var nationalSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username"}) var nationalChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", "use_national": true, diff --git a/handlers/line/line_test.go b/handlers/line/line_test.go index 467c31432..575629a31 100644 --- a/handlers/line/line_test.go +++ b/handlers/line/line_test.go @@ -251,7 +251,7 @@ var noEvent = `{ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "LN", "2020", "US", - map[string]interface{}{ + map[string]any{ "secret": "Secret", "auth_token": "the-auth-token", }), @@ -608,7 +608,7 @@ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "LN", "2020", "US", - map[string]interface{}{ + map[string]any{ "auth_token": "AccessToken", }, ) diff --git a/handlers/m3tech/m3tech_test.go b/handlers/m3tech/m3tech_test.go index 1556fbbc0..28ecf9e02 100644 --- a/handlers/m3tech/m3tech_test.go +++ b/handlers/m3tech/m3tech_test.go @@ -97,7 +97,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "M3", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", }, diff --git a/handlers/macrokiosk/macrokiosk_test.go b/handlers/macrokiosk/macrokiosk_test.go index 27e373167..910a7ac3d 100644 --- a/handlers/macrokiosk/macrokiosk_test.go +++ b/handlers/macrokiosk/macrokiosk_test.go @@ -130,7 +130,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MK", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", configMacrokioskSenderID: "macro", diff --git a/handlers/mblox/mblox_test.go b/handlers/mblox/mblox_test.go index a603ee6d0..ff5a91bf5 100644 --- a/handlers/mblox/mblox_test.go +++ b/handlers/mblox/mblox_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MB", "2020", "BR", map[string]interface{}{"username": "zv-username", "password": "zv-password"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MB", "2020", "BR", map[string]any{"username": "zv-username", "password": "zv-password"}), } var ( @@ -158,7 +158,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MB", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", }, diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 0351cda8a..2b4df4353 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -248,7 +248,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann func verifyToken(tokenString string, secret string) (jwt.MapClaims, error) { // Parse the token with the provided secret to get the claims - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { // Validate the signing method // We only allow HS256 // ref: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 05ee40783..ca2805501 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -1,18 +1,19 @@ package messagebird import ( - "github.com/golang-jwt/jwt/v5" - "github.com/nyaruka/courier" - . "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/courier/test" "net/http" "net/http/httptest" "testing" "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/nyaruka/courier" + . "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/test" ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]any{ "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", //API bearer token }), @@ -294,7 +295,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ } func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]interface{}{ + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]any{ "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", }) diff --git a/handlers/messangi/messangi_test.go b/handlers/messangi/messangi_test.go index 36693567f..ba422f9da 100644 --- a/handlers/messangi/messangi_test.go +++ b/handlers/messangi/messangi_test.go @@ -99,7 +99,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MG", "2020", "JM", - map[string]interface{}{ + map[string]any{ "public_key": "my-public-key", "private_key": "my-private-key", "instance_id": 7, diff --git a/handlers/mtarget/mtarget.go b/handlers/mtarget/mtarget.go index 17dc50457..fc2e802d3 100644 --- a/handlers/mtarget/mtarget.go +++ b/handlers/mtarget/mtarget.go @@ -108,7 +108,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp // we have all our parts, grab them and put them together // build up the list of keys we are looking up - keys := make([]interface{}, longCount+1) + keys := make([]any, longCount+1) keys[0] = mapKey for i := 1; i < longCount+1; i++ { keys[i] = fmt.Sprintf("%d", i) diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/mtarget_test.go index acf13827b..ff4a71a93 100644 --- a/handlers/mtarget/mtarget_test.go +++ b/handlers/mtarget/mtarget_test.go @@ -125,7 +125,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MT", "2020", "FR", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", }, diff --git a/handlers/mtn/mtn_test.go b/handlers/mtn/mtn_test.go index 83e01446f..6427c43f3 100644 --- a/handlers/mtn/mtn_test.go +++ b/handlers/mtn/mtn_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]interface{}{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]any{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key"}), } var ( @@ -242,8 +242,8 @@ var cpAddressSendTestCases = []ChannelSendTestCase{ } func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]interface{}{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]any{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) - var cpAddressChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]interface{}{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key", configCPAddress: "FOO"}) + var cpAddressChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]any{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key", configCPAddress: "FOO"}) RunChannelSendTestCases(t, cpAddressChannel, newHandler(), cpAddressSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) } diff --git a/handlers/nexmo/nexmo_test.go b/handlers/nexmo/nexmo_test.go index 04b67466f..a7566ef6d 100644 --- a/handlers/nexmo/nexmo_test.go +++ b/handlers/nexmo/nexmo_test.go @@ -211,7 +211,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NX", "2020", "US", - map[string]interface{}{ + map[string]any{ configNexmoAPIKey: "nexmo-api-key", configNexmoAPISecret: "nexmo-api-secret", configNexmoAppID: "nexmo-app-id", diff --git a/handlers/novo/novo_test.go b/handlers/novo/novo_test.go index ab516548f..1698498ba 100644 --- a/handlers/novo/novo_test.go +++ b/handlers/novo/novo_test.go @@ -10,7 +10,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NV", "2020", "TT", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NV", "2020", "TT", map[string]any{ "merchant_id": "my-merchant-id", "merchant_secret": "my-merchant-secret", "secret": "sesame", @@ -118,7 +118,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NV", "2020", "TT", - map[string]interface{}{ + map[string]any{ "merchant_id": "my-merchant-id", "merchant_secret": "my-merchant-secret", "secret": "sesame", diff --git a/handlers/playmobile/playmobile_test.go b/handlers/playmobile/playmobile_test.go index d4953ccec..ca371260e 100644 --- a/handlers/playmobile/playmobile_test.go +++ b/handlers/playmobile/playmobile_test.go @@ -11,7 +11,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PM", "1122", "UZ", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PM", "1122", "UZ", map[string]any{ "incoming_prefixes": []string{"abc", "DE"}, }), } @@ -180,7 +180,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PM", "1122", "UZ", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", "shortcode": "1122", diff --git a/handlers/plivo/plivo_test.go b/handlers/plivo/plivo_test.go index 8616d5e7f..d23b37d52 100644 --- a/handlers/plivo/plivo_test.go +++ b/handlers/plivo/plivo_test.go @@ -126,7 +126,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PL", "2020", "US", - map[string]interface{}{ + map[string]any{ configPlivoAuthID: "AuthID", configPlivoAuthToken: "AuthToken", configPlivoAPPID: "AppID", diff --git a/handlers/redrabbit/redrabbit_test.go b/handlers/redrabbit/redrabbit_test.go index 2afa17eca..9a6d65e96 100644 --- a/handlers/redrabbit/redrabbit_test.go +++ b/handlers/redrabbit/redrabbit_test.go @@ -99,7 +99,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "RR", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", }, diff --git a/handlers/rocketchat/rocketchat_test.go b/handlers/rocketchat/rocketchat_test.go index fb37685c9..e82f7a64f 100644 --- a/handlers/rocketchat/rocketchat_test.go +++ b/handlers/rocketchat/rocketchat_test.go @@ -16,7 +16,7 @@ const ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "RC", "1234", "", - map[string]interface{}{ + map[string]any{ configBaseURL: "https://my.rocket.chat/api/apps/public/684202ed-1461-4983-9ea7-fde74b15026c", configSecret: "123456789", configBotUsername: "rocket.cat", diff --git a/handlers/shaqodoon/shaqodoon_test.go b/handlers/shaqodoon/shaqodoon_test.go index 5697982fd..5bb9b73a7 100644 --- a/handlers/shaqodoon/shaqodoon_test.go +++ b/handlers/shaqodoon/shaqodoon_test.go @@ -84,7 +84,7 @@ var getSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SQ", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigSendURL: "SendURL", courier.ConfigPassword: "Password", courier.ConfigUsername: "Username"}) diff --git a/handlers/slack/slack_test.go b/handlers/slack/slack_test.go index 1027d6e25..06a844b12 100644 --- a/handlers/slack/slack_test.go +++ b/handlers/slack/slack_test.go @@ -25,7 +25,7 @@ const ( ) var testChannels = []courier.Channel{ - test.NewMockChannel(channelUUID, "SL", "2022", "US", map[string]interface{}{"bot_token": "xoxb-abc123", "verification_token": "one-long-verification-token"}), + test.NewMockChannel(channelUUID, "SL", "2022", "US", map[string]any{"bot_token": "xoxb-abc123", "verification_token": "one-long-verification-token"}), } const helloMsg = `{ diff --git a/handlers/smscentral/smscentral_test.go b/handlers/smscentral/smscentral_test.go index 58e803f4b..365ae249c 100644 --- a/handlers/smscentral/smscentral_test.go +++ b/handlers/smscentral/smscentral_test.go @@ -14,7 +14,7 @@ const ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SC", "2020", "US", map[string]interface{}{"username": "Username", "password": "Password"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SC", "2020", "US", map[string]any{"username": "Username", "password": "Password"}), } var handleTestCases = []ChannelHandleTestCase{ @@ -101,7 +101,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SC", "2020", "US", - map[string]interface{}{ + map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", }) diff --git a/handlers/start/start_test.go b/handlers/start/start_test.go index e39444c75..e1fd0b200 100644 --- a/handlers/start/start_test.go +++ b/handlers/start/start_test.go @@ -12,7 +12,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ST", "2020", "UA", map[string]interface{}{"username": "st-username", "password": "st-password"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ST", "2020", "UA", map[string]any{"username": "st-username", "password": "st-password"}), } const ( @@ -243,6 +243,6 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ST", "2020", "UA", map[string]interface{}{"username": "Username", "password": "Password"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ST", "2020", "UA", map[string]any{"username": "Username", "password": "Password"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) } diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/telegram_test.go index bc2b4df66..de6e1f68c 100644 --- a/handlers/telegram/telegram_test.go +++ b/handlers/telegram/telegram_test.go @@ -14,7 +14,7 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "TG", "2020", "US", map[string]interface{}{"auth_token": "a123"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "TG", "2020", "US", map[string]any{"auth_token": "a123"}), } var helloMsg = `{ @@ -941,7 +941,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TG", "2020", "US", - map[string]interface{}{courier.ConfigAuthToken: "auth_token"}) + map[string]any{courier.ConfigAuthToken: "auth_token"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"auth_token"}, nil) } diff --git a/handlers/telesom/telesom_test.go b/handlers/telesom/telesom_test.go index 0a5a67404..ec823f409 100644 --- a/handlers/telesom/telesom_test.go +++ b/handlers/telesom/telesom_test.go @@ -139,7 +139,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TS", "2020", "US", - map[string]interface{}{ + map[string]any{ "password": "Password", "username": "Username", "secret": "secret", diff --git a/handlers/test.go b/handlers/test.go index 0a2702821..b241722b1 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -51,7 +51,7 @@ type ChannelHandleTestCase struct { ExpectedExternalID string ExpectedMsgID int64 ExpectedEvent courier.ChannelEventType - ExpectedEventExtra map[string]interface{} + ExpectedEventExtra map[string]any ExpectedErrors []*courier.ChannelError } diff --git a/handlers/thinq/thinq_test.go b/handlers/thinq/thinq_test.go index 120d20ba1..e47ef9f3b 100644 --- a/handlers/thinq/thinq_test.go +++ b/handlers/thinq/thinq_test.go @@ -156,7 +156,7 @@ var sendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TQ", "+12065551212", "US", - map[string]interface{}{ + map[string]any{ configAccountID: "1234", configAPITokenUser: "user1", configAPIToken: "sesame", diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index 60568b5e8..c37f78dc8 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -18,19 +18,19 @@ import ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "T", "2020", "US", map[string]interface{}{"auth_token": "6789"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "T", "2020", "US", map[string]any{"auth_token": "6789"}), } var tmsTestChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TMS", "2020", "US", map[string]interface{}{"auth_token": "6789"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TMS", "2020", "US", map[string]any{"auth_token": "6789"}), } var twTestChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TW", "2020", "US", map[string]interface{}{"auth_token": "6789"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TW", "2020", "US", map[string]any{"auth_token": "6789"}), } var swTestChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "2020", "US", map[string]interface{}{"auth_token": "6789"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "2020", "US", map[string]any{"auth_token": "6789"}), } var ( @@ -301,7 +301,7 @@ func TestHandler(t *testing.T) { RunChannelTestCases(t, swTestChannels, newTWIMLHandler("SW", "SignalWire", false), swTestCases) waChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "+12065551212", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "6789", }, @@ -310,7 +310,7 @@ func TestHandler(t *testing.T) { RunChannelTestCases(t, []courier.Channel{waChannel}, newTWIMLHandler("T", "TwilioWhatsApp", true), waTestCases) twaChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TWA", "+12065551212", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "6789", }, @@ -729,25 +729,25 @@ var twaSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "T", "2020", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken"}) var tmsDefaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56cd", "TMS", "2021", "US", - map[string]interface{}{ + map[string]any{ configMessagingServiceSID: "messageServiceSID", configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken"}) var twDefaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TW", "2020", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken", configSendURL: "SEND_URL", }) var swChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "2020", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken", configSendURL: "BASE_URL", @@ -759,7 +759,7 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, swChannel, newTWIMLHandler("SW", "SignalWire", false), swSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) waChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "+12065551212", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken", }, @@ -769,7 +769,7 @@ func TestSending(t *testing.T) { RunChannelSendTestCases(t, waChannel, newTWIMLHandler("T", "Twilio Whatsapp", true), waSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) twaChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TWA", "+12065551212", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken", }, @@ -783,7 +783,7 @@ func TestBuildAttachmentRequest(t *testing.T) { mb := test.NewMockBackend() var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "T", "2020", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken"}) @@ -793,7 +793,7 @@ func TestBuildAttachmentRequest(t *testing.T) { assert.Equal(t, "Basic YWNjb3VudFNJRDphdXRoVG9rZW4=", req.Header.Get("Authorization")) var swChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "2020", "US", - map[string]interface{}{ + map[string]any{ configAccountSID: "accountSID", courier.ConfigAuthToken: "authToken", configSendURL: "BASE_URL", diff --git a/handlers/twitter/twitter_test.go b/handlers/twitter/twitter_test.go index b9c20f780..05d9b28fe 100644 --- a/handlers/twitter/twitter_test.go +++ b/handlers/twitter/twitter_test.go @@ -17,7 +17,7 @@ import ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "TWT", "tweeter", "", - map[string]interface{}{ + map[string]any{ configHandleID: "835740314006511618", configAPIKey: "apiKey", configAPISecret: "apiSecret", diff --git a/handlers/viber/keyboard.go b/handlers/viber/keyboard.go index a57c18c60..ed031dffc 100644 --- a/handlers/viber/keyboard.go +++ b/handlers/viber/keyboard.go @@ -36,7 +36,7 @@ const ( var textSizes = map[string]bool{"small": true, "regular": true, "large": true} // NewKeyboardFromReplies create a keyboard from the given quick replies -func NewKeyboardFromReplies(replies []string, buttonConfig map[string]interface{}) *Keyboard { +func NewKeyboardFromReplies(replies []string, buttonConfig map[string]any) *Keyboard { rows := StringsToRows(replies, maxColumns, maxRowRunes, paddingRunes) buttons := []KeyboardButton{} @@ -60,8 +60,8 @@ func NewKeyboardFromReplies(replies []string, buttonConfig map[string]interface{ return &Keyboard{"keyboard", false, buttons} } -//ApplyConfig apply the configs from the channel to KeyboardButton -func (b *KeyboardButton) ApplyConfig(buttonConfig map[string]interface{}) { +// ApplyConfig apply the configs from the channel to KeyboardButton +func (b *KeyboardButton) ApplyConfig(buttonConfig map[string]any) { bgColor := strings.TrimSpace(fmt.Sprint(buttonConfig["bg_color"])) textStyle := strings.TrimSpace(fmt.Sprint(buttonConfig["text"])) textSize := strings.TrimSpace(fmt.Sprint(buttonConfig["text_size"])) diff --git a/handlers/viber/keyboard_test.go b/handlers/viber/keyboard_test.go index 719ffd38a..37d4d30f7 100644 --- a/handlers/viber/keyboard_test.go +++ b/handlers/viber/keyboard_test.go @@ -11,7 +11,7 @@ func TestKeyboardFromReplies(t *testing.T) { tsc := []struct { replies []string expected *viber.Keyboard - buttonConfig map[string]interface{} + buttonConfig map[string]any }{ { []string{"OK"}, @@ -22,7 +22,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "OK", Text: "OK", Columns: "6"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"Yes", "No", "Maybe"}, @@ -35,7 +35,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "Maybe", Text: "Maybe", Columns: "2"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"A", "B", "C", "D"}, @@ -49,7 +49,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "D", Text: "D", Columns: "6"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"\"A\"", ""}, @@ -61,7 +61,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "", Text: "<B>", Columns: "3"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"Vanilla", "Chocolate", "Mint", "Lemon Sorbet", "Papaya", "Strawberry"}, @@ -77,7 +77,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "Strawberry", Text: "Strawberry", Columns: "6"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"A", "B", "C", "D", "Chicken", "Fish", "Peanut Butter Pickle"}, @@ -94,7 +94,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "regular", ActionBody: "Peanut Butter Pickle", Text: "Peanut Butter Pickle", Columns: "6"}, }, }, - map[string]interface{}{}, + map[string]any{}, }, { []string{"Foo", "Bar", "Baz"}, @@ -107,7 +107,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "large", ActionBody: "Baz", Text: "Baz
", Columns: "2", BgColor: "#f7bb3f"}, }, }, - map[string]interface{}{ + map[string]any{ "bg_color": "#f7bb3f", "text": "*
", "text_size": "large", @@ -124,7 +124,7 @@ func TestKeyboardFromReplies(t *testing.T) { {ActionType: "reply", TextSize: "small", ActionBody: "Maybe", Text: "Maybe
", Columns: "2"}, }, }, - map[string]interface{}{ + map[string]any{ "text": "*
", "text_size": "small", }, diff --git a/handlers/viber/viber.go b/handlers/viber/viber.go index 3c85fef22..c65339022 100644 --- a/handlers/viber/viber.go +++ b/handlers/viber/viber.go @@ -362,7 +362,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann var keyboard *Keyboard if len(qrs) > 0 { - buttonLayout := msg.Channel().ConfigForKey("button_layout", map[string]interface{}{}).(map[string]interface{}) + buttonLayout := msg.Channel().ConfigForKey("button_layout", map[string]any{}).(map[string]any) keyboard = NewKeyboardFromReplies(qrs, buttonLayout) } parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) diff --git a/handlers/viber/viber_test.go b/handlers/viber/viber_test.go index fb663c7a6..f692bd02e 100644 --- a/handlers/viber/viber_test.go +++ b/handlers/viber/viber_test.go @@ -215,16 +215,16 @@ func TestSending(t *testing.T) { maxMsgLength = 160 descriptionMaxLength = 10 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", - map[string]interface{}{ + map[string]any{ courier.ConfigAuthToken: "Token", }) var invalidTokenChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", - map[string]interface{}{}, + map[string]any{}, ) var buttonLayoutChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2021", "", - map[string]interface{}{ + map[string]any{ courier.ConfigAuthToken: "Token", - "button_layout": map[string]interface{}{"bg_color": "#f7bb3f", "text": "*

", "text_size": "large"}, + "button_layout": map[string]any{"bg_color": "#f7bb3f", "text": "*

", "text_size": "large"}, }) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Token"}, nil) RunChannelSendTestCases(t, invalidTokenChannel, newHandler(), invalidTokenSendTestCases, []string{"Token"}, nil) @@ -232,13 +232,13 @@ func TestSending(t *testing.T) { } var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", map[string]any{ courier.ConfigAuthToken: "Token", }), } var testChannelsWithWelcomeMessage = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", map[string]interface{}{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "VP", "2020", "", map[string]any{ courier.ConfigAuthToken: "Token", configViberWelcomeMessage: "Welcome to VP, Please subscribe here for more.", }), diff --git a/handlers/vk/vk_test.go b/handlers/vk/vk_test.go index 6c1c12b60..bf7cb3ba6 100644 --- a/handlers/vk/vk_test.go +++ b/handlers/vk/vk_test.go @@ -29,7 +29,7 @@ var testChannels = []courier.Channel{ "VK", "123456789", "", - map[string]interface{}{ + map[string]any{ courier.ConfigAuthToken: "token123xyz", courier.ConfigSecret: "abc123xyz", configServerVerificationString: "a1b2c3", diff --git a/handlers/wavy/wavy_test.go b/handlers/wavy/wavy_test.go index 6213dd3a3..b7e8425bf 100644 --- a/handlers/wavy/wavy_test.go +++ b/handlers/wavy/wavy_test.go @@ -208,7 +208,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WV", "2020", "BR", - map[string]interface{}{ + map[string]any{ courier.ConfigUsername: "user1", courier.ConfigAuthToken: "token", }) diff --git a/handlers/wechat/wechat_test.go b/handlers/wechat/wechat_test.go index 2ff51aa8d..015269163 100644 --- a/handlers/wechat/wechat_test.go +++ b/handlers/wechat/wechat_test.go @@ -24,7 +24,7 @@ import ( var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WC", "2020", "US", - map[string]interface{}{courier.ConfigSecret: "secret123", configAppSecret: "app-secret123", configAppID: "app-id"}), + map[string]any{courier.ConfigSecret: "secret123", configAppSecret: "app-secret123", configAppID: "app-id"}), } var ( @@ -352,6 +352,6 @@ func setupBackend(mb *test.MockBackend) { func TestSending(t *testing.T) { maxMsgLength = 160 - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WC", "2020", "US", map[string]interface{}{configAppSecret: "secret123", configAppID: "app-id"}) + var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WC", "2020", "US", map[string]any{configAppSecret: "secret123", configAppID: "app-id"}) RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) } diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 679c4f1b1..867651117 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -175,7 +175,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w events := make([]courier.Event, 0, 2) // the list of data we will return in our response - data := make([]interface{}, 0, 2) + data := make([]any, 0, 2) seenMsgIDs := make(map[string]bool, 2) @@ -555,8 +555,8 @@ func (h *handler) WriteRequestError(ctx context.Context, w http.ResponseWriter, return courier.WriteError(w, http.StatusOK, err) } -func buildPayloads(msg courier.Msg, h *handler, clog *courier.ChannelLog) ([]interface{}, error) { - var payloads []interface{} +func buildPayloads(msg courier.Msg, h *handler, clog *courier.ChannelLog) ([]any, error) { + var payloads []any var err error parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) @@ -907,7 +907,7 @@ func (h *handler) fetchMediaID(msg courier.Msg, mimeType, mediaURL string, clog return mediaID, nil } -func sendWhatsAppMsg(rc redis.Conn, msg courier.Msg, sendPath *url.URL, payload interface{}, clog *courier.ChannelLog) (string, string, error) { +func sendWhatsAppMsg(rc redis.Conn, msg courier.Msg, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { jsonBody, err := json.Marshal(payload) if err != nil { @@ -967,7 +967,7 @@ func sendWhatsAppMsg(rc redis.Conn, msg courier.Msg, sendPath *url.URL, payload wppID, err := jsonparser.GetString(checkResp, "contacts", "[0]", "wa_id") if err == nil { - var updatedPayload interface{} + var updatedPayload any // handle msg type casting switch v := payload.(type) { diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp/whatsapp_test.go index 9f78c214a..d62f414a3 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp/whatsapp_test.go @@ -22,7 +22,7 @@ var testChannels = []courier.Channel{ "WA", "250788383383", "RW", - map[string]interface{}{ + map[string]any{ "auth_token": "the-auth-token", "base_url": "https://foo.bar/", }), @@ -31,7 +31,7 @@ var testChannels = []courier.Channel{ "D3", "250788383383", "RW", - map[string]interface{}{ + map[string]any{ "auth_token": "the-auth-token", "base_url": "https://foo.bar/", }), @@ -40,7 +40,7 @@ var testChannels = []courier.Channel{ "TXW", "250788383383", "RW", - map[string]interface{}{ + map[string]any{ "auth_token": "the-auth-token", "base_url": "https://foo.bar/", }), @@ -1099,7 +1099,7 @@ func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTes func TestSending(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WA", "250788383383", "US", - map[string]interface{}{ + map[string]any{ "auth_token": "token123", "base_url": "https://foo.bar/", "fb_namespace": "waba_namespace", @@ -1107,7 +1107,7 @@ func TestSending(t *testing.T) { }) var hsmSupportChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WA", "250788383383", "US", - map[string]interface{}{ + map[string]any{ "auth_token": "token123", "base_url": "https://foo.bar/", "fb_namespace": "waba_namespace", @@ -1116,7 +1116,7 @@ func TestSending(t *testing.T) { }) var d3Channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "D3", "250788383383", "US", - map[string]interface{}{ + map[string]any{ "auth_token": "token123", "base_url": "https://foo.bar/", "fb_namespace": "waba_namespace", @@ -1124,7 +1124,7 @@ func TestSending(t *testing.T) { }) var txwChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TXW", "250788383383", "US", - map[string]interface{}{ + map[string]any{ "auth_token": "token123", "base_url": "https://foo.bar/", "fb_namespace": "waba_namespace", diff --git a/handlers/yo/yo_test.go b/handlers/yo/yo_test.go index 87b4286d8..445f32562 100644 --- a/handlers/yo/yo_test.go +++ b/handlers/yo/yo_test.go @@ -22,7 +22,7 @@ var ( ) var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]interface{}{"username": "yo-username", "password": "yo-password"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]any{"username": "yo-username", "password": "yo-password"}), } var handleTestCases = []ChannelHandleTestCase{ @@ -99,7 +99,7 @@ var getSendTestCases = []ChannelSendTestCase{ } func TestSending(t *testing.T) { - var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]interface{}{"username": "yo-username", "password": "yo-password"}) + var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]any{"username": "yo-username", "password": "yo-password"}) RunChannelSendTestCases(t, getChannel, newHandler(), getSendTestCases, []string{"yo-password"}, nil) } diff --git a/handlers/zenvia/zenvia_test.go b/handlers/zenvia/zenvia_test.go index d9eb3077e..c099b19e5 100644 --- a/handlers/zenvia/zenvia_test.go +++ b/handlers/zenvia/zenvia_test.go @@ -11,12 +11,12 @@ import ( ) var testWhatsappChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVW", "2020", "BR", map[string]interface{}{"api_key": "zv-api-token"}), - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]interface{}{"api_key": "zv-api-token"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVW", "2020", "BR", map[string]any{"api_key": "zv-api-token"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]any{"api_key": "zv-api-token"}), } var testSMSChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]interface{}{"api_key": "zv-api-token"}), + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]any{"api_key": "zv-api-token"}), } var ( @@ -376,9 +376,9 @@ var defaultSMSSendTestCases = []ChannelSendTestCase{ func TestSending(t *testing.T) { maxMsgLength = 160 - var defaultWhatsappChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVW", "2020", "BR", map[string]interface{}{"api_key": "zv-api-token"}) + var defaultWhatsappChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVW", "2020", "BR", map[string]any{"api_key": "zv-api-token"}) RunChannelSendTestCases(t, defaultWhatsappChannel, newHandler("ZVW", "Zenvia WhatsApp"), defaultWhatsappSendTestCases, []string{"zv-api-token"}, nil) - var defaultSMSChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]interface{}{"api_key": "zv-api-token"}) + var defaultSMSChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]any{"api_key": "zv-api-token"}) RunChannelSendTestCases(t, defaultSMSChannel, newHandler("ZVS", "Zenvia SMS"), defaultSMSSendTestCases, []string{"zv-api-token"}, nil) } diff --git a/responses.go b/responses.go index ad26a09eb..528a7e244 100644 --- a/responses.go +++ b/responses.go @@ -20,7 +20,7 @@ func writeAndLogRequestError(ctx context.Context, h ChannelHandler, w http.Respo // WriteError writes a JSON response for the passed in error func WriteError(w http.ResponseWriter, statusCode int, err error) error { - errors := []interface{}{NewErrorData(err.Error())} + errors := []any{NewErrorData(err.Error())} vErrs, isValidation := err.(validator.ValidationErrors) if isValidation { @@ -33,23 +33,23 @@ func WriteError(w http.ResponseWriter, statusCode int, err error) error { // WriteIgnored writes a JSON response indicating that we ignored the request func WriteIgnored(w http.ResponseWriter, details string) error { - return WriteDataResponse(w, http.StatusOK, "Ignored", []interface{}{NewInfoData(details)}) + return WriteDataResponse(w, http.StatusOK, "Ignored", []any{NewInfoData(details)}) } // WriteAndLogUnauthorized writes a JSON response for the passed in message and logs an info message func WriteAndLogUnauthorized(w http.ResponseWriter, r *http.Request, c Channel, err error) error { LogRequestError(r, c, err) - return WriteDataResponse(w, http.StatusUnauthorized, "Unauthorized", []interface{}{NewErrorData(err.Error())}) + return WriteDataResponse(w, http.StatusUnauthorized, "Unauthorized", []any{NewErrorData(err.Error())}) } // WriteChannelEventSuccess writes a JSON response for the passed in event indicating we handled it func WriteChannelEventSuccess(w http.ResponseWriter, event ChannelEvent) error { - return WriteDataResponse(w, http.StatusOK, "Event Accepted", []interface{}{NewEventReceiveData(event)}) + return WriteDataResponse(w, http.StatusOK, "Event Accepted", []any{NewEventReceiveData(event)}) } // WriteMsgSuccess writes a JSON response for the passed in msg indicating we handled it func WriteMsgSuccess(w http.ResponseWriter, msgs []Msg) error { - data := []interface{}{} + data := []any{} for _, msg := range msgs { data = append(data, NewMsgReceiveData(msg)) } @@ -59,7 +59,7 @@ func WriteMsgSuccess(w http.ResponseWriter, msgs []Msg) error { // WriteStatusSuccess writes a JSON response for the passed in status update indicating we handled it func WriteStatusSuccess(w http.ResponseWriter, statuses []StatusUpdate) error { - data := []interface{}{} + data := []any{} for _, status := range statuses { data = append(data, NewStatusData(status)) } @@ -68,7 +68,7 @@ func WriteStatusSuccess(w http.ResponseWriter, statuses []StatusUpdate) error { } // WriteDataResponse writes a JSON formatted response with the passed in status code, message and data -func WriteDataResponse(w http.ResponseWriter, statusCode int, message string, data []interface{}) error { +func WriteDataResponse(w http.ResponseWriter, statusCode int, message string, data []any) error { return writeJSONResponse(w, statusCode, &dataResponse{message, data}) } @@ -100,12 +100,12 @@ func NewMsgReceiveData(msg Msg) MsgReceiveData { // EventReceiveData is our response payload for a channel event type EventReceiveData struct { - Type string `json:"type"` - ChannelUUID ChannelUUID `json:"channel_uuid"` - EventType ChannelEventType `json:"event_type"` - URN urns.URN `json:"urn"` - ReceivedOn time.Time `json:"received_on"` - Extra map[string]interface{} `json:"extra,omitempty"` + Type string `json:"type"` + ChannelUUID ChannelUUID `json:"channel_uuid"` + EventType ChannelEventType `json:"event_type"` + URN urns.URN `json:"urn"` + ReceivedOn time.Time `json:"received_on"` + Extra map[string]any `json:"extra,omitempty"` } // NewEventReceiveData creates a new receive data for the passed in event @@ -163,11 +163,11 @@ func NewInfoData(info string) InfoData { } type dataResponse struct { - Message string `json:"message"` - Data []interface{} `json:"data"` + Message string `json:"message"` + Data []any `json:"data"` } -func writeJSONResponse(w http.ResponseWriter, statusCode int, response interface{}) error { +func writeJSONResponse(w http.ResponseWriter, statusCode int, response any) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) return json.NewEncoder(w).Encode(response) diff --git a/server.go b/server.go index db8a74ca0..4717da99d 100644 --- a/server.go +++ b/server.go @@ -411,7 +411,7 @@ func (s *server) handleFetchAttachment(w http.ResponseWriter, r *http.Request) { func (s *server) handle404(w http.ResponseWriter, r *http.Request) { logrus.WithField("url", r.URL.String()).WithField("method", r.Method).WithField("resp_status", "404").Info("not found") - errors := []interface{}{NewErrorData(fmt.Sprintf("not found: %s", r.URL.String()))} + errors := []any{NewErrorData(fmt.Sprintf("not found: %s", r.URL.String()))} err := WriteDataResponse(w, http.StatusNotFound, "Not Found", errors) if err != nil { logrus.WithError(err).Error() @@ -420,7 +420,7 @@ func (s *server) handle404(w http.ResponseWriter, r *http.Request) { func (s *server) handle405(w http.ResponseWriter, r *http.Request) { logrus.WithField("url", r.URL.String()).WithField("method", r.Method).WithField("resp_status", "405").Info("invalid method") - errors := []interface{}{NewErrorData(fmt.Sprintf("method not allowed: %s", r.Method))} + errors := []any{NewErrorData(fmt.Sprintf("method not allowed: %s", r.Method))} err := WriteDataResponse(w, http.StatusMethodNotAllowed, "Method Not Allowed", errors) if err != nil { logrus.WithError(err).Error() diff --git a/server_test.go b/server_test.go index 94d989181..da5a12fa9 100644 --- a/server_test.go +++ b/server_test.go @@ -94,7 +94,7 @@ func TestFetchAttachment(t *testing.T) { config.AuthToken = "sesame" mb := test.NewMockBackend() - mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]interface{}{}) + mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) mb.AddChannel(mockChannel) server := courier.NewServerWithLogger(config, mb, logger) diff --git a/spool.go b/spool.go index 00fbe6ead..6e4cee5a9 100644 --- a/spool.go +++ b/spool.go @@ -23,7 +23,7 @@ func RegisterFlusher(directory string, flusherFunc FlusherFunc) { } // WriteToSpool writes the passed in object to the passed in subdir -func WriteToSpool(spoolDir string, subdir string, contents interface{}) error { +func WriteToSpool(spoolDir string, subdir string, contents any) error { contentBytes, err := json.MarshalIndent(contents, "", " ") if err != nil { return err diff --git a/test/channel.go b/test/channel.go index a47375d1e..6f5157315 100644 --- a/test/channel.go +++ b/test/channel.go @@ -17,8 +17,8 @@ type MockChannel struct { address courier.ChannelAddress country string role string - config map[string]interface{} - orgConfig map[string]interface{} + config map[string]any + orgConfig map[string]any } // UUID returns the uuid for this channel @@ -51,7 +51,7 @@ func (c *MockChannel) ChannelAddress() courier.ChannelAddress { return c.address func (c *MockChannel) Country() string { return c.country } // SetConfig sets the passed in config parameter -func (c *MockChannel) SetConfig(key string, value interface{}) { +func (c *MockChannel) SetConfig(key string, value any) { c.config[key] = value } @@ -65,7 +65,7 @@ func (c *MockChannel) CallbackDomain(fallbackDomain string) string { } // ConfigForKey returns the config value for the passed in key -func (c *MockChannel) ConfigForKey(key string, defaultValue interface{}) interface{} { +func (c *MockChannel) ConfigForKey(key string, defaultValue any) any { value, found := c.config[key] if !found { return defaultValue @@ -120,7 +120,7 @@ func (c *MockChannel) IntConfigForKey(key string, defaultValue int) int { } // OrgConfigForKey returns the org config value for the passed in key -func (c *MockChannel) OrgConfigForKey(key string, defaultValue interface{}) interface{} { +func (c *MockChannel) OrgConfigForKey(key string, defaultValue any) any { value, found := c.orgConfig[key] if !found { return defaultValue @@ -153,7 +153,7 @@ func (c *MockChannel) HasRole(role courier.ChannelRole) bool { } // NewMockChannel creates a new mock channel for the passed in type, address, country and config -func NewMockChannel(uuid string, channelType string, address string, country string, config map[string]interface{}) *MockChannel { +func NewMockChannel(uuid string, channelType string, address string, country string, config map[string]any) *MockChannel { return &MockChannel{ uuid: courier.ChannelUUID(uuid), channelType: courier.ChannelType(channelType), @@ -162,6 +162,6 @@ func NewMockChannel(uuid string, channelType string, address string, country str country: country, config: config, role: "SR", - orgConfig: map[string]interface{}{}, + orgConfig: map[string]any{}, } } diff --git a/test/channel_event.go b/test/channel_event.go index 4742d9ac6..1ae1c5d6e 100644 --- a/test/channel_event.go +++ b/test/channel_event.go @@ -15,7 +15,7 @@ type mockChannelEvent struct { occurredOn time.Time contactName string - extra map[string]interface{} + extra map[string]any } func (e *mockChannelEvent) EventID() int64 { return 0 } @@ -23,11 +23,11 @@ func (e *mockChannelEvent) ChannelUUID() courier.ChannelUUID { return e.chann func (e *mockChannelEvent) EventType() courier.ChannelEventType { return e.eventType } func (e *mockChannelEvent) CreatedOn() time.Time { return e.createdOn } func (e *mockChannelEvent) OccurredOn() time.Time { return e.occurredOn } -func (e *mockChannelEvent) Extra() map[string]interface{} { return e.extra } +func (e *mockChannelEvent) Extra() map[string]any { return e.extra } func (e *mockChannelEvent) ContactName() string { return e.contactName } func (e *mockChannelEvent) URN() urns.URN { return e.urn } -func (e *mockChannelEvent) WithExtra(extra map[string]interface{}) courier.ChannelEvent { +func (e *mockChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { e.extra = extra return e } diff --git a/test/handler.go b/test/handler.go index ef970f745..8c5a6b0ea 100644 --- a/test/handler.go +++ b/test/handler.go @@ -31,7 +31,7 @@ func (h *mockHandler) UseChannelRouteUUID() bool { return true } func (h *mockHandler) RedactValues(courier.Channel) []string { return []string{"sesame"} } func (h *mockHandler) GetChannel(ctx context.Context, r *http.Request) (courier.Channel, error) { - dmChannel := NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]interface{}{}) + dmChannel := NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) return dmChannel, nil } From c8938d53b9b55c142bc3df20c92aa09d1a4798be Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 31 Aug 2023 16:56:37 -0500 Subject: [PATCH 059/170] Record external ids in redis and use for resolving ids for status updates (WIP) --- backends/rapidpro/backend.go | 28 +++++++++++++++++++++++----- backends/rapidpro/status.go | 3 +++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 4c02a874e..485a391cc 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -73,6 +73,9 @@ type backend struct { receivedExternalIDs *redisx.IntervalHash // using external id receivedMsgs *redisx.IntervalHash // using content hash + // tracking of external ids of messages we've sent in case we need one before its status update has been written + sentExternalIDs *redisx.IntervalHash + // both sqlx and redis provide wait stats which are cummulative that we need to convert into increments dbWaitDuration time.Duration dbWaitCount int64 @@ -95,6 +98,8 @@ func newBackend(cfg *courier.Config) courier.Backend { receivedMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), receivedExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), + + sentExternalIDs: redisx.NewIntervalHash("sent-external-ids", time.Hour, 2), } } @@ -536,11 +541,24 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp } } - // if we have an id and are marking an outgoing msg as errored, then clear our sent flag - if status.ID() != courier.NilMsgID && status.Status() == courier.MsgStatusErrored { - err := b.ClearMsgSent(ctx, status.ID()) - if err != nil { - logrus.WithError(err).WithField("msg", status.ID()).Error("error clearing sent flags") + if status.ID() != courier.NilMsgID { + // this is a message we've just sent and were given an external id for + if status.ExternalID() != "" { + rc := b.redisPool.Get() + defer rc.Close() + + err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%s|%s", status.ChannelUUID(), status.ExternalID()), fmt.Sprintf("%d", status.ID())) + if err != nil { + logrus.WithError(err).WithField("msg", status.ID()).Error("error recording external id") + } + } + + // we sent a message that errored so clear our sent flag to allow it to be retried + if status.Status() == courier.MsgStatusErrored { + err := b.ClearMsgSent(ctx, status.ID()) + if err != nil { + logrus.WithError(err).WithField("msg", status.ID()).Error("error clearing sent flags") + } } } diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index dc9efc83a..95e447714 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -277,6 +277,9 @@ SELECT id, channel_id, external_id // resolveStatusUpdateMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel id + external id pair // found for a status, that status will be left with a nil msg ID. func resolveStatusUpdateMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*StatusUpdate) error { + + // TODO look for msg ids in redis + // create a mapping of channel id + external id -> status type ext struct { channelID courier.ChannelID From 740cbceb4167edb4010e03488e0a2bc7034d7c7b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 1 Sep 2023 11:55:34 -0500 Subject: [PATCH 060/170] Update to latest redisx --- backends/rapidpro/msg.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 04669d189..7ac7577fa 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -270,7 +270,7 @@ func (b *backend) recordMsgReceived(msg *DBMsg) { func (b *backend) clearMsgSeen(rc redis.Conn, msg *DBMsg) { fingerprint := fmt.Sprintf("%s|%s", msg.Channel().UUID(), msg.URN().Identity()) - b.receivedMsgs.Remove(rc, fingerprint) + b.receivedMsgs.Del(rc, fingerprint) } //----------------------------------------------------------------------------- diff --git a/go.mod b/go.mod index cd72dd51f..9a967bff5 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.40.0 github.com/nyaruka/null/v2 v2.0.3 - github.com/nyaruka/redisx v0.4.0 + github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 8e689dd93..9a51db781 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= github.com/nyaruka/phonenumbers v1.1.8/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= -github.com/nyaruka/redisx v0.4.0 h1:DZpbNPjflOZzeR7OQArcwgKx4OTt1vpNOJjT+aBSe7U= -github.com/nyaruka/redisx v0.4.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= +github.com/nyaruka/redisx v0.5.0 h1:XH1pjG17lhj2DZJbrrZ2yZuPLAXrrHidXVA7cIuQq4g= +github.com/nyaruka/redisx v0.5.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= From ac6ee38221cc876d61d768704fac2cce25efcb42 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 1 Sep 2023 13:15:51 -0500 Subject: [PATCH 061/170] Try to resolve sent external ids from redis --- backends/rapidpro/backend.go | 13 ++++---- backends/rapidpro/backend_test.go | 37 +++++++++++++++++++++++ backends/rapidpro/status.go | 50 +++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 485a391cc..9d1c5bc8c 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -96,10 +96,9 @@ func newBackend(cfg *courier.Config) courier.Backend { mediaCache: redisx.NewIntervalHash("media-lookups", time.Hour*24, 2), mediaMutexes: *syncx.NewHashMutex(8), - receivedMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), - receivedExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), - - sentExternalIDs: redisx.NewIntervalHash("sent-external-ids", time.Hour, 2), + receivedMsgs: redisx.NewIntervalHash("seen-msgs", time.Second*2, 2), // 2 - 4 seconds + receivedExternalIDs: redisx.NewIntervalHash("seen-external-ids", time.Hour*24, 2), // 24 - 48 hours + sentExternalIDs: redisx.NewIntervalHash("sent-external-ids", time.Hour, 2), // 1 - 2 hours } } @@ -247,7 +246,7 @@ func (b *backend) Start() error { } // create our batched writers and start them - b.statusWriter = NewStatusWriter(b.db, b.config.SpoolDir, b.writerWG) + b.statusWriter = NewStatusWriter(b, b.config.SpoolDir, b.writerWG) b.statusWriter.Start() b.dbLogWriter = NewDBLogWriter(b.db, b.writerWG) @@ -530,6 +529,8 @@ func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalI // WriteStatusUpdate writes the passed in MsgStatus to our store func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { + su := status.(*StatusUpdate) + if status.ID() == courier.NilMsgID && status.ExternalID() == "" { return errors.New("message status with no id or external id") } @@ -547,7 +548,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp rc := b.redisPool.Get() defer rc.Close() - err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%s|%s", status.ChannelUUID(), status.ExternalID()), fmt.Sprintf("%d", status.ID())) + err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.ID())) if err != nil { logrus.WithError(err).WithField("msg", status.ID()).Error("error recording external id") } diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 0160d8c31..60f556ff0 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -684,6 +684,43 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) } +func (ts *BackendTestSuite) TestSentExternalIDCaching() { + r := ts.b.redisPool.Get() + defer r.Close() + + ctx := context.Background() + channel := ts.getChannel("KN", "dbc126ed-66bc-4e28-b67b-81dc3327c95d") + clog := courier.NewChannelLog(courier.ChannelLogTypeMsgSend, channel, nil) + + ts.clearRedis() + + // create a status update from a send which will have id and external id + status1 := ts.b.NewStatusUpdate(channel, 10000, courier.MsgStatusSent, clog) + status1.SetExternalID("ex457") + err := ts.b.WriteStatusUpdate(ctx, status1) + ts.NoError(err) + + keys, err := redis.Strings(r.Do("KEYS", "sent-external-ids:*")) + ts.NoError(err) + ts.Len(keys, 1) + assertredis.HGetAll(ts.T(), ts.b.redisPool, keys[0], map[string]string{"10|ex457": "10000"}) + + // mimic a delay in that status being written by reverting the db changes + ts.b.db.MustExec(`UPDATE msgs_msg SET status = 'W', external_id = NULL WHERE id = 10000`) + + // create a callback status update which only has external id + status2 := ts.b.NewStatusUpdateByExternalID(channel, "ex457", courier.MsgStatusDelivered, clog) + + err = ts.b.WriteStatusUpdate(ctx, status2) + ts.NoError(err) + + // give batcher time to write it + time.Sleep(time.Millisecond * 700) + + // msg status successfully updated in the database + assertdb.Query(ts.T(), ts.b.db, `SELECT status FROM msgs_msg WHERE id = 10000`).Returns("D") +} + func (ts *BackendTestSuite) TestHealth() { // all should be well in test land ts.Equal(ts.b.Health(), "") diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 95e447714..01b129764 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -9,7 +9,6 @@ import ( "sync" "time" - "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/syncx" @@ -115,7 +114,7 @@ func (b *backend) flushStatusFile(filename string, contents []byte) error { } // try to flush to our db - _, err = writeStatusUpdatesToDB(ctx, b.db, []*StatusUpdate{status}) + _, err = b.writeStatusUpdatesToDB(ctx, []*StatusUpdate{status}) return err } @@ -187,28 +186,28 @@ type StatusWriter struct { *syncx.Batcher[*StatusUpdate] } -func NewStatusWriter(db *sqlx.DB, spoolDir string, wg *sync.WaitGroup) *StatusWriter { +func NewStatusWriter(b *backend, spoolDir string, wg *sync.WaitGroup) *StatusWriter { return &StatusWriter{ Batcher: syncx.NewBatcher[*StatusUpdate](func(batch []*StatusUpdate) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - writeStatuseUpdates(ctx, db, spoolDir, batch) + b.writeStatuseUpdates(ctx, spoolDir, batch) }, 1000, time.Millisecond*500, 1000, wg), } } // tries to write a batch of message statuses to the database and spools those that fail -func writeStatuseUpdates(ctx context.Context, db *sqlx.DB, spoolDir string, batch []*StatusUpdate) { +func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batch []*StatusUpdate) { log := logrus.WithField("comp", "status writer") - unresolved, err := writeStatusUpdatesToDB(ctx, db, batch) + unresolved, err := b.writeStatusUpdatesToDB(ctx, batch) // if we received an error, try again one at a time (in case it is one value hanging us up) if err != nil { for _, s := range batch { - _, err = writeStatusUpdatesToDB(ctx, db, []*StatusUpdate{s}) + _, err = b.writeStatusUpdatesToDB(ctx, []*StatusUpdate{s}) if err != nil { log := log.WithField("msg_id", s.ID()) @@ -234,7 +233,7 @@ func writeStatuseUpdates(ctx context.Context, db *sqlx.DB, spoolDir string, batc // writes a batch of msg status updates to the database - messages that can't be resolved are returned and aren't // considered an error -func writeStatusUpdatesToDB(ctx context.Context, db *sqlx.DB, statuses []*StatusUpdate) ([]*StatusUpdate, error) { +func (b *backend) writeStatusUpdatesToDB(ctx context.Context, statuses []*StatusUpdate) ([]*StatusUpdate, error) { // get the statuses which have external ID instead of a message ID missingID := make([]*StatusUpdate, 0, 500) for _, s := range statuses { @@ -245,7 +244,7 @@ func writeStatusUpdatesToDB(ctx context.Context, db *sqlx.DB, statuses []*Status // try to resolve channel ID + external ID to message IDs if len(missingID) > 0 { - if err := resolveStatusUpdateMsgIDs(ctx, db, missingID); err != nil { + if err := b.resolveStatusUpdateMsgIDs(ctx, missingID); err != nil { return nil, err } } @@ -261,7 +260,7 @@ func writeStatusUpdatesToDB(ctx context.Context, db *sqlx.DB, statuses []*Status } } - err := dbutil.BulkQuery(ctx, db, sqlUpdateMsgByID, resolved) + err := dbutil.BulkQuery(ctx, b.db, sqlUpdateMsgByID, resolved) if err != nil { return nil, errors.Wrap(err, "error updating status") } @@ -276,26 +275,47 @@ SELECT id, channel_id, external_id // resolveStatusUpdateMsgIDs tries to resolve msg IDs for the given statuses - if there's no matching channel id + external id pair // found for a status, that status will be left with a nil msg ID. -func resolveStatusUpdateMsgIDs(ctx context.Context, db *sqlx.DB, statuses []*StatusUpdate) error { +func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*StatusUpdate) error { + rc := b.redisPool.Get() + defer rc.Close() - // TODO look for msg ids in redis + chAndExtKeys := make([]string, len(statuses)) + for i, s := range statuses { + chAndExtKeys[i] = fmt.Sprintf("%d|%s", s.ChannelID_, s.ExternalID_) + } + cachedIDs, err := b.sentExternalIDs.MGet(rc, chAndExtKeys...) + if err != nil { + // log error but we continue and try to get ids from the database + logrus.WithError(err).Error("error looking up sent message ids in redis") + } + + // collect the statuses that couldn't be resolved from cache, update the ones that could + notInCache := make([]*StatusUpdate, 0, len(statuses)) + for i := range cachedIDs { + id, err := strconv.Atoi(cachedIDs[i]) + if err != nil { + notInCache = append(notInCache, statuses[i]) + } else { + statuses[i].ID_ = courier.MsgID(id) + } + } // create a mapping of channel id + external id -> status type ext struct { channelID courier.ChannelID externalID string } - statusesByExt := make(map[ext]*StatusUpdate, len(statuses)) + statusesByExt := make(map[ext]*StatusUpdate, len(notInCache)) for _, s := range statuses { statusesByExt[ext{s.ChannelID_, s.ExternalID_}] = s } - sql, params, err := dbutil.BulkSQL(db, sqlResolveStatusMsgIDs, statuses) + sql, params, err := dbutil.BulkSQL(b.db, sqlResolveStatusMsgIDs, notInCache) if err != nil { return err } - rows, err := db.QueryContext(ctx, sql, params...) + rows, err := b.db.QueryContext(ctx, sql, params...) if err != nil { return err } From 0ee3f757709e7dc5dc6adc70667204126f809d5c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Sep 2023 10:02:16 -0500 Subject: [PATCH 062/170] Update CHANGELOG.md for v8.3.9 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cfb4df04..29213427c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.9 (2023-09-05) +------------------------- + * Try to resolve sent external ids from redis + * For received messages without external id, de-dupe by hash of text+attachments instead of just text + v8.3.8 (2023-08-31) ------------------------- * Update to latest redisx which fixes accuracy for sub-minute interval hashes From 09db8bf804de83cc2758e57ce63900bf5a18a622 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Sep 2023 13:44:44 -0500 Subject: [PATCH 063/170] Rework handler tests so that test cases must explicitly say if they don't generate a channel log --- handlers/bongolive/bongolive_test.go | 4 ++-- handlers/external/external_test.go | 1 + handlers/facebookapp/facebookapp_test.go | 23 ++++++++++++++++++++--- handlers/playmobile/playmobile_test.go | 6 ++---- handlers/telegram/telegram_test.go | 3 ++- handlers/test.go | 12 +++++++----- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/handlers/bongolive/bongolive_test.go b/handlers/bongolive/bongolive_test.go index 68055ab48..2e4782ee8 100644 --- a/handlers/bongolive/bongolive_test.go +++ b/handlers/bongolive/bongolive_test.go @@ -46,8 +46,8 @@ var testCases = []ChannelHandleTestCase{ { Label: "Status No params", URL: receiveURL, - Data: "", - ExpectedRespStatus: 405, + Data: "&", + ExpectedRespStatus: 400, ExpectedBodyContains: "", }, { diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index e8b1510c5..d7d697489 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -135,6 +135,7 @@ var handleTestCases = []ChannelHandleTestCase{ URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/wired/", ExpectedRespStatus: 404, ExpectedBodyContains: `page not found`, + NoLogsExpected: true, }, { Label: "Sent Valid", diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index fc398f291..8e356b441 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -207,6 +207,7 @@ var testCasesFBA = []ChannelHandleTestCase{ Data: string(test.ReadFile("./testdata/fba/notPage.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notpage", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -215,6 +216,7 @@ var testCasesFBA = []ChannelHandleTestCase{ Data: string(test.ReadFile("./testdata/fba/noEntriesFBA.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "no entries found", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -239,6 +241,7 @@ var testCasesFBA = []ChannelHandleTestCase{ Data: "not JSON", ExpectedRespStatus: 200, ExpectedBodyContains: "unable to parse request JSON", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -345,6 +348,7 @@ var testCasesIG = []ChannelHandleTestCase{ Data: string(test.ReadFile("./testdata/ig/noEntriesIG.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "no entries found", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -353,6 +357,7 @@ var testCasesIG = []ChannelHandleTestCase{ Data: string(test.ReadFile("./testdata/ig/notInstagram.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notinstagram", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -377,6 +382,7 @@ var testCasesIG = []ChannelHandleTestCase{ Data: "not JSON", ExpectedRespStatus: 200, ExpectedBodyContains: "unable to parse request JSON", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { @@ -676,10 +682,11 @@ var testCasesWAC = []ChannelHandleTestCase{ Data: "not json", ExpectedRespStatus: 200, ExpectedBodyContains: "unable to parse", + NoLogsExpected: true, PrepRequest: addValidSignature, }, { - Label: "Receive Invalid JSON", + Label: "Receive Invalid From", URL: wacReceiveURL, Data: string(test.ReadFile("./testdata/wac/invalidFrom.json")), ExpectedRespStatus: 200, @@ -687,7 +694,7 @@ var testCasesWAC = []ChannelHandleTestCase{ PrepRequest: addValidSignature, }, { - Label: "Receive Invalid JSON", + Label: "Receive Invalid Timestamp", URL: wacReceiveURL, Data: string(test.ReadFile("./testdata/wac/invalidTimestamp.json")), ExpectedRespStatus: 200, @@ -799,7 +806,7 @@ func TestHandler(t *testing.T) { // invalid auth token if accessToken != "Bearer a123" && accessToken != "Bearer wac_admin_system_user_token" { fmt.Printf("Access token: %s\n", accessToken) - http.Error(w, "invalid auth token", 403) + http.Error(w, "invalid auth token", http.StatusForbidden) return } @@ -858,6 +865,7 @@ func TestVerify(t *testing.T) { URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", ExpectedRespStatus: 200, ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, NoQueueErrorCheck: true, NoInvalidChannelCheck: true, }, @@ -866,24 +874,28 @@ func TestVerify(t *testing.T) { URL: "/c/fba/receive", ExpectedRespStatus: 200, ExpectedBodyContains: "unknown request", + NoLogsExpected: true, }, { Label: "Verify No Secret", URL: "/c/fba/receive?hub.mode=subscribe", ExpectedRespStatus: 200, ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, }, { Label: "Invalid Secret", URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=blah", ExpectedRespStatus: 200, ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, }, { Label: "Valid Secret", URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", ExpectedRespStatus: 200, ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, }, }) @@ -893,6 +905,7 @@ func TestVerify(t *testing.T) { URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", ExpectedRespStatus: 200, ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, NoQueueErrorCheck: true, NoInvalidChannelCheck: true, }, @@ -901,24 +914,28 @@ func TestVerify(t *testing.T) { URL: "/c/ig/receive", ExpectedRespStatus: 200, ExpectedBodyContains: "unknown request", + NoLogsExpected: true, }, { Label: "Verify No Secret", URL: "/c/ig/receive?hub.mode=subscribe", ExpectedRespStatus: 200, ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, }, { Label: "Invalid Secret", URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=blah", ExpectedRespStatus: 200, ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, }, { Label: "Valid Secret", URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", ExpectedRespStatus: 200, ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, }, }) } diff --git a/handlers/playmobile/playmobile_test.go b/handlers/playmobile/playmobile_test.go index ca371260e..e65b43129 100644 --- a/handlers/playmobile/playmobile_test.go +++ b/handlers/playmobile/playmobile_test.go @@ -27,8 +27,6 @@ var ( SMS Response Accepted ` - invalidXML = `` - noMessages = `` receiveWithPrefix = ` @@ -100,9 +98,9 @@ var testCases = []ChannelHandleTestCase{ { Label: "Invalid XML", URL: receiveURL, - Data: invalidXML, + Data: `<>`, ExpectedBodyContains: "", - ExpectedRespStatus: 405, + ExpectedRespStatus: 400, }, { Label: "Receive With Prefix", diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/telegram_test.go index de6e1f68c..f0750e725 100644 --- a/handlers/telegram/telegram_test.go +++ b/handlers/telegram/telegram_test.go @@ -671,6 +671,7 @@ var testCases = []ChannelHandleTestCase{ Data: invalidFileID, ExpectedRespStatus: 200, ExpectedBodyContains: "unable to resolve file", + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, }, { Label: "Receive NoOk FileID", @@ -693,7 +694,7 @@ var testCases = []ChannelHandleTestCase{ Data: errorFile, ExpectedRespStatus: 200, ExpectedBodyContains: "unable to resolve file", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("500", "error loading file")}, }, { Label: "Receive NotOk FileID", diff --git a/handlers/test.go b/handlers/test.go index b241722b1..116fceab8 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -53,6 +53,7 @@ type ChannelHandleTestCase struct { ExpectedEvent courier.ChannelEventType ExpectedEventExtra map[string]any ExpectedErrors []*courier.ChannelError + NoLogsExpected bool } // MockedRequest is a fake HTTP request @@ -220,12 +221,13 @@ func RunChannelTestCases(t *testing.T, channels []courier.Channel, handler couri require.Equal(*tc.ExpectedContactName, mb.LastContactName()) } - // if we're expecting a message, status or event, check we have a log for it - if tc.ExpectedMsgText != nil || tc.ExpectedMsgStatus != "" || tc.ExpectedEvent != "" { - assert.Greater(t, len(mb.WrittenChannelLogs()), 0, "expected at least one channel log") + // unless we know there won't be a log, check one was written + if !tc.NoLogsExpected { + if assert.Equal(t, 1, len(mb.WrittenChannelLogs()), "expected a channel log") { - clog := mb.WrittenChannelLogs()[0] - assert.Equal(t, tc.ExpectedErrors, clog.Errors(), "unexpected errors logged") + clog := mb.WrittenChannelLogs()[0] + assert.Equal(t, tc.ExpectedErrors, clog.Errors(), "unexpected errors logged") + } } }) } From f7fe70c3eec582f62f989323e7528fcd35f350df Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Sep 2023 14:07:55 -0500 Subject: [PATCH 064/170] Rename test support functions for clarity --- .../africastalking/africastalking_test.go | 18 ++--- handlers/arabiacell/arabiacell_test.go | 12 +-- handlers/bandwidth/bandwidth_test.go | 12 +-- handlers/bongolive/bongolive_test.go | 12 +-- handlers/burstsms/burstsms_test.go | 12 +-- handlers/clickatell/clickatell_test.go | 12 +-- handlers/clickmobile/clickmobile_test.go | 12 +-- handlers/clicksend/clicksend_test.go | 12 +-- handlers/dart/dart_test.go | 12 +-- handlers/dialog360/dialog360_test.go | 14 ++-- handlers/discord/discord_test.go | 12 +-- handlers/dmark/dmark_test.go | 12 +-- handlers/external/external_test.go | 76 +++++++++---------- handlers/facebook/facebook_test.go | 14 ++-- handlers/facebookapp/facebookapp_test.go | 36 ++++----- handlers/firebase/firebase_test.go | 16 ++-- handlers/freshchat/freshchat_test.go | 16 ++-- handlers/globe/globe_test.go | 12 +-- .../highconnection/highconnection_test.go | 12 +-- handlers/hormuud/hormuud_test.go | 16 ++-- handlers/i2sms/i2sms_test.go | 12 +-- handlers/infobip/infobip_test.go | 16 ++-- handlers/jasmin/jasmin_test.go | 12 +-- handlers/jiochat/jiochat_test.go | 14 ++-- handlers/justcall/justcall_test.go | 12 +-- handlers/kaleyra/kaleyra_test.go | 16 ++-- handlers/kannel/kannel_test.go | 20 ++--- handlers/line/line_test.go | 12 +-- handlers/m3tech/m3tech_test.go | 12 +-- handlers/macrokiosk/macrokiosk_test.go | 46 ++++++----- handlers/mblox/mblox_test.go | 12 +-- handlers/messagebird/messagebird_test.go | 10 +-- handlers/messangi/messangi_test.go | 12 +-- handlers/mtarget/mtarget_test.go | 12 +-- handlers/mtn/mtn_test.go | 16 ++-- handlers/nexmo/nexmo_test.go | 12 +-- handlers/novo/novo_test.go | 12 +-- handlers/playmobile/playmobile_test.go | 12 +-- handlers/plivo/plivo_test.go | 12 +-- handlers/redrabbit/redrabbit_test.go | 6 +- handlers/rocketchat/rocketchat_test.go | 12 +-- handlers/shaqodoon/shaqodoon_test.go | 12 +-- handlers/slack/slack_test.go | 26 +++---- handlers/smscentral/smscentral_test.go | 12 +-- handlers/start/start_test.go | 12 +-- handlers/telegram/telegram_test.go | 14 ++-- handlers/telesom/telesom_test.go | 12 +-- handlers/test.go | 18 ++--- handlers/thinq/thinq_test.go | 12 +-- handlers/twiml/twiml_test.go | 52 ++++++------- handlers/twitter/twitter_test.go | 16 ++-- handlers/viber/viber_test.go | 26 +++---- handlers/vk/vk_test.go | 18 ++--- handlers/wavy/wavy_test.go | 12 +-- handlers/wechat/wechat_test.go | 14 ++-- handlers/whatsapp/whatsapp_test.go | 36 ++++----- handlers/yo/yo_test.go | 12 +-- handlers/zenvia/zenvia_test.go | 20 ++--- 58 files changed, 490 insertions(+), 484 deletions(-) diff --git a/handlers/africastalking/africastalking_test.go b/handlers/africastalking/africastalking_test.go index 639ff2783..623bfe087 100644 --- a/handlers/africastalking/africastalking_test.go +++ b/handlers/africastalking/africastalking_test.go @@ -19,7 +19,7 @@ const ( statusURL = "/c/at/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" ) -var testCases = []ChannelHandleTestCase{ +var incomingTestCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -102,12 +102,12 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), incomingTestCases) } func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler(), testCases) + RunChannelBenchmarks(b, testChannels, newHandler(), incomingTestCases) } // setSendURL takes care of setting the sendURL to call @@ -115,7 +115,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var outgoingTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -162,7 +162,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var sharedSendTestCases = []ChannelSendTestCase{ +var sharedSendTestCases = []OutgoingTestCase{ { Label: "Shared Send", MsgText: "Simple Message ☺", @@ -177,7 +177,7 @@ var sharedSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", map[string]any{ courier.ConfigUsername: "Username", @@ -190,6 +190,6 @@ func TestSending(t *testing.T) { configIsShared: true, }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"KEY"}, nil) - RunChannelSendTestCases(t, sharedChannel, newHandler(), sharedSendTestCases, []string{"KEY"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), outgoingTestCases, []string{"KEY"}, nil) + RunOutgoingTestCases(t, sharedChannel, newHandler(), sharedSendTestCases, []string{"KEY"}, nil) } diff --git a/handlers/arabiacell/arabiacell_test.go b/handlers/arabiacell/arabiacell_test.go index 379ec8dd2..8b523118c 100644 --- a/handlers/arabiacell/arabiacell_test.go +++ b/handlers/arabiacell/arabiacell_test.go @@ -17,7 +17,7 @@ const ( receiveURL = "/c/ac/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -36,8 +36,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -48,7 +48,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -104,7 +104,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", map[string]any{ courier.ConfigUsername: "user1", @@ -112,5 +112,5 @@ func TestSending(t *testing.T) { configServiceID: "service1", configChargingLevel: "0", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"pass1"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"pass1"}, nil) } diff --git a/handlers/bandwidth/bandwidth_test.go b/handlers/bandwidth/bandwidth_test.go index 58dd5b22d..418c7e34a 100644 --- a/handlers/bandwidth/bandwidth_test.go +++ b/handlers/bandwidth/bandwidth_test.go @@ -179,7 +179,7 @@ var invalidStatus = `[ } ]` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -238,8 +238,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -251,7 +251,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL + "?%s" } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -319,11 +319,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BW", "2020", "US", map[string]any{courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configAccountID: "accound-id", configApplicationID: "application-id"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1")}, nil) } func TestBuildAttachmentRequest(t *testing.T) { diff --git a/handlers/bongolive/bongolive_test.go b/handlers/bongolive/bongolive_test.go index 2e4782ee8..cfab36f41 100644 --- a/handlers/bongolive/bongolive_test.go +++ b/handlers/bongolive/bongolive_test.go @@ -17,7 +17,7 @@ const ( receiveURL = "/c/bl/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -74,8 +74,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -86,7 +86,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -144,11 +144,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BL", "2020", "KE", map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"pass1"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"pass1"}, nil) } diff --git a/handlers/burstsms/burstsms_test.go b/handlers/burstsms/burstsms_test.go index 3282ddfd7..0e5565ee8 100644 --- a/handlers/burstsms/burstsms_test.go +++ b/handlers/burstsms/burstsms_test.go @@ -19,7 +19,7 @@ const ( statusURL = "/c/bs/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL + "?response=Msg&mobile=254791541111", @@ -50,8 +50,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -62,7 +62,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -110,11 +110,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "BS", "2020", "US", map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1")}, nil) } diff --git a/handlers/clickatell/clickatell_test.go b/handlers/clickatell/clickatell_test.go index 07b1c3dbd..d848ed9b9 100644 --- a/handlers/clickatell/clickatell_test.go +++ b/handlers/clickatell/clickatell_test.go @@ -18,7 +18,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, var successSendResponse = `{"messages":[{"apiMessageId":"id1002","accepted":true,"to":"12067799299","error":null}],"error":null}` var failSendResponse = `{"messages":[],"error":"Two-Way integration error - From number is not related to integration"}` -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -76,14 +76,14 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CT", "2020", "US", map[string]any{ courier.ConfigAPIKey: "API-KEY", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"API-KEY"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"API-KEY"}, nil) } var testChannels = []courier.Channel{ @@ -130,7 +130,7 @@ const ( }` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Valid Receive", URL: receiveURL, @@ -224,8 +224,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { diff --git a/handlers/clickmobile/clickmobile_test.go b/handlers/clickmobile/clickmobile_test.go index 9f932d720..b5066a206 100644 --- a/handlers/clickmobile/clickmobile_test.go +++ b/handlers/clickmobile/clickmobile_test.go @@ -64,7 +64,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CM", "2020", "MW", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -134,8 +134,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -149,7 +149,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -197,7 +197,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CM", "2020", "MW", map[string]any{ "password": "Password", @@ -211,5 +211,5 @@ func TestSending(t *testing.T) { // mock time so we can have predictable MD5 hashes dates.SetNowSource(dates.NewFixedNowSource(time.Date(2018, 4, 11, 18, 24, 30, 123456000, time.UTC))) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/clicksend/clicksend_test.go b/handlers/clicksend/clicksend_test.go index b8f1742e1..03289c75b 100644 --- a/handlers/clicksend/clicksend_test.go +++ b/handlers/clicksend/clicksend_test.go @@ -18,7 +18,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "CS", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -39,8 +39,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -110,7 +110,7 @@ const failureResponse = `{ ] }` -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -167,7 +167,7 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "GL", "2020", "US", map[string]any{ "username": "Aladdin", @@ -175,5 +175,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{httpx.BasicAuth("Aladdin", "open sesame")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{httpx.BasicAuth("Aladdin", "open sesame")}, nil) } diff --git a/handlers/dart/dart_test.go b/handlers/dart/dart_test.go index a4c04ede0..b0a2a1ed2 100644 --- a/handlers/dart/dart_test.go +++ b/handlers/dart/dart_test.go @@ -18,7 +18,7 @@ const ( statusURL = "/c/da/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/delivered/" ) -var daTestCases = []ChannelHandleTestCase{ +var daTestCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL + "?userid=testusr&password=test&original=6289881134560&sendto=2020&message=Msg&messageid=foo", @@ -85,8 +85,8 @@ var daTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, daTestChannels, NewHandler("DA", "DartMedia", sendURL, maxMsgLength), daTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, daTestChannels, NewHandler("DA", "DartMedia", sendURL, maxMsgLength), daTestCases) } func BenchmarkHandler(b *testing.B) { @@ -99,7 +99,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, daHandler.sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -165,7 +165,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultDAChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "DA", "2020", "ID", map[string]any{ @@ -173,5 +173,5 @@ func TestSending(t *testing.T) { courier.ConfigPassword: "Password", }) - RunChannelSendTestCases(t, defaultDAChannel, NewHandler("DA", "Dartmedia", sendURL, maxMsgLength), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultDAChannel, NewHandler("DA", "Dartmedia", sendURL, maxMsgLength), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index fb87917a7..f0877305d 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -33,7 +33,7 @@ var ( d3CReceiveURL = "/c/d3c/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive" ) -var testCasesD3C = []ChannelHandleTestCase{ +var testCasesD3C = []IncomingTestCase{ { Label: "Receive Message WAC", URL: d3CReceiveURL, @@ -255,7 +255,7 @@ var testCasesD3C = []ChannelHandleTestCase{ }, } -func buildMockD3MediaService(testChannels []courier.Channel, testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockD3MediaService(testChannels []courier.Channel, testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fileURL := "" @@ -292,12 +292,12 @@ func buildMockD3MediaService(testChannels []courier.Channel, testCases []Channel return server } -func TestHandler(t *testing.T) { +func TestIncoming(t *testing.T) { d3MediaService := buildMockD3MediaService(testChannels, testCasesD3C) defer d3MediaService.Close() - RunChannelTestCases(t, testChannels, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), testCasesD3C) + RunIncomingTestCases(t, testChannels, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), testCasesD3C) } func BenchmarkHandler(b *testing.B) { @@ -321,7 +321,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig("base_url", s.URL) } -var SendTestCasesD3C = []ChannelSendTestCase{ +var SendTestCasesD3C = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -602,7 +602,7 @@ var SendTestCasesD3C = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 @@ -612,7 +612,7 @@ func TestSending(t *testing.T) { }) checkRedacted := []string{"the-auth-token"} - RunChannelSendTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, nil) + RunOutgoingTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, nil) } func TestGetSupportedLanguage(t *testing.T) { assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.NilLocale)) diff --git a/handlers/discord/discord_test.go b/handlers/discord/discord_test.go index ae721875b..3a150860d 100644 --- a/handlers/discord/discord_test.go +++ b/handlers/discord/discord_test.go @@ -10,8 +10,8 @@ import ( "github.com/nyaruka/courier/utils" ) -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -22,7 +22,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("bac782c2-7aeb-4389-92f5-97887744f573", "DS", "discord", "US", map[string]any{courier.ConfigSendAuthorization: "sesame"}), } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Recieve Message", URL: "/c/ds/bac782c2-7aeb-4389-92f5-97887744f573/receive", @@ -77,7 +77,7 @@ var testCases = []ChannelHandleTestCase{ }, } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Simple Send", MsgText: "Hello World", @@ -118,6 +118,6 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, sendURL) } -func TestSending(t *testing.T) { - RunChannelSendTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"sesame"}, nil) +func TestOutgoing(t *testing.T) { + RunOutgoingTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"sesame"}, nil) } diff --git a/handlers/dmark/dmark_test.go b/handlers/dmark/dmark_test.go index 0a1580945..6511f3ef2 100644 --- a/handlers/dmark/dmark_test.go +++ b/handlers/dmark/dmark_test.go @@ -19,7 +19,7 @@ const ( statusURL = "/c/dk/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -82,8 +82,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -95,7 +95,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -133,11 +133,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", map[string]any{ courier.ConfigAuthToken: "Authy", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Authy"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Authy"}, nil) } diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index d7d697489..3c44972b4 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -24,7 +24,7 @@ var gmChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "GM", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL + "?sender=%2B2349067554729&text=Join", @@ -204,7 +204,7 @@ var testSOAPReceiveChannels = []courier.Channel{ ), } -var handleSOAPReceiveTestCases = []ChannelHandleTestCase{ +var handleSOAPReceiveTestCases = []IncomingTestCase{ { Label: "Receive Valid Post SOAP", URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/", @@ -223,7 +223,7 @@ var handleSOAPReceiveTestCases = []ChannelHandleTestCase{ }, } -var gmTestCases = []ChannelHandleTestCase{ +var gmTestCases = []IncomingTestCase{ { Label: "Receive Non Plus Message", URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/?sender=2207222333&text=Join", @@ -245,7 +245,7 @@ var customChannels = []courier.Channel{ ), } -var customTestCases = []ChannelHandleTestCase{ +var customTestCases = []IncomingTestCase{ { Label: "Receive Custom Message", URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/?from_number=12067799192&messageText=Join×tamp=2017-06-23T12:30:00Z", @@ -265,11 +265,11 @@ var customTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) - RunChannelTestCases(t, testSOAPReceiveChannels, newHandler(), handleSOAPReceiveTestCases) - RunChannelTestCases(t, gmChannels, newHandler(), gmTestCases) - RunChannelTestCases(t, customChannels, newHandler(), customTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) + RunIncomingTestCases(t, testSOAPReceiveChannels, newHandler(), handleSOAPReceiveTestCases) + RunIncomingTestCases(t, gmChannels, newHandler(), gmTestCases) + RunIncomingTestCases(t, customChannels, newHandler(), customTestCases) } func BenchmarkHandler(b *testing.B) { @@ -285,7 +285,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, sendURL) } -var longSendTestCases = []ChannelSendTestCase{ +var longSendTestCases = []OutgoingTestCase{ { Label: "Long Send", MsgText: "This is a long message that will be longer than 30....... characters", MsgURN: "tel:+250788383383", @@ -298,7 +298,7 @@ var longSendTestCases = []ChannelSendTestCase{ }, } -var getSendSmartEncodingTestCases = []ChannelSendTestCase{ +var getSendSmartEncodingTestCases = []OutgoingTestCase{ { Label: "Smart Encoding", MsgText: "Fancy “Smart” Quotes", @@ -312,7 +312,7 @@ var getSendSmartEncodingTestCases = []ChannelSendTestCase{ }, } -var postSendSmartEncodingTestCases = []ChannelSendTestCase{ +var postSendSmartEncodingTestCases = []OutgoingTestCase{ { Label: "Smart Encoding", MsgText: "Fancy “Smart” Quotes", @@ -326,7 +326,7 @@ var postSendSmartEncodingTestCases = []ChannelSendTestCase{ }, } -var getSendTestCases = []ChannelSendTestCase{ +var getSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -373,7 +373,7 @@ var getSendTestCases = []ChannelSendTestCase{ }, } -var postSendTestCases = []ChannelSendTestCase{ +var postSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -420,7 +420,7 @@ var postSendTestCases = []ChannelSendTestCase{ }, } -var postSendCustomContentTypeTestCases = []ChannelSendTestCase{ +var postSendCustomContentTypeTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -434,7 +434,7 @@ var postSendCustomContentTypeTestCases = []ChannelSendTestCase{ }, } -var jsonSendTestCases = []ChannelSendTestCase{ +var jsonSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -493,7 +493,7 @@ var jsonSendTestCases = []ChannelSendTestCase{ }, } -var jsonLongSendTestCases = []ChannelSendTestCase{ +var jsonLongSendTestCases = []OutgoingTestCase{ { Label: "Send Quick Replies", MsgText: "This is a long message that will be longer than 30....... characters", @@ -508,7 +508,7 @@ var jsonLongSendTestCases = []ChannelSendTestCase{ }, } -var xmlSendTestCases = []ChannelSendTestCase{ +var xmlSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -568,7 +568,7 @@ var xmlSendTestCases = []ChannelSendTestCase{ }, } -var xmlLongSendTestCases = []ChannelSendTestCase{ +var xmlLongSendTestCases = []OutgoingTestCase{ { Label: "Send Quick Replies", MsgText: "This is a long message that will be longer than 30....... characters", @@ -583,7 +583,7 @@ var xmlLongSendTestCases = []ChannelSendTestCase{ }, } -var xmlSendWithResponseContentTestCases = []ChannelSendTestCase{ +var xmlSendWithResponseContentTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -655,7 +655,7 @@ var xmlSendWithResponseContentTestCases = []ChannelSendTestCase{ }, } -var nationalGetSendTestCases = []ChannelSendTestCase{ +var nationalGetSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -669,7 +669,7 @@ var nationalGetSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", map[string]any{ "send_path": "?to={{to}}&text={{text}}&from={{from}}{{quick_replies}}", @@ -727,16 +727,16 @@ func TestSending(t *testing.T) { courier.ConfigSendMethod: http.MethodPut, }) - RunChannelSendTestCases(t, getChannel, newHandler(), getSendTestCases, nil, nil) - RunChannelSendTestCases(t, getSmartChannel, newHandler(), getSendTestCases, nil, nil) - RunChannelSendTestCases(t, getSmartChannel, newHandler(), getSendSmartEncodingTestCases, nil, nil) - RunChannelSendTestCases(t, postChannel, newHandler(), postSendTestCases, nil, nil) - RunChannelSendTestCases(t, postChannelCustomContentType, newHandler(), postSendCustomContentTypeTestCases, nil, nil) - RunChannelSendTestCases(t, postSmartChannel, newHandler(), postSendTestCases, nil, nil) - RunChannelSendTestCases(t, postSmartChannel, newHandler(), postSendSmartEncodingTestCases, nil, nil) - RunChannelSendTestCases(t, jsonChannel, newHandler(), jsonSendTestCases, nil, nil) - RunChannelSendTestCases(t, xmlChannel, newHandler(), xmlSendTestCases, nil, nil) - RunChannelSendTestCases(t, xmlChannelWithResponseContent, newHandler(), xmlSendWithResponseContentTestCases, nil, nil) + RunOutgoingTestCases(t, getChannel, newHandler(), getSendTestCases, nil, nil) + RunOutgoingTestCases(t, getSmartChannel, newHandler(), getSendTestCases, nil, nil) + RunOutgoingTestCases(t, getSmartChannel, newHandler(), getSendSmartEncodingTestCases, nil, nil) + RunOutgoingTestCases(t, postChannel, newHandler(), postSendTestCases, nil, nil) + RunOutgoingTestCases(t, postChannelCustomContentType, newHandler(), postSendCustomContentTypeTestCases, nil, nil) + RunOutgoingTestCases(t, postSmartChannel, newHandler(), postSendTestCases, nil, nil) + RunOutgoingTestCases(t, postSmartChannel, newHandler(), postSendSmartEncodingTestCases, nil, nil) + RunOutgoingTestCases(t, jsonChannel, newHandler(), jsonSendTestCases, nil, nil) + RunOutgoingTestCases(t, xmlChannel, newHandler(), xmlSendTestCases, nil, nil) + RunOutgoingTestCases(t, xmlChannelWithResponseContent, newHandler(), xmlSendWithResponseContentTestCases, nil, nil) var getChannel30IntLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", map[string]any{ @@ -770,10 +770,10 @@ func TestSending(t *testing.T) { courier.ConfigSendHeaders: map[string]any{"Authorization": "Token ABCDEF", "foo": "bar"}, }) - RunChannelSendTestCases(t, getChannel30IntLength, newHandler(), longSendTestCases, nil, nil) - RunChannelSendTestCases(t, getChannel30StrLength, newHandler(), longSendTestCases, nil, nil) - RunChannelSendTestCases(t, jsonChannel30IntLength, newHandler(), jsonLongSendTestCases, nil, nil) - RunChannelSendTestCases(t, xmlChannel30IntLength, newHandler(), xmlLongSendTestCases, nil, nil) + RunOutgoingTestCases(t, getChannel30IntLength, newHandler(), longSendTestCases, nil, nil) + RunOutgoingTestCases(t, getChannel30StrLength, newHandler(), longSendTestCases, nil, nil) + RunOutgoingTestCases(t, jsonChannel30IntLength, newHandler(), jsonLongSendTestCases, nil, nil) + RunOutgoingTestCases(t, xmlChannel30IntLength, newHandler(), xmlLongSendTestCases, nil, nil) var nationalChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", map[string]any{ @@ -781,7 +781,7 @@ func TestSending(t *testing.T) { "use_national": true, courier.ConfigSendMethod: http.MethodGet}) - RunChannelSendTestCases(t, nationalChannel, newHandler(), nationalGetSendTestCases, nil, nil) + RunOutgoingTestCases(t, nationalChannel, newHandler(), nationalGetSendTestCases, nil, nil) var jsonChannelWithSendAuthorization = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "EX", "2020", "US", map[string]any{ @@ -791,6 +791,6 @@ func TestSending(t *testing.T) { courier.ConfigSendMethod: http.MethodPost, courier.ConfigSendAuthorization: "Token ABCDEF", }) - RunChannelSendTestCases(t, jsonChannelWithSendAuthorization, newHandler(), jsonSendTestCases, []string{"Token ABCDEF"}, nil) + RunOutgoingTestCases(t, jsonChannelWithSendAuthorization, newHandler(), jsonSendTestCases, []string{"Token ABCDEF"}, nil) } diff --git a/handlers/facebook/facebook_test.go b/handlers/facebook/facebook_test.go index a81583327..efdc371bb 100644 --- a/handlers/facebook/facebook_test.go +++ b/handlers/facebook/facebook_test.go @@ -417,7 +417,7 @@ var unkownMessagingEntry = `{ }] }` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Message", URL: receiveURL, @@ -609,7 +609,7 @@ var testCases = []ChannelHandleTestCase{ } // mocks the call to the Facebook graph API -func buildMockFBGraph(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockFBGraph(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { accessToken := r.URL.Query().Get("access_token") defer r.Body.Close() @@ -658,8 +658,8 @@ func TestDescribeURN(t *testing.T) { AssertChannelLogRedaction(t, clog, []string{"a123", "mysecret"}) } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -691,7 +691,7 @@ func TestVerify(t *testing.T) { subscribeURL = server.URL subscribeTimeout = time.Millisecond - RunChannelTestCases(t, testChannels, newHandler(), []ChannelHandleTestCase{ + RunIncomingTestCases(t, testChannels, newHandler(), []IncomingTestCase{ { Label: "Receive Message", URL: receiveURL, @@ -739,7 +739,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -862,5 +862,5 @@ func TestSending(t *testing.T) { maxMsgLength = 100 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FB", "2020", "US", map[string]any{courier.ConfigAuthToken: "access_token"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"access_token"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"access_token"}, nil) } diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 8e356b441..907dcaa3a 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -30,7 +30,7 @@ var testChannelsWAC = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), } -var testCasesFBA = []ChannelHandleTestCase{ +var testCasesFBA = []IncomingTestCase{ { Label: "Receive Message FBA", URL: "/c/fba/receive", @@ -254,7 +254,7 @@ var testCasesFBA = []ChannelHandleTestCase{ }, } -var testCasesIG = []ChannelHandleTestCase{ +var testCasesIG = []IncomingTestCase{ { Label: "Receive Message", URL: "/c/ig/receive", @@ -422,7 +422,7 @@ func addInvalidSignature(r *http.Request) { } // mocks the call to the Facebook graph API -func buildMockFBGraphFBA(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockFBGraphFBA(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { accessToken := r.URL.Query().Get("access_token") defer r.Body.Close() @@ -446,7 +446,7 @@ func buildMockFBGraphFBA(testCases []ChannelHandleTestCase) *httptest.Server { } // mocks the call to the Facebook graph API -func buildMockFBGraphIG(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockFBGraphIG(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { accessToken := r.URL.Query().Get("access_token") defer r.Body.Close() @@ -545,7 +545,7 @@ func TestDescribeURNForWAC(t *testing.T) { var wacReceiveURL = "/c/wac/receive" -var testCasesWAC = []ChannelHandleTestCase{ +var testCasesWAC = []IncomingTestCase{ { Label: "Receive Message WAC", URL: wacReceiveURL, @@ -798,7 +798,7 @@ var testCasesWAC = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { +func TestIncoming(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { accessToken := r.Header.Get("Authorization") defer r.Body.Close() @@ -841,9 +841,9 @@ func TestHandler(t *testing.T) { })) graphURL = server.URL - RunChannelTestCases(t, testChannelsWAC, newHandler("WAC", "Cloud API WhatsApp", false), testCasesWAC) - RunChannelTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) - RunChannelTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) + RunIncomingTestCases(t, testChannelsWAC, newHandler("WAC", "Cloud API WhatsApp", false), testCasesWAC) + RunIncomingTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) + RunIncomingTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) } func BenchmarkHandler(b *testing.B) { @@ -859,7 +859,7 @@ func BenchmarkHandler(b *testing.B) { } func TestVerify(t *testing.T) { - RunChannelTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), []ChannelHandleTestCase{ + RunIncomingTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), []IncomingTestCase{ { Label: "Valid Secret", URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", @@ -899,7 +899,7 @@ func TestVerify(t *testing.T) { }, }) - RunChannelTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), []ChannelHandleTestCase{ + RunIncomingTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), []IncomingTestCase{ { Label: "Valid Secret", URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", @@ -946,7 +946,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, graphURL = s.URL } -var SendTestCasesFBA = []ChannelSendTestCase{ +var SendTestCasesFBA = []OutgoingTestCase{ { Label: "Text only chat message", MsgText: "Simple Message", @@ -1102,7 +1102,7 @@ var SendTestCasesFBA = []ChannelSendTestCase{ }, } -var SendTestCasesIG = []ChannelSendTestCase{ +var SendTestCasesIG = []OutgoingTestCase{ { Label: "Text only chat message", MsgText: "Simple Message", @@ -1256,7 +1256,7 @@ var SendTestCasesIG = []ChannelSendTestCase{ }, } -var SendTestCasesWAC = []ChannelSendTestCase{ +var SendTestCasesWAC = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -1537,7 +1537,7 @@ var SendTestCasesWAC = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { // shorter max msg length for testing maxMsgLength = 100 @@ -1547,9 +1547,9 @@ func TestSending(t *testing.T) { checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} - RunChannelSendTestCases(t, ChannelFBA, newHandler("FBA", "Facebook", false), SendTestCasesFBA, checkRedacted, nil) - RunChannelSendTestCases(t, ChannelIG, newHandler("IG", "Instagram", false), SendTestCasesIG, checkRedacted, nil) - RunChannelSendTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, nil) + RunOutgoingTestCases(t, ChannelFBA, newHandler("FBA", "Facebook", false), SendTestCasesFBA, checkRedacted, nil) + RunOutgoingTestCases(t, ChannelIG, newHandler("IG", "Instagram", false), SendTestCasesIG, checkRedacted, nil) + RunOutgoingTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, nil) } func TestSigning(t *testing.T) { diff --git a/handlers/firebase/firebase_test.go b/handlers/firebase/firebase_test.go index b9594e547..81c2ade13 100644 --- a/handlers/firebase/firebase_test.go +++ b/handlers/firebase/firebase_test.go @@ -43,7 +43,7 @@ var testChannels = []courier.Channel{ }), } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -86,8 +86,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -99,7 +99,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var notificationSendTestCases = []ChannelSendTestCase{ +var notificationSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -115,7 +115,7 @@ var notificationSendTestCases = []ChannelSendTestCase{ }, } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -191,7 +191,7 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { - RunChannelSendTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"FCMKey"}, nil) - RunChannelSendTestCases(t, testChannels[1], newHandler(), notificationSendTestCases, []string{"FCMKey"}, nil) +func TestOutgoing(t *testing.T) { + RunOutgoingTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"FCMKey"}, nil) + RunOutgoingTestCases(t, testChannels[1], newHandler(), notificationSendTestCases, []string{"FCMKey"}, nil) } diff --git a/handlers/freshchat/freshchat_test.go b/handlers/freshchat/freshchat_test.go index 8139f76e1..618494981 100644 --- a/handlers/freshchat/freshchat_test.go +++ b/handlers/freshchat/freshchat_test.go @@ -28,7 +28,7 @@ const ( invalidSignature = `f7wMD1BBhcj60U0z3dCY519qmxQ8qfVUU212Dapw9vpZfRBfjjmukUK2GwbAb0Nc+TGQHxN4iP4WD+Y/mSx6f4bmkBsvCy3l4OCQ/FEK0y5R7f+GLLDhgbTh90MwuLDHhvxB5dxIeu59leL+4yO+l/8M3Tm48aQurVBi9IAlzFsMtc1S1CiRxsDUb/rD6IRekPa0pUAbkno9qJ/CGXh0kZMdsYzRkzZmKCs79OWrvU94ha0ptyt5wArfmD1oSzY3PjeL2w8LWDc0QV21H/Hvj42azIUqebiNRtZ2E+f34AfQsyfcPuy1k/6qLuYGOdU1uZidPuPcGpeSIm0GW6k9HQ==` ) -var sigtestCases = []ChannelHandleTestCase{ +var sigtestCases = []IncomingTestCase{ { Label: "Receive Valid w Signature", Headers: map[string]string{"Content-Type": "application/json", "X-FreshChat-Signature": validSignature}, @@ -51,7 +51,7 @@ var sigtestCases = []ChannelHandleTestCase{ }, } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid w Sig", Headers: map[string]string{"Content-Type": "application/json", "X-FreshChat-Signature": validSignature}, @@ -73,9 +73,9 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", true), sigtestCases) - RunChannelTestCases(t, testChannels, newHandler("FC", "FreshChat", false), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler("FC", "FreshChat", true), sigtestCases) + RunIncomingTestCases(t, testChannels, newHandler("FC", "FreshChat", false), testCases) } func BenchmarkHandler(b *testing.B) { @@ -86,7 +86,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, apiURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -126,11 +126,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FC", "2020", "US", map[string]any{ "username": "c8fddfaf-622a-4a0e-b060-4f3ccbeab606", "secret": cert, "auth_token": "enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ=", }) - RunChannelSendTestCases(t, defaultChannel, newHandler("FC", "FreshChat", false), defaultSendTestCases, []string{cert, "enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler("FC", "FreshChat", false), defaultSendTestCases, []string{cert, "enYtdXNlcm5hbWU6enYtcGFzc3dvcmQ="}, nil) } diff --git a/handlers/globe/globe_test.go b/handlers/globe/globe_test.go index 765e6f1c4..1237c7962 100644 --- a/handlers/globe/globe_test.go +++ b/handlers/globe/globe_test.go @@ -109,7 +109,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "GL", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -157,8 +157,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -170,7 +170,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL + "?%s" } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -213,7 +213,7 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "GL", "2020", "US", map[string]any{ "app_id": "12345", @@ -222,5 +222,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{"mysecret", "opensesame"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{"mysecret", "opensesame"}, nil) } diff --git a/handlers/highconnection/highconnection_test.go b/handlers/highconnection/highconnection_test.go index 4bb8ed4cc..e8976e657 100644 --- a/handlers/highconnection/highconnection_test.go +++ b/handlers/highconnection/highconnection_test.go @@ -19,7 +19,7 @@ const ( statusURL = "/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -78,8 +78,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -91,7 +91,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -202,7 +202,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US", map[string]any{ @@ -210,5 +210,5 @@ func TestSending(t *testing.T) { courier.ConfigUsername: "Username", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/hormuud/hormuud_test.go b/handlers/hormuud/hormuud_test.go index 39aeb82c1..8e81b25f9 100644 --- a/handlers/hormuud/hormuud_test.go +++ b/handlers/hormuud/hormuud_test.go @@ -24,7 +24,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HM", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveValidMessage, @@ -62,8 +62,8 @@ var handleTestCases = []ChannelHandleTestCase{ // {Label: "Status Valid", URL: statusValid, Status: 200, Response: `"status":"S"`}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } // setSendURL takes care of setting the send_url to our test server host @@ -71,7 +71,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -117,7 +117,7 @@ var sendTestCases = []ChannelSendTestCase{ }, } -var tokenTestCases = []ChannelSendTestCase{ +var tokenTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -127,7 +127,7 @@ var tokenTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { // set up a token server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("valid") == "true" { @@ -149,9 +149,9 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{"sesame"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), sendTestCases, []string{"sesame"}, nil) tokenURL = server.URL + "?invalid=true" - RunChannelSendTestCases(t, defaultChannel, newHandler(), tokenTestCases, []string{"sesame"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), tokenTestCases, []string{"sesame"}, nil) } diff --git a/handlers/i2sms/i2sms_test.go b/handlers/i2sms/i2sms_test.go index e367601f7..e0dda402d 100644 --- a/handlers/i2sms/i2sms_test.go +++ b/handlers/i2sms/i2sms_test.go @@ -18,7 +18,7 @@ const ( receiveURL = "/c/i2/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -37,8 +37,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -49,7 +49,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -98,12 +98,12 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "I2", "2020", "US", map[string]any{ courier.ConfigUsername: "user1", courier.ConfigPassword: "pass1", configChannelHash: "hash123", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1"), "hash123"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("user1", "pass1"), "hash123"}, nil) } diff --git a/handlers/infobip/infobip_test.go b/handlers/infobip/infobip_test.go index df234fd90..7260cd0b3 100644 --- a/handlers/infobip/infobip_test.go +++ b/handlers/infobip/infobip_test.go @@ -193,7 +193,7 @@ var invalidStatus = `{ ] }` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -289,8 +289,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -302,7 +302,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -383,7 +383,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var transSendTestCases = []ChannelSendTestCase{ +var transSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -402,14 +402,14 @@ var transSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IB", "2020", "US", map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) var transChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IB", "2020", "US", map[string]any{ @@ -418,5 +418,5 @@ func TestSending(t *testing.T) { configTransliteration: "COLOMBIAN", }) - RunChannelSendTestCases(t, transChannel, newHandler(), transSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) + RunOutgoingTestCases(t, transChannel, newHandler(), transSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) } diff --git a/handlers/jasmin/jasmin_test.go b/handlers/jasmin/jasmin_test.go index 2fd8f0db8..6c0afe3fd 100644 --- a/handlers/jasmin/jasmin_test.go +++ b/handlers/jasmin/jasmin_test.go @@ -19,7 +19,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JS", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -78,8 +78,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -91,7 +91,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig("send_url", s.URL) } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -157,12 +157,12 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JS", "2020", "US", map[string]any{ "password": "Password", "username": "Username", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/jiochat/jiochat_test.go b/handlers/jiochat/jiochat_test.go index 5c624627f..4f82f7b6b 100644 --- a/handlers/jiochat/jiochat_test.go +++ b/handlers/jiochat/jiochat_test.go @@ -141,7 +141,7 @@ func addInvalidSignature(r *http.Request) { r.URL.RawQuery = query.Encode() } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Message", URL: receiveURL, @@ -218,8 +218,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -227,7 +227,7 @@ func BenchmarkHandler(b *testing.B) { } // mocks the call to the Jiochat API -func buildMockJCAPI(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockJCAPI(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authorizationHeader := r.Header.Get("Authorization") defer r.Body.Close() @@ -348,7 +348,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -412,9 +412,9 @@ func setupBackend(mb *test.MockBackend) { rc.Do("SET", "channel-token:8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ACCESS_TOKEN") } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JC", "2020", "US", map[string]any{configAppSecret: "secret123", configAppID: "app-id"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) } diff --git a/handlers/justcall/justcall_test.go b/handlers/justcall/justcall_test.go index f0b60e054..49b3b0e28 100644 --- a/handlers/justcall/justcall_test.go +++ b/handlers/justcall/justcall_test.go @@ -193,7 +193,7 @@ var unknownStatus = `{ } }` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -260,8 +260,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -273,7 +273,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -322,8 +322,8 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "JCL", "2020", "US", map[string]any{courier.ConfigAPIKey: "api_key", courier.ConfigSecret: "api_secret"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"api_key", "api_secret"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"api_key", "api_secret"}, nil) } diff --git a/handlers/kaleyra/kaleyra_test.go b/handlers/kaleyra/kaleyra_test.go index ce12f7942..adfc8aedd 100644 --- a/handlers/kaleyra/kaleyra_test.go +++ b/handlers/kaleyra/kaleyra_test.go @@ -27,7 +27,7 @@ var testChannels = []courier.Channel{ ), } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Msg", URL: receiveMsgURL + "?created_at=1603914166&type=text&from=14133881111&name=John%20Cruz&body=Hello%20World", @@ -102,8 +102,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -114,7 +114,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, baseURL = s.URL } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -209,8 +209,8 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase @@ -223,7 +223,7 @@ func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTes return casesWithMockedUrls } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { mediaServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() res.WriteHeader(200) @@ -239,5 +239,5 @@ func TestSending(t *testing.T) { })) mockedSendTestCases := mockAttachmentURLs(mediaServer, sendTestCases) - RunChannelSendTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, []string{"123456"}, nil) + RunOutgoingTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, []string{"123456"}, nil) } diff --git a/handlers/kannel/kannel_test.go b/handlers/kannel/kannel_test.go index 08104c912..aed0d898f 100644 --- a/handlers/kannel/kannel_test.go +++ b/handlers/kannel/kannel_test.go @@ -18,7 +18,7 @@ var ignoreChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", map[string]any{"ignore_sent": true}), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/?backend=NIG_MTN&sender=%2B2349067554729&message=Join&ts=1493735509&id=asdf-asdf&to=24453", @@ -86,7 +86,7 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -var ignoreTestCases = []ChannelHandleTestCase{ +var ignoreTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/?backend=NIG_MTN&sender=%2B2349067554729&message=Join&ts=1493735509&id=asdf-asdf&to=24453", @@ -119,9 +119,9 @@ var ignoreTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) - RunChannelTestCases(t, ignoreChannels, newHandler(), ignoreTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) + RunIncomingTestCases(t, ignoreChannels, newHandler(), ignoreTestCases) } func BenchmarkHandler(b *testing.B) { @@ -138,7 +138,7 @@ func setSendURLWithQuery(s *httptest.Server, h courier.ChannelHandler, c courier c.(*test.MockChannel).SetConfig("send_url", s.URL+"?auth=foo") } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -224,7 +224,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var nationalSendTestCases = []ChannelSendTestCase{ +var nationalSendTestCases = []OutgoingTestCase{ { Label: "National Send", MsgText: "success", @@ -238,7 +238,7 @@ var nationalSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "KN", "2020", "US", map[string]any{ "password": "Password", @@ -253,6 +253,6 @@ func TestSending(t *testing.T) { "dlr_mask": "3", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) - RunChannelSendTestCases(t, nationalChannel, newHandler(), nationalSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, nationalChannel, newHandler(), nationalSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/line/line_test.go b/handlers/line/line_test.go index 575629a31..ff1afce83 100644 --- a/handlers/line/line_test.go +++ b/handlers/line/line_test.go @@ -257,7 +257,7 @@ var testChannels = []courier.Channel{ }), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -383,8 +383,8 @@ func addInvalidSignature(r *http.Request) { r.Header.Set(signatureHeader, "invalidsig") } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -408,7 +408,7 @@ Ut tincidunt massa eu purus lacinia sodales a volutpat neque. Cras dolor quam, e Vivamus justo dolor, gravida at quam eu, hendrerit rutrum justo. Sed hendrerit nisi vitae nisl ornare tristique. Proin vulputate id justo non aliquet.` -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -604,7 +604,7 @@ func setupMedia(mb *test.MockBackend) { mb.MockMedia(filePDF) } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "LN", "2020", "US", @@ -613,7 +613,7 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"AccessToken"}, setupMedia) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"AccessToken"}, setupMedia) } func TestBuildAttachmentRequest(t *testing.T) { diff --git a/handlers/m3tech/m3tech_test.go b/handlers/m3tech/m3tech_test.go index 28ecf9e02..87dfb922d 100644 --- a/handlers/m3tech/m3tech_test.go +++ b/handlers/m3tech/m3tech_test.go @@ -13,7 +13,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "M3", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: "/c/m3/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive?from=+923161909799&text=hello+world", @@ -39,8 +39,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -52,7 +52,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -95,7 +95,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "M3", "2020", "US", map[string]any{ "password": "Password", @@ -103,5 +103,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/macrokiosk/macrokiosk_test.go b/handlers/macrokiosk/macrokiosk_test.go index 910a7ac3d..74c01b62a 100644 --- a/handlers/macrokiosk/macrokiosk_test.go +++ b/handlers/macrokiosk/macrokiosk_test.go @@ -30,7 +30,7 @@ var ( unknownStatus = "msgid=12345&status=UNKNOWN" ) -var testCases = []ChannelHandleTestCase{ +var incomingTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "-1", ExpectedMsgText: Sp("Hello"), ExpectedURN: "tel:+60124361111", ExpectedDate: time.Date(2016, 3, 30, 11, 33, 06, 0, time.UTC), ExpectedExternalID: "abc1234"}, @@ -50,12 +50,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Unknown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) -} - -func BenchmarkHandler(b *testing.B) { - RunChannelBenchmarks(b, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), incomingTestCases) } // setSendURL takes care of setting the send_url to our test server host @@ -63,8 +59,9 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ - {Label: "Plain Send", +var outgoingTestCases = []OutgoingTestCase{ + { + Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -76,8 +73,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ "Accept": "application/json", }, ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"Simple Message ☺","from":"macro","servid":"service-id","type":"5"}`, - SendPrep: setSendURL}, - {Label: "Long Send", + 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:+250788383383", ExpectedMsgStatus: "W", @@ -89,8 +88,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ "Accept": "application/json", }, 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}, - {Label: "Send Attachment", + SendPrep: setSendURL, + }, + { + Label: "Send Attachment", MsgText: "My pic!", MsgURN: "tel:+250788383383", MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, @@ -103,8 +104,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ "Accept": "application/json", }, ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"My pic!\nhttps://foo.bar/image.jpg","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL}, - {Label: "No External Id", + SendPrep: setSendURL, + }, + { + Label: "No External Id", MsgText: "No External ID", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "E", @@ -116,18 +119,21 @@ var defaultSendTestCases = []ChannelSendTestCase{ "Accept": "application/json", }, ExpectedRequestBody: `{"user":"Username","pass":"Password","to":"250788383383","text":"No External ID","from":"macro","servid":"service-id","type":"0"}`, - SendPrep: setSendURL}, - {Label: "Error Sending", + SendPrep: setSendURL, + }, + { + 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}, + SendPrep: setSendURL, + }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MK", "2020", "US", map[string]any{ @@ -138,5 +144,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), outgoingTestCases, []string{"Password"}, nil) } diff --git a/handlers/mblox/mblox_test.go b/handlers/mblox/mblox_test.go index ff5a91bf5..7bccba680 100644 --- a/handlers/mblox/mblox_test.go +++ b/handlers/mblox/mblox_test.go @@ -61,7 +61,7 @@ var ( }` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Hello World"), ExpectedURN: "tel:+12067799294", ExpectedDate: time.Date(2016, 3, 30, 19, 33, 06, 643000000, time.UTC), ExpectedExternalID: "OzQ5UqIOdoY8"}, @@ -74,8 +74,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Status Missing Batch ID", URL: receiveURL, Data: missingBatchID, ExpectedRespStatus: 400, ExpectedBodyContains: "missing one of 'batch_id' or 'status' in request body"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -87,7 +87,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:+250788383383", @@ -155,7 +155,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MB", "2020", "US", map[string]any{ @@ -164,5 +164,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index ca2805501..31e6512da 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -83,7 +83,7 @@ func addInvalidBodyHash(r *http.Request) { r.Header.Set("Messagebird-Signature-Jwt", signedJWT) } -var defaultReceiveTestCases = []ChannelHandleTestCase{ +var defaultReceiveTestCases = []IncomingTestCase{ { Label: "Receive Valid text w Signature", Headers: map[string]string{"Content-Type": "application/json"}, @@ -179,7 +179,7 @@ var defaultReceiveTestCases = []ChannelHandleTestCase{ } func TestReceiving(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), defaultReceiveTestCases) + RunIncomingTestCases(t, testChannels, newHandler("MBD", "Messagebird", true), defaultReceiveTestCases) } func BenchmarkHandler(b *testing.B) { @@ -194,7 +194,7 @@ func setMmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Chann mmsURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -294,10 +294,10 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MBD", "18005551212", "US", map[string]any{ "secret": "my_super_secret", // secret key to sign for sig "auth_token": "authtoken", }) - RunChannelSendTestCases(t, defaultChannel, newHandler("MBD", "Messagebird", false), defaultSendTestCases, []string{"my_super_secret", "authtoken"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler("MBD", "Messagebird", false), defaultSendTestCases, []string{"my_super_secret", "authtoken"}, nil) } diff --git a/handlers/messangi/messangi_test.go b/handlers/messangi/messangi_test.go index ba422f9da..8750a4515 100644 --- a/handlers/messangi/messangi_test.go +++ b/handlers/messangi/messangi_test.go @@ -17,7 +17,7 @@ const ( receiveURL = "/c/mg/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -34,8 +34,8 @@ var testCases = []ChannelHandleTestCase{ ExpectedBodyContains: "required field 'mobile'"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -46,7 +46,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -96,7 +96,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MG", "2020", "JM", map[string]any{ @@ -105,5 +105,5 @@ func TestSending(t *testing.T) { "instance_id": 7, "carrier_id": 2, }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"my-private-key"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"my-private-key"}, nil) } diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/mtarget_test.go index ff4a71a93..566b64298 100644 --- a/handlers/mtarget/mtarget_test.go +++ b/handlers/mtarget/mtarget_test.go @@ -31,7 +31,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MT", "2020", "FR", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ {Label: "Receive Valid Message", URL: receiveURL, Data: receiveValidMessage, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("hello world"), ExpectedURN: "tel:+923161909799", ExpectedExternalID: "foo"}, {Label: "Invalid URN", URL: receiveURL, Data: receiveInvalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, @@ -50,8 +50,8 @@ var handleTestCases = []ChannelHandleTestCase{ {Label: "Status Missing ID", URL: statusURL, Data: statusMissingID, ExpectedRespStatus: 400, ExpectedBodyContains: "missing required field 'MsgId'"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -63,7 +63,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -123,7 +123,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MT", "2020", "FR", map[string]any{ "password": "Password", @@ -131,5 +131,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/mtn/mtn_test.go b/handlers/mtn/mtn_test.go index 6427c43f3..2c990ac6e 100644 --- a/handlers/mtn/mtn_test.go +++ b/handlers/mtn/mtn_test.go @@ -79,7 +79,7 @@ var missingTransactionID = `{ "deliveryStatus": "EXPIRED" }` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -150,8 +150,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -163,7 +163,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, apiHostURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:+250788383383", @@ -224,7 +224,7 @@ func setupBackend(mb *test.MockBackend) { rc.Do("SET", "channel-token:8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ACCESS_TOKEN") } -var cpAddressSendTestCases = []ChannelSendTestCase{ +var cpAddressSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:+250788383383", @@ -241,9 +241,9 @@ var cpAddressSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]any{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) var cpAddressChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "MTN", "2020", "US", map[string]any{courier.ConfigAuthToken: "customer-secret123", courier.ConfigAPIKey: "customer-key", configCPAddress: "FOO"}) - RunChannelSendTestCases(t, cpAddressChannel, newHandler(), cpAddressSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) + RunOutgoingTestCases(t, cpAddressChannel, newHandler(), cpAddressSendTestCases, []string{"customer-key", "customer-secret123"}, setupBackend) } diff --git a/handlers/nexmo/nexmo_test.go b/handlers/nexmo/nexmo_test.go index a7566ef6d..be0d9f087 100644 --- a/handlers/nexmo/nexmo_test.go +++ b/handlers/nexmo/nexmo_test.go @@ -18,7 +18,7 @@ const ( receiveURL = "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Valid Receive", URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive?to=2020&msisdn=2349067554729&text=Join&messageId=external1", @@ -105,8 +105,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -118,7 +118,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -208,7 +208,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NX", "2020", "US", map[string]any{ @@ -218,5 +218,5 @@ func TestSending(t *testing.T) { configNexmoAppPrivateKey: "nexmo-app-private-key", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"nexmo-api-secret", "nexmo-app-private-key"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"nexmo-api-secret", "nexmo-app-private-key"}, nil) } diff --git a/handlers/novo/novo_test.go b/handlers/novo/novo_test.go index 1698498ba..08bba4379 100644 --- a/handlers/novo/novo_test.go +++ b/handlers/novo/novo_test.go @@ -21,7 +21,7 @@ const ( receiveURL = "/c/nv/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -49,8 +49,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -61,7 +61,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL + "?%s" } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -115,7 +115,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "NV", "2020", "TT", map[string]any{ @@ -123,5 +123,5 @@ func TestSending(t *testing.T) { "merchant_secret": "my-merchant-secret", "secret": "sesame", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"my-merchant-secret", "sesame"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"my-merchant-secret", "sesame"}, nil) } diff --git a/handlers/playmobile/playmobile_test.go b/handlers/playmobile/playmobile_test.go index e65b43129..332c24607 100644 --- a/handlers/playmobile/playmobile_test.go +++ b/handlers/playmobile/playmobile_test.go @@ -71,7 +71,7 @@ var ( }` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -120,8 +120,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -132,7 +132,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL + "?%s" } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:99999999999", @@ -176,7 +176,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PM", "1122", "UZ", map[string]any{ "password": "Password", @@ -185,5 +185,5 @@ func TestSending(t *testing.T) { "base_url": "http://91.204.239.42", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) } diff --git a/handlers/plivo/plivo_test.go b/handlers/plivo/plivo_test.go index d23b37d52..44f7eadd1 100644 --- a/handlers/plivo/plivo_test.go +++ b/handlers/plivo/plivo_test.go @@ -29,7 +29,7 @@ var ( unknownStatus = "MessageUUID=12345&status=UNKNOWN&To=%2B60124361111&From=2020" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Hello"), ExpectedURN: "tel:+60124361111", ExpectedExternalID: "abc1234"}, {Label: "Invalid URN", URL: receiveURL, Data: invalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, @@ -42,8 +42,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Unkown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -55,7 +55,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL + "/%s/" } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message ☺", MsgURN: "tel:+250788383383", @@ -123,7 +123,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "PL", "2020", "US", map[string]any{ @@ -133,5 +133,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("AuthID", "AuthToken")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("AuthID", "AuthToken")}, nil) } diff --git a/handlers/redrabbit/redrabbit_test.go b/handlers/redrabbit/redrabbit_test.go index 9a6d65e96..d3c115ca0 100644 --- a/handlers/redrabbit/redrabbit_test.go +++ b/handlers/redrabbit/redrabbit_test.go @@ -14,7 +14,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -97,7 +97,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "RR", "2020", "US", map[string]any{ "password": "Password", @@ -105,5 +105,5 @@ func TestSending(t *testing.T) { }, ) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/rocketchat/rocketchat_test.go b/handlers/rocketchat/rocketchat_test.go index e82f7a64f..a68c97f70 100644 --- a/handlers/rocketchat/rocketchat_test.go +++ b/handlers/rocketchat/rocketchat_test.go @@ -49,7 +49,7 @@ const attachmentMsg = `{ "attachments": [{"type": "image/jpg", "url": "https://link.to/image.jpg"}] }` -var testCases = []handlers.ChannelHandleTestCase{ +var testCases = []handlers.IncomingTestCase{ { Label: "Receive Hello Msg", URL: receiveURL, @@ -96,8 +96,8 @@ var testCases = []handlers.ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - handlers.RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + handlers.RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -108,7 +108,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig(configBaseURL, s.URL) } -var sendTestCases = []handlers.ChannelSendTestCase{ +var sendTestCases = []handlers.OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -145,6 +145,6 @@ var sendTestCases = []handlers.ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { - handlers.RunChannelSendTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"123456789"}, nil) +func TestOutgoing(t *testing.T) { + handlers.RunOutgoingTestCases(t, testChannels[0], newHandler(), sendTestCases, []string{"123456789"}, nil) } diff --git a/handlers/shaqodoon/shaqodoon_test.go b/handlers/shaqodoon/shaqodoon_test.go index 5bb9b73a7..70637e4a1 100644 --- a/handlers/shaqodoon/shaqodoon_test.go +++ b/handlers/shaqodoon/shaqodoon_test.go @@ -26,7 +26,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SQ", "2020", "US", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ {Label: "Receive Valid Message", URL: receiveValidMessage, Data: "empty", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("Join"), ExpectedURN: "tel:+2349067554729"}, {Label: "Receive Badly Escaped", URL: receiveBadlyEscaped, Data: "empty", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", @@ -43,8 +43,8 @@ var handleTestCases = []ChannelHandleTestCase{ {Label: "Receive Invalid Date", URL: receiveInvalidDate, Data: "empty", ExpectedRespStatus: 400, ExpectedBodyContains: "invalid date format, must be RFC 3339"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -55,7 +55,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, s.URL) } -var getSendTestCases = []ChannelSendTestCase{ +var getSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -82,12 +82,12 @@ var getSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SQ", "2020", "US", map[string]any{ courier.ConfigSendURL: "SendURL", courier.ConfigPassword: "Password", courier.ConfigUsername: "Username"}) - RunChannelSendTestCases(t, getChannel, newHandler(), getSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, getChannel, newHandler(), getSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/slack/slack_test.go b/handlers/slack/slack_test.go index 06a844b12..e5f8849f3 100644 --- a/handlers/slack/slack_test.go +++ b/handlers/slack/slack_test.go @@ -128,7 +128,7 @@ func setSendUrl(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, apiURL = s.URL } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Hello Msg", URL: receiveURL, @@ -177,7 +177,7 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -211,7 +211,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var fileSendTestCases = []ChannelSendTestCase{ +var fileSendTestCases = []OutgoingTestCase{ { Label: "Send Image", MsgText: "", @@ -244,15 +244,15 @@ var fileSendTestCases = []ChannelSendTestCase{ }, } -func TestHandler(t *testing.T) { +func TestIncoming(t *testing.T) { slackServiceMock := buildMockSlackService(handleTestCases) defer slackServiceMock.Close() - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } -func TestSending(t *testing.T) { - RunChannelSendTestCases(t, testChannels[0], newHandler(), defaultSendTestCases, []string{"xoxb-abc123", "one-long-verification-token"}, nil) +func TestOutgoing(t *testing.T) { + RunOutgoingTestCases(t, testChannels[0], newHandler(), defaultSendTestCases, []string{"xoxb-abc123", "one-long-verification-token"}, nil) } func TestSendFiles(t *testing.T) { @@ -260,11 +260,11 @@ func TestSendFiles(t *testing.T) { defer fileServer.Close() fileSendTestCases := mockAttachmentURLs(fileServer, fileSendTestCases) - RunChannelSendTestCases(t, testChannels[0], newHandler(), fileSendTestCases, []string{"xoxb-abc123", "one-long-verification-token"}, nil) + RunOutgoingTestCases(t, testChannels[0], newHandler(), fileSendTestCases, []string{"xoxb-abc123", "one-long-verification-token"}, nil) } func TestVerification(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), []ChannelHandleTestCase{ + RunIncomingTestCases(t, testChannels, newHandler(), []IncomingTestCase{ {Label: "Valid token", URL: receiveURL, ExpectedRespStatus: 200, Data: `{"token":"one-long-verification-token","challenge":"challenge123","type":"url_verification"}`, Headers: map[string]string{"content-type": "text/plain"}, @@ -285,7 +285,7 @@ func buildMockAttachmentFileServer() *httptest.Server { })) } -func buildMockSlackService(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockSlackService(testCases []IncomingTestCase) *httptest.Server { files := make(map[string]File) @@ -342,8 +342,8 @@ func buildMockSlackService(testCases []ChannelHandleTestCase) *httptest.Server { return server } -func mockAttachmentURLs(fileServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(fileServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase @@ -356,7 +356,7 @@ func mockAttachmentURLs(fileServer *httptest.Server, testCases []ChannelSendTest } func TestDescribeURN(t *testing.T) { - server := buildMockSlackService([]ChannelHandleTestCase{}) + server := buildMockSlackService([]IncomingTestCase{}) defer server.Close() handler := newHandler() diff --git a/handlers/smscentral/smscentral_test.go b/handlers/smscentral/smscentral_test.go index 365ae249c..77894fbf6 100644 --- a/handlers/smscentral/smscentral_test.go +++ b/handlers/smscentral/smscentral_test.go @@ -17,7 +17,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SC", "2020", "US", map[string]any{"username": "Username", "password": "Password"}), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: receiveURL, @@ -59,8 +59,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -72,7 +72,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -99,12 +99,12 @@ var defaultSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SC", "2020", "US", map[string]any{ courier.ConfigPassword: "Password", courier.ConfigUsername: "Username", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password"}, nil) } diff --git a/handlers/start/start_test.go b/handlers/start/start_test.go index e1fd0b200..f8a21256b 100644 --- a/handlers/start/start_test.go +++ b/handlers/start/start_test.go @@ -72,7 +72,7 @@ const ( ` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -151,8 +151,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -164,7 +164,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -241,8 +241,8 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ST", "2020", "UA", map[string]any{"username": "Username", "password": "Password"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{httpx.BasicAuth("Username", "Password")}, nil) } diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/telegram_test.go index f0750e725..c03cf61e9 100644 --- a/handlers/telegram/telegram_test.go +++ b/handlers/telegram/telegram_test.go @@ -515,7 +515,7 @@ var contactMsg = ` } }` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Message", @@ -712,7 +712,7 @@ var testCases = []ChannelHandleTestCase{ }, } -func buildMockTelegramService(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockTelegramService(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fileID := r.FormValue("file_id") defer r.Body.Close() @@ -768,11 +768,11 @@ func buildMockTelegramService(testCases []ChannelHandleTestCase) *httptest.Serve return server } -func TestHandler(t *testing.T) { +func TestIncoming(t *testing.T) { telegramService := buildMockTelegramService(testCases) defer telegramService.Close() - RunChannelTestCases(t, testChannels, newHandler(), testCases) + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -787,7 +787,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, apiURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -940,9 +940,9 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TG", "2020", "US", map[string]any{courier.ConfigAuthToken: "auth_token"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"auth_token"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"auth_token"}, nil) } diff --git a/handlers/telesom/telesom_test.go b/handlers/telesom/telesom_test.go index ec823f409..62348deab 100644 --- a/handlers/telesom/telesom_test.go +++ b/handlers/telesom/telesom_test.go @@ -15,7 +15,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TS", "2020", "SO", nil), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: "/c/ts/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/?mobile=%2B2349067554729&msg=Join", @@ -74,8 +74,8 @@ var handleTestCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -89,7 +89,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -137,7 +137,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TS", "2020", "US", map[string]any{ "password": "Password", @@ -150,5 +150,5 @@ func TestSending(t *testing.T) { // mock time so we can have predictable MD5 hashes dates.SetNowSource(dates.NewFixedNowSource(time.Date(2018, 4, 11, 18, 24, 30, 123456000, time.UTC))) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password", "secret"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Password", "secret"}, nil) } diff --git a/handlers/test.go b/handlers/test.go index 116fceab8..b335f2d93 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -27,8 +27,8 @@ import ( // RequestPrepFunc is our type for a hook for tests to use before a request is fired in a test type RequestPrepFunc func(*http.Request) -// ChannelHandleTestCase defines the test values for a particular test case -type ChannelHandleTestCase struct { +// IncomingTestCase defines the test values for a particular test case +type IncomingTestCase struct { Label string NoQueueErrorCheck bool NoInvalidChannelCheck bool @@ -145,8 +145,8 @@ func newServer(backend courier.Backend) courier.Server { } -// RunChannelTestCases runs all the passed in tests cases for the passed in channel configurations -func RunChannelTestCases(t *testing.T, channels []courier.Channel, handler courier.ChannelHandler, testCases []ChannelHandleTestCase) { +// RunIncomingTestCases runs all the passed in tests cases for the passed in channel configurations +func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler courier.ChannelHandler, testCases []IncomingTestCase) { mb := test.NewMockBackend() s := newServer(mb) @@ -254,8 +254,8 @@ func RunChannelTestCases(t *testing.T, channels []courier.Channel, handler couri // SendPrepFunc allows test cases to modify the channel, msg or server before a message is sent type SendPrepFunc func(*httptest.Server, courier.ChannelHandler, courier.Channel, courier.Msg) -// ChannelSendTestCase defines the test values for a particular test case -type ChannelSendTestCase struct { +// OutgoingTestCase defines the test values for a particular test case +type OutgoingTestCase struct { Label string SendPrep SendPrepFunc @@ -291,8 +291,8 @@ type ChannelSendTestCase struct { ExpectedNewURN string } -// RunChannelSendTestCases runs all the passed in test cases against the channel -func RunChannelSendTestCases(t *testing.T, channel courier.Channel, handler courier.ChannelHandler, testCases []ChannelSendTestCase, checkRedacted []string, setupBackend func(*test.MockBackend)) { +// RunOutgoingTestCases runs all the passed in test cases against the channel +func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier.ChannelHandler, testCases []OutgoingTestCase, checkRedacted []string, setupBackend func(*test.MockBackend)) { mb := test.NewMockBackend() if setupBackend != nil { setupBackend(mb) @@ -455,7 +455,7 @@ func RunChannelSendTestCases(t *testing.T, channel courier.Channel, handler cour } // RunChannelBenchmarks runs all the passed in test cases for the passed in channels -func RunChannelBenchmarks(b *testing.B, channels []courier.Channel, handler courier.ChannelHandler, testCases []ChannelHandleTestCase) { +func RunChannelBenchmarks(b *testing.B, channels []courier.Channel, handler courier.ChannelHandler, testCases []IncomingTestCase) { mb := test.NewMockBackend() s := newServer(mb) diff --git a/handlers/thinq/thinq_test.go b/handlers/thinq/thinq_test.go index e47ef9f3b..75e34b9b0 100644 --- a/handlers/thinq/thinq_test.go +++ b/handlers/thinq/thinq_test.go @@ -24,7 +24,7 @@ const ( var testJpgBase64 = base64.StdEncoding.EncodeToString(test.ReadFile("../../test/testdata/test.jpg")) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -86,8 +86,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { @@ -95,7 +95,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendMMSURL = s.URL + "?account_id=%s" } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -154,12 +154,12 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TQ", "+12065551212", "US", map[string]any{ configAccountID: "1234", configAPITokenUser: "user1", configAPIToken: "sesame", }) - RunChannelSendTestCases(t, channel, newHandler(), sendTestCases, []string{httpx.BasicAuth("user1", "sesame")}, nil) + RunOutgoingTestCases(t, channel, newHandler(), sendTestCases, []string{httpx.BasicAuth("user1", "sesame")}, nil) } diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index c37f78dc8..a152f6978 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -79,7 +79,7 @@ var ( waReceivePrefixlessURN = "ToCountry=US&ToState=CA&SmsMessageSid=SM681a1f26d9ec591431ce406e8f399525&NumMedia=0&ToCity=&FromZip=60625&SmsSid=SM681a1f26d9ec591431ce406e8f399525&FromState=IL&SmsStatus=received&FromCity=CHICAGO&Body=Msg&FromCountry=US&To=%2B12028831111&ToZip=&NumSegments=1&MessageSid=SM681a1f26d9ec591431ce406e8f399525&AccountSid=acctid&From=%2B14133881111&ApiVersion=2010-04-01" ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: receiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, @@ -126,7 +126,7 @@ var testCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, } -var tmsTestCases = []ChannelHandleTestCase{ +var tmsTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: tmsReceiveURL, Data: receiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, @@ -173,7 +173,7 @@ var tmsTestCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, } -var twTestCases = []ChannelHandleTestCase{ +var twTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: twReceiveURL, Data: receiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, @@ -220,7 +220,7 @@ var twTestCases = []ChannelHandleTestCase{ PrepRequest: addValidSignature}, } -var swTestCases = []ChannelHandleTestCase{ +var swTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: swReceiveURL, Data: receiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, {Label: "Receive No Params", URL: swReceiveURL, Data: " ", ExpectedRespStatus: 400, ExpectedBodyContains: "field 'messagesid' required"}, @@ -249,13 +249,13 @@ var swTestCases = []ChannelHandleTestCase{ {Label: "Status ID Invalid", URL: swStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, } -var waTestCases = []ChannelHandleTestCase{ +var waTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: waReceiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "whatsapp:14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, } -var twaTestCases = []ChannelHandleTestCase{ +var twaTestCases = []IncomingTestCase{ {Label: "Receive Valid", URL: twaReceiveURL, Data: waReceiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Msg"), ExpectedURN: "whatsapp:14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", PrepRequest: addValidSignature}, @@ -294,11 +294,11 @@ func addInvalidSignature(r *http.Request) { r.Header.Set(signatureHeader, "invalidsig") } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newTWIMLHandler("T", "Twilio", true), testCases) - RunChannelTestCases(t, tmsTestChannels, newTWIMLHandler("TMS", "Twilio Messaging Service", true), tmsTestCases) - RunChannelTestCases(t, twTestChannels, newTWIMLHandler("TW", "TwiML API", true), twTestCases) - RunChannelTestCases(t, swTestChannels, newTWIMLHandler("SW", "SignalWire", false), swTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newTWIMLHandler("T", "Twilio", true), testCases) + RunIncomingTestCases(t, tmsTestChannels, newTWIMLHandler("TMS", "Twilio Messaging Service", true), tmsTestCases) + RunIncomingTestCases(t, twTestChannels, newTWIMLHandler("TW", "TwiML API", true), twTestCases) + RunIncomingTestCases(t, swTestChannels, newTWIMLHandler("SW", "SignalWire", false), swTestCases) waChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "+12065551212", "US", map[string]any{ @@ -307,7 +307,7 @@ func TestHandler(t *testing.T) { }, ) waChannel.SetScheme(urns.WhatsAppScheme) - RunChannelTestCases(t, []courier.Channel{waChannel}, newTWIMLHandler("T", "TwilioWhatsApp", true), waTestCases) + RunIncomingTestCases(t, []courier.Channel{waChannel}, newTWIMLHandler("T", "TwilioWhatsApp", true), waTestCases) twaChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TWA", "+12065551212", "US", map[string]any{ @@ -316,7 +316,7 @@ func TestHandler(t *testing.T) { }, ) twaChannel.SetScheme(urns.WhatsAppScheme) - RunChannelTestCases(t, []courier.Channel{twaChannel}, newTWIMLHandler("TWA", "Twilio WhatsApp", true), twaTestCases) + RunIncomingTestCases(t, []courier.Channel{twaChannel}, newTWIMLHandler("TWA", "Twilio WhatsApp", true), twaTestCases) } func BenchmarkHandler(b *testing.B) { @@ -334,7 +334,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, } } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -441,7 +441,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var tmsDefaultSendTestCases = []ChannelSendTestCase{ +var tmsDefaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -526,7 +526,7 @@ var tmsDefaultSendTestCases = []ChannelSendTestCase{ }, } -var twDefaultSendTestCases = []ChannelSendTestCase{ +var twDefaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -611,7 +611,7 @@ var twDefaultSendTestCases = []ChannelSendTestCase{ }, } -var swSendTestCases = []ChannelSendTestCase{ +var swSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -696,7 +696,7 @@ var swSendTestCases = []ChannelSendTestCase{ }, } -var waSendTestCases = []ChannelSendTestCase{ +var waSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -711,7 +711,7 @@ var waSendTestCases = []ChannelSendTestCase{ }, } -var twaSendTestCases = []ChannelSendTestCase{ +var twaSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -726,7 +726,7 @@ var twaSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "T", "2020", "US", map[string]any{ @@ -753,10 +753,10 @@ func TestSending(t *testing.T) { configSendURL: "BASE_URL", }) - RunChannelSendTestCases(t, defaultChannel, newTWIMLHandler("T", "Twilio", true), defaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) - RunChannelSendTestCases(t, tmsDefaultChannel, newTWIMLHandler("TMS", "Twilio Messaging Service", true), tmsDefaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) - RunChannelSendTestCases(t, twDefaultChannel, newTWIMLHandler("TW", "TwiML", true), twDefaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) - RunChannelSendTestCases(t, swChannel, newTWIMLHandler("SW", "SignalWire", false), swSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, defaultChannel, newTWIMLHandler("T", "Twilio", true), defaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, tmsDefaultChannel, newTWIMLHandler("TMS", "Twilio Messaging Service", true), tmsDefaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, twDefaultChannel, newTWIMLHandler("TW", "TwiML", true), twDefaultSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, swChannel, newTWIMLHandler("SW", "SignalWire", false), swSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) waChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "SW", "+12065551212", "US", map[string]any{ @@ -766,7 +766,7 @@ func TestSending(t *testing.T) { ) waChannel.SetScheme(urns.WhatsAppScheme) - RunChannelSendTestCases(t, waChannel, newTWIMLHandler("T", "Twilio Whatsapp", true), waSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, waChannel, newTWIMLHandler("T", "Twilio Whatsapp", true), waSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) twaChannel := test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TWA", "+12065551212", "US", map[string]any{ @@ -776,7 +776,7 @@ func TestSending(t *testing.T) { ) twaChannel.SetScheme(urns.WhatsAppScheme) - RunChannelSendTestCases(t, twaChannel, newTWIMLHandler("TWA", "Twilio Whatsapp", true), twaSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) + RunOutgoingTestCases(t, twaChannel, newTWIMLHandler("TWA", "Twilio Whatsapp", true), twaSendTestCases, []string{httpx.BasicAuth("accountSID", "authToken")}, nil) } func TestBuildAttachmentRequest(t *testing.T) { diff --git a/handlers/twitter/twitter_test.go b/handlers/twitter/twitter_test.go index 05d9b28fe..ee9fae918 100644 --- a/handlers/twitter/twitter_test.go +++ b/handlers/twitter/twitter_test.go @@ -166,7 +166,7 @@ var attachment = `{ var notJSON = `blargh` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Message", URL: "/c/twt/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", Data: helloMsg, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedContactName: Sp("Nicolas Pottier"), ExpectedURN: "twitterid:272953809#nicpottier", ExpectedMsgText: Sp("Hello World & good wishes."), ExpectedExternalID: "958501034212564996", ExpectedDate: time.Date(2018, 1, 31, 0, 43, 49, 301000000, time.UTC)}, @@ -180,8 +180,8 @@ var testCases = []ChannelHandleTestCase{ {Label: "Webhook Verification Error", URL: "/c/twt/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", ExpectedRespStatus: 400, ExpectedBodyContains: "missing required 'crc_token'"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler("TWT", "Twitter Activity"), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler("TWT", "Twitter Activity"), testCases) } func BenchmarkHandler(b *testing.B) { @@ -194,7 +194,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, uploadDomain = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -370,8 +370,8 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase for j, attachment := range testCase.MsgAttachments { @@ -385,7 +385,7 @@ func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTes } return casesWithMockedUrls } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { // fake media server that just replies with 200 and "media body" for content mediaServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() @@ -394,5 +394,5 @@ func TestSending(t *testing.T) { })) attachmentMockedSendTestCase := mockAttachmentURLs(mediaServer, defaultSendTestCases) - RunChannelSendTestCases(t, testChannels[0], newHandler("TWT", "Twitter Activity"), attachmentMockedSendTestCase, []string{"apiSecret", "accessTokenSecret"}, nil) + RunOutgoingTestCases(t, testChannels[0], newHandler("TWT", "Twitter Activity"), attachmentMockedSendTestCase, []string{"apiSecret", "accessTokenSecret"}, nil) } diff --git a/handlers/viber/viber_test.go b/handlers/viber/viber_test.go index f692bd02e..2a0fad59e 100644 --- a/handlers/viber/viber_test.go +++ b/handlers/viber/viber_test.go @@ -20,7 +20,7 @@ func setSendURL(server *httptest.Server, h courier.ChannelHandler, channel couri sendURL = server.URL } -func buildMockAttachmentService(testCases []ChannelSendTestCase) *httptest.Server { +func buildMockAttachmentService(testCases []OutgoingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { headers := w.Header() if r.Method == http.MethodHead { @@ -44,7 +44,7 @@ func buildMockAttachmentService(testCases []ChannelSendTestCase) *httptest.Serve return server } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", @@ -187,14 +187,14 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var invalidTokenSendTestCases = []ChannelSendTestCase{ +var invalidTokenSendTestCases = []OutgoingTestCase{ { Label: "Invalid token", ExpectedErrors: []*courier.ChannelError{courier.NewChannelError("", "", "missing auth token in config")}, }, } -var buttonLayoutSendTestCases = []ChannelSendTestCase{ +var buttonLayoutSendTestCases = []OutgoingTestCase{ { Label: "Quick Reply With Layout With Column, Row and BgColor definitions", MsgText: "Select a, b, c or d.", @@ -208,7 +208,7 @@ var buttonLayoutSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { attachmentService := buildMockAttachmentService(defaultSendTestCases) defer attachmentService.Close() @@ -226,9 +226,9 @@ func TestSending(t *testing.T) { courier.ConfigAuthToken: "Token", "button_layout": map[string]any{"bg_color": "#f7bb3f", "text": "*

", "text_size": "large"}, }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Token"}, nil) - RunChannelSendTestCases(t, invalidTokenChannel, newHandler(), invalidTokenSendTestCases, []string{"Token"}, nil) - RunChannelSendTestCases(t, buttonLayoutChannel, newHandler(), buttonLayoutSendTestCases, []string{"Token"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"Token"}, nil) + RunOutgoingTestCases(t, invalidTokenChannel, newHandler(), invalidTokenSendTestCases, []string{"Token"}, nil) + RunOutgoingTestCases(t, buttonLayoutChannel, newHandler(), buttonLayoutSendTestCases, []string{"Token"}, nil) } var testChannels = []courier.Channel{ @@ -493,7 +493,7 @@ var ( }` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveURL, Data: validMsg, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("incoming msg"), ExpectedURN: "viber:xy5/5y6O81+/kbWHpLhBoA==", ExpectedExternalID: "4987381189870374000", PrepRequest: addValidSignature}, @@ -534,7 +534,7 @@ var testCases = []ChannelHandleTestCase{ ExpectedAttachments: []string{"https://viber.github.io/docs/img/stickers/40133.png"}, PrepRequest: addValidSignature}, } -var testWelcomeMessageCases = []ChannelHandleTestCase{ +var testWelcomeMessageCases = []IncomingTestCase{ { Label: "Receive Valid", URL: receiveURL, @@ -579,9 +579,9 @@ func addInvalidSignature(r *http.Request) { r.Header.Set(viberSignatureHeader, "invalidsig") } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) - RunChannelTestCases(t, testChannelsWithWelcomeMessage, newHandler(), testWelcomeMessageCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) + RunIncomingTestCases(t, testChannelsWithWelcomeMessage, newHandler(), testWelcomeMessageCases) } func BenchmarkHandler(b *testing.B) { diff --git a/handlers/vk/vk_test.go b/handlers/vk/vk_test.go index bf7cb3ba6..347f1d8c3 100644 --- a/handlers/vk/vk_test.go +++ b/handlers/vk/vk_test.go @@ -215,7 +215,7 @@ const msgKeyboard = `{ const keyboardJson = `{"one_time":true,"buttons":[[{"action":{"type":"text","label":"A","payload":"\"A\""},"color":"primary"},{"action":{"type":"text","label":"B","payload":"\"B\""},"color":"primary"},{"action":{"type":"text","label":"C","payload":"\"C\""},"color":"primary"},{"action":{"type":"text","label":"D","payload":"\"D\""},"color":"primary"},{"action":{"type":"text","label":"E","payload":"\"E\""},"color":"primary"}]],"inline":false}` -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Message", URL: receiveURL, @@ -344,11 +344,11 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } -func buildMockVKService(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockVKService(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, actionGetUser) { userId := r.URL.Query()["user_ids"][0] @@ -365,7 +365,7 @@ func buildMockVKService(testCases []ChannelHandleTestCase) *httptest.Server { } func TestDescribeURN(t *testing.T) { - server := buildMockVKService([]ChannelHandleTestCase{}) + server := buildMockVKService([]IncomingTestCase{}) defer server.Close() handler := newHandler() @@ -386,7 +386,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, URLPhotoUploadServer = s.URL + "/upload/photo" } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Send simple message", MsgText: "Simple message", @@ -472,8 +472,8 @@ var sendTestCases = []ChannelSendTestCase{ }, } -func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase @@ -497,5 +497,5 @@ func TestSend(t *testing.T) { res.Write([]byte("media body")) })) mockedSendTestCases := mockAttachmentURLs(mediaServer, sendTestCases) - RunChannelSendTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, []string{"token123xyz", "abc123xyz"}, nil) + RunOutgoingTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, []string{"token123xyz", "abc123xyz"}, nil) } diff --git a/handlers/wavy/wavy_test.go b/handlers/wavy/wavy_test.go index b7e8425bf..902bf7a53 100644 --- a/handlers/wavy/wavy_test.go +++ b/handlers/wavy/wavy_test.go @@ -74,7 +74,7 @@ var ( ` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Message", URL: receiveURL, @@ -160,8 +160,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -172,7 +172,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -206,11 +206,11 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WV", "2020", "BR", map[string]any{ courier.ConfigUsername: "user1", courier.ConfigAuthToken: "token", }) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"token"}, nil) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"token"}, nil) } diff --git a/handlers/wechat/wechat_test.go b/handlers/wechat/wechat_test.go index 015269163..f3f6f357a 100644 --- a/handlers/wechat/wechat_test.go +++ b/handlers/wechat/wechat_test.go @@ -138,7 +138,7 @@ func addInvalidSignature(r *http.Request) { r.URL.RawQuery = query.Encode() } -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ {Label: "Receive Message", URL: receiveURL, Data: validMsg, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedMsgText: Sp("Simple Message"), ExpectedURN: "wechat:1234", ExpectedExternalID: "123456", ExpectedDate: time.Date(2018, 2, 16, 9, 47, 4, 438000000, time.UTC)}, @@ -163,8 +163,8 @@ var testCases = []ChannelHandleTestCase{ PrepRequest: addInvalidSignature}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -172,7 +172,7 @@ func BenchmarkHandler(b *testing.B) { } // mocks the call to the WeChat API -func buildMockWCAPI(testCases []ChannelHandleTestCase) *httptest.Server { +func buildMockWCAPI(testCases []IncomingTestCase) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { accessToken := r.URL.Query().Get("access_token") defer r.Body.Close() @@ -289,7 +289,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURL = s.URL } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -350,8 +350,8 @@ func setupBackend(mb *test.MockBackend) { rc.Do("SET", "channel-token:8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ACCESS_TOKEN") } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WC", "2020", "US", map[string]any{configAppSecret: "secret123", configAppID: "app-id"}) - RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) + RunOutgoingTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, []string{"secret123"}, setupBackend) } diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp/whatsapp_test.go index d62f414a3..28aba6937 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp/whatsapp_test.go @@ -302,7 +302,7 @@ var ( txReceiveURL = "/c/txw/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive" ) -var waTestCases = []ChannelHandleTestCase{ +var waTestCases = []IncomingTestCase{ { Label: "Receive Valid Message", URL: waReceiveURL, @@ -518,8 +518,8 @@ func TestBuildAttachmentRequest(t *testing.T) { assert.Equal(t, "Bearer the-auth-token", req.Header.Get("Authorization")) } -func replaceTestcaseURLs(tcs []ChannelHandleTestCase, url string) []ChannelHandleTestCase { - replaced := make([]ChannelHandleTestCase, len(tcs)) +func replaceTestcaseURLs(tcs []IncomingTestCase, url string) []IncomingTestCase { + replaced := make([]IncomingTestCase, len(tcs)) for i, tc := range tcs { tc.URL = url replaced[i] = tc @@ -527,10 +527,10 @@ func replaceTestcaseURLs(tcs []ChannelHandleTestCase, url string) []ChannelHandl return replaced } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), waTestCases) - RunChannelTestCases(t, testChannels, newWAHandler(courier.ChannelType("D3"), "360Dialog"), replaceTestcaseURLs(waTestCases, d3ReceiveURL)) - RunChannelTestCases(t, testChannels, newWAHandler(courier.ChannelType("TXW"), "TextIt"), replaceTestcaseURLs(waTestCases, txReceiveURL)) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), waTestCases) + RunIncomingTestCases(t, testChannels, newWAHandler(courier.ChannelType("D3"), "360Dialog"), replaceTestcaseURLs(waTestCases, d3ReceiveURL)) + RunIncomingTestCases(t, testChannels, newWAHandler(courier.ChannelType("TXW"), "TextIt"), replaceTestcaseURLs(waTestCases, txReceiveURL)) } func BenchmarkHandler(b *testing.B) { @@ -545,7 +545,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, c.(*test.MockChannel).SetConfig("base_url", s.URL) } -var defaultSendTestCases = []ChannelSendTestCase{ +var defaultSendTestCases = []OutgoingTestCase{ { Label: "Link Sending", MsgText: "Link Sending https://link.com", @@ -954,7 +954,7 @@ var defaultSendTestCases = []ChannelSendTestCase{ }, } -var mediaCacheSendTestCases = []ChannelSendTestCase{ +var mediaCacheSendTestCases = []OutgoingTestCase{ { Label: "Media Upload Error", MsgText: "document caption", @@ -1068,7 +1068,7 @@ var mediaCacheSendTestCases = []ChannelSendTestCase{ }, } -var hsmSupportSendTestCases = []ChannelSendTestCase{ +var hsmSupportSendTestCases = []OutgoingTestCase{ { Label: "Template Send", MsgText: "templated message", @@ -1083,8 +1083,8 @@ var hsmSupportSendTestCases = []ChannelSendTestCase{ }, } -func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase @@ -1097,7 +1097,7 @@ func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTes return casesWithMockedUrls } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WA", "250788383383", "US", map[string]any{ "auth_token": "token123", @@ -1131,10 +1131,10 @@ func TestSending(t *testing.T) { "version": "v2.35.2", }) - RunChannelSendTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), defaultSendTestCases, []string{"token123"}, nil) - RunChannelSendTestCases(t, hsmSupportChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), hsmSupportSendTestCases, []string{"token123"}, nil) - RunChannelSendTestCases(t, d3Channel, newWAHandler(courier.ChannelType("D3"), "360Dialog"), defaultSendTestCases, []string{"token123"}, nil) - RunChannelSendTestCases(t, txwChannel, newWAHandler(courier.ChannelType("TXW"), "TextIt"), defaultSendTestCases, []string{"token123"}, nil) + RunOutgoingTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), defaultSendTestCases, []string{"token123"}, nil) + RunOutgoingTestCases(t, hsmSupportChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), hsmSupportSendTestCases, []string{"token123"}, nil) + RunOutgoingTestCases(t, d3Channel, newWAHandler(courier.ChannelType("D3"), "360Dialog"), defaultSendTestCases, []string{"token123"}, nil) + RunOutgoingTestCases(t, txwChannel, newWAHandler(courier.ChannelType("TXW"), "TextIt"), defaultSendTestCases, []string{"token123"}, nil) mediaServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() @@ -1144,7 +1144,7 @@ func TestSending(t *testing.T) { defer mediaServer.Close() mediaCacheSendTestCases := mockAttachmentURLs(mediaServer, mediaCacheSendTestCases) - RunChannelSendTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), mediaCacheSendTestCases, []string{"token123"}, nil) + RunOutgoingTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), mediaCacheSendTestCases, []string{"token123"}, nil) } func TestGetSupportedLanguage(t *testing.T) { diff --git a/handlers/yo/yo_test.go b/handlers/yo/yo_test.go index 445f32562..223f1f57a 100644 --- a/handlers/yo/yo_test.go +++ b/handlers/yo/yo_test.go @@ -25,7 +25,7 @@ var testChannels = []courier.Channel{ test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]any{"username": "yo-username", "password": "yo-password"}), } -var handleTestCases = []ChannelHandleTestCase{ +var handleTestCases = []IncomingTestCase{ {Label: "Receive Valid Message", URL: receiveValidMessage, Data: "", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("Join"), ExpectedURN: "tel:+2349067554729"}, {Label: "Receive Valid From", URL: receiveValidMessageFrom, Data: "", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", @@ -40,8 +40,8 @@ var handleTestCases = []ChannelHandleTestCase{ {Label: "Receive Invalid Date", URL: receiveInvalidDate, Data: "", ExpectedRespStatus: 400, ExpectedBodyContains: "invalid date format, must be RFC 3339"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), handleTestCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), handleTestCases) } func BenchmarkHandler(b *testing.B) { @@ -53,7 +53,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, sendURLs = []string{s.URL} } -var getSendTestCases = []ChannelSendTestCase{ +var getSendTestCases = []OutgoingTestCase{ {Label: "Plain Send", MsgText: "Simple Message", MsgURN: "tel:+250788383383", ExpectedMsgStatus: "W", @@ -98,8 +98,8 @@ var getSendTestCases = []ChannelSendTestCase{ SendPrep: setSendURL}, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { var getChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "YO", "2020", "US", map[string]any{"username": "yo-username", "password": "yo-password"}) - RunChannelSendTestCases(t, getChannel, newHandler(), getSendTestCases, []string{"yo-password"}, nil) + RunOutgoingTestCases(t, getChannel, newHandler(), getSendTestCases, []string{"yo-password"}, nil) } diff --git a/handlers/zenvia/zenvia_test.go b/handlers/zenvia/zenvia_test.go index c099b19e5..a60fa0253 100644 --- a/handlers/zenvia/zenvia_test.go +++ b/handlers/zenvia/zenvia_test.go @@ -162,7 +162,7 @@ var missingFieldsReceive = `{ } }` -var testWhatappCases = []ChannelHandleTestCase{ +var testWhatappCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveWhatsappURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Msg"), ExpectedURN: "whatsapp:254791541111", ExpectedDate: time.Date(2017, 5, 3, 03, 04, 45, 0, time.UTC)}, @@ -183,7 +183,7 @@ var testWhatappCases = []ChannelHandleTestCase{ {Label: "Wrong JSON schema", URL: statusWhatsppURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, } -var testSMSCases = []ChannelHandleTestCase{ +var testSMSCases = []IncomingTestCase{ {Label: "Receive Valid", URL: receiveSMSURL, Data: validReceive, ExpectedRespStatus: 200, ExpectedBodyContains: "Message Accepted", ExpectedMsgText: Sp("Msg"), ExpectedURN: "whatsapp:254791541111", ExpectedDate: time.Date(2017, 5, 3, 03, 04, 45, 0, time.UTC)}, @@ -204,9 +204,9 @@ var testSMSCases = []ChannelHandleTestCase{ {Label: "Wrong JSON schema", URL: statusSMSURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testWhatsappChannels, newHandler("ZVW", "Zenvia WhatsApp"), testWhatappCases) - RunChannelTestCases(t, testSMSChannels, newHandler("ZVS", "Zenvia SMS"), testSMSCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testWhatsappChannels, newHandler("ZVW", "Zenvia WhatsApp"), testWhatappCases) + RunIncomingTestCases(t, testSMSChannels, newHandler("ZVS", "Zenvia SMS"), testSMSCases) } func BenchmarkHandler(b *testing.B) { @@ -220,7 +220,7 @@ func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, smsSendURL = s.URL } -var defaultWhatsappSendTestCases = []ChannelSendTestCase{ +var defaultWhatsappSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -298,7 +298,7 @@ var defaultWhatsappSendTestCases = []ChannelSendTestCase{ }, } -var defaultSMSSendTestCases = []ChannelSendTestCase{ +var defaultSMSSendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message ☺", @@ -374,11 +374,11 @@ var defaultSMSSendTestCases = []ChannelSendTestCase{ }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { maxMsgLength = 160 var defaultWhatsappChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVW", "2020", "BR", map[string]any{"api_key": "zv-api-token"}) - RunChannelSendTestCases(t, defaultWhatsappChannel, newHandler("ZVW", "Zenvia WhatsApp"), defaultWhatsappSendTestCases, []string{"zv-api-token"}, nil) + RunOutgoingTestCases(t, defaultWhatsappChannel, newHandler("ZVW", "Zenvia WhatsApp"), defaultWhatsappSendTestCases, []string{"zv-api-token"}, nil) var defaultSMSChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "ZVS", "2020", "BR", map[string]any{"api_key": "zv-api-token"}) - RunChannelSendTestCases(t, defaultSMSChannel, newHandler("ZVS", "Zenvia SMS"), defaultSMSSendTestCases, []string{"zv-api-token"}, nil) + RunOutgoingTestCases(t, defaultSMSChannel, newHandler("ZVS", "Zenvia SMS"), defaultSMSSendTestCases, []string{"zv-api-token"}, nil) } From 6300a008205021d879f3a1a12f28223ac6727810 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Sep 2023 15:15:57 -0500 Subject: [PATCH 065/170] Don't rely on knowing msg id to determine if a log is attached --- attachments.go | 2 +- attachments_test.go | 2 +- backends/rapidpro/backend_test.go | 2 +- backends/rapidpro/channel_log.go | 2 +- channel_log.go | 23 +++++++++++------------ channel_log_test.go | 6 +++--- server.go | 4 ++-- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/attachments.go b/attachments.go index 06fd9bb35..022645ff8 100644 --- a/attachments.go +++ b/attachments.go @@ -57,7 +57,7 @@ func fetchAttachment(ctx context.Context, b Backend, r *http.Request) (*fetchAtt return nil, errors.Wrap(err, "error getting channel") } - clog := NewChannelLogForAttachmentFetch(ch, fa.MsgID, GetHandler(ch.ChannelType()).RedactValues(ch)) + clog := NewChannelLogForAttachmentFetch(ch, GetHandler(ch.ChannelType()).RedactValues(ch)) attachment, err := FetchAndStoreAttachment(ctx, b, ch, fa.URL, clog) diff --git a/attachments_test.go b/attachments_test.go index 111a96f46..6f2f9f5fa 100644 --- a/attachments_test.go +++ b/attachments_test.go @@ -40,7 +40,7 @@ func TestFetchAndStoreAttachment(t *testing.T) { mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) mb.AddChannel(mockChannel) - clog := courier.NewChannelLogForAttachmentFetch(mockChannel, courier.MsgID(123), []string{"sesame"}) + clog := courier.NewChannelLogForAttachmentFetch(mockChannel, []string{"sesame"}) att, err := courier.FetchAndStoreAttachment(ctx, mb, mockChannel, "http://mock.com/media/hello.jpg", clog) assert.NoError(t, err) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 60f556ff0..5bfeca540 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1018,7 +1018,7 @@ func (ts *BackendTestSuite) TestWriteChanneLog() { clog2 := courier.NewChannelLog(courier.ChannelLogTypeMsgSend, channel, nil) clog2.HTTP(trace) - clog2.SetMsgID(1234) + clog2.SetAttached(true) // log is attached to a message so will be written to storage err = ts.b.WriteChannelLog(ctx, clog2) diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 3d558a394..638f43c7d 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -77,7 +77,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) } // if log is attached to a call or message, only write to storage - if clog.MsgID() != courier.NilMsgID { + if clog.Attached() { v := &stChannelLog{ UUID: clog.UUID(), Type: clog.Type(), diff --git a/channel_log.go b/channel_log.go index 722bfd8d2..c4585d69b 100644 --- a/channel_log.go +++ b/channel_log.go @@ -100,12 +100,12 @@ type ChannelLog struct { uuid ChannelLogUUID type_ ChannelLogType channel Channel - msgID MsgID httpLogs []*httpx.Log errors []*ChannelError createdOn time.Time elapsed time.Duration + attached bool recorder *httpx.Recorder redactor stringsx.Redactor } @@ -113,30 +113,29 @@ type ChannelLog struct { // NewChannelLogForIncoming creates a new channel log for an incoming request, the type of which won't be known // until the handler completes. func NewChannelLogForIncoming(logType ChannelLogType, ch Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { - return newChannelLog(logType, ch, r, NilMsgID, redactVals) + return newChannelLog(logType, ch, r, false, redactVals) } // NewChannelLogForSend creates a new channel log for a message send func NewChannelLogForSend(msg Msg, redactVals []string) *ChannelLog { - return newChannelLog(ChannelLogTypeMsgSend, msg.Channel(), nil, msg.ID(), redactVals) + return newChannelLog(ChannelLogTypeMsgSend, msg.Channel(), nil, true, redactVals) } // NewChannelLogForSend creates a new channel log for an attachment fetch -func NewChannelLogForAttachmentFetch(ch Channel, msgID MsgID, redactVals []string) *ChannelLog { - return newChannelLog(ChannelLogTypeAttachmentFetch, ch, nil, msgID, redactVals) +func NewChannelLogForAttachmentFetch(ch Channel, redactVals []string) *ChannelLog { + return newChannelLog(ChannelLogTypeAttachmentFetch, ch, nil, true, redactVals) } // NewChannelLog creates a new channel log with the given type and channel func NewChannelLog(t ChannelLogType, ch Channel, redactVals []string) *ChannelLog { - return newChannelLog(t, ch, nil, NilMsgID, redactVals) + return newChannelLog(t, ch, nil, false, redactVals) } -func newChannelLog(t ChannelLogType, ch Channel, r *httpx.Recorder, mid MsgID, redactVals []string) *ChannelLog { +func newChannelLog(t ChannelLogType, ch Channel, r *httpx.Recorder, attached bool, redactVals []string) *ChannelLog { return &ChannelLog{ uuid: ChannelLogUUID(uuids.New()), type_: t, channel: ch, - msgID: mid, recorder: r, createdOn: dates.Now(), @@ -183,12 +182,12 @@ func (l *ChannelLog) Channel() Channel { return l.channel } -func (l *ChannelLog) MsgID() MsgID { - return l.msgID +func (l *ChannelLog) Attached() bool { + return l.attached } -func (l *ChannelLog) SetMsgID(id MsgID) { - l.msgID = id +func (l *ChannelLog) SetAttached(a bool) { + l.attached = a } func (l *ChannelLog) HTTPLogs() []*httpx.Log { diff --git a/channel_log_test.go b/channel_log_test.go index 88c4daa67..5961d328a 100644 --- a/channel_log_test.go +++ b/channel_log_test.go @@ -48,7 +48,7 @@ func TestChannelLog(t *testing.T) { assert.Equal(t, courier.ChannelLogUUID("c00e5d67-c275-4389-aded-7d8b151cbd5b"), clog.UUID()) assert.Equal(t, courier.ChannelLogTypeTokenRefresh, clog.Type()) assert.Equal(t, channel, clog.Channel()) - assert.Equal(t, courier.NilMsgID, clog.MsgID()) + assert.False(t, clog.Attached()) assert.Equal(t, 2, len(clog.HTTPLogs())) assert.Equal(t, 2, len(clog.Errors())) assert.False(t, clog.CreatedOn().IsZero()) @@ -74,10 +74,10 @@ func TestChannelLog(t *testing.T) { assert.Equal(t, "this is an error", err2.Message()) assert.Equal(t, "", err2.Code()) - clog.SetMsgID(123) + clog.SetAttached(true) clog.SetType(courier.ChannelLogTypeEventReceive) - assert.Equal(t, courier.MsgID(123), clog.MsgID()) + assert.True(t, clog.Attached()) assert.Equal(t, courier.ChannelLogTypeEventReceive, clog.Type()) } diff --git a/server.go b/server.go index 4717da99d..a0560b39d 100644 --- a/server.go +++ b/server.go @@ -331,11 +331,11 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe for _, event := range events { switch e := event.(type) { case Msg: - clog.SetMsgID(e.ID()) + clog.SetAttached(true) analytics.Gauge(fmt.Sprintf("courier.msg_receive_%s", channel.ChannelType()), secondDuration) LogMsgReceived(r, e) case StatusUpdate: - clog.SetMsgID(e.ID()) + clog.SetAttached(true) analytics.Gauge(fmt.Sprintf("courier.msg_status_%s", channel.ChannelType()), secondDuration) LogMsgStatusReceived(r, e) case ChannelEvent: From 754b88fbdf939c7e884b910b0ab911b772db9828 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Sep 2023 15:33:50 -0500 Subject: [PATCH 066/170] Update CHANGELOG.md for v8.3.10 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29213427c..47ec10d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.10 (2023-09-05) +------------------------- + * Don't rely on knowing msg id to determine if a log is attached + * Rework handler tests so that test cases must explicitly say if they don't generate a channel log + v8.3.9 (2023-09-05) ------------------------- * Try to resolve sent external ids from redis From 05cc97fa68f257f8a2c6ed2545fe8bb3b7a1cc5f Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Wed, 6 Sep 2023 13:18:12 +0200 Subject: [PATCH 067/170] No need to try making DB queries when all msg IDs got resolved from redis --- backends/rapidpro/backend_test.go | 3 +++ backends/rapidpro/status.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 5bfeca540..a46f143c9 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -700,6 +700,9 @@ func (ts *BackendTestSuite) TestSentExternalIDCaching() { err := ts.b.WriteStatusUpdate(ctx, status1) ts.NoError(err) + // give batcher time to write it + time.Sleep(time.Millisecond * 600) + keys, err := redis.Strings(r.Do("KEYS", "sent-external-ids:*")) ts.NoError(err) ts.Len(keys, 1) diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 01b129764..6411cffe1 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -300,6 +300,10 @@ func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*Sta } } + if len(notInCache) == 0 { + return nil + } + // create a mapping of channel id + external id -> status type ext struct { channelID courier.ChannelID From 264e29ad48473f97893272b25c98f4f733ad09a3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 09:57:16 -0500 Subject: [PATCH 068/170] Add logging of requests with no associated channel --- server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server.go b/server.go index a0560b39d..7d19daf49 100644 --- a/server.go +++ b/server.go @@ -349,6 +349,10 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe if err := s.backend.WriteChannelLog(ctx, clog); err != nil { logrus.WithError(err).Error("error writing channel log") } + } else { + logrus.WithError(err).WithFields( + logrus.Fields{"channel_type": handler.ChannelType(), "request": string(recorder.Trace.RequestTrace), "status": recorder.Trace.Response.StatusCode}, + ).Info("non-channel specific request") } } } From 7ad07da40cb5d6d9370dc8619f9a77a7471dfb04 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 09:58:28 -0500 Subject: [PATCH 069/170] Update CHANGELOG.md for v8.3.11 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ec10d3f..abedae088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.11 (2023-09-06) +------------------------- + * Add logging of requests with no associated channel + * No need to try making DB queries when all msg IDs got resolved from redis + v8.3.10 (2023-09-05) ------------------------- * Don't rely on knowing msg id to determine if a log is attached From fa3e922124c2102635a1d2d95a2db131444a1150 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 10:23:11 -0500 Subject: [PATCH 070/170] Cleanup ChannelType type --- backends/rapidpro/backend.go | 2 +- backends/rapidpro/channel.go | 2 +- channel.go | 8 ++------ handlers/twiml/twiml.go | 2 +- handlers/twiml/twiml_test.go | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 9d1c5bc8c..e163f34de 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -857,7 +857,7 @@ func (b *backend) Status() string { channel, err := getChannel(context.Background(), b.db, courier.AnyChannelType, channelUUID) channelType := "!!" if err == nil { - channelType = channel.ChannelType().String() + channelType = string(channel.ChannelType()) } // get # of items in our normal queue diff --git a/backends/rapidpro/channel.go b/backends/rapidpro/channel.go index 2dba1fe67..25ff5f070 100644 --- a/backends/rapidpro/channel.go +++ b/backends/rapidpro/channel.go @@ -164,7 +164,7 @@ func getChannelByAddress(ctx context.Context, db *sqlx.DB, channelType courier.C // if it wasn't found in the DB, clear our cache and return that it wasn't found if dbErr == courier.ErrChannelNotFound { clearLocalChannelByAddress(address) - return cachedChannel, fmt.Errorf("unable to find channel with type: %s and address: %s", channelType.String(), address.String()) + return cachedChannel, fmt.Errorf("unable to find channel with type: %s and address: %s", string(channelType), address.String()) } // if we had some other db error, return it if our cached channel was only just expired diff --git a/channel.go b/channel.go index f549eec29..b93c3da72 100644 --- a/channel.go +++ b/channel.go @@ -55,15 +55,11 @@ const ( ConfigSendHeaders = "headers" ) -// ChannelType is our typing of the two char channel types +// ChannelType is the 1-3 letter code used for channel types in the database type ChannelType string // AnyChannelType is our empty channel type used when doing lookups without channel type assertions -var AnyChannelType = ChannelType("") - -func (ct ChannelType) String() string { - return string(ct) -} +const AnyChannelType = ChannelType("") // ChannelRole is a role that a channel can perform type ChannelRole string diff --git a/handlers/twiml/twiml.go b/handlers/twiml/twiml.go index 7dbf79c22..5c18164ca 100644 --- a/handlers/twiml/twiml.go +++ b/handlers/twiml/twiml.go @@ -217,7 +217,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // build our callback URL callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) - callbackURL := fmt.Sprintf("https://%s/c/%s/%s/status?id=%d&action=callback", callbackDomain, strings.ToLower(h.ChannelType().String()), msg.Channel().UUID(), msg.ID()) + callbackURL := fmt.Sprintf("https://%s/c/%s/%s/status?id=%d&action=callback", callbackDomain, strings.ToLower(string(h.ChannelType())), msg.Channel().UUID(), msg.ID()) accountSID := msg.Channel().StringConfigForKey(configAccountSID, "") if accountSID == "" { diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index a152f6978..63a00a7c6 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -327,7 +327,7 @@ func BenchmarkHandler(b *testing.B) { // 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.Msg) { - if c.ChannelType().String() == "TW" || c.ChannelType().String() == "SW" { + if c.ChannelType() == courier.ChannelType("TW") || c.ChannelType() == courier.ChannelType("SW") { c.(*test.MockChannel).SetConfig("send_url", s.URL) } else { twilioBaseURL = s.URL From 93c522e51efdda91949e06f22dc6d2fc9480b20b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 10:41:48 -0500 Subject: [PATCH 071/170] Do more debug logging and less info logging --- backends/rapidpro/backend.go | 6 ++- backends/rapidpro/channel_log.go | 9 +++- log.go | 87 +++++++++++++++++--------------- sender.go | 2 +- 4 files changed, 58 insertions(+), 46 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index e163f34de..b6a939da1 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -529,6 +529,7 @@ func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalI // WriteStatusUpdate writes the passed in MsgStatus to our store func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { + log := logrus.WithFields(logrus.Fields{"msg_id": status.ID(), "msg_external_id": status.ExternalID(), "status": status.Status()}) su := status.(*StatusUpdate) if status.ID() == courier.NilMsgID && status.ExternalID() == "" { @@ -550,7 +551,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.ID())) if err != nil { - logrus.WithError(err).WithField("msg", status.ID()).Error("error recording external id") + log.WithError(err).Error("error recording external id") } } @@ -558,13 +559,14 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp if status.Status() == courier.MsgStatusErrored { err := b.ClearMsgSent(ctx, status.ID()) if err != nil { - logrus.WithError(err).WithField("msg", status.ID()).Error("error clearing sent flags") + log.WithError(err).Error("error clearing sent flags") } } } // queue the status to written by the batch writer b.statusWriter.Queue(status.(*StatusUpdate)) + log.Debug("status update queued") return nil } diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 638f43c7d..47c1025c1 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -57,6 +57,7 @@ type channelError struct { // queues the passed in channel log to a writer func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) { + log := logrus.WithFields(logrus.Fields{"log_uuid": clog.UUID(), "log_type": clog.Type(), "channel_uuid": clog.Channel().UUID()}) dbChan := clog.Channel().(*DBChannel) // so that we don't save null @@ -78,6 +79,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) // if log is attached to a call or message, only write to storage if clog.Attached() { + log = log.WithField("storage", "s3") v := &stChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -88,10 +90,11 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) ChannelUUID: clog.Channel().UUID(), } if b.stLogWriter.Queue(v) <= 0 { - logrus.Error("channel log storage writer buffer full") + log.Error("channel log writer buffer full") } } else { // otherwise write to database so it's retrievable + log = log.WithField("storage", "db") v := &dbChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -103,9 +106,11 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) ElapsedMS: int(clog.Elapsed() / time.Millisecond), } if b.dbLogWriter.Queue(v) <= 0 { - logrus.Error("channel log db writer buffer full") + log.Error("channel log writer buffer full") } } + + log.Debug("channel log queued") } type DBLogWriter struct { diff --git a/log.go b/log.go index a89d3ccb3..8bdef851a 100644 --- a/log.go +++ b/log.go @@ -9,64 +9,69 @@ import ( // LogMsgStatusReceived logs our that we received a new MsgStatus func LogMsgStatusReceived(r *http.Request, status StatusUpdate) { - log := logrus.WithFields(logrus.Fields{ - "channel_uuid": status.ChannelUUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "status": status.Status(), - }) - - if status.ID() != NilMsgID { - log = log.WithField("msg_id", status.ID()) - } else { - log = log.WithField("msg_external_id", status.ExternalID()) + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "channel_uuid": status.ChannelUUID(), + "url": r.Context().Value(contextRequestURL), + "elapsed_ms": getElapsedMS(r), + "status": status.Status(), + "msg_id": status.ID(), + "msg_external_id": status.ExternalID(), + }).Debug("status updated") } - log.Info("status updated") } // LogMsgReceived logs that we received the passed in message func LogMsgReceived(r *http.Request, msg Msg) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": msg.Channel().UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "msg_uuid": msg.UUID(), - "msg_id": msg.ID(), - "msg_urn": msg.URN().Identity(), - "msg_text": msg.Text(), - "msg_attachments": msg.Attachments(), - }).Info("msg received") + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "channel_uuid": msg.Channel().UUID(), + "url": r.Context().Value(contextRequestURL), + "elapsed_ms": getElapsedMS(r), + "msg_uuid": msg.UUID(), + "msg_id": msg.ID(), + "msg_urn": msg.URN().Identity(), + "msg_text": msg.Text(), + "msg_attachments": msg.Attachments(), + }).Debug("msg received") + } } // LogChannelEventReceived logs that we received the passed in channel event func LogChannelEventReceived(r *http.Request, event ChannelEvent) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": event.ChannelUUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "event_type": event.EventType(), - "event_urn": event.URN().Identity(), - }).Info("evt received") + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "channel_uuid": event.ChannelUUID(), + "url": r.Context().Value(contextRequestURL), + "elapsed_ms": getElapsedMS(r), + "event_type": event.EventType(), + "event_urn": event.URN().Identity(), + }).Debug("event received") + } } // LogRequestIgnored logs that we ignored the passed in request func LogRequestIgnored(r *http.Request, channel Channel, details string) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": channel.UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "details": details, - }).Info("request ignored") + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "channel_uuid": channel.UUID(), + "url": r.Context().Value(contextRequestURL), + "elapsed_ms": getElapsedMS(r), + "details": details, + }).Debug("request ignored") + } } // LogRequestHandled logs that we handled the passed in request but didn't create any events func LogRequestHandled(r *http.Request, channel Channel, details string) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": channel.UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "details": details, - }).Info("request handled") + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "channel_uuid": channel.UUID(), + "url": r.Context().Value(contextRequestURL), + "elapsed_ms": getElapsedMS(r), + "details": details, + }).Debug("request handled") + } } // LogRequestError logs that errored during parsing (this is logged as an info as it isn't an error on our side) diff --git a/sender.go b/sender.go index 27da570ed..826aaa987 100644 --- a/sender.go +++ b/sender.go @@ -231,7 +231,7 @@ func (w *Sender) sendMessage(msg Msg) { log.WithField("elapsed", duration).Warning("msg errored") analytics.Gauge(fmt.Sprintf("courier.msg_send_error_%s", msg.Channel().ChannelType()), secondDuration) } else { - log.WithField("elapsed", duration).Info("msg sent") + log.WithField("elapsed", duration).Debug("msg sent") analytics.Gauge(fmt.Sprintf("courier.msg_send_%s", msg.Channel().ChannelType()), secondDuration) } } From b616eb79cf323392b0d959005a898b2bf8e9fa40 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 10:52:38 -0500 Subject: [PATCH 072/170] Update CHANGELOG.md for v8.3.12 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abedae088..d421f61fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.12 (2023-09-06) +------------------------- + * Do more debug logging and less info logging + v8.3.11 (2023-09-06) ------------------------- * Add logging of requests with no associated channel From 8046709cb80daad29796adc11c40f43a76f163fb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 14:24:39 -0500 Subject: [PATCH 073/170] Update to latest null library and use Map[string] for channel event extra --- backends/rapidpro/backend_test.go | 10 +++++----- backends/rapidpro/channel.go | 8 ++++---- backends/rapidpro/channel_event.go | 10 +++++----- backends/rapidpro/contact.go | 2 +- backends/rapidpro/msg.go | 2 +- backends/rapidpro/urn.go | 2 +- channel.go | 2 +- channel_event.go | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- handlers/facebook/facebook.go | 6 +++--- handlers/facebook/facebook_test.go | 12 ++++++------ handlers/facebookapp/facebookapp.go | 6 +++--- handlers/facebookapp/facebookapp_test.go | 14 +++++++------- handlers/test.go | 2 +- msg.go | 2 +- responses.go | 12 ++++++------ test/channel_event.go | 6 +++--- 18 files changed, 53 insertions(+), 53 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index a46f143c9..45960b3cc 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -24,7 +24,7 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" "github.com/nyaruka/redisx/assertredis" "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" @@ -1269,7 +1269,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}).WithContactName("kermit frog") + event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}).WithContactName("kermit frog") err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) @@ -1281,7 +1281,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) - ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) } @@ -1309,7 +1309,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1323,7 +1323,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) - ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) diff --git a/backends/rapidpro/channel.go b/backends/rapidpro/channel.go index 25ff5f070..ab4ebbf50 100644 --- a/backends/rapidpro/channel.go +++ b/backends/rapidpro/channel.go @@ -12,7 +12,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/lib/pq" "github.com/nyaruka/courier" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" ) type LogPolicy string @@ -288,12 +288,12 @@ type DBChannel struct { Name_ sql.NullString `db:"name"` Address_ sql.NullString `db:"address"` Country_ sql.NullString `db:"country"` - Config_ null.Map `db:"config"` + Config_ null.Map[any] `db:"config"` Role_ string `db:"role"` LogPolicy LogPolicy `db:"log_policy"` - OrgConfig_ null.Map `db:"org_config"` - OrgIsAnon_ bool `db:"org_is_anon"` + OrgConfig_ null.Map[any] `db:"org_config"` + OrgIsAnon_ bool `db:"org_is_anon"` expiration time.Time } diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index 198ba2582..cf1a42885 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -13,7 +13,7 @@ import ( "github.com/lib/pq" "github.com/nyaruka/courier" "github.com/nyaruka/gocommon/urns" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" "github.com/sirupsen/logrus" ) @@ -166,7 +166,7 @@ type DBChannelEvent struct { ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` URN_ urns.URN `json:"urn" db:"urn"` EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` - Extra_ null.Map `json:"extra" db:"extra"` + Extra_ null.Map[string] `json:"extra" db:"extra"` OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` CreatedOn_ time.Time `json:"created_on" db:"created_on"` LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` @@ -183,7 +183,7 @@ func (e *DBChannelEvent) ChannelID() courier.ChannelID { return e.Channel func (e *DBChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } func (e *DBChannelEvent) ContactName() string { return e.ContactName_ } func (e *DBChannelEvent) URN() urns.URN { return e.URN_ } -func (e *DBChannelEvent) Extra() map[string]any { return e.Extra_ } +func (e *DBChannelEvent) Extra() map[string]string { return e.Extra_ } func (e *DBChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } func (e *DBChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } func (e *DBChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } @@ -193,8 +193,8 @@ func (e *DBChannelEvent) WithContactName(name string) courier.ChannelEvent { e.ContactName_ = name return e } -func (e *DBChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { - e.Extra_ = null.Map(extra) +func (e *DBChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { + e.Extra_ = null.Map[string](extra) return e } diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 62bf0bd53..6a30878f2 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -14,7 +14,7 @@ import ( "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 7ac7577fa..8c17267ec 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -19,7 +19,7 @@ import ( "github.com/nyaruka/courier/queue" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" "github.com/pkg/errors" "github.com/sirupsen/logrus" filetype "gopkg.in/h2non/filetype.v1" diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index f74ec2e48..d052bbc8d 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -5,7 +5,7 @@ import ( "database/sql/driver" "fmt" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" "github.com/pkg/errors" "github.com/jmoiron/sqlx" diff --git a/channel.go b/channel.go index b93c3da72..a8bbf19fd 100644 --- a/channel.go +++ b/channel.go @@ -5,7 +5,7 @@ import ( "errors" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" ) const ( diff --git a/channel_event.go b/channel_event.go index bfa5a1f91..62648721d 100644 --- a/channel_event.go +++ b/channel_event.go @@ -26,12 +26,12 @@ type ChannelEvent interface { ChannelUUID() ChannelUUID URN() urns.URN EventType() ChannelEventType - Extra() map[string]any + Extra() map[string]string CreatedOn() time.Time OccurredOn() time.Time WithContactName(name string) ChannelEvent - WithExtra(extra map[string]any) ChannelEvent + WithExtra(extra map[string]string) ChannelEvent WithOccurredOn(time.Time) ChannelEvent EventID() int64 diff --git a/go.mod b/go.mod index 9a967bff5..564d7da03 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.40.0 - github.com/nyaruka/null/v2 v2.0.3 + github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 9a51db781..0aa4c4d3e 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ github.com/nyaruka/gocommon v1.40.0 h1:PvwLljY8rirgUu1ROwZM+gkTdNRbOnrGBFBf70OZt github.com/nyaruka/gocommon v1.40.0/go.mod h1:J+1hsRQ7GPJKGbU9IUTKIvP74ONG3iiM5y5S9dfqSq4= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= -github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= -github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= +github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= +github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= github.com/nyaruka/phonenumbers v1.1.8/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= github.com/nyaruka/redisx v0.5.0 h1:XH1pjG17lhj2DZJbrrZ2yZuPLAXrrHidXVA7cIuQq4g= diff --git a/handlers/facebook/facebook.go b/handlers/facebook/facebook.go index 644ce92f0..5ed15193e 100644 --- a/handlers/facebook/facebook.go +++ b/handlers/facebook/facebook.go @@ -273,7 +273,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ referrerIDKey: msg.OptIn.Ref, } event = event.WithExtra(extra) @@ -295,7 +295,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -326,7 +326,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } diff --git a/handlers/facebook/facebook_test.go b/handlers/facebook/facebook_test.go index efdc371bb..da3449d7e 100644 --- a/handlers/facebook/facebook_test.go +++ b/handlers/facebook/facebook_test.go @@ -484,7 +484,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:ref:optin_user_ref", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, }, { Label: "Receive OptIn", @@ -495,7 +495,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, }, { Label: "Receive Get Started", @@ -506,7 +506,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started"}, }, { Label: "Receive Referral Postback", @@ -517,7 +517,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, }, { Label: "Receive Referral", @@ -528,7 +528,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, }, { Label: "Receive Referral", @@ -539,7 +539,7 @@ var testCases = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, }, { Label: "Receive DLR", diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 9c25ae533..e7328a13d 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -609,7 +609,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ referrerIDKey: msg.OptIn.Ref, } event = event.WithExtra(extra) @@ -631,7 +631,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -662,7 +662,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 907dcaa3a..c52c9cd2a 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -112,7 +112,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:ref:optin_user_ref", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, PrepRequest: addValidSignature, }, { @@ -124,7 +124,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "optin_ref"}, + ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, PrepRequest: addValidSignature, }, { @@ -136,7 +136,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started"}, PrepRequest: addValidSignature, }, { @@ -148,7 +148,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, PrepRequest: addValidSignature, }, { @@ -160,7 +160,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, PrepRequest: addValidSignature, }, { @@ -172,7 +172,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedURN: "facebook:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, + ExpectedEventExtra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, PrepRequest: addValidSignature, }, { @@ -323,7 +323,7 @@ var testCasesIG = []IncomingTestCase{ ExpectedURN: "instagram:5678", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]any{"title": "icebreaker question", "payload": "get_started"}, + ExpectedEventExtra: map[string]string{"title": "icebreaker question", "payload": "get_started"}, PrepRequest: addValidSignature, }, { diff --git a/handlers/test.go b/handlers/test.go index b335f2d93..1d9cb86c3 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -51,7 +51,7 @@ type IncomingTestCase struct { ExpectedExternalID string ExpectedMsgID int64 ExpectedEvent courier.ChannelEventType - ExpectedEventExtra map[string]any + ExpectedEventExtra map[string]string ExpectedErrors []*courier.ChannelError NoLogsExpected bool } diff --git a/msg.go b/msg.go index 3f91c0c12..d694c8caa 100644 --- a/msg.go +++ b/msg.go @@ -9,7 +9,7 @@ import ( "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/null/v2" + "github.com/nyaruka/null/v3" ) // MsgID is our typing of the db int type diff --git a/responses.go b/responses.go index 528a7e244..9e4eb2b0c 100644 --- a/responses.go +++ b/responses.go @@ -100,12 +100,12 @@ func NewMsgReceiveData(msg Msg) MsgReceiveData { // EventReceiveData is our response payload for a channel event type EventReceiveData struct { - Type string `json:"type"` - ChannelUUID ChannelUUID `json:"channel_uuid"` - EventType ChannelEventType `json:"event_type"` - URN urns.URN `json:"urn"` - ReceivedOn time.Time `json:"received_on"` - Extra map[string]any `json:"extra,omitempty"` + Type string `json:"type"` + ChannelUUID ChannelUUID `json:"channel_uuid"` + EventType ChannelEventType `json:"event_type"` + URN urns.URN `json:"urn"` + ReceivedOn time.Time `json:"received_on"` + Extra map[string]string `json:"extra,omitempty"` } // NewEventReceiveData creates a new receive data for the passed in event diff --git a/test/channel_event.go b/test/channel_event.go index 1ae1c5d6e..24f1be2c1 100644 --- a/test/channel_event.go +++ b/test/channel_event.go @@ -15,7 +15,7 @@ type mockChannelEvent struct { occurredOn time.Time contactName string - extra map[string]any + extra map[string]string } func (e *mockChannelEvent) EventID() int64 { return 0 } @@ -23,11 +23,11 @@ func (e *mockChannelEvent) ChannelUUID() courier.ChannelUUID { return e.chann func (e *mockChannelEvent) EventType() courier.ChannelEventType { return e.eventType } func (e *mockChannelEvent) CreatedOn() time.Time { return e.createdOn } func (e *mockChannelEvent) OccurredOn() time.Time { return e.occurredOn } -func (e *mockChannelEvent) Extra() map[string]any { return e.extra } +func (e *mockChannelEvent) Extra() map[string]string { return e.extra } func (e *mockChannelEvent) ContactName() string { return e.contactName } func (e *mockChannelEvent) URN() urns.URN { return e.urn } -func (e *mockChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { +func (e *mockChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { e.extra = extra return e } From dcf1899878cb3311feca3db8a2243ab178bfb233 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 15:03:52 -0500 Subject: [PATCH 074/170] Start writing ContactURN.auth_tokens --- backends/rapidpro/schema.sql | 1 + backends/rapidpro/urn.go | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/backends/rapidpro/schema.sql b/backends/rapidpro/schema.sql index 4983ee4b2..5f83dbe27 100644 --- a/backends/rapidpro/schema.sql +++ b/backends/rapidpro/schema.sql @@ -53,6 +53,7 @@ CREATE TABLE contacts_contacturn ( contact_id integer references contacts_contact(id) on delete cascade, org_id integer NOT NULL references orgs_org(id) on delete cascade, auth text, + auth_tokens jsonb, UNIQUE (org_id, identity) ); diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index d052bbc8d..b6d9b6df7 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -28,14 +28,15 @@ func (i ContactURNID) MarshalJSON() ([]byte, error) { return null.MarshalInt(i) // NewDBContactURN returns a new ContactURN object for the passed in org, contact and string urn, this is not saved to the DB yet func newDBContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, urn urns.URN, auth string) *DBContactURN { return &DBContactURN{ - OrgID: org, - ChannelID: channelID, - ContactID: contactID, - Identity: string(urn.Identity()), - Scheme: urn.Scheme(), - Path: urn.Path(), - Display: null.String(urn.Display()), - Auth: null.String(auth), + OrgID: org, + ChannelID: channelID, + ContactID: contactID, + Identity: string(urn.Identity()), + Scheme: urn.Scheme(), + Path: urn.Path(), + Display: null.String(urn.Display()), + Auth: null.String(auth), + AuthTokens: null.Map[string]{"default": auth}, } } @@ -112,6 +113,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns if auth != "" { contactURNs[0].Auth = null.String(auth) + contactURNs[0].AuthTokens = null.Map[string]{"default": auth} } return updateContactURN(db, contactURNs[0]) } @@ -134,6 +136,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns if auth != "" { existing.Auth = null.String(auth) + existing.AuthTokens = null.Map[string]{"default": auth} } } else { existing.Priority = currPriority @@ -225,6 +228,8 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn // update our auth if we have a value set if auth != "" && auth != string(contactURN.Auth) { contactURN.Auth = null.String(auth) + contactURN.AuthTokens = null.Map[string]{"default": auth} + err = updateContactURN(db, contactURN) } @@ -233,8 +238,8 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn const insertURN = ` INSERT INTO - contacts_contacturn(org_id, identity, path, scheme, display, auth, priority, channel_id, contact_id) - VALUES(:org_id, :identity, :path, :scheme, :display, :auth, :priority, :channel_id, :contact_id) + contacts_contacturn(org_id, identity, path, scheme, display, auth, auth_tokens, priority, channel_id, contact_id) + VALUES(:org_id, :identity, :path, :scheme, :display, :auth, :auth_tokens, :priority, :channel_id, :contact_id) RETURNING id ` @@ -260,6 +265,7 @@ SET contact_id = :contact_id, display = :display, auth = :auth, + auth_tokens = :auth_tokens, priority = :priority WHERE id = :id @@ -273,7 +279,8 @@ SET identity = :identity, path = :path, display = :display, - auth = :auth, + auth = :auth, + auth_tokens = :auth_tokens, priority = :priority WHERE id = :id @@ -318,6 +325,7 @@ type DBContactURN struct { Path string `db:"path"` Display null.String `db:"display"` Auth null.String `db:"auth"` + AuthTokens null.Map[string] `db:"auth_tokens"` Priority int `db:"priority"` ChannelID courier.ChannelID `db:"channel_id"` ContactID ContactID `db:"contact_id"` From 99f8defbd814e275484b7ded6bfd6898ced14cc2 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Sep 2023 15:18:27 -0500 Subject: [PATCH 075/170] Update CHANGELOG.md for v8.3.13 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d421f61fe..e9acaf0c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.13 (2023-09-06) +------------------------- + * Start writing ContactURN.auth_tokens + * Update to latest null library and use Map[string] for channel event extra + v8.3.12 (2023-09-06) ------------------------- * Do more debug logging and less info logging From da995d463ca557f92a8ab6f152320387f2306481 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 8 Sep 2023 14:03:08 -0500 Subject: [PATCH 076/170] Read from ContactURN.auth_tokens instead of .auth --- backends/rapidpro/backend_test.go | 8 ++++---- backends/rapidpro/urn.go | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 45960b3cc..44cff0d9f 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -334,14 +334,14 @@ func (ts *BackendTestSuite) TestContactURN() { contactURNs, err := contactURNsForContact(tx, contact.ID_) ts.NoError(err) - ts.Equal(null.String("chestnut"), contactURNs[0].Auth) + ts.Equal(null.Map[string]{"default": "chestnut"}, contactURNs[0].AuthTokens) // now build a URN for our number with the kannel channel knURN, err := contactURNForURN(tx, knChannel, contact.ID_, urn, "sesame") ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(knURN.OrgID, knChannel.OrgID_) - ts.Equal(null.String("sesame"), knURN.Auth) + ts.Equal(null.Map[string]{"default": "sesame"}, knURN.AuthTokens) tx, err = ts.b.db.Beginx() ts.NoError(err) @@ -361,7 +361,7 @@ func (ts *BackendTestSuite) TestContactURN() { ts.Equal(twURN.ChannelID, twChannel.ID()) // auth should be unchanged - ts.Equal(null.String("sesame"), twURN.Auth) + ts.Equal(null.Map[string]{"default": "sesame"}, twURN.AuthTokens) tx, err = ts.b.db.Beginx() ts.NoError(err) @@ -370,7 +370,7 @@ func (ts *BackendTestSuite) TestContactURN() { twURN, err = contactURNForURN(tx, twChannel, contact.ID_, urn, "peanut") ts.NoError(err) ts.NoError(tx.Commit()) - ts.Equal(null.String("peanut"), twURN.Auth) + ts.Equal(null.Map[string]{"default": "peanut"}, twURN.AuthTokens) // test that we don't use display when looking up URNs tgChannel := ts.getChannel("TG", "dbc126ed-66bc-4e28-b67b-81dc3327c98a") diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index b6d9b6df7..1f73f7ed6 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -35,7 +35,7 @@ func newDBContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID Scheme: urn.Scheme(), Path: urn.Path(), Display: null.String(urn.Display()), - Auth: null.String(auth), + Auth: null.String(auth), // TODO remove AuthTokens: null.Map[string]{"default": auth}, } } @@ -47,6 +47,7 @@ SELECT scheme, display, auth, + auth_tokens, priority, contact_id, channel_id @@ -104,7 +105,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns display := urn.Display() // if display, channel id or auth changed, update them - if string(contactURNs[0].Display) != display || contactURNs[0].ChannelID != channel.ID() || (auth != "" && string(contactURNs[0].Auth) != auth) { + if string(contactURNs[0].Display) != display || contactURNs[0].ChannelID != channel.ID() || (auth != "" && contactURNs[0].AuthTokens["default"] != auth) { contactURNs[0].Display = null.String(display) if channel.HasRole(courier.ChannelRoleSend) { @@ -112,7 +113,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns } if auth != "" { - contactURNs[0].Auth = null.String(auth) + contactURNs[0].Auth = null.String(auth) // TODO remove contactURNs[0].AuthTokens = null.Map[string]{"default": auth} } return updateContactURN(db, contactURNs[0]) @@ -135,7 +136,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns } if auth != "" { - existing.Auth = null.String(auth) + existing.Auth = null.String(auth) // TODO remove existing.AuthTokens = null.Map[string]{"default": auth} } } else { @@ -165,6 +166,7 @@ SELECT path, display, auth, + auth_tokens, priority, channel_id, contact_id @@ -226,8 +228,8 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn } // update our auth if we have a value set - if auth != "" && auth != string(contactURN.Auth) { - contactURN.Auth = null.String(auth) + if auth != "" && auth != contactURN.AuthTokens["default"] { + contactURN.Auth = null.String(auth) // TODO remove contactURN.AuthTokens = null.Map[string]{"default": auth} err = updateContactURN(db, contactURN) From c26ba25eb74c147f496399313e0e1e4c3ebc428e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 8 Sep 2023 14:17:10 -0500 Subject: [PATCH 077/170] Update to latest gocommon and use i18n.Locale --- backends/rapidpro/msg.go | 5 ++-- go.mod | 3 ++- go.sum | 6 +++-- handlers/dialog360/dialog360.go | 9 ++++--- handlers/dialog360/dialog360_test.go | 19 ++++++++------- handlers/facebookapp/facebookapp.go | 9 ++++--- handlers/facebookapp/facebookapp_test.go | 19 ++++++++------- handlers/test.go | 3 ++- handlers/whatsapp/whatsapp.go | 9 ++++--- handlers/whatsapp/whatsapp_test.go | 19 ++++++++------- msg.go | 31 +++--------------------- test/msg.go | 7 +++--- 12 files changed, 63 insertions(+), 76 deletions(-) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 8c17267ec..db5ac5e99 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -17,6 +17,7 @@ import ( "github.com/lib/pq" "github.com/nyaruka/courier" "github.com/nyaruka/courier/queue" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" @@ -336,7 +337,7 @@ func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } func (m *DBMsg) Text() string { return m.Text_ } func (m *DBMsg) Attachments() []string { return m.Attachments_ } func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } -func (m *DBMsg) Locale() courier.Locale { return courier.Locale(string(m.Locale_)) } +func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } func (m *DBMsg) URN() urns.URN { return m.URN_ } func (m *DBMsg) URNAuth() string { return m.URNAuth_ } @@ -408,7 +409,7 @@ func (m *DBMsg) WithAttachment(url string) courier.Msg { return m } -func (m *DBMsg) WithLocale(lc courier.Locale) courier.Msg { m.Locale_ = null.String(lc); return m } +func (m *DBMsg) WithLocale(lc i18n.Locale) courier.Msg { m.Locale_ = null.String(lc); return m } // WithURNAuth can be used to add a URN auth setting to a message func (m *DBMsg) WithURNAuth(auth string) courier.Msg { diff --git a/go.mod b/go.mod index 564d7da03..f01da50c0 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.40.0 + github.com/nyaruka/gocommon v1.41.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -46,6 +46,7 @@ require ( github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.0.0 // indirect + github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect diff --git a/go.sum b/go.sum index 0aa4c4d3e..6a187c876 100644 --- a/go.sum +++ b/go.sum @@ -70,10 +70,12 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.40.0 h1:PvwLljY8rirgUu1ROwZM+gkTdNRbOnrGBFBf70OZt/8= -github.com/nyaruka/gocommon v1.40.0/go.mod h1:J+1hsRQ7GPJKGbU9IUTKIvP74ONG3iiM5y5S9dfqSq4= +github.com/nyaruka/gocommon v1.41.1 h1:SpIXqLCBF3Un/AjzIiqC/DO4jU7Zt7SyDh/t9SyjIrQ= +github.com/nyaruka/gocommon v1.41.1/go.mod h1:cJ2XmEX+FDOzBvE19IW+hG8EFVsSrNgCp7NrxAlP4Xg= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= +github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= +github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index f66c94c69..313b63b90 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -15,6 +15,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -874,16 +875,16 @@ type MsgTemplating struct { Variables []string `json:"variables"` } -func getSupportedLanguage(lc courier.Locale) languageInfo { +func getSupportedLanguage(lc i18n.Locale) languageInfo { // look for exact match if lang := supportedLanguages[lc]; lang.code != "" { return lang } // if we have a country, strip that off and look again for a match - l, c := lc.ToParts() + l, c := lc.Split() if c != "" { - if lang := supportedLanguages[courier.Locale(l)]; lang.code != "" { + if lang := supportedLanguages[i18n.Locale(l)]; lang.code != "" { return lang } } @@ -897,7 +898,7 @@ type languageInfo struct { // Mapping from engine locales to supported languages. Note that these are not all valid BCP47 codes, e.g. fil // see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[courier.Locale]languageInfo{ +var supportedLanguages = map[i18n.Locale]languageInfo{ "afr": {code: "af", menu: "Kieslys"}, // Afrikaans "sqi": {code: "sq", menu: "Menu"}, // Albanian "ara": {code: "ar", menu: "قائمة"}, // Arabic diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index f0877305d..3c7c88dfd 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -14,6 +14,7 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) @@ -615,13 +616,13 @@ func TestOutgoing(t *testing.T) { RunOutgoingTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, nil) } func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.NilLocale)) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("eng"))) - assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(courier.Locale("eng-US"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por-PT"))) - assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(courier.Locale("por-BR"))) - assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(courier.Locale("fil"))) - assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(courier.Locale("fra-CA"))) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("run"))) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.NilLocale)) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("eng"))) + assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(i18n.Locale("eng-US"))) + assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por"))) + assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por-PT"))) + assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(i18n.Locale("por-BR"))) + assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(i18n.Locale("fil"))) + assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(i18n.Locale("fra-CA"))) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("run"))) } diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index e7328a13d..2def2902d 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -18,6 +18,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -1564,16 +1565,16 @@ type MsgTemplating struct { Variables []string `json:"variables"` } -func getSupportedLanguage(lc courier.Locale) languageInfo { +func getSupportedLanguage(lc i18n.Locale) languageInfo { // look for exact match if lang := supportedLanguages[lc]; lang.code != "" { return lang } // if we have a country, strip that off and look again for a match - l, c := lc.ToParts() + l, c := lc.Split() if c != "" { - if lang := supportedLanguages[courier.Locale(l)]; lang.code != "" { + if lang := supportedLanguages[i18n.Locale(l)]; lang.code != "" { return lang } } @@ -1587,7 +1588,7 @@ type languageInfo struct { // Mapping from engine locales to supported languages. Note that these are not all valid BCP47 codes, e.g. fil // see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[courier.Locale]languageInfo{ +var supportedLanguages = map[i18n.Locale]languageInfo{ "afr": {code: "af", menu: "Kieslys"}, // Afrikaans "sqi": {code: "sq", menu: "Menu"}, // Albanian "ara": {code: "ar", menu: "قائمة"}, // Arabic diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index c52c9cd2a..4e5b1ae3e 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -14,6 +14,7 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/stretchr/testify/assert" ) @@ -1603,13 +1604,13 @@ func TestBuildAttachmentRequest(t *testing.T) { } func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.NilLocale)) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("eng"))) - assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(courier.Locale("eng-US"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por-PT"))) - assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(courier.Locale("por-BR"))) - assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(courier.Locale("fil"))) - assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(courier.Locale("fra-CA"))) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("run"))) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.NilLocale)) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("eng"))) + assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(i18n.Locale("eng-US"))) + assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por"))) + assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por-PT"))) + assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(i18n.Locale("por-BR"))) + assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(i18n.Locale("fil"))) + assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(i18n.Locale("fra-CA"))) + assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("run"))) } diff --git a/handlers/test.go b/handlers/test.go index 1d9cb86c3..db1fe09d6 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -18,6 +18,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -264,7 +265,7 @@ type OutgoingTestCase struct { MsgURNAuth string MsgAttachments []string MsgQuickReplies []string - MsgLocale courier.Locale + MsgLocale i18n.Locale MsgTopic string MsgHighPriority bool MsgResponseToExternalID string diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 867651117..3c1981648 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -17,6 +17,7 @@ import ( "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" @@ -1147,16 +1148,16 @@ type MsgTemplating struct { Variables []string `json:"variables"` } -func getSupportedLanguage(lc courier.Locale) string { +func getSupportedLanguage(lc i18n.Locale) string { // look for exact match if lang := supportedLanguages[lc]; lang != "" { return lang } // if we have a country, strip that off and look again for a match - l, c := lc.ToParts() + l, c := lc.Split() if c != "" { - if lang := supportedLanguages[courier.Locale(l)]; lang != "" { + if lang := supportedLanguages[i18n.Locale(l)]; lang != "" { return lang } } @@ -1164,7 +1165,7 @@ func getSupportedLanguage(lc courier.Locale) string { } // Mapping from engine locales to supported languages, see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[courier.Locale]string{ +var supportedLanguages = map[i18n.Locale]string{ "afr": "af", // Afrikaans "sqi": "sq", // Albanian "ara": "ar", // Arabic diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp/whatsapp_test.go index 28aba6937..d05b010fb 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp/whatsapp_test.go @@ -13,6 +13,7 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) @@ -1148,13 +1149,13 @@ func TestOutgoing(t *testing.T) { } func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, "en", getSupportedLanguage(courier.NilLocale)) - assert.Equal(t, "en", getSupportedLanguage(courier.Locale("eng"))) - assert.Equal(t, "en_US", getSupportedLanguage(courier.Locale("eng-US"))) - assert.Equal(t, "pt_PT", getSupportedLanguage(courier.Locale("por"))) - assert.Equal(t, "pt_PT", getSupportedLanguage(courier.Locale("por-PT"))) - assert.Equal(t, "pt_BR", getSupportedLanguage(courier.Locale("por-BR"))) - assert.Equal(t, "fil", getSupportedLanguage(courier.Locale("fil"))) - assert.Equal(t, "fr", getSupportedLanguage(courier.Locale("fra-CA"))) - assert.Equal(t, "en", getSupportedLanguage(courier.Locale("run"))) + assert.Equal(t, "en", getSupportedLanguage(i18n.NilLocale)) + assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("eng"))) + assert.Equal(t, "en_US", getSupportedLanguage(i18n.Locale("eng-US"))) + assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por"))) + assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por-PT"))) + assert.Equal(t, "pt_BR", getSupportedLanguage(i18n.Locale("por-BR"))) + assert.Equal(t, "fil", getSupportedLanguage(i18n.Locale("fil"))) + assert.Equal(t, "fr", getSupportedLanguage(i18n.Locale("fra-CA"))) + assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("run"))) } diff --git a/msg.go b/msg.go index d694c8caa..7e19f35cd 100644 --- a/msg.go +++ b/msg.go @@ -4,9 +4,9 @@ import ( "database/sql/driver" "encoding/json" "strconv" - "strings" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" @@ -46,31 +46,6 @@ const ( MsgOriginChat MsgOrigin = "chat" ) -//----------------------------------------------------------------------------- -// Locale -//----------------------------------------------------------------------------- - -// Locale is the combination of a language and optional country, e.g. US English, Brazilian Portuguese, encoded as the -// language code followed by the country code, e.g. eng-US, por-BR -type Locale string - -func (l Locale) ToParts() (string, string) { - if l == NilLocale || len(l) < 3 { - return "", "" - } - - parts := strings.SplitN(string(l), "-", 2) - lang := parts[0] - country := "" - if len(parts) > 1 { - country = parts[1] - } - - return lang, country -} - -var NilLocale = Locale("") - //----------------------------------------------------------------------------- // Msg interface //----------------------------------------------------------------------------- @@ -81,7 +56,7 @@ type Msg interface { UUID() MsgUUID Text() string Attachments() []string - Locale() Locale + Locale() i18n.Locale ExternalID() string URN() urns.URN URNAuth() string @@ -110,7 +85,7 @@ type Msg interface { WithID(id MsgID) Msg WithUUID(uuid MsgUUID) Msg WithAttachment(url string) Msg - WithLocale(Locale) Msg + WithLocale(i18n.Locale) Msg WithURNAuth(auth string) Msg WithMetadata(metadata json.RawMessage) Msg WithFlow(flow *FlowReference) Msg diff --git a/test/msg.go b/test/msg.go index e5e7112b7..66ad6eea1 100644 --- a/test/msg.go +++ b/test/msg.go @@ -5,6 +5,7 @@ import ( "time" "github.com/nyaruka/courier" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" ) @@ -16,7 +17,7 @@ type mockMsg struct { urnAuth string text string attachments []string - locale courier.Locale + locale i18n.Locale externalID string contactName string highPriority bool @@ -69,7 +70,7 @@ func (m *mockMsg) EventID() int64 { return int64(m.id) } func (m *mockMsg) UUID() courier.MsgUUID { return m.uuid } func (m *mockMsg) Text() string { return m.text } func (m *mockMsg) Attachments() []string { return m.attachments } -func (m *mockMsg) Locale() courier.Locale { return m.locale } +func (m *mockMsg) Locale() i18n.Locale { return m.locale } func (m *mockMsg) ExternalID() string { return m.externalID } func (m *mockMsg) URN() urns.URN { return m.urn } func (m *mockMsg) URNAuth() string { return m.urnAuth } @@ -95,7 +96,7 @@ func (m *mockMsg) WithAttachment(url string) courier.Msg { m.attachments = append(m.attachments, url) return m } -func (m *mockMsg) WithLocale(lc courier.Locale) courier.Msg { m.locale = lc; return m } +func (m *mockMsg) WithLocale(lc i18n.Locale) courier.Msg { m.locale = lc; return m } func (m *mockMsg) WithMetadata(metadata json.RawMessage) courier.Msg { m.metadata = metadata; return m } func (m *mockMsg) WithFlow(flow *courier.FlowReference) courier.Msg { m.flow = flow; return m } From b422abff05687d5ca218d50e2dedcdf19789b19e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 8 Sep 2023 14:56:21 -0500 Subject: [PATCH 078/170] Move whatsapp language matching into own util package and use i18n.BCP47Matcher --- handlers/dialog360/dialog360.go | 110 ++--------------------- handlers/dialog360/dialog360_test.go | 12 --- handlers/facebookapp/facebookapp.go | 110 ++--------------------- handlers/facebookapp/facebookapp_test.go | 13 --- handlers/whatsapp/whatsapp.go | 97 +------------------- handlers/whatsapp/whatsapp_test.go | 13 --- utils/whatsapp/languages.go | 107 ++++++++++++++++++++++ utils/whatsapp/languages_test.go | 27 ++++++ 8 files changed, 148 insertions(+), 341 deletions(-) create mode 100644 utils/whatsapp/languages.go create mode 100644 utils/whatsapp/languages_test.go diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index 313b63b90..5a65ed44f 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -15,7 +15,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" - "github.com/nyaruka/gocommon/i18n" + "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -535,7 +535,8 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) } qrs := msg.QuickReplies() - lang := getSupportedLanguage(msg.Locale()) + lang := whatsapp.GetSupportedLanguage(msg.Locale()) + menuButton := whatsapp.GetMenuButton(lang) var payloadAudio wacMTPayload @@ -552,7 +553,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "template" - template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang.code}} + template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang}} payload.Template = &template component := &wacComponent{Type: "body"} @@ -614,7 +615,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann Button string "json:\"button,omitempty\"" Sections []wacMTSection "json:\"sections,omitempty\"" Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: lang.menu, Sections: []wacMTSection{ + }{Button: menuButton, Sections: []wacMTSection{ section, }} @@ -769,7 +770,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann Button string "json:\"button,omitempty\"" Sections []wacMTSection "json:\"sections,omitempty\"" Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: lang.menu, Sections: []wacMTSection{ + }{Button: menuButton, Sections: []wacMTSection{ section, }} @@ -874,102 +875,3 @@ type MsgTemplating struct { Namespace string `json:"namespace"` Variables []string `json:"variables"` } - -func getSupportedLanguage(lc i18n.Locale) languageInfo { - // look for exact match - if lang := supportedLanguages[lc]; lang.code != "" { - return lang - } - - // if we have a country, strip that off and look again for a match - l, c := lc.Split() - if c != "" { - if lang := supportedLanguages[i18n.Locale(l)]; lang.code != "" { - return lang - } - } - return supportedLanguages["eng"] // fallback to English -} - -type languageInfo struct { - code string - menu string // translation of "Menu" -} - -// Mapping from engine locales to supported languages. Note that these are not all valid BCP47 codes, e.g. fil -// see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[i18n.Locale]languageInfo{ - "afr": {code: "af", menu: "Kieslys"}, // Afrikaans - "sqi": {code: "sq", menu: "Menu"}, // Albanian - "ara": {code: "ar", menu: "قائمة"}, // Arabic - "aze": {code: "az", menu: "Menu"}, // Azerbaijani - "ben": {code: "bn", menu: "Menu"}, // Bengali - "bul": {code: "bg", menu: "Menu"}, // Bulgarian - "cat": {code: "ca", menu: "Menu"}, // Catalan - "zho": {code: "zh_CN", menu: "菜单"}, // Chinese - "zho-CN": {code: "zh_CN", menu: "菜单"}, // Chinese (CHN) - "zho-HK": {code: "zh_HK", menu: "菜单"}, // Chinese (HKG) - "zho-TW": {code: "zh_TW", menu: "菜单"}, // Chinese (TAI) - "hrv": {code: "hr", menu: "Menu"}, // Croatian - "ces": {code: "cs", menu: "Menu"}, // Czech - "dah": {code: "da", menu: "Menu"}, // Danish - "nld": {code: "nl", menu: "Menu"}, // Dutch - "eng": {code: "en", menu: "Menu"}, // English - "eng-GB": {code: "en_GB", menu: "Menu"}, // English (UK) - "eng-US": {code: "en_US", menu: "Menu"}, // English (US) - "est": {code: "et", menu: "Menu"}, // Estonian - "fil": {code: "fil", menu: "Menu"}, // Filipino - "fin": {code: "fi", menu: "Menu"}, // Finnish - "fra": {code: "fr", menu: "Menu"}, // French - "kat": {code: "ka", menu: "Menu"}, // Georgian - "deu": {code: "de", menu: "Menü"}, // German - "ell": {code: "el", menu: "Menu"}, // Greek - "guj": {code: "gu", menu: "Menu"}, // Gujarati - "hau": {code: "ha", menu: "Menu"}, // Hausa - "enb": {code: "he", menu: "תפריט"}, // Hebrew - "hin": {code: "hi", menu: "Menu"}, // Hindi - "hun": {code: "hu", menu: "Menu"}, // Hungarian - "ind": {code: "id", menu: "Menu"}, // Indonesian - "gle": {code: "ga", menu: "Roghchlár"}, // Irish - "ita": {code: "it", menu: "Menu"}, // Italian - "jpn": {code: "ja", menu: "Menu"}, // Japanese - "kan": {code: "kn", menu: "Menu"}, // Kannada - "kaz": {code: "kk", menu: "Menu"}, // Kazakh - "kin": {code: "rw_RW", menu: "Menu"}, // Kinyarwanda - "kor": {code: "ko", menu: "Menu"}, // Korean - "kir": {code: "ky_KG", menu: "Menu"}, // Kyrgyzstan - "lao": {code: "lo", menu: "Menu"}, // Lao - "lav": {code: "lv", menu: "Menu"}, // Latvian - "lit": {code: "lt", menu: "Menu"}, // Lithuanian - "mal": {code: "ml", menu: "Menu"}, // Malayalam - "mkd": {code: "mk", menu: "Menu"}, // Macedonian - "msa": {code: "ms", menu: "Menu"}, // Malay - "mar": {code: "mr", menu: "Menu"}, // Marathi - "nob": {code: "nb", menu: "Menu"}, // Norwegian - "fas": {code: "fa", menu: "Menu"}, // Persian - "pol": {code: "pl", menu: "Menu"}, // Polish - "por": {code: "pt_PT", menu: "Menu"}, // Portuguese - "por-BR": {code: "pt_BR", menu: "Menu"}, // Portuguese (BR) - "por-PT": {code: "pt_PT", menu: "Menu"}, // Portuguese (POR) - "pan": {code: "pa", menu: "Menu"}, // Punjabi - "ron": {code: "ro", menu: "Menu"}, // Romanian - "rus": {code: "ru", menu: "Menu"}, // Russian - "srp": {code: "sr", menu: "Menu"}, // Serbian - "slk": {code: "sk", menu: "Menu"}, // Slovak - "slv": {code: "sl", menu: "Menu"}, // Slovenian - "spa": {code: "es", menu: "Menú"}, // Spanish - "spa-AR": {code: "es_AR", menu: "Menú"}, // Spanish (ARG) - "spa-ES": {code: "es_ES", menu: "Menú"}, // Spanish (SPA) - "spa-MX": {code: "es_MX", menu: "Menú"}, // Spanish (MEX) - "swa": {code: "sw", menu: "Menyu"}, // Swahili - "swe": {code: "sv", menu: "Menu"}, // Swedish - "tam": {code: "ta", menu: "Menu"}, // Tamil - "tel": {code: "te", menu: "Menu"}, // Telugu - "tha": {code: "th", menu: "Menu"}, // Thai - "tur": {code: "tr", menu: "Menu"}, // Turkish - "ukr": {code: "uk", menu: "Menu"}, // Ukrainian - "urd": {code: "ur", menu: "Menu"}, // Urdu - "uzb": {code: "uz", menu: "Menu"}, // Uzbek - "vie": {code: "vi", menu: "Menu"}, // Vietnamese - "zul": {code: "zu", menu: "Menu"}, // Zulu -} diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index 3c7c88dfd..e807a7911 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -14,7 +14,6 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" - "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) @@ -615,14 +614,3 @@ func TestOutgoing(t *testing.T) { RunOutgoingTestCases(t, ChannelWAC, newWAHandler(courier.ChannelType("D3C"), "360Dialog"), SendTestCasesD3C, checkRedacted, nil) } -func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.NilLocale)) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("eng"))) - assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(i18n.Locale("eng-US"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por-PT"))) - assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(i18n.Locale("por-BR"))) - assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(i18n.Locale("fil"))) - assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(i18n.Locale("fra-CA"))) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("run"))) -} diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 2def2902d..b0a8ba089 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -18,7 +18,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" - "github.com/nyaruka/gocommon/i18n" + "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -1116,7 +1116,8 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) } qrs := msg.QuickReplies() - lang := getSupportedLanguage(msg.Locale()) + lang := whatsapp.GetSupportedLanguage(msg.Locale()) + menuButton := whatsapp.GetMenuButton(lang) var payloadAudio wacMTPayload @@ -1133,7 +1134,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "template" - template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang.code}} + template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang}} payload.Template = &template component := &wacComponent{Type: "body"} @@ -1195,7 +1196,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, Button string "json:\"button,omitempty\"" Sections []wacMTSection "json:\"sections,omitempty\"" Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: lang.menu, Sections: []wacMTSection{ + }{Button: menuButton, Sections: []wacMTSection{ section, }} @@ -1350,7 +1351,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, Button string "json:\"button,omitempty\"" Sections []wacMTSection "json:\"sections,omitempty\"" Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: lang.menu, Sections: []wacMTSection{ + }{Button: menuButton, Sections: []wacMTSection{ section, }} @@ -1564,102 +1565,3 @@ type MsgTemplating struct { Namespace string `json:"namespace"` Variables []string `json:"variables"` } - -func getSupportedLanguage(lc i18n.Locale) languageInfo { - // look for exact match - if lang := supportedLanguages[lc]; lang.code != "" { - return lang - } - - // if we have a country, strip that off and look again for a match - l, c := lc.Split() - if c != "" { - if lang := supportedLanguages[i18n.Locale(l)]; lang.code != "" { - return lang - } - } - return supportedLanguages["eng"] // fallback to English -} - -type languageInfo struct { - code string - menu string // translation of "Menu" -} - -// Mapping from engine locales to supported languages. Note that these are not all valid BCP47 codes, e.g. fil -// see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[i18n.Locale]languageInfo{ - "afr": {code: "af", menu: "Kieslys"}, // Afrikaans - "sqi": {code: "sq", menu: "Menu"}, // Albanian - "ara": {code: "ar", menu: "قائمة"}, // Arabic - "aze": {code: "az", menu: "Menu"}, // Azerbaijani - "ben": {code: "bn", menu: "Menu"}, // Bengali - "bul": {code: "bg", menu: "Menu"}, // Bulgarian - "cat": {code: "ca", menu: "Menu"}, // Catalan - "zho": {code: "zh_CN", menu: "菜单"}, // Chinese - "zho-CN": {code: "zh_CN", menu: "菜单"}, // Chinese (CHN) - "zho-HK": {code: "zh_HK", menu: "菜单"}, // Chinese (HKG) - "zho-TW": {code: "zh_TW", menu: "菜单"}, // Chinese (TAI) - "hrv": {code: "hr", menu: "Menu"}, // Croatian - "ces": {code: "cs", menu: "Menu"}, // Czech - "dah": {code: "da", menu: "Menu"}, // Danish - "nld": {code: "nl", menu: "Menu"}, // Dutch - "eng": {code: "en", menu: "Menu"}, // English - "eng-GB": {code: "en_GB", menu: "Menu"}, // English (UK) - "eng-US": {code: "en_US", menu: "Menu"}, // English (US) - "est": {code: "et", menu: "Menu"}, // Estonian - "fil": {code: "fil", menu: "Menu"}, // Filipino - "fin": {code: "fi", menu: "Menu"}, // Finnish - "fra": {code: "fr", menu: "Menu"}, // French - "kat": {code: "ka", menu: "Menu"}, // Georgian - "deu": {code: "de", menu: "Menü"}, // German - "ell": {code: "el", menu: "Menu"}, // Greek - "guj": {code: "gu", menu: "Menu"}, // Gujarati - "hau": {code: "ha", menu: "Menu"}, // Hausa - "enb": {code: "he", menu: "תפריט"}, // Hebrew - "hin": {code: "hi", menu: "Menu"}, // Hindi - "hun": {code: "hu", menu: "Menu"}, // Hungarian - "ind": {code: "id", menu: "Menu"}, // Indonesian - "gle": {code: "ga", menu: "Roghchlár"}, // Irish - "ita": {code: "it", menu: "Menu"}, // Italian - "jpn": {code: "ja", menu: "Menu"}, // Japanese - "kan": {code: "kn", menu: "Menu"}, // Kannada - "kaz": {code: "kk", menu: "Menu"}, // Kazakh - "kin": {code: "rw_RW", menu: "Menu"}, // Kinyarwanda - "kor": {code: "ko", menu: "Menu"}, // Korean - "kir": {code: "ky_KG", menu: "Menu"}, // Kyrgyzstan - "lao": {code: "lo", menu: "Menu"}, // Lao - "lav": {code: "lv", menu: "Menu"}, // Latvian - "lit": {code: "lt", menu: "Menu"}, // Lithuanian - "mal": {code: "ml", menu: "Menu"}, // Malayalam - "mkd": {code: "mk", menu: "Menu"}, // Macedonian - "msa": {code: "ms", menu: "Menu"}, // Malay - "mar": {code: "mr", menu: "Menu"}, // Marathi - "nob": {code: "nb", menu: "Menu"}, // Norwegian - "fas": {code: "fa", menu: "Menu"}, // Persian - "pol": {code: "pl", menu: "Menu"}, // Polish - "por": {code: "pt_PT", menu: "Menu"}, // Portuguese - "por-BR": {code: "pt_BR", menu: "Menu"}, // Portuguese (BR) - "por-PT": {code: "pt_PT", menu: "Menu"}, // Portuguese (POR) - "pan": {code: "pa", menu: "Menu"}, // Punjabi - "ron": {code: "ro", menu: "Menu"}, // Romanian - "rus": {code: "ru", menu: "Menu"}, // Russian - "srp": {code: "sr", menu: "Menu"}, // Serbian - "slk": {code: "sk", menu: "Menu"}, // Slovak - "slv": {code: "sl", menu: "Menu"}, // Slovenian - "spa": {code: "es", menu: "Menú"}, // Spanish - "spa-AR": {code: "es_AR", menu: "Menú"}, // Spanish (ARG) - "spa-ES": {code: "es_ES", menu: "Menú"}, // Spanish (SPA) - "spa-MX": {code: "es_MX", menu: "Menú"}, // Spanish (MEX) - "swa": {code: "sw", menu: "Menyu"}, // Swahili - "swe": {code: "sv", menu: "Menu"}, // Swedish - "tam": {code: "ta", menu: "Menu"}, // Tamil - "tel": {code: "te", menu: "Menu"}, // Telugu - "tha": {code: "th", menu: "Menu"}, // Thai - "tur": {code: "tr", menu: "Menu"}, // Turkish - "ukr": {code: "uk", menu: "Menu"}, // Ukrainian - "urd": {code: "ur", menu: "Menu"}, // Urdu - "uzb": {code: "uz", menu: "Menu"}, // Uzbek - "vie": {code: "vi", menu: "Menu"}, // Vietnamese - "zul": {code: "zu", menu: "Menu"}, // Zulu -} diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 4e5b1ae3e..863c37856 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -14,7 +14,6 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" - "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/stretchr/testify/assert" ) @@ -1602,15 +1601,3 @@ func TestBuildAttachmentRequest(t *testing.T) { assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) assert.Equal(t, http.Header{}, req.Header) } - -func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.NilLocale)) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("eng"))) - assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(i18n.Locale("eng-US"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(i18n.Locale("por-PT"))) - assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(i18n.Locale("por-BR"))) - assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(i18n.Locale("fil"))) - assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(i18n.Locale("fra-CA"))) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(i18n.Locale("run"))) -} diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 3c1981648..003369fcd 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -16,8 +16,8 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/httpx" - "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" @@ -563,7 +563,7 @@ func buildPayloads(msg courier.Msg, h *handler, clog *courier.ChannelLog) ([]any parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) qrs := msg.QuickReplies() - langCode := getSupportedLanguage(msg.Locale()) + langCode := whatsapp.GetSupportedLanguage(msg.Locale()) wppVersion := msg.Channel().ConfigForKey("version", "0").(string) isInteractiveMsgCompatible := semver.Compare(wppVersion, interactiveMsgMinSupVersion) isInteractiveMsg := (isInteractiveMsgCompatible >= 0) && (len(qrs) > 0) @@ -1147,96 +1147,3 @@ type MsgTemplating struct { Namespace string `json:"namespace"` Variables []string `json:"variables"` } - -func getSupportedLanguage(lc i18n.Locale) string { - // look for exact match - if lang := supportedLanguages[lc]; lang != "" { - return lang - } - - // if we have a country, strip that off and look again for a match - l, c := lc.Split() - if c != "" { - if lang := supportedLanguages[i18n.Locale(l)]; lang != "" { - return lang - } - } - return "en" // fallback to English -} - -// Mapping from engine locales to supported languages, see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = map[i18n.Locale]string{ - "afr": "af", // Afrikaans - "sqi": "sq", // Albanian - "ara": "ar", // Arabic - "aze": "az", // Azerbaijani - "ben": "bn", // Bengali - "bul": "bg", // Bulgarian - "cat": "ca", // Catalan - "zho": "zh_CN", // Chinese - "zho-CN": "zh_CN", // Chinese (CHN) - "zho-HK": "zh_HK", // Chinese (HKG) - "zho-TW": "zh_TW", // Chinese (TAI) - "hrv": "hr", // Croatian - "ces": "cs", // Czech - "dah": "da", // Danish - "nld": "nl", // Dutch - "eng": "en", // English - "eng-GB": "en_GB", // English (UK) - "eng-US": "en_US", // English (US) - "est": "et", // Estonian - "fil": "fil", // Filipino - "fin": "fi", // Finnish - "fra": "fr", // French - "kat": "ka", // Georgian - "deu": "de", // German - "ell": "el", // Greek - "guj": "gu", // Gujarati - "hau": "ha", // Hausa - "enb": "he", // Hebrew - "hin": "hi", // Hindi - "hun": "hu", // Hungarian - "ind": "id", // Indonesian - "gle": "ga", // Irish - "ita": "it", // Italian - "jpn": "ja", // Japanese - "kan": "kn", // Kannada - "kaz": "kk", // Kazakh - "kin": "rw_RW", // Kinyarwanda - "kor": "ko", // Korean - "kir": "ky_KG", // Kyrgyzstan - "lao": "lo", // Lao - "lav": "lv", // Latvian - "lit": "lt", // Lithuanian - "mal": "ml", // Malayalam - "mkd": "mk", // Macedonian - "msa": "ms", // Malay - "mar": "mr", // Marathi - "nob": "nb", // Norwegian - "fas": "fa", // Persian - "pol": "pl", // Polish - "por": "pt_PT", // Portuguese - "por-BR": "pt_BR", // Portuguese (BR) - "por-PT": "pt_PT", // Portuguese (POR) - "pan": "pa", // Punjabi - "ron": "ro", // Romanian - "rus": "ru", // Russian - "srp": "sr", // Serbian - "slk": "sk", // Slovak - "slv": "sl", // Slovenian - "spa": "es", // Spanish - "spa-AR": "es_AR", // Spanish (ARG) - "spa-ES": "es_ES", // Spanish (SPA) - "spa-MX": "es_MX", // Spanish (MEX) - "swa": "sw", // Swahili - "swe": "sv", // Swedish - "tam": "ta", // Tamil - "tel": "te", // Telugu - "tha": "th", // Thai - "tur": "tr", // Turkish - "ukr": "uk", // Ukrainian - "urd": "ur", // Urdu - "uzb": "uz", // Uzbek - "vie": "vi", // Vietnamese - "zul": "zu", // Zulu -} diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp/whatsapp_test.go index d05b010fb..4384b8691 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp/whatsapp_test.go @@ -13,7 +13,6 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" - "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) @@ -1147,15 +1146,3 @@ func TestOutgoing(t *testing.T) { RunOutgoingTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), mediaCacheSendTestCases, []string{"token123"}, nil) } - -func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, "en", getSupportedLanguage(i18n.NilLocale)) - assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("eng"))) - assert.Equal(t, "en_US", getSupportedLanguage(i18n.Locale("eng-US"))) - assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por"))) - assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por-PT"))) - assert.Equal(t, "pt_BR", getSupportedLanguage(i18n.Locale("por-BR"))) - assert.Equal(t, "fil", getSupportedLanguage(i18n.Locale("fil"))) - assert.Equal(t, "fr", getSupportedLanguage(i18n.Locale("fra-CA"))) - assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("run"))) -} diff --git a/utils/whatsapp/languages.go b/utils/whatsapp/languages.go new file mode 100644 index 000000000..978eaca2c --- /dev/null +++ b/utils/whatsapp/languages.go @@ -0,0 +1,107 @@ +package whatsapp + +import "github.com/nyaruka/gocommon/i18n" + +func GetSupportedLanguage(lc i18n.Locale) string { + if lc == i18n.NilLocale { + return "en" + } + return supportedLanguages.ForLocales(lc, "en") +} + +// see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ +var supportedLanguages = i18n.NewBCP47Matcher( + "af", // Afrikaans + "sq", // Albanian + "ar", // Arabic + "az", // Azerbaijani + "bn", // Bengali + "bg", // Bulgarian + "ca", // Catalan + "zh_CN", // Chinese (CHN) + "zh_HK", // Chinese (HKG) + "zh_TW", // Chinese (TAI) + "hr", // Croatian + "cs", // Czech + "da", // Danish + "nl", // Dutch + "en", // English + "en_GB", // English (UK) + "en_US", // English (US) + "et", // Estonian + "fil", // Filipino + "fi", // Finnish + "fr", // French + "ka", // Georgian + "de", // German + "el", // Greek + "gu", // Gujarati + "ha", // Hausa + "he", // Hebrew + "hi", // Hindi + "hu", // Hungarian + "id", // Indonesian + "ga", // Irish + "it", // Italian + "ja", // Japanese + "kn", // Kannada + "kk", // Kazakh + "rw_RW", // Kinyarwanda + "ko", // Korean + "ky_KG", // Kyrgyzstan + "lo", // Lao + "lv", // Latvian + "lt", // Lithuanian + "mk", // Macedonian + "ms", // Malay + "ml", // Malayalam + "mr", // Marathi + "nb", // Norwegian + "fa", // Persian + "pl", // Polish + "pt_BR", // Portuguese (BR) + "pt_PT", // Portuguese (POR) + "pa", // Punjabi + "ro", // Romanian + "ru", // Russian + "sr", // Serbian + "sk", // Slovak + "sl", // Slovenian + "es", // Spanish + "es_AR", // Spanish (ARG) + "es_ES", // Spanish (SPA) + "es_MX", // Spanish (MEX) + "sw", // Swahili + "sv", // Swedish + "ta", // Tamil + "te", // Telugu + "th", // Thai + "tr", // Turkish + "uk", // Ukrainian + "ur", // Urdu + "uz", // Uzbek + "vi", // Vietnamese + "zu", // Zulu +) + +func GetMenuButton(lang string) string { + if trans := menuTranslations[lang]; trans != "" { + return trans + } + return "Menu" +} + +var menuTranslations = map[string]string{ + "af": "Kieslys", + "ar": "قائمة", + "zh_CN": "菜单", + "zh_HK": "菜单", + "zh_TW": "菜单", + "he": "תפריט", + "ga": "Roghchlár", + "es": "Menú", + "es_AR": "Menú", + "es_ES": "Menú", + "es_MX": "Menú", + "sw": "Menyu", +} diff --git a/utils/whatsapp/languages_test.go b/utils/whatsapp/languages_test.go new file mode 100644 index 000000000..38d496785 --- /dev/null +++ b/utils/whatsapp/languages_test.go @@ -0,0 +1,27 @@ +package whatsapp_test + +import ( + "testing" + + "github.com/nyaruka/courier/utils/whatsapp" + "github.com/nyaruka/gocommon/i18n" + "gopkg.in/go-playground/assert.v1" +) + +func TestGetSupportedLanguage(t *testing.T) { + assert.Equal(t, "en", whatsapp.GetSupportedLanguage(i18n.NilLocale)) + assert.Equal(t, "en", whatsapp.GetSupportedLanguage("eng")) + assert.Equal(t, "en_US", whatsapp.GetSupportedLanguage("eng-US")) + assert.Equal(t, "pt_BR", whatsapp.GetSupportedLanguage("por")) + assert.Equal(t, "pt_PT", whatsapp.GetSupportedLanguage("por-PT")) + assert.Equal(t, "pt_BR", whatsapp.GetSupportedLanguage("por-BR")) + assert.Equal(t, "fil", whatsapp.GetSupportedLanguage("fil")) + assert.Equal(t, "fr", whatsapp.GetSupportedLanguage("fra-CA")) + assert.Equal(t, "en", whatsapp.GetSupportedLanguage("run")) +} + +func TestGetMenuButton(t *testing.T) { + assert.Equal(t, "Menu", whatsapp.GetMenuButton("en")) + assert.Equal(t, "Menú", whatsapp.GetMenuButton("es_MX")) + assert.Equal(t, "Menyu", whatsapp.GetMenuButton("sw")) +} From 04e38a0255e2ba9ed8190e7f123573c8a9bc1a76 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 11 Sep 2023 15:39:29 -0500 Subject: [PATCH 079/170] Update CHANGELOG.md for v8.3.14 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9acaf0c2..7737061e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.14 (2023-09-11) +------------------------- + * Move whatsapp language matching into own util package and use i18n.BCP47Matcher + * Update to latest gocommon and use i18n.Locale + * Read from ContactURN.auth_tokens instead of .auth + v8.3.13 (2023-09-06) ------------------------- * Start writing ContactURN.auth_tokens From fdaf9226cbb32d82eb6fd1b3ff8140ee3cefa19b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 11 Sep 2023 19:36:13 -0500 Subject: [PATCH 080/170] Stop reading from ContactURN.auth and remove from model --- backends/rapidpro/schema.sql | 1 - backends/rapidpro/urn.go | 20 +++++--------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/backends/rapidpro/schema.sql b/backends/rapidpro/schema.sql index 5f83dbe27..94bc18257 100644 --- a/backends/rapidpro/schema.sql +++ b/backends/rapidpro/schema.sql @@ -52,7 +52,6 @@ CREATE TABLE contacts_contacturn ( channel_id integer references channels_channel(id) on delete cascade, contact_id integer references contacts_contact(id) on delete cascade, org_id integer NOT NULL references orgs_org(id) on delete cascade, - auth text, auth_tokens jsonb, UNIQUE (org_id, identity) ); diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 1f73f7ed6..38e43b1a3 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -5,12 +5,11 @@ import ( "database/sql/driver" "fmt" - "github.com/nyaruka/null/v3" - "github.com/pkg/errors" - "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" "github.com/nyaruka/gocommon/urns" + "github.com/nyaruka/null/v3" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -35,7 +34,6 @@ func newDBContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID Scheme: urn.Scheme(), Path: urn.Path(), Display: null.String(urn.Display()), - Auth: null.String(auth), // TODO remove AuthTokens: null.Map[string]{"default": auth}, } } @@ -45,8 +43,7 @@ SELECT id, identity, scheme, - display, - auth, + display, auth_tokens, priority, contact_id, @@ -113,7 +110,6 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns } if auth != "" { - contactURNs[0].Auth = null.String(auth) // TODO remove contactURNs[0].AuthTokens = null.Map[string]{"default": auth} } return updateContactURN(db, contactURNs[0]) @@ -136,7 +132,6 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns } if auth != "" { - existing.Auth = null.String(auth) // TODO remove existing.AuthTokens = null.Map[string]{"default": auth} } } else { @@ -165,7 +160,6 @@ SELECT scheme, path, display, - auth, auth_tokens, priority, channel_id, @@ -229,7 +223,6 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn // update our auth if we have a value set if auth != "" && auth != contactURN.AuthTokens["default"] { - contactURN.Auth = null.String(auth) // TODO remove contactURN.AuthTokens = null.Map[string]{"default": auth} err = updateContactURN(db, contactURN) @@ -240,8 +233,8 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn const insertURN = ` INSERT INTO - contacts_contacturn(org_id, identity, path, scheme, display, auth, auth_tokens, priority, channel_id, contact_id) - VALUES(:org_id, :identity, :path, :scheme, :display, :auth, :auth_tokens, :priority, :channel_id, :contact_id) + contacts_contacturn(org_id, identity, path, scheme, display, auth_tokens, priority, channel_id, contact_id) + VALUES(:org_id, :identity, :path, :scheme, :display, :auth_tokens, :priority, :channel_id, :contact_id) RETURNING id ` @@ -266,7 +259,6 @@ SET channel_id = :channel_id, contact_id = :contact_id, display = :display, - auth = :auth, auth_tokens = :auth_tokens, priority = :priority WHERE @@ -281,7 +273,6 @@ SET identity = :identity, path = :path, display = :display, - auth = :auth, auth_tokens = :auth_tokens, priority = :priority WHERE @@ -326,7 +317,6 @@ type DBContactURN struct { Scheme string `db:"scheme"` Path string `db:"path"` Display null.String `db:"display"` - Auth null.String `db:"auth"` AuthTokens null.Map[string] `db:"auth_tokens"` Priority int `db:"priority"` ChannelID courier.ChannelID `db:"channel_id"` From 414d56ee008d43a11d5a4c2d0b1900fb3e36babe Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Tue, 12 Sep 2023 15:49:38 +0200 Subject: [PATCH 081/170] Move reused WAC structs in utils whatsapp package for DRY codes --- handlers/dialog360/dialog360.go | 363 +++++------------------- handlers/facebookapp/facebookapp.go | 411 +++++----------------------- handlers/whatsapp/whatsapp.go | 16 +- utils/whatsapp/structs.go | 273 ++++++++++++++++++ utils/whatsapp/variables.go | 15 + 5 files changed, 420 insertions(+), 658 deletions(-) create mode 100644 utils/whatsapp/structs.go create mode 100644 utils/whatsapp/variables.go diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index 5a65ed44f..0d96724d0 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -48,26 +48,6 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -var waStatusMapping = map[string]courier.MsgStatus{ - "sent": courier.MsgStatusSent, - "delivered": courier.MsgStatusDelivered, - "read": courier.MsgStatusDelivered, - "failed": courier.MsgStatusFailed, -} - -var waIgnoreStatuses = map[string]bool{ - "deleted": true, -} - -type Sender struct { - ID string `json:"id"` - UserRef string `json:"user_ref,omitempty"` -} - -type User struct { - ID string `json:"id"` -} - // { // "object":"page", // "entry":[{ @@ -86,110 +66,8 @@ type User struct { // }] // } -type wacMedia struct { - Caption string `json:"caption"` - Filename string `json:"filename"` - ID string `json:"id"` - Mimetype string `json:"mime_type"` - SHA256 string `json:"sha256"` -} -type moPayload struct { - Object string `json:"object"` - Entry []struct { - ID string `json:"id"` - Time int64 `json:"time"` - Changes []struct { - Field string `json:"field"` - Value struct { - MessagingProduct string `json:"messaging_product"` - Metadata *struct { - DisplayPhoneNumber string `json:"display_phone_number"` - PhoneNumberID string `json:"phone_number_id"` - } `json:"metadata"` - Contacts []struct { - Profile struct { - Name string `json:"name"` - } `json:"profile"` - WaID string `json:"wa_id"` - } `json:"contacts"` - Messages []struct { - ID string `json:"id"` - From string `json:"from"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Context *struct { - Forwarded bool `json:"forwarded"` - FrequentlyForwarded bool `json:"frequently_forwarded"` - From string `json:"from"` - ID string `json:"id"` - } `json:"context"` - Text struct { - Body string `json:"body"` - } `json:"text"` - Image *wacMedia `json:"image"` - Audio *wacMedia `json:"audio"` - Video *wacMedia `json:"video"` - Document *wacMedia `json:"document"` - Voice *wacMedia `json:"voice"` - Location *struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Name string `json:"name"` - Address string `json:"address"` - } `json:"location"` - Button *struct { - Text string `json:"text"` - Payload string `json:"payload"` - } `json:"button"` - Interactive struct { - Type string `json:"type"` - ButtonReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"button_reply,omitempty"` - ListReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"list_reply,omitempty"` - } `json:"interactive,omitempty"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"messages"` - Statuses []struct { - ID string `json:"id"` - RecipientID string `json:"recipient_id"` - Status string `json:"status"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Conversation *struct { - ID string `json:"id"` - Origin *struct { - Type string `json:"type"` - } `json:"origin"` - } `json:"conversation"` - Pricing *struct { - PricingModel string `json:"pricing_model"` - Billable bool `json:"billable"` - Category string `json:"category"` - } `json:"pricing"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"statuses"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"value"` - } `json:"changes"` - } `json:"entry"` -} - // receiveEvent is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *moPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.WACMOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { // is not a 'whatsapp_business_account' object? ignore it if payload.Object != "whatsapp_business_account" { @@ -212,7 +90,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -313,9 +191,9 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri for _, status := range change.Value.Statuses { - msgStatus, found := waStatusMapping[status.Status] + msgStatus, found := whatsapp.WACStatusMapping[status.Status] if !found { - if waIgnoreStatuses[status.Status] { + if whatsapp.WACIgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, fmt.Sprintf("unknown status: %s", status.Status)) @@ -403,110 +281,6 @@ func resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.Chan return fileURL, nil } -type wacMTMedia struct { - ID string `json:"id,omitempty"` - Link string `json:"link,omitempty"` - Caption string `json:"caption,omitempty"` - Filename string `json:"filename,omitempty"` -} - -type wacMTSection struct { - Title string `json:"title,omitempty"` - Rows []wacMTSectionRow `json:"rows" validate:"required"` -} - -type wacMTSectionRow struct { - ID string `json:"id" validate:"required"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` -} - -type wacMTButton struct { - Type string `json:"type" validate:"required"` - Reply struct { - ID string `json:"id" validate:"required"` - Title string `json:"title" validate:"required"` - } `json:"reply" validate:"required"` -} - -type wacParam struct { - Type string `json:"type"` - Text string `json:"text"` -} - -type wacComponent struct { - Type string `json:"type"` - SubType string `json:"sub_type"` - Index string `json:"index"` - Params []*wacParam `json:"parameters"` -} - -type wacText struct { - Body string `json:"body"` - PreviewURL bool `json:"preview_url"` -} - -type wacLanguage struct { - Policy string `json:"policy"` - Code string `json:"code"` -} - -type wacTemplate struct { - Name string `json:"name"` - Language *wacLanguage `json:"language"` - Components []*wacComponent `json:"components"` -} - -type wacInteractive struct { - Type string `json:"type"` - Header *struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - Video *wacMTMedia `json:"video,omitempty"` - Image *wacMTMedia `json:"image,omitempty"` - Document *wacMTMedia `json:"document,omitempty"` - } `json:"header,omitempty"` - Body struct { - Text string `json:"text"` - } `json:"body" validate:"required"` - Footer *struct { - Text string `json:"text"` - } `json:"footer,omitempty"` - Action *struct { - Button string `json:"button,omitempty"` - Sections []wacMTSection `json:"sections,omitempty"` - Buttons []wacMTButton `json:"buttons,omitempty"` - } `json:"action,omitempty"` -} - -type wacMTPayload struct { - MessagingProduct string `json:"messaging_product"` - RecipientType string `json:"recipient_type"` - To string `json:"to"` - Type string `json:"type"` - - Text *wacText `json:"text,omitempty"` - - Document *wacMTMedia `json:"document,omitempty"` - Image *wacMTMedia `json:"image,omitempty"` - Audio *wacMTMedia `json:"audio,omitempty"` - Video *wacMTMedia `json:"video,omitempty"` - - Interactive *wacInteractive `json:"interactive,omitempty"` - - Template *wacTemplate `json:"template,omitempty"` -} - -type wacMTResponse struct { - Messages []*struct { - ID string `json:"id"` - } `json:"messages"` - Error struct { - Message string `json:"message"` - Code int `json:"code"` - } `json:"error"` -} - // Send implements courier.ChannelHandler func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { conn := h.Backend().RedisPool().Get() @@ -538,10 +312,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio wacMTPayload + var payloadAudio whatsapp.WACMTPayload for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? @@ -553,20 +327,20 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "template" - template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang}} + template := whatsapp.WACTemplate{Name: templating.Template.Name, Language: &whatsapp.WACLanguage{Policy: "deterministic", Code: lang}} payload.Template = &template - component := &wacComponent{Type: "body"} + component := &whatsapp.WACComponent{Type: "body"} for _, v := range templating.Variables { - component.Params = append(component.Params, &wacParam{Type: "text", Text: v}) + component.Params = append(component.Params, &whatsapp.WACParam{Type: "text", Text: v}) } template.Components = append(payload.Template.Components, component) } else { if i < (len(msgParts) + len(msg.Attachments()) - 1) { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -578,44 +352,44 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := wacInteractive{Type: "button", Body: struct { + interactive := whatsapp.WACInteractive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - btns := make([]wacMTButton, len(qrs)) + btns := make([]whatsapp.WACMTButton, len(qrs)) for i, qr := range qrs { - btns[i] = wacMTButton{ + btns[i] = whatsapp.WACMTButton{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := wacInteractive{Type: "list", Body: struct { + interactive := whatsapp.WACInteractive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := wacMTSection{ - Rows: make([]wacMTSectionRow, len(qrs)), + section := whatsapp.WACMTSection{ + Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = wacMTSectionRow{ + section.Rows[i] = whatsapp.WACMTSectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []wacMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.WACMTSection{ section, }} @@ -625,7 +399,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } else { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -643,7 +417,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } payload.Type = attType - media := wacMTMedia{Link: attURL} + media := whatsapp.WACMTMedia{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -671,7 +445,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := wacInteractive{Type: "button", Body: struct { + interactive := whatsapp.WACInteractive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i]}} @@ -683,49 +457,49 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } if attType == "image" { - image := wacMTMedia{ + image := whatsapp.WACMTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := wacMTMedia{ + video := whatsapp.WACMTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := wacMTMedia{ + document := whatsapp.WACMTMedia{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: attURL}} + payloadAudio = whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.WACMTMedia{Link: attURL}} status, err := requestD3C(payloadAudio, accessToken, status, sendURL, zeroIndex, clog) if err != nil { return status, nil @@ -736,41 +510,41 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - btns := make([]wacMTButton, len(qrs)) + btns := make([]whatsapp.WACMTButton, len(qrs)) for i, qr := range qrs { - btns[i] = wacMTButton{ + btns[i] = whatsapp.WACMTButton{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := wacInteractive{Type: "list", Body: struct { + interactive := whatsapp.WACInteractive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := wacMTSection{ - Rows: make([]wacMTSectionRow, len(qrs)), + section := whatsapp.WACMTSection{ + Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = wacMTSectionRow{ + section.Rows[i] = whatsapp.WACMTSectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []wacMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.WACMTSection{ section, }} @@ -780,7 +554,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } else { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -807,7 +581,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func requestD3C(payload wacMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestD3C(payload whatsapp.WACMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -823,7 +597,7 @@ func requestD3C(payload wacMTPayload, accessToken string, status courier.StatusU req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &wacMTResponse{} + respPayload := &whatsapp.WACMTResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -844,13 +618,13 @@ func requestD3C(payload wacMTPayload, accessToken string, status courier.StatusU return status, nil } -func (h *handler) getTemplating(msg courier.Msg) (*MsgTemplating, error) { +func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.WACMsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } metadata := &struct { - Templating *MsgTemplating `json:"templating"` + Templating *whatsapp.WACMsgTemplating `json:"templating"` }{} if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { return nil, err @@ -866,12 +640,3 @@ func (h *handler) getTemplating(msg courier.Msg) (*MsgTemplating, error) { return metadata.Templating, nil } - -type MsgTemplating struct { - Template struct { - Name string `json:"name" validate:"required"` - UUID string `json:"uuid" validate:"required"` - } `json:"template" validate:"required,dive"` - Namespace string `json:"namespace"` - Variables []string `json:"variables"` -} diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index b0a8ba089..5b47fe349 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -60,17 +60,6 @@ const ( payloadKey = "payload" ) -var waStatusMapping = map[string]courier.MsgStatus{ - "sent": courier.MsgStatusSent, - "delivered": courier.MsgStatusDelivered, - "read": courier.MsgStatusDelivered, - "failed": courier.MsgStatusFailed, -} - -var waIgnoreStatuses = map[string]bool{ - "deleted": true, -} - func newHandler(channelType courier.ChannelType, name string, useUUIDRoutes bool) courier.ChannelHandler { return &handler{handlers.NewBaseHandlerWithParams(channelType, name, useUUIDRoutes, []string{courier.ConfigAuthToken})} } @@ -94,15 +83,6 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -type Sender struct { - ID string `json:"id"` - UserRef string `json:"user_ref,omitempty"` -} - -type User struct { - ID string `json:"id"` -} - // { // "object":"page", // "entry":[{ @@ -128,152 +108,6 @@ type wacMedia struct { Mimetype string `json:"mime_type"` SHA256 string `json:"sha256"` } -type moPayload struct { - Object string `json:"object"` - Entry []struct { - ID string `json:"id"` - Time int64 `json:"time"` - Changes []struct { - Field string `json:"field"` - Value struct { - MessagingProduct string `json:"messaging_product"` - Metadata *struct { - DisplayPhoneNumber string `json:"display_phone_number"` - PhoneNumberID string `json:"phone_number_id"` - } `json:"metadata"` - Contacts []struct { - Profile struct { - Name string `json:"name"` - } `json:"profile"` - WaID string `json:"wa_id"` - } `json:"contacts"` - Messages []struct { - ID string `json:"id"` - From string `json:"from"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Context *struct { - Forwarded bool `json:"forwarded"` - FrequentlyForwarded bool `json:"frequently_forwarded"` - From string `json:"from"` - ID string `json:"id"` - } `json:"context"` - Text struct { - Body string `json:"body"` - } `json:"text"` - Image *wacMedia `json:"image"` - Audio *wacMedia `json:"audio"` - Video *wacMedia `json:"video"` - Document *wacMedia `json:"document"` - Voice *wacMedia `json:"voice"` - Location *struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Name string `json:"name"` - Address string `json:"address"` - } `json:"location"` - Button *struct { - Text string `json:"text"` - Payload string `json:"payload"` - } `json:"button"` - Interactive struct { - Type string `json:"type"` - ButtonReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"button_reply,omitempty"` - ListReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"list_reply,omitempty"` - } `json:"interactive,omitempty"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"messages"` - Statuses []struct { - ID string `json:"id"` - RecipientID string `json:"recipient_id"` - Status string `json:"status"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Conversation *struct { - ID string `json:"id"` - Origin *struct { - Type string `json:"type"` - } `json:"origin"` - } `json:"conversation"` - Pricing *struct { - PricingModel string `json:"pricing_model"` - Billable bool `json:"billable"` - Category string `json:"category"` - } `json:"pricing"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"statuses"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"value"` - } `json:"changes"` - Messaging []struct { - Sender Sender `json:"sender"` - Recipient User `json:"recipient"` - Timestamp int64 `json:"timestamp"` - - OptIn *struct { - Ref string `json:"ref"` - UserRef string `json:"user_ref"` - } `json:"optin"` - - Referral *struct { - Ref string `json:"ref"` - Source string `json:"source"` - Type string `json:"type"` - AdID string `json:"ad_id"` - } `json:"referral"` - - Postback *struct { - MID string `json:"mid"` - Title string `json:"title"` - Payload string `json:"payload"` - Referral struct { - Ref string `json:"ref"` - Source string `json:"source"` - Type string `json:"type"` - AdID string `json:"ad_id"` - } `json:"referral"` - } `json:"postback"` - - Message *struct { - IsEcho bool `json:"is_echo"` - MID string `json:"mid"` - Text string `json:"text"` - IsDeleted bool `json:"is_deleted"` - Attachments []struct { - Type string `json:"type"` - Payload *struct { - URL string `json:"url"` - StickerID int64 `json:"sticker_id"` - Coordinates *struct { - Lat float64 `json:"lat"` - Long float64 `json:"long"` - } `json:"coordinates"` - } - } `json:"attachments"` - } `json:"message"` - - Delivery *struct { - MIDs []string `json:"mids"` - Watermark int64 `json:"watermark"` - } `json:"delivery"` - } `json:"messaging"` - } `json:"entry"` -} func (h *handler) RedactValues(ch courier.Channel) []string { vals := h.BaseHandler.RedactValues(ch) @@ -292,7 +126,7 @@ func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Chan return nil, nil } - payload := &moPayload{} + payload := &whatsapp.WACMOPayload{} err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, err @@ -373,7 +207,7 @@ func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (st } // receiveEvents is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *moPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.WACMOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -406,7 +240,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -509,9 +343,9 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri for _, status := range change.Value.Statuses { - msgStatus, found := waStatusMapping[status.Status] + msgStatus, found := whatsapp.WACStatusMapping[status.Status] if !found { - if waIgnoreStatuses[status.Status] { + if whatsapp.WACIgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status: %s", status.Status)) @@ -544,7 +378,7 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri return events, data, nil } -func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *moPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { var err error // the list of events we deal with @@ -995,110 +829,6 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, return status, nil } -type wacMTMedia struct { - ID string `json:"id,omitempty"` - Link string `json:"link,omitempty"` - Caption string `json:"caption,omitempty"` - Filename string `json:"filename,omitempty"` -} - -type wacMTSection struct { - Title string `json:"title,omitempty"` - Rows []wacMTSectionRow `json:"rows" validate:"required"` -} - -type wacMTSectionRow struct { - ID string `json:"id" validate:"required"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` -} - -type wacMTButton struct { - Type string `json:"type" validate:"required"` - Reply struct { - ID string `json:"id" validate:"required"` - Title string `json:"title" validate:"required"` - } `json:"reply" validate:"required"` -} - -type wacParam struct { - Type string `json:"type"` - Text string `json:"text"` -} - -type wacComponent struct { - Type string `json:"type"` - SubType string `json:"sub_type"` - Index string `json:"index"` - Params []*wacParam `json:"parameters"` -} - -type wacText struct { - Body string `json:"body"` - PreviewURL bool `json:"preview_url"` -} - -type wacLanguage struct { - Policy string `json:"policy"` - Code string `json:"code"` -} - -type wacTemplate struct { - Name string `json:"name"` - Language *wacLanguage `json:"language"` - Components []*wacComponent `json:"components"` -} - -type wacInteractive struct { - Type string `json:"type"` - Header *struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - Video *wacMTMedia `json:"video,omitempty"` - Image *wacMTMedia `json:"image,omitempty"` - Document *wacMTMedia `json:"document,omitempty"` - } `json:"header,omitempty"` - Body struct { - Text string `json:"text"` - } `json:"body" validate:"required"` - Footer *struct { - Text string `json:"text"` - } `json:"footer,omitempty"` - Action *struct { - Button string `json:"button,omitempty"` - Sections []wacMTSection `json:"sections,omitempty"` - Buttons []wacMTButton `json:"buttons,omitempty"` - } `json:"action,omitempty"` -} - -type wacMTPayload struct { - MessagingProduct string `json:"messaging_product"` - RecipientType string `json:"recipient_type"` - To string `json:"to"` - Type string `json:"type"` - - Text *wacText `json:"text,omitempty"` - - Document *wacMTMedia `json:"document,omitempty"` - Image *wacMTMedia `json:"image,omitempty"` - Audio *wacMTMedia `json:"audio,omitempty"` - Video *wacMTMedia `json:"video,omitempty"` - - Interactive *wacInteractive `json:"interactive,omitempty"` - - Template *wacTemplate `json:"template,omitempty"` -} - -type wacMTResponse struct { - Messages []*struct { - ID string `json:"id"` - } `json:"messages"` - Error struct { - Message string `json:"message"` - Code int `json:"code"` - } `json:"error"` -} - func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := h.Server().Config().WhatsappAdminSystemUserToken @@ -1119,10 +849,10 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio wacMTPayload + var payloadAudio whatsapp.WACMTPayload for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? @@ -1134,20 +864,20 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "template" - template := wacTemplate{Name: templating.Template.Name, Language: &wacLanguage{Policy: "deterministic", Code: lang}} + template := whatsapp.WACTemplate{Name: templating.Template.Name, Language: &whatsapp.WACLanguage{Policy: "deterministic", Code: lang}} payload.Template = &template - component := &wacComponent{Type: "body"} + component := &whatsapp.WACComponent{Type: "body"} for _, v := range templating.Variables { - component.Params = append(component.Params, &wacParam{Type: "text", Text: v}) + component.Params = append(component.Params, &whatsapp.WACParam{Type: "text", Text: v}) } template.Components = append(payload.Template.Components, component) } else { if i < (len(msgParts) + len(msg.Attachments()) - 1) { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -1159,44 +889,44 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := wacInteractive{Type: "button", Body: struct { + interactive := whatsapp.WACInteractive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - btns := make([]wacMTButton, len(qrs)) + btns := make([]whatsapp.WACMTButton, len(qrs)) for i, qr := range qrs { - btns[i] = wacMTButton{ + btns[i] = whatsapp.WACMTButton{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := wacInteractive{Type: "list", Body: struct { + interactive := whatsapp.WACInteractive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := wacMTSection{ - Rows: make([]wacMTSectionRow, len(qrs)), + section := whatsapp.WACMTSection{ + Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = wacMTSectionRow{ + section.Rows[i] = whatsapp.WACMTSectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []wacMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.WACMTSection{ section, }} @@ -1206,7 +936,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } else { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -1224,7 +954,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } payload.Type = attType - media := wacMTMedia{Link: attURL} + media := whatsapp.WACMTMedia{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -1252,7 +982,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := wacInteractive{Type: "button", Body: struct { + interactive := whatsapp.WACInteractive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i]}} @@ -1264,49 +994,49 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } if attType == "image" { - image := wacMTMedia{ + image := whatsapp.WACMTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := wacMTMedia{ + video := whatsapp.WACMTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := wacMTMedia{ + document := whatsapp.WACMTMedia{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *wacMTMedia "json:\"video,omitempty\"" - Image *wacMTMedia "json:\"image,omitempty\"" - Document *wacMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" + Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" + Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: attURL}} + payloadAudio = whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.WACMTMedia{Link: attURL}} status, err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil @@ -1317,41 +1047,41 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } - btns := make([]wacMTButton, len(qrs)) + btns := make([]whatsapp.WACMTButton, len(qrs)) for i, qr := range qrs { - btns[i] = wacMTButton{ + btns[i] = whatsapp.WACMTButton{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := wacInteractive{Type: "list", Body: struct { + interactive := whatsapp.WACInteractive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := wacMTSection{ - Rows: make([]wacMTSectionRow, len(qrs)), + section := whatsapp.WACMTSection{ + Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = wacMTSectionRow{ + section.Rows[i] = whatsapp.WACMTSectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []wacMTSection "json:\"sections,omitempty\"" - Buttons []wacMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []wacMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" + Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.WACMTSection{ section, }} @@ -1361,7 +1091,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } else { // this is still a msg part - text := &wacText{PreviewURL: false} + text := &whatsapp.WACText{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -1388,7 +1118,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, return status, nil } -func requestWAC(payload wacMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestWAC(payload whatsapp.WACMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -1404,7 +1134,7 @@ func requestWAC(payload wacMTPayload, accessToken string, status courier.StatusU req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &wacMTResponse{} + respPayload := &whatsapp.WACMTResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -1516,13 +1246,13 @@ func fbCalculateSignature(appSecret string, body []byte) (string, error) { return hex.EncodeToString(mac.Sum(nil)), nil } -func (h *handler) getTemplating(msg courier.Msg) (*MsgTemplating, error) { +func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.WACMsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } metadata := &struct { - Templating *MsgTemplating `json:"templating"` + Templating *whatsapp.WACMsgTemplating `json:"templating"` }{} if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { return nil, err @@ -1556,12 +1286,3 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, } var _ courier.AttachmentRequestBuilder = (*handler)(nil) - -type MsgTemplating struct { - Template struct { - Name string `json:"name" validate:"required"` - UUID string `json:"uuid" validate:"required"` - } `json:"template" validate:"required,dive"` - Namespace string `json:"namespace"` - Variables []string `json:"variables"` -} diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 003369fcd..46c9ef18f 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -260,9 +260,9 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w // now with any status updates for _, status := range payload.Statuses { - msgStatus, found := waStatusMapping[status.Status] + msgStatus, found := whatsapp.WACStatusMapping[status.Status] if !found { - if waIgnoreStatuses[status.Status] { + if whatsapp.WACIgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status: %s", status.Status)) @@ -319,18 +319,6 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, var _ courier.AttachmentRequestBuilder = (*handler)(nil) -var waStatusMapping = map[string]courier.MsgStatus{ - "sending": courier.MsgStatusWired, - "sent": courier.MsgStatusSent, - "delivered": courier.MsgStatusDelivered, - "read": courier.MsgStatusDelivered, - "failed": courier.MsgStatusFailed, -} - -var waIgnoreStatuses = map[string]bool{ - "deleted": true, -} - // { // "to": "16315555555", // "type": "text | audio | document | image", diff --git a/utils/whatsapp/structs.go b/utils/whatsapp/structs.go new file mode 100644 index 000000000..5790d526f --- /dev/null +++ b/utils/whatsapp/structs.go @@ -0,0 +1,273 @@ +package whatsapp + +type WACMsgTemplating struct { + Template struct { + Name string `json:"name" validate:"required"` + UUID string `json:"uuid" validate:"required"` + } `json:"template" validate:"required,dive"` + Namespace string `json:"namespace"` + Variables []string `json:"variables"` +} + +type WACMOMedia struct { + Caption string `json:"caption"` + Filename string `json:"filename"` + ID string `json:"id"` + Mimetype string `json:"mime_type"` + SHA256 string `json:"sha256"` +} +type WACMOPayload struct { + Object string `json:"object"` + Entry []struct { + ID string `json:"id"` + Time int64 `json:"time"` + Changes []struct { + Field string `json:"field"` + Value struct { + MessagingProduct string `json:"messaging_product"` + Metadata *struct { + DisplayPhoneNumber string `json:"display_phone_number"` + PhoneNumberID string `json:"phone_number_id"` + } `json:"metadata"` + Contacts []struct { + Profile struct { + Name string `json:"name"` + } `json:"profile"` + WaID string `json:"wa_id"` + } `json:"contacts"` + Messages []struct { + ID string `json:"id"` + From string `json:"from"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` + Context *struct { + Forwarded bool `json:"forwarded"` + FrequentlyForwarded bool `json:"frequently_forwarded"` + From string `json:"from"` + ID string `json:"id"` + } `json:"context"` + Text struct { + Body string `json:"body"` + } `json:"text"` + Image *WACMOMedia `json:"image"` + Audio *WACMOMedia `json:"audio"` + Video *WACMOMedia `json:"video"` + Document *WACMOMedia `json:"document"` + Voice *WACMOMedia `json:"voice"` + Location *struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Name string `json:"name"` + Address string `json:"address"` + } `json:"location"` + Button *struct { + Text string `json:"text"` + Payload string `json:"payload"` + } `json:"button"` + Interactive struct { + Type string `json:"type"` + ButtonReply struct { + ID string `json:"id"` + Title string `json:"title"` + } `json:"button_reply,omitempty"` + ListReply struct { + ID string `json:"id"` + Title string `json:"title"` + } `json:"list_reply,omitempty"` + } `json:"interactive,omitempty"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"messages"` + Statuses []struct { + ID string `json:"id"` + RecipientID string `json:"recipient_id"` + Status string `json:"status"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` + Conversation *struct { + ID string `json:"id"` + Origin *struct { + Type string `json:"type"` + } `json:"origin"` + } `json:"conversation"` + Pricing *struct { + PricingModel string `json:"pricing_model"` + Billable bool `json:"billable"` + Category string `json:"category"` + } `json:"pricing"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"statuses"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"value"` + } `json:"changes"` + Messaging []struct { + Sender *struct { + ID string `json:"id"` + UserRef string `json:"user_ref,omitempty"` + } `json:"sender"` + Recipient *struct { + ID string `json:"id"` + } `json:"recipient"` + Timestamp int64 `json:"timestamp"` + + OptIn *struct { + Ref string `json:"ref"` + UserRef string `json:"user_ref"` + } `json:"optin"` + + Referral *struct { + Ref string `json:"ref"` + Source string `json:"source"` + Type string `json:"type"` + AdID string `json:"ad_id"` + } `json:"referral"` + + Postback *struct { + MID string `json:"mid"` + Title string `json:"title"` + Payload string `json:"payload"` + Referral struct { + Ref string `json:"ref"` + Source string `json:"source"` + Type string `json:"type"` + AdID string `json:"ad_id"` + } `json:"referral"` + } `json:"postback"` + + Message *struct { + IsEcho bool `json:"is_echo"` + MID string `json:"mid"` + Text string `json:"text"` + IsDeleted bool `json:"is_deleted"` + Attachments []struct { + Type string `json:"type"` + Payload *struct { + URL string `json:"url"` + StickerID int64 `json:"sticker_id"` + Coordinates *struct { + Lat float64 `json:"lat"` + Long float64 `json:"long"` + } `json:"coordinates"` + } + } `json:"attachments"` + } `json:"message"` + + Delivery *struct { + MIDs []string `json:"mids"` + Watermark int64 `json:"watermark"` + } `json:"delivery"` + } `json:"messaging"` + } `json:"entry"` +} + +type WACMTMedia struct { + ID string `json:"id,omitempty"` + Link string `json:"link,omitempty"` + Caption string `json:"caption,omitempty"` + Filename string `json:"filename,omitempty"` +} + +type WACMTSection struct { + Title string `json:"title,omitempty"` + Rows []WACMTSectionRow `json:"rows" validate:"required"` +} + +type WACMTSectionRow struct { + ID string `json:"id" validate:"required"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` +} + +type WACMTButton struct { + Type string `json:"type" validate:"required"` + Reply struct { + ID string `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + } `json:"reply" validate:"required"` +} + +type WACParam struct { + Type string `json:"type"` + Text string `json:"text"` +} + +type WACComponent struct { + Type string `json:"type"` + SubType string `json:"sub_type"` + Index string `json:"index"` + Params []*WACParam `json:"parameters"` +} + +type WACText struct { + Body string `json:"body"` + PreviewURL bool `json:"preview_url"` +} + +type WACLanguage struct { + Policy string `json:"policy"` + Code string `json:"code"` +} + +type WACTemplate struct { + Name string `json:"name"` + Language *WACLanguage `json:"language"` + Components []*WACComponent `json:"components"` +} + +type WACInteractive struct { + Type string `json:"type"` + Header *struct { + Type string `json:"type"` + Text string `json:"text,omitempty"` + Video *WACMTMedia `json:"video,omitempty"` + Image *WACMTMedia `json:"image,omitempty"` + Document *WACMTMedia `json:"document,omitempty"` + } `json:"header,omitempty"` + Body struct { + Text string `json:"text"` + } `json:"body" validate:"required"` + Footer *struct { + Text string `json:"text"` + } `json:"footer,omitempty"` + Action *struct { + Button string `json:"button,omitempty"` + Sections []WACMTSection `json:"sections,omitempty"` + Buttons []WACMTButton `json:"buttons,omitempty"` + } `json:"action,omitempty"` +} + +type WACMTPayload struct { + MessagingProduct string `json:"messaging_product"` + RecipientType string `json:"recipient_type"` + To string `json:"to"` + Type string `json:"type"` + + Text *WACText `json:"text,omitempty"` + + Document *WACMTMedia `json:"document,omitempty"` + Image *WACMTMedia `json:"image,omitempty"` + Audio *WACMTMedia `json:"audio,omitempty"` + Video *WACMTMedia `json:"video,omitempty"` + + Interactive *WACInteractive `json:"interactive,omitempty"` + + Template *WACTemplate `json:"template,omitempty"` +} + +type WACMTResponse struct { + Messages []*struct { + ID string `json:"id"` + } `json:"messages"` + Error struct { + Message string `json:"message"` + Code int `json:"code"` + } `json:"error"` +} diff --git a/utils/whatsapp/variables.go b/utils/whatsapp/variables.go new file mode 100644 index 000000000..af1857ebd --- /dev/null +++ b/utils/whatsapp/variables.go @@ -0,0 +1,15 @@ +package whatsapp + +import "github.com/nyaruka/courier" + +var WACStatusMapping = map[string]courier.MsgStatus{ + "sending": courier.MsgStatusWired, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, +} + +var WACIgnoreStatuses = map[string]bool{ + "deleted": true, +} From 4e218279719a0e8723c7f011dc0a36ef15b69a25 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 12 Sep 2023 09:31:19 -0500 Subject: [PATCH 082/170] Update CHANGELOG.md for v8.3.15 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7737061e9..b37378710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.15 (2023-09-12) +------------------------- + * Stop reading from ContactURN.auth and remove from model + v8.3.14 (2023-09-11) ------------------------- * Move whatsapp language matching into own util package and use i18n.BCP47Matcher From a13a424ad95a3f942f5afa9a5f5ab4548e2e4eac Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Tue, 12 Sep 2023 16:43:50 +0200 Subject: [PATCH 083/170] Remove WAC prefixes, rename file to api.go --- handlers/dialog360/dialog360.go | 128 ++++++++++++------------- handlers/facebookapp/facebookapp.go | 132 +++++++++++++------------- handlers/whatsapp/whatsapp.go | 16 +++- utils/whatsapp/{structs.go => api.go} | 114 +++++++++++++--------- utils/whatsapp/variables.go | 15 --- 5 files changed, 214 insertions(+), 191 deletions(-) rename utils/whatsapp/{structs.go => api.go} (65%) delete mode 100644 utils/whatsapp/variables.go diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/dialog360.go index 0d96724d0..977887ecd 100644 --- a/handlers/dialog360/dialog360.go +++ b/handlers/dialog360/dialog360.go @@ -67,7 +67,7 @@ func (h *handler) Initialize(s courier.Server) error { // } // receiveEvent is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.WACMOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.MOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { // is not a 'whatsapp_business_account' object? ignore it if payload.Object != "whatsapp_business_account" { @@ -90,7 +90,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -191,9 +191,9 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri for _, status := range change.Value.Statuses { - msgStatus, found := whatsapp.WACStatusMapping[status.Status] + msgStatus, found := whatsapp.StatusMapping[status.Status] if !found { - if whatsapp.WACIgnoreStatuses[status.Status] { + if whatsapp.IgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, fmt.Sprintf("unknown status: %s", status.Status)) @@ -312,10 +312,10 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio whatsapp.WACMTPayload + var payloadAudio whatsapp.MTPayload for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? @@ -327,20 +327,20 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "template" - template := whatsapp.WACTemplate{Name: templating.Template.Name, Language: &whatsapp.WACLanguage{Policy: "deterministic", Code: lang}} + template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: lang}} payload.Template = &template - component := &whatsapp.WACComponent{Type: "body"} + component := &whatsapp.Component{Type: "body"} for _, v := range templating.Variables { - component.Params = append(component.Params, &whatsapp.WACParam{Type: "text", Text: v}) + component.Params = append(component.Params, &whatsapp.Param{Type: "text", Text: v}) } template.Components = append(payload.Template.Components, component) } else { if i < (len(msgParts) + len(msg.Attachments()) - 1) { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -352,44 +352,44 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := whatsapp.WACInteractive{Type: "button", Body: struct { + interactive := whatsapp.Interactive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - btns := make([]whatsapp.WACMTButton, len(qrs)) + btns := make([]whatsapp.Button, len(qrs)) for i, qr := range qrs { - btns[i] = whatsapp.WACMTButton{ + btns[i] = whatsapp.Button{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := whatsapp.WACInteractive{Type: "list", Body: struct { + interactive := whatsapp.Interactive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := whatsapp.WACMTSection{ - Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), + section := whatsapp.Section{ + Rows: make([]whatsapp.SectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = whatsapp.WACMTSectionRow{ + section.Rows[i] = whatsapp.SectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []whatsapp.WACMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.Section{ section, }} @@ -399,7 +399,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } else { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -417,7 +417,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } payload.Type = attType - media := whatsapp.WACMTMedia{Link: attURL} + media := whatsapp.MTMedia{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -445,7 +445,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := whatsapp.WACInteractive{Type: "button", Body: struct { + interactive := whatsapp.Interactive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i]}} @@ -457,49 +457,49 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } if attType == "image" { - image := whatsapp.WACMTMedia{ + image := whatsapp.MTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := whatsapp.WACMTMedia{ + video := whatsapp.MTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := whatsapp.WACMTMedia{ + document := whatsapp.MTMedia{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.WACMTMedia{Link: attURL}} + payloadAudio = whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.MTMedia{Link: attURL}} status, err := requestD3C(payloadAudio, accessToken, status, sendURL, zeroIndex, clog) if err != nil { return status, nil @@ -510,41 +510,41 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } - btns := make([]whatsapp.WACMTButton, len(qrs)) + btns := make([]whatsapp.Button, len(qrs)) for i, qr := range qrs { - btns[i] = whatsapp.WACMTButton{ + btns[i] = whatsapp.Button{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := whatsapp.WACInteractive{Type: "list", Body: struct { + interactive := whatsapp.Interactive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := whatsapp.WACMTSection{ - Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), + section := whatsapp.Section{ + Rows: make([]whatsapp.SectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = whatsapp.WACMTSectionRow{ + section.Rows[i] = whatsapp.SectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []whatsapp.WACMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.Section{ section, }} @@ -554,7 +554,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } else { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -581,7 +581,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func requestD3C(payload whatsapp.WACMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestD3C(payload whatsapp.MTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -597,7 +597,7 @@ func requestD3C(payload whatsapp.WACMTPayload, accessToken string, status courie req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &whatsapp.WACMTResponse{} + respPayload := &whatsapp.MTResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -618,13 +618,13 @@ func requestD3C(payload whatsapp.WACMTPayload, accessToken string, status courie return status, nil } -func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.WACMsgTemplating, error) { +func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.MsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } metadata := &struct { - Templating *whatsapp.WACMsgTemplating `json:"templating"` + Templating *whatsapp.MsgTemplating `json:"templating"` }{} if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { return nil, err diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 5b47fe349..b013b5346 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -126,7 +126,7 @@ func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Chan return nil, nil } - payload := &whatsapp.WACMOPayload{} + payload := &whatsapp.MOPayload{} err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, err @@ -207,7 +207,7 @@ func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (st } // receiveEvents is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.WACMOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.MOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -240,7 +240,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -343,9 +343,9 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri for _, status := range change.Value.Statuses { - msgStatus, found := whatsapp.WACStatusMapping[status.Status] + msgStatus, found := whatsapp.StatusMapping[status.Status] if !found { - if whatsapp.WACIgnoreStatuses[status.Status] { + if whatsapp.IgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status: %s", status.Status)) @@ -378,7 +378,7 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri return events, data, nil } -func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.WACMOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { var err error // the list of events we deal with @@ -849,10 +849,10 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio whatsapp.WACMTPayload + var payloadAudio whatsapp.MTPayload for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? @@ -864,20 +864,20 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "template" - template := whatsapp.WACTemplate{Name: templating.Template.Name, Language: &whatsapp.WACLanguage{Policy: "deterministic", Code: lang}} + template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: lang}} payload.Template = &template - component := &whatsapp.WACComponent{Type: "body"} + component := &whatsapp.Component{Type: "body"} for _, v := range templating.Variables { - component.Params = append(component.Params, &whatsapp.WACParam{Type: "text", Text: v}) + component.Params = append(component.Params, &whatsapp.Param{Type: "text", Text: v}) } template.Components = append(payload.Template.Components, component) } else { if i < (len(msgParts) + len(msg.Attachments()) - 1) { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -889,44 +889,44 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := whatsapp.WACInteractive{Type: "button", Body: struct { + interactive := whatsapp.Interactive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - btns := make([]whatsapp.WACMTButton, len(qrs)) + btns := make([]whatsapp.Button, len(qrs)) for i, qr := range qrs { - btns[i] = whatsapp.WACMTButton{ + btns[i] = whatsapp.Button{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := whatsapp.WACInteractive{Type: "list", Body: struct { + interactive := whatsapp.Interactive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := whatsapp.WACMTSection{ - Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), + section := whatsapp.Section{ + Rows: make([]whatsapp.SectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = whatsapp.WACMTSectionRow{ + section.Rows[i] = whatsapp.SectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []whatsapp.WACMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.Section{ section, }} @@ -936,7 +936,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } else { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -954,7 +954,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } payload.Type = attType - media := whatsapp.WACMTMedia{Link: attURL} + media := whatsapp.MTMedia{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -982,7 +982,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, payload.Type = "interactive" // We can use buttons if len(qrs) <= 3 { - interactive := whatsapp.WACInteractive{Type: "button", Body: struct { + interactive := whatsapp.Interactive{Type: "button", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i]}} @@ -994,49 +994,49 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } if attType == "image" { - image := whatsapp.WACMTMedia{ + image := whatsapp.MTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := whatsapp.WACMTMedia{ + video := whatsapp.MTMedia{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := whatsapp.WACMTMedia{ + document := whatsapp.MTMedia{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.WACMTMedia "json:\"video,omitempty\"" - Image *whatsapp.WACMTMedia "json:\"image,omitempty\"" - Document *whatsapp.WACMTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.MTMedia "json:\"video,omitempty\"" + Image *whatsapp.MTMedia "json:\"image,omitempty\"" + Document *whatsapp.MTMedia "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = whatsapp.WACMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.WACMTMedia{Link: attURL}} + payloadAudio = whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.MTMedia{Link: attURL}} status, err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil @@ -1047,41 +1047,41 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } - btns := make([]whatsapp.WACMTButton, len(qrs)) + btns := make([]whatsapp.Button, len(qrs)) for i, qr := range qrs { - btns[i] = whatsapp.WACMTButton{ + btns[i] = whatsapp.Button{ Type: "reply", } btns[i].Reply.ID = fmt.Sprint(i) btns[i].Reply.Title = qr } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" }{Buttons: btns} payload.Interactive = &interactive } else if len(qrs) <= 10 { - interactive := whatsapp.WACInteractive{Type: "list", Body: struct { + interactive := whatsapp.Interactive{Type: "list", Body: struct { Text string "json:\"text\"" }{Text: msgParts[i-len(msg.Attachments())]}} - section := whatsapp.WACMTSection{ - Rows: make([]whatsapp.WACMTSectionRow, len(qrs)), + section := whatsapp.Section{ + Rows: make([]whatsapp.SectionRow, len(qrs)), } for i, qr := range qrs { - section.Rows[i] = whatsapp.WACMTSectionRow{ + section.Rows[i] = whatsapp.SectionRow{ ID: fmt.Sprint(i), Title: qr, } } interactive.Action = &struct { - Button string "json:\"button,omitempty\"" - Sections []whatsapp.WACMTSection "json:\"sections,omitempty\"" - Buttons []whatsapp.WACMTButton "json:\"buttons,omitempty\"" - }{Button: menuButton, Sections: []whatsapp.WACMTSection{ + Button string "json:\"button,omitempty\"" + Sections []whatsapp.Section "json:\"sections,omitempty\"" + Buttons []whatsapp.Button "json:\"buttons,omitempty\"" + }{Button: menuButton, Sections: []whatsapp.Section{ section, }} @@ -1091,7 +1091,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, } } else { // this is still a msg part - text := &whatsapp.WACText{PreviewURL: false} + text := &whatsapp.Text{PreviewURL: false} payload.Type = "text" if strings.Contains(msgParts[i-len(msg.Attachments())], "https://") || strings.Contains(msgParts[i-len(msg.Attachments())], "http://") { text.PreviewURL = true @@ -1118,7 +1118,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, return status, nil } -func requestWAC(payload whatsapp.WACMTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestWAC(payload whatsapp.MTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -1134,7 +1134,7 @@ func requestWAC(payload whatsapp.WACMTPayload, accessToken string, status courie req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &whatsapp.WACMTResponse{} + respPayload := &whatsapp.MTResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -1246,13 +1246,13 @@ func fbCalculateSignature(appSecret string, body []byte) (string, error) { return hex.EncodeToString(mac.Sum(nil)), nil } -func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.WACMsgTemplating, error) { +func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.MsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } metadata := &struct { - Templating *whatsapp.WACMsgTemplating `json:"templating"` + Templating *whatsapp.MsgTemplating `json:"templating"` }{} if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { return nil, err diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp/whatsapp.go index 46c9ef18f..003369fcd 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp/whatsapp.go @@ -260,9 +260,9 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w // now with any status updates for _, status := range payload.Statuses { - msgStatus, found := whatsapp.WACStatusMapping[status.Status] + msgStatus, found := waStatusMapping[status.Status] if !found { - if whatsapp.WACIgnoreStatuses[status.Status] { + if waIgnoreStatuses[status.Status] { data = append(data, courier.NewInfoData(fmt.Sprintf("ignoring status: %s", status.Status))) } else { handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown status: %s", status.Status)) @@ -319,6 +319,18 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, var _ courier.AttachmentRequestBuilder = (*handler)(nil) +var waStatusMapping = map[string]courier.MsgStatus{ + "sending": courier.MsgStatusWired, + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, +} + +var waIgnoreStatuses = map[string]bool{ + "deleted": true, +} + // { // "to": "16315555555", // "type": "text | audio | document | image", diff --git a/utils/whatsapp/structs.go b/utils/whatsapp/api.go similarity index 65% rename from utils/whatsapp/structs.go rename to utils/whatsapp/api.go index 5790d526f..08d78c3b7 100644 --- a/utils/whatsapp/structs.go +++ b/utils/whatsapp/api.go @@ -1,6 +1,20 @@ package whatsapp -type WACMsgTemplating struct { +import "github.com/nyaruka/courier" + +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#message-status-updates +var StatusMapping = map[string]courier.MsgStatus{ + "sent": courier.MsgStatusSent, + "delivered": courier.MsgStatusDelivered, + "read": courier.MsgStatusDelivered, + "failed": courier.MsgStatusFailed, +} + +var IgnoreStatuses = map[string]bool{ + "deleted": true, +} + +type MsgTemplating struct { Template struct { Name string `json:"name" validate:"required"` UUID string `json:"uuid" validate:"required"` @@ -9,14 +23,17 @@ type WACMsgTemplating struct { Variables []string `json:"variables"` } -type WACMOMedia struct { +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#example-2 +type MOMedia struct { Caption string `json:"caption"` Filename string `json:"filename"` ID string `json:"id"` Mimetype string `json:"mime_type"` SHA256 string `json:"sha256"` } -type WACMOPayload struct { + +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#notification-payload-object +type MOPayload struct { Object string `json:"object"` Entry []struct { ID string `json:"id"` @@ -49,11 +66,11 @@ type WACMOPayload struct { Text struct { Body string `json:"body"` } `json:"text"` - Image *WACMOMedia `json:"image"` - Audio *WACMOMedia `json:"audio"` - Video *WACMOMedia `json:"video"` - Document *WACMOMedia `json:"document"` - Voice *WACMOMedia `json:"voice"` + Image *MOMedia `json:"image"` + Audio *MOMedia `json:"audio"` + Video *MOMedia `json:"video"` + Document *MOMedia `json:"document"` + Voice *MOMedia `json:"voice"` Location *struct { Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` @@ -168,25 +185,26 @@ type WACMOPayload struct { } `json:"entry"` } -type WACMTMedia struct { +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#media-messages +type MTMedia struct { ID string `json:"id,omitempty"` Link string `json:"link,omitempty"` Caption string `json:"caption,omitempty"` Filename string `json:"filename,omitempty"` } -type WACMTSection struct { - Title string `json:"title,omitempty"` - Rows []WACMTSectionRow `json:"rows" validate:"required"` +type Section struct { + Title string `json:"title,omitempty"` + Rows []SectionRow `json:"rows" validate:"required"` } -type WACMTSectionRow struct { +type SectionRow struct { ID string `json:"id" validate:"required"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` } -type WACMTButton struct { +type Button struct { Type string `json:"type" validate:"required"` Reply struct { ID string `json:"id" validate:"required"` @@ -194,42 +212,46 @@ type WACMTButton struct { } `json:"reply" validate:"required"` } -type WACParam struct { +type Param struct { Type string `json:"type"` Text string `json:"text"` } -type WACComponent struct { - Type string `json:"type"` - SubType string `json:"sub_type"` - Index string `json:"index"` - Params []*WACParam `json:"parameters"` +type Component struct { + Type string `json:"type"` + SubType string `json:"sub_type"` + Index string `json:"index"` + Params []*Param `json:"parameters"` } -type WACText struct { +type Text struct { Body string `json:"body"` PreviewURL bool `json:"preview_url"` } -type WACLanguage struct { +type Language struct { Policy string `json:"policy"` Code string `json:"code"` } -type WACTemplate struct { - Name string `json:"name"` - Language *WACLanguage `json:"language"` - Components []*WACComponent `json:"components"` +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-object +// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-messages +type Template struct { + Name string `json:"name"` + Language *Language `json:"language"` + Components []*Component `json:"components"` } -type WACInteractive struct { +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-object +// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-messages +type Interactive struct { Type string `json:"type"` Header *struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - Video *WACMTMedia `json:"video,omitempty"` - Image *WACMTMedia `json:"image,omitempty"` - Document *WACMTMedia `json:"document,omitempty"` + Type string `json:"type"` + Text string `json:"text,omitempty"` + Video *MTMedia `json:"video,omitempty"` + Image *MTMedia `json:"image,omitempty"` + Document *MTMedia `json:"document,omitempty"` } `json:"header,omitempty"` Body struct { Text string `json:"text"` @@ -238,31 +260,35 @@ type WACInteractive struct { Text string `json:"text"` } `json:"footer,omitempty"` Action *struct { - Button string `json:"button,omitempty"` - Sections []WACMTSection `json:"sections,omitempty"` - Buttons []WACMTButton `json:"buttons,omitempty"` + Button string `json:"button,omitempty"` + Sections []Section `json:"sections,omitempty"` + Buttons []Button `json:"buttons,omitempty"` } `json:"action,omitempty"` } -type WACMTPayload struct { +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#request-syntax +// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#message-object +type MTPayload struct { MessagingProduct string `json:"messaging_product"` RecipientType string `json:"recipient_type"` To string `json:"to"` Type string `json:"type"` - Text *WACText `json:"text,omitempty"` + Text *Text `json:"text,omitempty"` - Document *WACMTMedia `json:"document,omitempty"` - Image *WACMTMedia `json:"image,omitempty"` - Audio *WACMTMedia `json:"audio,omitempty"` - Video *WACMTMedia `json:"video,omitempty"` + Document *MTMedia `json:"document,omitempty"` + Image *MTMedia `json:"image,omitempty"` + Audio *MTMedia `json:"audio,omitempty"` + Video *MTMedia `json:"video,omitempty"` - Interactive *WACInteractive `json:"interactive,omitempty"` + Interactive *Interactive `json:"interactive,omitempty"` - Template *WACTemplate `json:"template,omitempty"` + Template *Template `json:"template,omitempty"` } -type WACMTResponse struct { +// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#response-syntax +// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#successful-response +type MTResponse struct { Messages []*struct { ID string `json:"id"` } `json:"messages"` diff --git a/utils/whatsapp/variables.go b/utils/whatsapp/variables.go deleted file mode 100644 index af1857ebd..000000000 --- a/utils/whatsapp/variables.go +++ /dev/null @@ -1,15 +0,0 @@ -package whatsapp - -import "github.com/nyaruka/courier" - -var WACStatusMapping = map[string]courier.MsgStatus{ - "sending": courier.MsgStatusWired, - "sent": courier.MsgStatusSent, - "delivered": courier.MsgStatusDelivered, - "read": courier.MsgStatusDelivered, - "failed": courier.MsgStatusFailed, -} - -var WACIgnoreStatuses = map[string]bool{ - "deleted": true, -} From c70d4e8e5a42d85822f774f6844b01350a1e6b24 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 12 Sep 2023 10:12:28 -0500 Subject: [PATCH 084/170] Bit of tidying up of ContactURN --- backends/rapidpro/backend.go | 6 +- backends/rapidpro/backend_test.go | 38 +++--- backends/rapidpro/contact.go | 2 +- backends/rapidpro/urn.go | 184 +++++++++++------------------- 4 files changed, 91 insertions(+), 139 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index b6a939da1..0055f5e8a 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -334,7 +334,7 @@ func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contac } dbChannel := c.(*DBChannel) dbContact := contact.(*DBContact) - _, err = contactURNForURN(tx, dbChannel, dbContact.ID_, urn, "") + _, err = getOrCreateContactURN(tx, dbChannel, dbContact.ID_, urn, "") if err != nil { return urns.NilURN, err } @@ -586,12 +586,12 @@ func (b *backend) updateContactURN(ctx context.Context, status courier.StatusUpd return err } // retrieve the old URN - oldContactURN, err := selectContactURN(tx, dbChannel.OrgID(), old) + oldContactURN, err := getContactURNByIdentity(tx, dbChannel.OrgID(), old) if err != nil { return errors.Wrap(err, "error retrieving old contact URN") } // retrieve the new URN - newContactURN, err := selectContactURN(tx, dbChannel.OrgID(), new) + newContactURN, err := getContactURNByIdentity(tx, dbChannel.OrgID(), new) if err != nil { // only update the old URN path if the new URN doesn't exist if err == sql.ErrNoRows { diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 44cff0d9f..281899f5d 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -286,7 +286,7 @@ func (ts *BackendTestSuite) TestAddAndRemoveContactURN() { tx, err := ts.b.db.Beginx() ts.NoError(err) - contactURNs, err := contactURNsForContact(tx, contact.ID_) + contactURNs, err := getURNsForContact(tx, contact.ID_) ts.NoError(err) ts.Equal(len(contactURNs), 1) @@ -298,7 +298,7 @@ func (ts *BackendTestSuite) TestAddAndRemoveContactURN() { tx, err = ts.b.db.Beginx() ts.NoError(err) - contactURNs, err = contactURNsForContact(tx, contact.ID_) + contactURNs, err = getURNsForContact(tx, contact.ID_) ts.NoError(err) ts.Equal(len(contactURNs), 2) @@ -308,7 +308,7 @@ func (ts *BackendTestSuite) TestAddAndRemoveContactURN() { tx, err = ts.b.db.Beginx() ts.NoError(err) - contactURNs, err = contactURNsForContact(tx, contact.ID_) + contactURNs, err = getURNsForContact(tx, contact.ID_) ts.NoError(err) ts.Equal(len(contactURNs), 1) } @@ -332,12 +332,12 @@ func (ts *BackendTestSuite) TestContactURN() { ts.NoError(err) ts.NotNil(contact) - contactURNs, err := contactURNsForContact(tx, contact.ID_) + contactURNs, err := getURNsForContact(tx, contact.ID_) ts.NoError(err) ts.Equal(null.Map[string]{"default": "chestnut"}, contactURNs[0].AuthTokens) // now build a URN for our number with the kannel channel - knURN, err := contactURNForURN(tx, knChannel, contact.ID_, urn, "sesame") + knURN, err := getOrCreateContactURN(tx, knChannel, contact.ID_, urn, "sesame") ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(knURN.OrgID, knChannel.OrgID_) @@ -347,7 +347,7 @@ func (ts *BackendTestSuite) TestContactURN() { ts.NoError(err) // then with our twilio channel - twURN, err := contactURNForURN(tx, twChannel, contact.ID_, urn, "") + twURN, err := getOrCreateContactURN(tx, twChannel, contact.ID_, urn, "") ts.NoError(err) ts.NoError(tx.Commit()) @@ -367,7 +367,7 @@ func (ts *BackendTestSuite) TestContactURN() { ts.NoError(err) // again with different auth - twURN, err = contactURNForURN(tx, twChannel, contact.ID_, urn, "peanut") + twURN, err = getOrCreateContactURN(tx, twChannel, contact.ID_, urn, "peanut") ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(null.Map[string]{"default": "peanut"}, twURN.AuthTokens) @@ -389,7 +389,7 @@ func (ts *BackendTestSuite) TestContactURN() { tx, err = ts.b.db.Beginx() ts.NoError(err) - tgContactURN, err := contactURNForURN(tx, tgChannel, tgContact.ID_, tgURNDisplay, "") + tgContactURN, err := getOrCreateContactURN(tx, tgChannel, tgContact.ID_, tgURNDisplay, "") ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(tgContact.URNID_, tgContactURN.ID) @@ -434,7 +434,7 @@ func (ts *BackendTestSuite) TestContactURNPriority() { tx, err := ts.b.db.Beginx() ts.NoError(err) - _, err = contactURNForURN(tx, twChannel, knContact.ID_, twURN, "") + _, err = getOrCreateContactURN(tx, twChannel, knContact.ID_, twURN, "") ts.NoError(err) ts.NoError(tx.Commit()) @@ -449,7 +449,7 @@ func (ts *BackendTestSuite) TestContactURNPriority() { tx, err = ts.b.db.Beginx() ts.NoError(err) - urns, err := contactURNsForContact(tx, twContact.ID_) + urns, err := getURNsForContact(tx, twContact.ID_) ts.NoError(err) ts.NoError(tx.Commit()) @@ -622,7 +622,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { // update URN when the new doesn't exist tx, _ := ts.b.db.BeginTxx(ctx, nil) oldURN, _ := urns.NewWhatsAppURN("55988776655") - _ = insertContactURN(tx, newDBContactURN(channel.OrgID_, channel.ID_, NilContactID, oldURN, "")) + _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, oldURN, "")) ts.NoError(tx.Commit()) @@ -633,7 +633,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) - contactURN, err := selectContactURN(tx, channel.OrgID_, newURN) + contactURN, err := getContactURNByIdentity(tx, channel.OrgID_, newURN) ts.NoError(err) ts.Equal(contactURN.Identity, newURN.Identity().String()) @@ -644,7 +644,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { newURN, _ = urns.NewWhatsAppURN("5599887766") tx, _ = ts.b.db.BeginTxx(ctx, nil) contact, _ := contactForURN(ctx, ts.b, channel.OrgID_, channel, oldURN, "", "", clog6) - _ = insertContactURN(tx, newDBContactURN(channel.OrgID_, channel.ID_, NilContactID, newURN, "")) + _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, newURN, "")) ts.NoError(tx.Commit()) @@ -654,8 +654,8 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) - newContactURN, _ := selectContactURN(tx, channel.OrgID_, newURN) - oldContactURN, _ := selectContactURN(tx, channel.OrgID_, oldURN) + newContactURN, _ := getContactURNByIdentity(tx, channel.OrgID_, newURN) + oldContactURN, _ := getContactURNByIdentity(tx, channel.OrgID_, oldURN) ts.Equal(newContactURN.ContactID, contact.ID_) ts.Equal(oldContactURN.ContactID, NilContactID) @@ -676,8 +676,8 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) tx, _ = ts.b.db.BeginTxx(ctx, nil) - oldContactURN, _ = selectContactURN(tx, channel.OrgID_, oldURN) - newContactURN, _ = selectContactURN(tx, channel.OrgID_, newURN) + oldContactURN, _ = getContactURNByIdentity(tx, channel.OrgID_, oldURN) + newContactURN, _ = getContactURNByIdentity(tx, channel.OrgID_, newURN) ts.Equal(oldContactURN.ContactID, NilContactID) ts.Equal(newContactURN.ContactID, otherContact.ID_) @@ -1115,7 +1115,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { ts.NoError(err) // load our URN - contactURN, err := contactURNForURN(tx, m.channel, m.ContactID_, urn, "") + contactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, "") if !ts.NoError(err) || !ts.NoError(tx.Commit()) { ts.FailNow("failed writing contact urn") } @@ -1255,7 +1255,7 @@ func (ts *BackendTestSuite) TestPreferredChannelCheckRole() { ts.NoError(err) // load our URN - exContactURN, err := contactURNForURN(tx, m.channel, m.ContactID_, urn, "") + exContactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, "") if !ts.NoError(err) || !ts.NoError(tx.Commit()) { ts.FailNow("failed writing contact urn") } diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 6a30878f2..ff80cbfa6 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -168,7 +168,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChanne // associate our URN // If we've inserted a duplicate URN then we'll get a uniqueness violation. // That means this contact URN was written by someone else after we tried to look it up. - contactURN, err := contactURNForURN(tx, channel, contact.ID_, urn, auth) + contactURN, err := getOrCreateContactURN(tx, channel, contact.ID_, urn, auth) if err != nil { tx.Rollback() diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 38e43b1a3..62c6b5b35 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -24,9 +24,24 @@ func (i ContactURNID) Value() (driver.Value, error) { return null.IntValue(i) } func (i *ContactURNID) UnmarshalJSON(b []byte) error { return null.UnmarshalInt(b, i) } func (i ContactURNID) MarshalJSON() ([]byte, error) { return null.MarshalInt(i) } -// NewDBContactURN returns a new ContactURN object for the passed in org, contact and string urn, this is not saved to the DB yet -func newDBContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, urn urns.URN, auth string) *DBContactURN { - return &DBContactURN{ +// ContactURN is our struct to map to database level URNs +type ContactURN struct { + ID ContactURNID `db:"id"` + OrgID OrgID `db:"org_id"` + ContactID ContactID `db:"contact_id"` + Identity string `db:"identity"` + Scheme string `db:"scheme"` + Path string `db:"path"` + Display null.String `db:"display"` + AuthTokens null.Map[string] `db:"auth_tokens"` + Priority int `db:"priority"` + ChannelID courier.ChannelID `db:"channel_id"` + PrevContactID ContactID +} + +// returns a new ContactURN object for the passed in org, contact and string URN +func newContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, urn urns.URN, auth string) *ContactURN { + return &ContactURN{ OrgID: org, ChannelID: channelID, ContactID: contactID, @@ -38,44 +53,38 @@ func newDBContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID } } -const selectContactURNs = ` -SELECT - id, - identity, - scheme, - display, - auth_tokens, - priority, - contact_id, - channel_id -FROM - contacts_contacturn -WHERE - contact_id = $1 -ORDER BY - priority desc -` - -// selectContactURNs returns all the ContactURNs for the passed in contact, sorted by priority -func contactURNsForContact(db *sqlx.Tx, contactID ContactID) ([]*DBContactURN, error) { +const sqlSelectURNsByContact = ` + SELECT id, org_id, contact_id, identity, scheme, path, display, auth_tokens, priority, channel_id + FROM contacts_contacturn + WHERE contact_id = $1 +ORDER BY priority DESC` + +const sqlSelectURNByIdentity = ` + SELECT id, org_id, contact_id, identity, scheme, path, display, auth_tokens, priority, channel_id + FROM contacts_contacturn + WHERE org_id = $1 AND identity = $2 +ORDER BY priority DESC + LIMIT 1` + +// returns all the ContactURNs for the passed in contact, sorted by priority +func getURNsForContact(db *sqlx.Tx, contactID ContactID) ([]*ContactURN, error) { // select all the URNs for this contact - rows, err := db.Queryx(selectContactURNs, contactID) + rows, err := db.Queryx(sqlSelectURNsByContact, contactID) if err != nil { return nil, err } defer rows.Close() - // read our URNs out - urns := make([]*DBContactURN, 0, 3) - idx := 0 + urns := make([]*ContactURN, 0, 3) + for rows.Next() { - u := &DBContactURN{} - err = rows.StructScan(u) - if err != nil { + u := &ContactURN{} + + if err := rows.StructScan(u); err != nil { return nil, err } + urns = append(urns, u) - idx++ } return urns, nil } @@ -86,7 +95,7 @@ func contactURNsForContact(db *sqlx.Tx, contactID ContactID) ([]*DBContactURN, e // Note that the URN must be one of the contact's URN before calling this method func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns.URN, auth string) error { scheme := urn.Scheme() - contactURNs, err := contactURNsForContact(db, contact.ID_) + contactURNs, err := getURNsForContact(db, contact.ID_) if err != nil { logrus.WithError(err).WithField("urn", urn.Identity()).WithField("channel_id", channel.ID()).Error("error looking up contact urns") return err @@ -152,47 +161,24 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns return nil } -const selectOrgURN = ` -SELECT - org_id, - id, - identity, - scheme, - path, - display, - auth_tokens, - priority, - channel_id, - contact_id -FROM - contacts_contacturn -WHERE - org_id = $1 AND - identity = $2 -ORDER BY - priority desc -LIMIT 1 -` - -// selectContactURN returns the ContactURN for the passed in org and URN -func selectContactURN(db *sqlx.Tx, org OrgID, urn urns.URN) (*DBContactURN, error) { - contactURN := newDBContactURN(org, courier.NilChannelID, NilContactID, urn, "") - err := db.Get(contactURN, selectOrgURN, org, urn.Identity()) - +// getContactURNByIdentity returns the ContactURN for the passed in org and identity +func getContactURNByIdentity(db *sqlx.Tx, org OrgID, urn urns.URN) (*ContactURN, error) { + contactURN := newContactURN(org, courier.NilChannelID, NilContactID, urn, "") + err := db.Get(contactURN, sqlSelectURNByIdentity, org, urn.Identity()) if err != nil { return nil, err } return contactURN, nil } -// contactURNForURN returns the ContactURN for the passed in org and URN, creating and associating +// getOrCreateContactURN returns the ContactURN for the passed in org and URN, creating and associating // it with the passed in contact if necessary -func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn urns.URN, auth string) (*DBContactURN, error) { - contactURN := newDBContactURN(channel.OrgID(), courier.NilChannelID, contactID, urn, auth) +func getOrCreateContactURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn urns.URN, auth string) (*ContactURN, error) { + contactURN := newContactURN(channel.OrgID(), courier.NilChannelID, contactID, urn, auth) if channel.HasRole(courier.ChannelRoleSend) { contactURN.ChannelID = channel.ID() } - err := db.Get(contactURN, selectOrgURN, channel.OrgID(), urn.Identity()) + err := db.Get(contactURN, sqlSelectURNByIdentity, channel.OrgID(), urn.Identity()) if err != nil && err != sql.ErrNoRows { return nil, errors.Wrap(err, "error looking up URN by identity") } @@ -231,16 +217,14 @@ func contactURNForURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn return contactURN, errors.Wrap(err, "error updating URN auth") } -const insertURN = ` -INSERT INTO - contacts_contacturn(org_id, identity, path, scheme, display, auth_tokens, priority, channel_id, contact_id) - VALUES(:org_id, :identity, :path, :scheme, :display, :auth_tokens, :priority, :channel_id, :contact_id) -RETURNING id -` +const sqlInsertURN = ` +INSERT INTO contacts_contacturn(org_id, identity, path, scheme, display, auth_tokens, priority, channel_id, contact_id) + VALUES(:org_id, :identity, :path, :scheme, :display, :auth_tokens, :priority, :channel_id, :contact_id) + RETURNING id` // InsertContactURN inserts the passed in urn, the id field will be populated with the result on success -func insertContactURN(db *sqlx.Tx, urn *DBContactURN) error { - rows, err := db.NamedQuery(insertURN, urn) +func insertContactURN(db *sqlx.Tx, urn *ContactURN) error { + rows, err := db.NamedQuery(sqlInsertURN, urn) if err != nil { return err } @@ -252,36 +236,19 @@ func insertContactURN(db *sqlx.Tx, urn *DBContactURN) error { return err } -const updateURN = ` -UPDATE - contacts_contacturn -SET - channel_id = :channel_id, - contact_id = :contact_id, - display = :display, - auth_tokens = :auth_tokens, - priority = :priority -WHERE - id = :id -` -const fullyUpdateURN = ` -UPDATE - contacts_contacturn -SET - channel_id = :channel_id, - contact_id = :contact_id, - identity = :identity, - path = :path, - display = :display, - auth_tokens = :auth_tokens, - priority = :priority -WHERE - id = :id -` +const sqlUpdateURN = ` +UPDATE contacts_contacturn + SET channel_id = :channel_id, contact_id = :contact_id, display = :display, auth_tokens = :auth_tokens, priority = :priority + WHERE id = :id` + +const sqlFullyUpdateURN = ` +UPDATE contacts_contacturn + SET channel_id = :channel_id, contact_id = :contact_id, identity = :identity, path = :path, display = :display, auth_tokens = :auth_tokens, priority = :priority + WHERE id = :id` // UpdateContactURN updates the Channel and Contact on an existing URN -func updateContactURN(db *sqlx.Tx, urn *DBContactURN) error { - rows, err := db.NamedQuery(updateURN, urn) +func updateContactURN(db *sqlx.Tx, urn *ContactURN) error { + rows, err := db.NamedQuery(sqlUpdateURN, urn) if err != nil { logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") return err @@ -295,8 +262,8 @@ func updateContactURN(db *sqlx.Tx, urn *DBContactURN) error { } // FullyUpdateContactURN updates the Identity, Channel and Contact on an existing URN -func fullyUpdateContactURN(db *sqlx.Tx, urn *DBContactURN) error { - rows, err := db.NamedQuery(fullyUpdateURN, urn) +func fullyUpdateContactURN(db *sqlx.Tx, urn *ContactURN) error { + rows, err := db.NamedQuery(sqlFullyUpdateURN, urn) if err != nil { logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") return err @@ -308,18 +275,3 @@ func fullyUpdateContactURN(db *sqlx.Tx, urn *DBContactURN) error { } return err } - -// DBContactURN is our struct to map to database level URNs -type DBContactURN struct { - OrgID OrgID `db:"org_id"` - ID ContactURNID `db:"id"` - Identity string `db:"identity"` - Scheme string `db:"scheme"` - Path string `db:"path"` - Display null.String `db:"display"` - AuthTokens null.Map[string] `db:"auth_tokens"` - Priority int `db:"priority"` - ChannelID courier.ChannelID `db:"channel_id"` - ContactID ContactID `db:"contact_id"` - PrevContactID ContactID -} From 6878144b0d4031eac5e0e39a6c6768fdc0950b98 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Tue, 12 Sep 2023 16:53:11 +0200 Subject: [PATCH 085/170] Rename legacy handlers --- cmd/courier/main.go | 6 +++--- handlers/{facebook => facebook_legacy}/facebook.go | 2 +- handlers/{facebook => facebook_legacy}/facebook_test.go | 2 +- handlers/{facebookapp/facebookapp.go => meta/meta.go} | 2 +- .../{facebookapp/facebookapp_test.go => meta/meta_test.go} | 2 +- .../{facebookapp => meta}/testdata/fba/attachmentFBA.json | 0 .../testdata/fba/differentPageFBA.json | 0 handlers/{facebookapp => meta}/testdata/fba/dlr.json | 0 .../{facebookapp => meta}/testdata/fba/duplicateMsgFBA.json | 0 handlers/{facebookapp => meta}/testdata/fba/echoFBA.json | 0 .../{facebookapp => meta}/testdata/fba/helloMsgFBA.json | 0 .../{facebookapp => meta}/testdata/fba/invalidURNFBA.json | 0 .../testdata/fba/locationAttachment.json | 0 .../{facebookapp => meta}/testdata/fba/noEntriesFBA.json | 0 .../testdata/fba/noMessagingEntriesFBA.json | 0 handlers/{facebookapp => meta}/testdata/fba/notPage.json | 0 handlers/{facebookapp => meta}/testdata/fba/optIn.json | 0 .../{facebookapp => meta}/testdata/fba/optInUserRef.json | 0 handlers/{facebookapp => meta}/testdata/fba/postback.json | 0 .../testdata/fba/postbackGetStarted.json | 0 .../testdata/fba/postbackReferral.json | 0 handlers/{facebookapp => meta}/testdata/fba/referral.json | 0 handlers/{facebookapp => meta}/testdata/fba/thumbsUp.json | 0 .../testdata/fba/unknownMessagingEntryFBA.json | 0 .../{facebookapp => meta}/testdata/ig/attachmentIG.json | 0 .../{facebookapp => meta}/testdata/ig/differentPageIG.json | 0 .../{facebookapp => meta}/testdata/ig/duplicateMsgIG.json | 0 handlers/{facebookapp => meta}/testdata/ig/echoIG.json | 0 handlers/{facebookapp => meta}/testdata/ig/helloMsgIG.json | 0 .../testdata/ig/icebreakerGetStarted.json | 0 .../{facebookapp => meta}/testdata/ig/invalidURNIG.json | 0 handlers/{facebookapp => meta}/testdata/ig/like_heart.json | 0 handlers/{facebookapp => meta}/testdata/ig/noEntriesIG.json | 0 .../testdata/ig/noMessagingEntriesIG.json | 0 .../{facebookapp => meta}/testdata/ig/notInstagram.json | 0 .../{facebookapp => meta}/testdata/ig/storyMentionIG.json | 0 .../testdata/ig/unknownMessagingEntryIG.json | 0 handlers/{facebookapp => meta}/testdata/ig/unsentMsgIG.json | 0 handlers/{facebookapp => meta}/testdata/wac/audioWAC.json | 0 .../{facebookapp => meta}/testdata/wac/buttonReplyWAC.json | 0 handlers/{facebookapp => meta}/testdata/wac/buttonWAC.json | 0 .../{facebookapp => meta}/testdata/wac/documentWAC.json | 0 .../{facebookapp => meta}/testdata/wac/duplicateWAC.json | 0 .../{facebookapp => meta}/testdata/wac/errorErrors.json | 0 handlers/{facebookapp => meta}/testdata/wac/errorMsg.json | 0 .../{facebookapp => meta}/testdata/wac/errorStatus.json | 0 handlers/{facebookapp => meta}/testdata/wac/helloWAC.json | 0 .../{facebookapp => meta}/testdata/wac/ignoreStatusWAC.json | 0 handlers/{facebookapp => meta}/testdata/wac/imageWAC.json | 0 .../{facebookapp => meta}/testdata/wac/invalidFrom.json | 0 .../testdata/wac/invalidStatusWAC.json | 0 .../testdata/wac/invalidTimestamp.json | 0 .../{facebookapp => meta}/testdata/wac/listReplyWAC.json | 0 .../{facebookapp => meta}/testdata/wac/locationWAC.json | 0 .../{facebookapp => meta}/testdata/wac/validStatusWAC.json | 0 handlers/{facebookapp => meta}/testdata/wac/videoWAC.json | 0 handlers/{facebookapp => meta}/testdata/wac/voiceWAC.json | 0 handlers/{whatsapp => whatsapp_legacy}/whatsapp.go | 2 +- handlers/{whatsapp => whatsapp_legacy}/whatsapp_test.go | 2 +- 59 files changed, 9 insertions(+), 9 deletions(-) rename handlers/{facebook => facebook_legacy}/facebook.go (99%) rename handlers/{facebook => facebook_legacy}/facebook_test.go (99%) rename handlers/{facebookapp/facebookapp.go => meta/meta.go} (99%) rename handlers/{facebookapp/facebookapp_test.go => meta/meta_test.go} (99%) rename handlers/{facebookapp => meta}/testdata/fba/attachmentFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/differentPageFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/dlr.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/duplicateMsgFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/echoFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/helloMsgFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/invalidURNFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/locationAttachment.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/noEntriesFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/noMessagingEntriesFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/notPage.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/optIn.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/optInUserRef.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/postback.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/postbackGetStarted.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/postbackReferral.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/referral.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/thumbsUp.json (100%) rename handlers/{facebookapp => meta}/testdata/fba/unknownMessagingEntryFBA.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/attachmentIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/differentPageIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/duplicateMsgIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/echoIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/helloMsgIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/icebreakerGetStarted.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/invalidURNIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/like_heart.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/noEntriesIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/noMessagingEntriesIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/notInstagram.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/storyMentionIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/unknownMessagingEntryIG.json (100%) rename handlers/{facebookapp => meta}/testdata/ig/unsentMsgIG.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/audioWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/buttonReplyWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/buttonWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/documentWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/duplicateWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/errorErrors.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/errorMsg.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/errorStatus.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/helloWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/ignoreStatusWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/imageWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/invalidFrom.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/invalidStatusWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/invalidTimestamp.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/listReplyWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/locationWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/validStatusWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/videoWAC.json (100%) rename handlers/{facebookapp => meta}/testdata/wac/voiceWAC.json (100%) rename handlers/{whatsapp => whatsapp_legacy}/whatsapp.go (99%) rename handlers/{whatsapp => whatsapp_legacy}/whatsapp_test.go (99%) diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 18a1263b8..467fb7572 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -24,8 +24,7 @@ import ( _ "github.com/nyaruka/courier/handlers/discord" _ "github.com/nyaruka/courier/handlers/dmark" _ "github.com/nyaruka/courier/handlers/external" - _ "github.com/nyaruka/courier/handlers/facebook" - _ "github.com/nyaruka/courier/handlers/facebookapp" + _ "github.com/nyaruka/courier/handlers/facebook_legacy" _ "github.com/nyaruka/courier/handlers/firebase" _ "github.com/nyaruka/courier/handlers/freshchat" _ "github.com/nyaruka/courier/handlers/globe" @@ -45,6 +44,7 @@ import ( _ "github.com/nyaruka/courier/handlers/mblox" _ "github.com/nyaruka/courier/handlers/messagebird" _ "github.com/nyaruka/courier/handlers/messangi" + _ "github.com/nyaruka/courier/handlers/meta" _ "github.com/nyaruka/courier/handlers/mtarget" _ "github.com/nyaruka/courier/handlers/mtn" _ "github.com/nyaruka/courier/handlers/nexmo" @@ -66,7 +66,7 @@ import ( _ "github.com/nyaruka/courier/handlers/vk" _ "github.com/nyaruka/courier/handlers/wavy" _ "github.com/nyaruka/courier/handlers/wechat" - _ "github.com/nyaruka/courier/handlers/whatsapp" + _ "github.com/nyaruka/courier/handlers/whatsapp_legacy" _ "github.com/nyaruka/courier/handlers/yo" _ "github.com/nyaruka/courier/handlers/zenvia" diff --git a/handlers/facebook/facebook.go b/handlers/facebook_legacy/facebook.go similarity index 99% rename from handlers/facebook/facebook.go rename to handlers/facebook_legacy/facebook.go index 5ed15193e..b36084148 100644 --- a/handlers/facebook/facebook.go +++ b/handlers/facebook_legacy/facebook.go @@ -1,4 +1,4 @@ -package facebook +package facebook_legacy import ( "bytes" diff --git a/handlers/facebook/facebook_test.go b/handlers/facebook_legacy/facebook_test.go similarity index 99% rename from handlers/facebook/facebook_test.go rename to handlers/facebook_legacy/facebook_test.go index da3449d7e..15672be6c 100644 --- a/handlers/facebook/facebook_test.go +++ b/handlers/facebook_legacy/facebook_test.go @@ -1,4 +1,4 @@ -package facebook +package facebook_legacy import ( "context" diff --git a/handlers/facebookapp/facebookapp.go b/handlers/meta/meta.go similarity index 99% rename from handlers/facebookapp/facebookapp.go rename to handlers/meta/meta.go index b013b5346..bf126852a 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/meta/meta.go @@ -1,4 +1,4 @@ -package facebookapp +package meta import ( "bytes" diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/meta/meta_test.go similarity index 99% rename from handlers/facebookapp/facebookapp_test.go rename to handlers/meta/meta_test.go index 863c37856..5adfe0d72 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/meta/meta_test.go @@ -1,4 +1,4 @@ -package facebookapp +package meta import ( "context" diff --git a/handlers/facebookapp/testdata/fba/attachmentFBA.json b/handlers/meta/testdata/fba/attachmentFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/attachmentFBA.json rename to handlers/meta/testdata/fba/attachmentFBA.json diff --git a/handlers/facebookapp/testdata/fba/differentPageFBA.json b/handlers/meta/testdata/fba/differentPageFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/differentPageFBA.json rename to handlers/meta/testdata/fba/differentPageFBA.json diff --git a/handlers/facebookapp/testdata/fba/dlr.json b/handlers/meta/testdata/fba/dlr.json similarity index 100% rename from handlers/facebookapp/testdata/fba/dlr.json rename to handlers/meta/testdata/fba/dlr.json diff --git a/handlers/facebookapp/testdata/fba/duplicateMsgFBA.json b/handlers/meta/testdata/fba/duplicateMsgFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/duplicateMsgFBA.json rename to handlers/meta/testdata/fba/duplicateMsgFBA.json diff --git a/handlers/facebookapp/testdata/fba/echoFBA.json b/handlers/meta/testdata/fba/echoFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/echoFBA.json rename to handlers/meta/testdata/fba/echoFBA.json diff --git a/handlers/facebookapp/testdata/fba/helloMsgFBA.json b/handlers/meta/testdata/fba/helloMsgFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/helloMsgFBA.json rename to handlers/meta/testdata/fba/helloMsgFBA.json diff --git a/handlers/facebookapp/testdata/fba/invalidURNFBA.json b/handlers/meta/testdata/fba/invalidURNFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/invalidURNFBA.json rename to handlers/meta/testdata/fba/invalidURNFBA.json diff --git a/handlers/facebookapp/testdata/fba/locationAttachment.json b/handlers/meta/testdata/fba/locationAttachment.json similarity index 100% rename from handlers/facebookapp/testdata/fba/locationAttachment.json rename to handlers/meta/testdata/fba/locationAttachment.json diff --git a/handlers/facebookapp/testdata/fba/noEntriesFBA.json b/handlers/meta/testdata/fba/noEntriesFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/noEntriesFBA.json rename to handlers/meta/testdata/fba/noEntriesFBA.json diff --git a/handlers/facebookapp/testdata/fba/noMessagingEntriesFBA.json b/handlers/meta/testdata/fba/noMessagingEntriesFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/noMessagingEntriesFBA.json rename to handlers/meta/testdata/fba/noMessagingEntriesFBA.json diff --git a/handlers/facebookapp/testdata/fba/notPage.json b/handlers/meta/testdata/fba/notPage.json similarity index 100% rename from handlers/facebookapp/testdata/fba/notPage.json rename to handlers/meta/testdata/fba/notPage.json diff --git a/handlers/facebookapp/testdata/fba/optIn.json b/handlers/meta/testdata/fba/optIn.json similarity index 100% rename from handlers/facebookapp/testdata/fba/optIn.json rename to handlers/meta/testdata/fba/optIn.json diff --git a/handlers/facebookapp/testdata/fba/optInUserRef.json b/handlers/meta/testdata/fba/optInUserRef.json similarity index 100% rename from handlers/facebookapp/testdata/fba/optInUserRef.json rename to handlers/meta/testdata/fba/optInUserRef.json diff --git a/handlers/facebookapp/testdata/fba/postback.json b/handlers/meta/testdata/fba/postback.json similarity index 100% rename from handlers/facebookapp/testdata/fba/postback.json rename to handlers/meta/testdata/fba/postback.json diff --git a/handlers/facebookapp/testdata/fba/postbackGetStarted.json b/handlers/meta/testdata/fba/postbackGetStarted.json similarity index 100% rename from handlers/facebookapp/testdata/fba/postbackGetStarted.json rename to handlers/meta/testdata/fba/postbackGetStarted.json diff --git a/handlers/facebookapp/testdata/fba/postbackReferral.json b/handlers/meta/testdata/fba/postbackReferral.json similarity index 100% rename from handlers/facebookapp/testdata/fba/postbackReferral.json rename to handlers/meta/testdata/fba/postbackReferral.json diff --git a/handlers/facebookapp/testdata/fba/referral.json b/handlers/meta/testdata/fba/referral.json similarity index 100% rename from handlers/facebookapp/testdata/fba/referral.json rename to handlers/meta/testdata/fba/referral.json diff --git a/handlers/facebookapp/testdata/fba/thumbsUp.json b/handlers/meta/testdata/fba/thumbsUp.json similarity index 100% rename from handlers/facebookapp/testdata/fba/thumbsUp.json rename to handlers/meta/testdata/fba/thumbsUp.json diff --git a/handlers/facebookapp/testdata/fba/unknownMessagingEntryFBA.json b/handlers/meta/testdata/fba/unknownMessagingEntryFBA.json similarity index 100% rename from handlers/facebookapp/testdata/fba/unknownMessagingEntryFBA.json rename to handlers/meta/testdata/fba/unknownMessagingEntryFBA.json diff --git a/handlers/facebookapp/testdata/ig/attachmentIG.json b/handlers/meta/testdata/ig/attachmentIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/attachmentIG.json rename to handlers/meta/testdata/ig/attachmentIG.json diff --git a/handlers/facebookapp/testdata/ig/differentPageIG.json b/handlers/meta/testdata/ig/differentPageIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/differentPageIG.json rename to handlers/meta/testdata/ig/differentPageIG.json diff --git a/handlers/facebookapp/testdata/ig/duplicateMsgIG.json b/handlers/meta/testdata/ig/duplicateMsgIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/duplicateMsgIG.json rename to handlers/meta/testdata/ig/duplicateMsgIG.json diff --git a/handlers/facebookapp/testdata/ig/echoIG.json b/handlers/meta/testdata/ig/echoIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/echoIG.json rename to handlers/meta/testdata/ig/echoIG.json diff --git a/handlers/facebookapp/testdata/ig/helloMsgIG.json b/handlers/meta/testdata/ig/helloMsgIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/helloMsgIG.json rename to handlers/meta/testdata/ig/helloMsgIG.json diff --git a/handlers/facebookapp/testdata/ig/icebreakerGetStarted.json b/handlers/meta/testdata/ig/icebreakerGetStarted.json similarity index 100% rename from handlers/facebookapp/testdata/ig/icebreakerGetStarted.json rename to handlers/meta/testdata/ig/icebreakerGetStarted.json diff --git a/handlers/facebookapp/testdata/ig/invalidURNIG.json b/handlers/meta/testdata/ig/invalidURNIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/invalidURNIG.json rename to handlers/meta/testdata/ig/invalidURNIG.json diff --git a/handlers/facebookapp/testdata/ig/like_heart.json b/handlers/meta/testdata/ig/like_heart.json similarity index 100% rename from handlers/facebookapp/testdata/ig/like_heart.json rename to handlers/meta/testdata/ig/like_heart.json diff --git a/handlers/facebookapp/testdata/ig/noEntriesIG.json b/handlers/meta/testdata/ig/noEntriesIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/noEntriesIG.json rename to handlers/meta/testdata/ig/noEntriesIG.json diff --git a/handlers/facebookapp/testdata/ig/noMessagingEntriesIG.json b/handlers/meta/testdata/ig/noMessagingEntriesIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/noMessagingEntriesIG.json rename to handlers/meta/testdata/ig/noMessagingEntriesIG.json diff --git a/handlers/facebookapp/testdata/ig/notInstagram.json b/handlers/meta/testdata/ig/notInstagram.json similarity index 100% rename from handlers/facebookapp/testdata/ig/notInstagram.json rename to handlers/meta/testdata/ig/notInstagram.json diff --git a/handlers/facebookapp/testdata/ig/storyMentionIG.json b/handlers/meta/testdata/ig/storyMentionIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/storyMentionIG.json rename to handlers/meta/testdata/ig/storyMentionIG.json diff --git a/handlers/facebookapp/testdata/ig/unknownMessagingEntryIG.json b/handlers/meta/testdata/ig/unknownMessagingEntryIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/unknownMessagingEntryIG.json rename to handlers/meta/testdata/ig/unknownMessagingEntryIG.json diff --git a/handlers/facebookapp/testdata/ig/unsentMsgIG.json b/handlers/meta/testdata/ig/unsentMsgIG.json similarity index 100% rename from handlers/facebookapp/testdata/ig/unsentMsgIG.json rename to handlers/meta/testdata/ig/unsentMsgIG.json diff --git a/handlers/facebookapp/testdata/wac/audioWAC.json b/handlers/meta/testdata/wac/audioWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/audioWAC.json rename to handlers/meta/testdata/wac/audioWAC.json diff --git a/handlers/facebookapp/testdata/wac/buttonReplyWAC.json b/handlers/meta/testdata/wac/buttonReplyWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/buttonReplyWAC.json rename to handlers/meta/testdata/wac/buttonReplyWAC.json diff --git a/handlers/facebookapp/testdata/wac/buttonWAC.json b/handlers/meta/testdata/wac/buttonWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/buttonWAC.json rename to handlers/meta/testdata/wac/buttonWAC.json diff --git a/handlers/facebookapp/testdata/wac/documentWAC.json b/handlers/meta/testdata/wac/documentWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/documentWAC.json rename to handlers/meta/testdata/wac/documentWAC.json diff --git a/handlers/facebookapp/testdata/wac/duplicateWAC.json b/handlers/meta/testdata/wac/duplicateWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/duplicateWAC.json rename to handlers/meta/testdata/wac/duplicateWAC.json diff --git a/handlers/facebookapp/testdata/wac/errorErrors.json b/handlers/meta/testdata/wac/errorErrors.json similarity index 100% rename from handlers/facebookapp/testdata/wac/errorErrors.json rename to handlers/meta/testdata/wac/errorErrors.json diff --git a/handlers/facebookapp/testdata/wac/errorMsg.json b/handlers/meta/testdata/wac/errorMsg.json similarity index 100% rename from handlers/facebookapp/testdata/wac/errorMsg.json rename to handlers/meta/testdata/wac/errorMsg.json diff --git a/handlers/facebookapp/testdata/wac/errorStatus.json b/handlers/meta/testdata/wac/errorStatus.json similarity index 100% rename from handlers/facebookapp/testdata/wac/errorStatus.json rename to handlers/meta/testdata/wac/errorStatus.json diff --git a/handlers/facebookapp/testdata/wac/helloWAC.json b/handlers/meta/testdata/wac/helloWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/helloWAC.json rename to handlers/meta/testdata/wac/helloWAC.json diff --git a/handlers/facebookapp/testdata/wac/ignoreStatusWAC.json b/handlers/meta/testdata/wac/ignoreStatusWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/ignoreStatusWAC.json rename to handlers/meta/testdata/wac/ignoreStatusWAC.json diff --git a/handlers/facebookapp/testdata/wac/imageWAC.json b/handlers/meta/testdata/wac/imageWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/imageWAC.json rename to handlers/meta/testdata/wac/imageWAC.json diff --git a/handlers/facebookapp/testdata/wac/invalidFrom.json b/handlers/meta/testdata/wac/invalidFrom.json similarity index 100% rename from handlers/facebookapp/testdata/wac/invalidFrom.json rename to handlers/meta/testdata/wac/invalidFrom.json diff --git a/handlers/facebookapp/testdata/wac/invalidStatusWAC.json b/handlers/meta/testdata/wac/invalidStatusWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/invalidStatusWAC.json rename to handlers/meta/testdata/wac/invalidStatusWAC.json diff --git a/handlers/facebookapp/testdata/wac/invalidTimestamp.json b/handlers/meta/testdata/wac/invalidTimestamp.json similarity index 100% rename from handlers/facebookapp/testdata/wac/invalidTimestamp.json rename to handlers/meta/testdata/wac/invalidTimestamp.json diff --git a/handlers/facebookapp/testdata/wac/listReplyWAC.json b/handlers/meta/testdata/wac/listReplyWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/listReplyWAC.json rename to handlers/meta/testdata/wac/listReplyWAC.json diff --git a/handlers/facebookapp/testdata/wac/locationWAC.json b/handlers/meta/testdata/wac/locationWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/locationWAC.json rename to handlers/meta/testdata/wac/locationWAC.json diff --git a/handlers/facebookapp/testdata/wac/validStatusWAC.json b/handlers/meta/testdata/wac/validStatusWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/validStatusWAC.json rename to handlers/meta/testdata/wac/validStatusWAC.json diff --git a/handlers/facebookapp/testdata/wac/videoWAC.json b/handlers/meta/testdata/wac/videoWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/videoWAC.json rename to handlers/meta/testdata/wac/videoWAC.json diff --git a/handlers/facebookapp/testdata/wac/voiceWAC.json b/handlers/meta/testdata/wac/voiceWAC.json similarity index 100% rename from handlers/facebookapp/testdata/wac/voiceWAC.json rename to handlers/meta/testdata/wac/voiceWAC.json diff --git a/handlers/whatsapp/whatsapp.go b/handlers/whatsapp_legacy/whatsapp.go similarity index 99% rename from handlers/whatsapp/whatsapp.go rename to handlers/whatsapp_legacy/whatsapp.go index 003369fcd..debc33fe3 100644 --- a/handlers/whatsapp/whatsapp.go +++ b/handlers/whatsapp_legacy/whatsapp.go @@ -1,4 +1,4 @@ -package whatsapp +package whatsapp_legacy import ( "bytes" diff --git a/handlers/whatsapp/whatsapp_test.go b/handlers/whatsapp_legacy/whatsapp_test.go similarity index 99% rename from handlers/whatsapp/whatsapp_test.go rename to handlers/whatsapp_legacy/whatsapp_test.go index 4384b8691..2bfd1584d 100644 --- a/handlers/whatsapp/whatsapp_test.go +++ b/handlers/whatsapp_legacy/whatsapp_test.go @@ -1,4 +1,4 @@ -package whatsapp +package whatsapp_legacy import ( "context" From a9867bbc7e5bb828415e4d4a4d9c8668ad361f30 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 12 Sep 2023 14:23:52 -0500 Subject: [PATCH 086/170] Allow handlers to create arbitrary auth tokens with messages and channel events --- backend.go | 4 +- backends/rapidpro/backend.go | 8 ++-- backends/rapidpro/backend_test.go | 70 ++++++++++++++-------------- backends/rapidpro/channel_event.go | 14 +++++- backends/rapidpro/contact.go | 10 ++-- backends/rapidpro/msg.go | 63 ++++++++++++++----------- backends/rapidpro/urn.go | 32 +++++++------ channel_event.go | 1 + handlers/facebook_legacy/facebook.go | 6 +-- handlers/firebase/firebase.go | 10 +++- handlers/meta/meta.go | 6 +-- handlers/test.go | 4 +- msg.go | 4 +- test/backend.go | 6 +-- test/channel_event.go | 12 ++++- test/contact.go | 8 ++-- test/msg.go | 57 ++++++++++++---------- utils/misc.go | 11 +++++ utils/misc_test.go | 8 ++++ 19 files changed, 200 insertions(+), 134 deletions(-) diff --git a/backend.go b/backend.go index 4ec95af20..1a569f0d3 100644 --- a/backend.go +++ b/backend.go @@ -30,10 +30,10 @@ type Backend interface { GetChannelByAddress(context.Context, ChannelType, ChannelAddress) (Channel, error) // GetContact returns (or creates) the contact for the passed in channel and URN - GetContact(context.Context, Channel, urns.URN, string, string, *ChannelLog) (Contact, error) + GetContact(context.Context, Channel, urns.URN, map[string]string, string, *ChannelLog) (Contact, error) // AddURNtoContact adds a URN to the passed in contact - AddURNtoContact(context context.Context, channel Channel, contact Contact, urn urns.URN) (urns.URN, error) + AddURNtoContact(context context.Context, channel Channel, contact Contact, urn urns.URN, authTokens map[string]string) (urns.URN, error) // RemoveURNFromcontact removes a URN from the passed in contact RemoveURNfromContact(context context.Context, channel Channel, contact Contact, urn urns.URN) (urns.URN, error) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 0055f5e8a..29a240893 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -321,20 +321,20 @@ func (b *backend) GetChannelByAddress(ctx context.Context, ct courier.ChannelTyp } // GetContact returns the contact for the passed in channel and URN -func (b *backend) GetContact(ctx context.Context, c courier.Channel, urn urns.URN, auth string, name string, clog *courier.ChannelLog) (courier.Contact, error) { +func (b *backend) GetContact(ctx context.Context, c courier.Channel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (courier.Contact, error) { dbChannel := c.(*DBChannel) - return contactForURN(ctx, b, dbChannel.OrgID_, dbChannel, urn, auth, name, clog) + return contactForURN(ctx, b, dbChannel.OrgID_, dbChannel, urn, authTokens, name, clog) } // AddURNtoContact adds a URN to the passed in contact -func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contact courier.Contact, urn urns.URN) (urns.URN, error) { +func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contact courier.Contact, urn urns.URN, authTokens map[string]string) (urns.URN, error) { tx, err := b.db.BeginTxx(ctx, nil) if err != nil { return urns.NilURN, err } dbChannel := c.(*DBChannel) dbContact := contact.(*DBContact) - _, err = getOrCreateContactURN(tx, dbChannel, dbContact.ID_, urn, "") + _, err = getOrCreateContactURN(tx, dbChannel, dbContact.ID_, urn, authTokens) if err != nil { return urns.NilURN, err } diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 281899f5d..73c23b835 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -204,13 +204,13 @@ func (ts *BackendTestSuite) TestContact() { now := time.Now() // create our new contact - contact, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, "", "Ryan Lewis", clog) + contact, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, nil, "Ryan Lewis", clog) ts.NoError(err) now2 := time.Now() // load this contact again by URN, should be same contact, name unchanged - contact2, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, "", "Other Name", clog) + contact2, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, nil, "Other Name", clog) ts.NoError(err) ts.Equal(contact.UUID_, contact2.UUID_) @@ -224,7 +224,7 @@ func (ts *BackendTestSuite) TestContact() { // load a contact by URN instead (this one is in our testdata) cURN, _ := urns.NewTelURNForCountry("+12067799192", "US") - contact, err = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, cURN, "", "", clog) + contact, err = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, cURN, nil, "", clog) ts.NoError(err) ts.NotNil(contact) @@ -236,7 +236,7 @@ func (ts *BackendTestSuite) TestContact() { // long name are truncated longName := "LongRandomNameHPGBRDjZvkz7y58jI2UPkio56IKGaMvaeDTvF74Q5SUkIHozFn1MLELfjX7vRrFto8YG2KPVaWzekgmFbkuxujIotFAgfhHqoHKW5c177FUtKf5YK9KbY8hp0x7PxIFY3MS5lMyMA5ELlqIgikThpr" - contact3, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, "", longName, clog) + contact3, err := contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, nil, longName, clog) ts.NoError(err) ts.Equal(null.String(longName[0:127]), contact3.Name_) @@ -258,10 +258,10 @@ func (ts *BackendTestSuite) TestContactRace() { var err1, err2 error go func() { - contact1, err1 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, "", "Ryan Lewis", clog) + contact1, err1 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, nil, "Ryan Lewis", clog) }() go func() { - contact2, err2 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, "", "Ryan Lewis", clog) + contact2, err2 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn, nil, "Ryan Lewis", clog) }() time.Sleep(time.Second) @@ -279,7 +279,7 @@ func (ts *BackendTestSuite) TestAddAndRemoveContactURN() { cURN, err := urns.NewTelURNForCountry("+12067799192", "US") ts.NoError(err) - contact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, cURN, "", "", clog) + contact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, cURN, nil, "", clog) ts.NoError(err) ts.NotNil(contact) @@ -291,7 +291,7 @@ func (ts *BackendTestSuite) TestAddAndRemoveContactURN() { ts.Equal(len(contactURNs), 1) urn, _ := urns.NewTelURNForCountry("12065551518", "US") - addedURN, err := ts.b.AddURNtoContact(ctx, knChannel, contact, urn) + addedURN, err := ts.b.AddURNtoContact(ctx, knChannel, contact, urn, nil) ts.NoError(err) ts.NotNil(addedURN) @@ -321,33 +321,33 @@ func (ts *BackendTestSuite) TestContactURN() { ctx := context.Background() - contact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, urn, "", "", clog) + contact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, urn, nil, "", clog) ts.NoError(err) ts.NotNil(contact) tx, err := ts.b.db.Beginx() ts.NoError(err) - contact, err = contactForURN(ctx, ts.b, twChannel.OrgID_, twChannel, urn, "chestnut", "", clog) + contact, err = contactForURN(ctx, ts.b, twChannel.OrgID_, twChannel, urn, map[string]string{"token1": "chestnut"}, "", clog) ts.NoError(err) ts.NotNil(contact) contactURNs, err := getURNsForContact(tx, contact.ID_) ts.NoError(err) - ts.Equal(null.Map[string]{"default": "chestnut"}, contactURNs[0].AuthTokens) + ts.Equal(null.Map[string]{"token1": "chestnut"}, contactURNs[0].AuthTokens) // now build a URN for our number with the kannel channel - knURN, err := getOrCreateContactURN(tx, knChannel, contact.ID_, urn, "sesame") + knURN, err := getOrCreateContactURN(tx, knChannel, contact.ID_, urn, map[string]string{"token2": "sesame"}) ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(knURN.OrgID, knChannel.OrgID_) - ts.Equal(null.Map[string]{"default": "sesame"}, knURN.AuthTokens) + ts.Equal(null.Map[string]{"token1": "chestnut", "token2": "sesame"}, knURN.AuthTokens) tx, err = ts.b.db.Beginx() ts.NoError(err) // then with our twilio channel - twURN, err := getOrCreateContactURN(tx, twChannel, contact.ID_, urn, "") + twURN, err := getOrCreateContactURN(tx, twChannel, contact.ID_, urn, nil) ts.NoError(err) ts.NoError(tx.Commit()) @@ -361,26 +361,26 @@ func (ts *BackendTestSuite) TestContactURN() { ts.Equal(twURN.ChannelID, twChannel.ID()) // auth should be unchanged - ts.Equal(null.Map[string]{"default": "sesame"}, twURN.AuthTokens) + ts.Equal(null.Map[string]{"token1": "chestnut", "token2": "sesame"}, twURN.AuthTokens) tx, err = ts.b.db.Beginx() ts.NoError(err) // again with different auth - twURN, err = getOrCreateContactURN(tx, twChannel, contact.ID_, urn, "peanut") + twURN, err = getOrCreateContactURN(tx, twChannel, contact.ID_, urn, map[string]string{"token3": "peanut"}) ts.NoError(err) ts.NoError(tx.Commit()) - ts.Equal(null.Map[string]{"default": "peanut"}, twURN.AuthTokens) + ts.Equal(null.Map[string]{"token1": "chestnut", "token2": "sesame", "token3": "peanut"}, twURN.AuthTokens) // test that we don't use display when looking up URNs tgChannel := ts.getChannel("TG", "dbc126ed-66bc-4e28-b67b-81dc3327c98a") tgURN, _ := urns.NewTelegramURN(12345, "") - tgContact, err := contactForURN(ctx, ts.b, tgChannel.OrgID_, tgChannel, tgURN, "", "", clog) + tgContact, err := contactForURN(ctx, ts.b, tgChannel.OrgID_, tgChannel, tgURN, nil, "", clog) ts.NoError(err) tgURNDisplay, _ := urns.NewTelegramURN(12345, "Jane") - displayContact, err := contactForURN(ctx, ts.b, tgChannel.OrgID_, tgChannel, tgURNDisplay, "", "", clog) + displayContact, err := contactForURN(ctx, ts.b, tgChannel.OrgID_, tgChannel, tgURNDisplay, nil, "", clog) ts.NoError(err) ts.Equal(tgContact.URNID_, displayContact.URNID_) @@ -389,7 +389,7 @@ func (ts *BackendTestSuite) TestContactURN() { tx, err = ts.b.db.Beginx() ts.NoError(err) - tgContactURN, err := getOrCreateContactURN(tx, tgChannel, tgContact.ID_, tgURNDisplay, "") + tgContactURN, err := getOrCreateContactURN(tx, tgChannel, tgContact.ID_, tgURNDisplay, nil) ts.NoError(err) ts.NoError(tx.Commit()) ts.Equal(tgContact.URNID_, tgContactURN.ID) @@ -402,13 +402,13 @@ func (ts *BackendTestSuite) TestContactURN() { wait.Add(2) go func() { var err2 error - contact2, err2 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn2, "", "", clog) + contact2, err2 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn2, nil, "", clog) ts.NoError(err2) wait.Done() }() go func() { var err3 error - contact3, err3 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn2, "", "", clog) + contact3, err3 = contactForURN(ctx, ts.b, knChannel.OrgID(), knChannel, urn2, nil, "", clog) ts.NoError(err3) wait.Done() }() @@ -428,19 +428,19 @@ func (ts *BackendTestSuite) TestContactURNPriority() { ctx := context.Background() - knContact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, knURN, "", "", clog) + knContact, err := contactForURN(ctx, ts.b, knChannel.OrgID_, knChannel, knURN, nil, "", clog) ts.NoError(err) tx, err := ts.b.db.Beginx() ts.NoError(err) - _, err = getOrCreateContactURN(tx, twChannel, knContact.ID_, twURN, "") + _, err = getOrCreateContactURN(tx, twChannel, knContact.ID_, twURN, nil) ts.NoError(err) ts.NoError(tx.Commit()) // ok, now looking up our contact should reset our URNs and their affinity.. // TwitterURN should be first all all URNs should now use Twitter channel - twContact, err := contactForURN(ctx, ts.b, twChannel.OrgID_, twChannel, twURN, "", "", clog) + twContact, err := contactForURN(ctx, ts.b, twChannel.OrgID_, twChannel, twURN, nil, "", clog) ts.NoError(err) ts.Equal(twContact.ID_, knContact.ID_) @@ -622,7 +622,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { // update URN when the new doesn't exist tx, _ := ts.b.db.BeginTxx(ctx, nil) oldURN, _ := urns.NewWhatsAppURN("55988776655") - _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, oldURN, "")) + _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, oldURN, nil)) ts.NoError(tx.Commit()) @@ -643,8 +643,8 @@ func (ts *BackendTestSuite) TestMsgStatus() { oldURN, _ = urns.NewWhatsAppURN("55999887766") newURN, _ = urns.NewWhatsAppURN("5599887766") tx, _ = ts.b.db.BeginTxx(ctx, nil) - contact, _ := contactForURN(ctx, ts.b, channel.OrgID_, channel, oldURN, "", "", clog6) - _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, newURN, "")) + contact, _ := contactForURN(ctx, ts.b, channel.OrgID_, channel, oldURN, nil, "", clog6) + _ = insertContactURN(tx, newContactURN(channel.OrgID_, channel.ID_, NilContactID, newURN, nil)) ts.NoError(tx.Commit()) @@ -665,8 +665,8 @@ func (ts *BackendTestSuite) TestMsgStatus() { oldURN, _ = urns.NewWhatsAppURN("55988776655") newURN, _ = urns.NewWhatsAppURN("5588776655") tx, _ = ts.b.db.BeginTxx(ctx, nil) - _, _ = contactForURN(ctx, ts.b, channel.OrgID_, channel, oldURN, "", "", clog6) - otherContact, _ := contactForURN(ctx, ts.b, channel.OrgID_, channel, newURN, "", "", clog6) + _, _ = contactForURN(ctx, ts.b, channel.OrgID_, channel, oldURN, nil, "", clog6) + otherContact, _ := contactForURN(ctx, ts.b, channel.OrgID_, channel, newURN, nil, "", clog6) ts.NoError(tx.Commit()) @@ -1115,7 +1115,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { ts.NoError(err) // load our URN - contactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, "") + contactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, nil) if !ts.NoError(err) || !ts.NoError(tx.Commit()) { ts.FailNow("failed writing contact urn") } @@ -1140,7 +1140,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { ts.NotNil(m.ModifiedOn_) ts.NotNil(m.QueuedOn_) - contact, err := contactForURN(ctx, ts.b, m.OrgID_, knChannel, urn, "", "", clog) + contact, err := contactForURN(ctx, ts.b, m.OrgID_, knChannel, urn, nil, "", clog) ts.NoError(err) ts.Equal(null.String("test contact"), contact.Name_) ts.Equal(m.OrgID_, contact.OrgID_) @@ -1255,7 +1255,7 @@ func (ts *BackendTestSuite) TestPreferredChannelCheckRole() { ts.NoError(err) // load our URN - exContactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, "") + exContactURN, err := getOrCreateContactURN(tx, m.channel, m.ContactID_, urn, nil) if !ts.NoError(err) || !ts.NoError(tx.Commit()) { ts.FailNow("failed writing contact urn") } @@ -1273,7 +1273,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) - contact, err := contactForURN(ctx, ts.b, channel.OrgID_, channel, urn, "", "", clog) + contact, err := contactForURN(ctx, ts.b, channel.OrgID_, channel, urn, nil, "", clog) ts.NoError(err) ts.Equal(null.String("kermit frog"), contact.Name_) @@ -1315,7 +1315,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) - contact, err := contactForURN(ctx, ts.b, channel.OrgID_, channel, urn, "", "", clog) + contact, err := contactForURN(ctx, ts.b, channel.OrgID_, channel, urn, nil, "", clog) ts.NoError(err) ts.Equal(null.String("kermit frog"), contact.Name_) diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index cf1a42885..f1bcb12e1 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -81,7 +81,7 @@ RETURNING id` // writeChannelEventToDB writes the passed in msg status to our db func writeChannelEventToDB(ctx context.Context, b *backend, e *DBChannelEvent, clog *courier.ChannelLog) error { // grab the contact for this event - contact, err := contactForURN(ctx, b, e.OrgID_, e.channel, e.URN_, "", e.ContactName_, clog) + contact, err := contactForURN(ctx, b, e.OrgID_, e.channel, e.URN_, e.URNAuthTokens_, e.ContactName_, clog) if err != nil { return err } @@ -171,10 +171,13 @@ type DBChannelEvent struct { CreatedOn_ time.Time `json:"created_on" db:"created_on"` LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` - ContactName_ string `json:"contact_name"` ContactID_ ContactID `json:"-" db:"contact_id"` ContactURNID_ ContactURNID `json:"-" db:"contact_urn_id"` + // used to update contact + ContactName_ string `json:"contact_name"` + URNAuthTokens_ map[string]string `json:"auth_tokens"` + channel *DBChannel } @@ -182,6 +185,7 @@ func (e *DBChannelEvent) EventID() int64 { return int64(e.I func (e *DBChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID_ } func (e *DBChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } func (e *DBChannelEvent) ContactName() string { return e.ContactName_ } +func (e *DBChannelEvent) AuthTokens() map[string]string { return e.URNAuthTokens_ } func (e *DBChannelEvent) URN() urns.URN { return e.URN_ } func (e *DBChannelEvent) Extra() map[string]string { return e.Extra_ } func (e *DBChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } @@ -193,6 +197,12 @@ func (e *DBChannelEvent) WithContactName(name string) courier.ChannelEvent { e.ContactName_ = name return e } + +func (e *DBChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.ChannelEvent { + e.URNAuthTokens_ = tokens + return e +} + func (e *DBChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { e.Extra_ = null.Map[string](extra) return e diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index ff80cbfa6..4ee3179c8 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -81,7 +81,7 @@ WHERE ` // contactForURN first tries to look up a contact for the passed in URN, if not finding one then creating one -func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChannel, urn urns.URN, auth string, name string, clog *courier.ChannelLog) (*DBContact, error) { +func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChannel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (*DBContact, error) { // try to look up our contact by URN contact := &DBContact{} err := b.db.GetContext(ctx, contact, lookupContactFromURNSQL, urn.Identity(), org) @@ -99,7 +99,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChanne return nil, errors.Wrap(err, "error beginning transaction") } - err = setDefaultURN(tx, channel, contact, urn, auth) + err = setDefaultURN(tx, channel, contact, urn, authTokens) if err != nil { logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") tx.Rollback() @@ -168,13 +168,13 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChanne // associate our URN // If we've inserted a duplicate URN then we'll get a uniqueness violation. // That means this contact URN was written by someone else after we tried to look it up. - contactURN, err := getOrCreateContactURN(tx, channel, contact.ID_, urn, auth) + contactURN, err := getOrCreateContactURN(tx, channel, contact.ID_, urn, authTokens) if err != nil { tx.Rollback() if dbutil.IsUniqueViolation(err) { // if this was a duplicate URN, start over with a contact lookup - return contactForURN(ctx, b, org, channel, urn, auth, name, clog) + return contactForURN(ctx, b, org, channel, urn, authTokens, name, clog) } return nil, errors.Wrap(err, "error getting URN for contact") } @@ -182,7 +182,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChanne // we stole the URN from another contact, roll back and start over if contactURN.PrevContactID != NilContactID { tx.Rollback() - return contactForURN(ctx, b, org, channel, urn, auth, name, clog) + return contactForURN(ctx, b, org, channel, urn, authTokens, name, clog) } // all is well, we created the new contact, commit and move forward diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index db5ac5e99..c9f13a527 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -146,8 +146,7 @@ INSERT INTO RETURNING id` func writeMsgToDB(ctx context.Context, b *backend, m *DBMsg, clog *courier.ChannelLog) error { - // grab the contact for this msg - contact, err := contactForURN(ctx, b, m.OrgID_, m.channel, m.URN_, m.URNAuth_, m.contactName, clog) + contact, err := contactForURN(ctx, b, m.OrgID_, m.channel, m.URN_, m.URNAuthTokens_, m.ContactName_, clog) // our db is down, write to the spool, we will write/queue this later if err != nil { @@ -325,33 +324,35 @@ type DBMsg struct { SessionWaitStartedOn_ *time.Time `json:"session_wait_started_on"` SessionStatus_ string `json:"session_status"` - contactName string + ContactName_ string `json:"contact_name"` + URNAuthTokens_ map[string]string `json:"auth_tokens"` channel *DBChannel workerToken queue.WorkerToken alreadyWritten bool } -func (m *DBMsg) ID() courier.MsgID { return m.ID_ } -func (m *DBMsg) EventID() int64 { return int64(m.ID_) } -func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } -func (m *DBMsg) Text() string { return m.Text_ } -func (m *DBMsg) Attachments() []string { return m.Attachments_ } -func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } -func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } -func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } -func (m *DBMsg) URN() urns.URN { return m.URN_ } -func (m *DBMsg) URNAuth() string { return m.URNAuth_ } -func (m *DBMsg) ContactName() string { return m.contactName } -func (m *DBMsg) HighPriority() bool { return m.HighPriority_ } -func (m *DBMsg) ReceivedOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) SentOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) ResponseToExternalID() string { return m.ResponseToExternalID_ } -func (m *DBMsg) IsResend() bool { return m.IsResend_ } -func (m *DBMsg) Channel() courier.Channel { return m.channel } -func (m *DBMsg) SessionStatus() string { return m.SessionStatus_ } -func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } -func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } -func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } +func (m *DBMsg) ID() courier.MsgID { return m.ID_ } +func (m *DBMsg) EventID() int64 { return int64(m.ID_) } +func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } +func (m *DBMsg) Text() string { return m.Text_ } +func (m *DBMsg) Attachments() []string { return m.Attachments_ } +func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } +func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } +func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } +func (m *DBMsg) URN() urns.URN { return m.URN_ } +func (m *DBMsg) URNAuth() string { return m.URNAuth_ } +func (m *DBMsg) URNAuthTokens() map[string]string { return m.URNAuthTokens_ } +func (m *DBMsg) ContactName() string { return m.ContactName_ } +func (m *DBMsg) HighPriority() bool { return m.HighPriority_ } +func (m *DBMsg) ReceivedOn() *time.Time { return m.SentOn_ } +func (m *DBMsg) SentOn() *time.Time { return m.SentOn_ } +func (m *DBMsg) ResponseToExternalID() string { return m.ResponseToExternalID_ } +func (m *DBMsg) IsResend() bool { return m.IsResend_ } +func (m *DBMsg) Channel() courier.Channel { return m.channel } +func (m *DBMsg) SessionStatus() string { return m.SessionStatus_ } +func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } +func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } +func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } func (m *DBMsg) FlowName() string { if m.Flow_ == nil { @@ -386,7 +387,7 @@ func (m *DBMsg) hash() string { } // WithContactName can be used to set the contact name on a msg -func (m *DBMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } +func (m *DBMsg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } // WithReceivedOn can be used to set sent_on on a msg in a chained call func (m *DBMsg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } @@ -411,8 +412,14 @@ func (m *DBMsg) WithAttachment(url string) courier.Msg { func (m *DBMsg) WithLocale(lc i18n.Locale) courier.Msg { m.Locale_ = null.String(lc); return m } -// WithURNAuth can be used to add a URN auth setting to a message -func (m *DBMsg) WithURNAuth(auth string) courier.Msg { - m.URNAuth_ = auth +// WithURNAuth sets the URN auth to be used for sending (only used to create messages in tests) +func (m *DBMsg) WithURNAuth(token string) courier.Msg { + m.URNAuth_ = token + return m +} + +// WithURNAuthTokens can be used to save URN auth tokens from an incoming message +func (m *DBMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { + m.URNAuthTokens_ = tokens return m } diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 62c6b5b35..0263880de 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -7,6 +7,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" + "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/null/v3" "github.com/pkg/errors" @@ -40,7 +41,7 @@ type ContactURN struct { } // returns a new ContactURN object for the passed in org, contact and string URN -func newContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, urn urns.URN, auth string) *ContactURN { +func newContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, urn urns.URN, authTokens map[string]string) *ContactURN { return &ContactURN{ OrgID: org, ChannelID: channelID, @@ -49,7 +50,7 @@ func newContactURN(org OrgID, channelID courier.ChannelID, contactID ContactID, Scheme: urn.Scheme(), Path: urn.Path(), Display: null.String(urn.Display()), - AuthTokens: null.Map[string]{"default": auth}, + AuthTokens: null.Map[string](authTokens), } } @@ -93,7 +94,7 @@ func getURNsForContact(db *sqlx.Tx, contactID ContactID) ([]*ContactURN, error) // that the passed in channel is the default one for that URN // // Note that the URN must be one of the contact's URN before calling this method -func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns.URN, auth string) error { +func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns.URN, authTokens map[string]string) error { scheme := urn.Scheme() contactURNs, err := getURNsForContact(db, contact.ID_) if err != nil { @@ -110,17 +111,18 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns if contactURNs[0].Identity == string(urn.Identity()) { display := urn.Display() - // if display, channel id or auth changed, update them - if string(contactURNs[0].Display) != display || contactURNs[0].ChannelID != channel.ID() || (auth != "" && contactURNs[0].AuthTokens["default"] != auth) { + // if display, channel id or auth tokens changed, update them + if string(contactURNs[0].Display) != display || contactURNs[0].ChannelID != channel.ID() || (authTokens != nil && !utils.MapContains(contactURNs[0].AuthTokens, authTokens)) { contactURNs[0].Display = null.String(display) if channel.HasRole(courier.ChannelRoleSend) { contactURNs[0].ChannelID = channel.ID() } - if auth != "" { - contactURNs[0].AuthTokens = null.Map[string]{"default": auth} + for k, v := range authTokens { + contactURNs[0].AuthTokens[k] = v } + return updateContactURN(db, contactURNs[0]) } return nil @@ -140,8 +142,8 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns existing.ChannelID = channel.ID() } - if auth != "" { - existing.AuthTokens = null.Map[string]{"default": auth} + for k, v := range authTokens { + contactURNs[0].AuthTokens[k] = v } } else { existing.Priority = currPriority @@ -163,7 +165,7 @@ func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns // getContactURNByIdentity returns the ContactURN for the passed in org and identity func getContactURNByIdentity(db *sqlx.Tx, org OrgID, urn urns.URN) (*ContactURN, error) { - contactURN := newContactURN(org, courier.NilChannelID, NilContactID, urn, "") + contactURN := newContactURN(org, courier.NilChannelID, NilContactID, urn, map[string]string{}) err := db.Get(contactURN, sqlSelectURNByIdentity, org, urn.Identity()) if err != nil { return nil, err @@ -173,8 +175,8 @@ func getContactURNByIdentity(db *sqlx.Tx, org OrgID, urn urns.URN) (*ContactURN, // getOrCreateContactURN returns the ContactURN for the passed in org and URN, creating and associating // it with the passed in contact if necessary -func getOrCreateContactURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn urns.URN, auth string) (*ContactURN, error) { - contactURN := newContactURN(channel.OrgID(), courier.NilChannelID, contactID, urn, auth) +func getOrCreateContactURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn urns.URN, authTokens map[string]string) (*ContactURN, error) { + contactURN := newContactURN(channel.OrgID(), courier.NilChannelID, contactID, urn, authTokens) if channel.HasRole(courier.ChannelRoleSend) { contactURN.ChannelID = channel.ID() } @@ -208,8 +210,10 @@ func getOrCreateContactURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, } // update our auth if we have a value set - if auth != "" && auth != contactURN.AuthTokens["default"] { - contactURN.AuthTokens = null.Map[string]{"default": auth} + if authTokens != nil { + for k, v := range authTokens { + contactURN.AuthTokens[k] = v + } err = updateContactURN(db, contactURN) } diff --git a/channel_event.go b/channel_event.go index 62648721d..ee64860d4 100644 --- a/channel_event.go +++ b/channel_event.go @@ -31,6 +31,7 @@ type ChannelEvent interface { OccurredOn() time.Time WithContactName(name string) ChannelEvent + WithURNAuthTokens(tokens map[string]string) ChannelEvent WithExtra(extra map[string]string) ChannelEvent WithOccurredOn(time.Time) ChannelEvent diff --git a/handlers/facebook_legacy/facebook.go b/handlers/facebook_legacy/facebook.go index b36084148..9e39bda86 100644 --- a/handlers/facebook_legacy/facebook.go +++ b/handlers/facebook_legacy/facebook.go @@ -581,11 +581,11 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann clog.RawError(errors.Errorf("unable to make facebook urn from %s", recipientID)) } - contact, err := h.Backend().GetContact(ctx, msg.Channel(), msg.URN(), "", "", clog) + 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) + 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())) } @@ -593,7 +593,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann 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) + 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())) } diff --git a/handlers/firebase/firebase.go b/handlers/firebase/firebase.go index 23abfd16b..553cb9c0a 100644 --- a/handlers/firebase/firebase.go +++ b/handlers/firebase/firebase.go @@ -74,8 +74,14 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } + // if a new auth token was provided, record that + var authTokens map[string]string + if form.FCMToken != "" { + authTokens = map[string]string{"default": form.FCMToken} + } + // build our msg - dbMsg := h.Backend().NewIncomingMsg(channel, urn, form.Msg, "", clog).WithReceivedOn(date).WithContactName(form.Name).WithURNAuth(form.FCMToken) + dbMsg := h.Backend().NewIncomingMsg(channel, urn, form.Msg, "", clog).WithReceivedOn(date).WithContactName(form.Name).WithURNAuthTokens(authTokens) // and finally write our message return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{dbMsg}, w, r, clog) @@ -102,7 +108,7 @@ func (h *handler) registerContact(ctx context.Context, channel courier.Channel, } // create our contact - contact, err := h.Backend().GetContact(ctx, channel, urn, form.FCMToken, form.Name, clog) + contact, err := h.Backend().GetContact(ctx, channel, urn, map[string]string{"default": form.FCMToken}, form.Name, clog) if err != nil { return nil, err } diff --git a/handlers/meta/meta.go b/handlers/meta/meta.go index bf126852a..ec90ed724 100644 --- a/handlers/meta/meta.go +++ b/handlers/meta/meta.go @@ -796,11 +796,11 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, clog.RawError(errors.Errorf("unable to make facebook urn from %s", recipientID)) } - contact, err := h.Backend().GetContact(ctx, msg.Channel(), msg.URN(), "", "", clog) + 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) + 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())) } @@ -808,7 +808,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, 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) + 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())) } diff --git a/handlers/test.go b/handlers/test.go index db1fe09d6..248aa3c6e 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -181,7 +181,7 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Equal(t, tc.ExpectedExternalID, msg.ExternalID()) } assert.Equal(t, tc.ExpectedURN, msg.URN()) - assert.Equal(t, tc.ExpectedURNAuth, msg.URNAuth()) + assert.Equal(t, tc.ExpectedURNAuth, msg.URNAuthTokens()["default"]) } else { assert.Empty(t, mb.WrittenMsgs(), "unexpected msg written") } @@ -431,7 +431,7 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier if tc.ExpectedContactURNs != nil { var contactUUID courier.ContactUUID for urn, shouldBePresent := range tc.ExpectedContactURNs { - contact, _ := mb.GetContact(ctx, channel, urns.URN(urn), "", "", clog) + contact, _ := mb.GetContact(ctx, channel, urns.URN(urn), nil, "", clog) if contactUUID == courier.NilContactUUID && shouldBePresent { contactUUID = contact.UUID() } diff --git a/msg.go b/msg.go index 7e19f35cd..d79fb5a1d 100644 --- a/msg.go +++ b/msg.go @@ -60,6 +60,7 @@ type Msg interface { ExternalID() string URN() urns.URN URNAuth() string + URNAuthTokens() map[string]string ContactName() string QuickReplies() []string Origin() MsgOrigin @@ -86,7 +87,8 @@ type Msg interface { WithUUID(uuid MsgUUID) Msg WithAttachment(url string) Msg WithLocale(i18n.Locale) Msg - WithURNAuth(auth string) Msg + WithURNAuth(token string) Msg + WithURNAuthTokens(tokens map[string]string) Msg WithMetadata(metadata json.RawMessage) Msg WithFlow(flow *FlowReference) Msg diff --git a/test/backend.go b/test/backend.go index c0a2734fa..ce4054c2a 100644 --- a/test/backend.go +++ b/test/backend.go @@ -281,17 +281,17 @@ func (mb *MockBackend) GetChannelByAddress(ctx context.Context, cType courier.Ch } // GetContact creates a new contact with the passed in channel and URN -func (mb *MockBackend) GetContact(ctx context.Context, channel courier.Channel, urn urns.URN, auth, name string, clog *courier.ChannelLog) (courier.Contact, error) { +func (mb *MockBackend) GetContact(ctx context.Context, channel courier.Channel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (courier.Contact, error) { contact, found := mb.contacts[urn] if !found { - contact = &mockContact{channel, urn, auth, courier.ContactUUID(uuids.New())} + contact = &mockContact{channel, urn, authTokens, courier.ContactUUID(uuids.New())} mb.contacts[urn] = contact } return contact, nil } // AddURNtoContact adds a URN to the passed in contact -func (mb *MockBackend) AddURNtoContact(context context.Context, channel courier.Channel, contact courier.Contact, urn urns.URN) (urns.URN, error) { +func (mb *MockBackend) AddURNtoContact(context context.Context, channel courier.Channel, contact courier.Contact, urn urns.URN, authTokens map[string]string) (urns.URN, error) { mb.contacts[urn] = contact return urn, nil } diff --git a/test/channel_event.go b/test/channel_event.go index 24f1be2c1..177f5300c 100644 --- a/test/channel_event.go +++ b/test/channel_event.go @@ -14,8 +14,9 @@ type mockChannelEvent struct { createdOn time.Time occurredOn time.Time - contactName string - extra map[string]string + contactName string + urnAuthTokens map[string]string + extra map[string]string } func (e *mockChannelEvent) EventID() int64 { return 0 } @@ -31,10 +32,17 @@ func (e *mockChannelEvent) WithExtra(extra map[string]string) courier.ChannelEve e.extra = extra return e } + func (e *mockChannelEvent) WithContactName(name string) courier.ChannelEvent { e.contactName = name return e } + +func (e *mockChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.ChannelEvent { + e.urnAuthTokens = tokens + return e +} + func (e *mockChannelEvent) WithOccurredOn(time time.Time) courier.ChannelEvent { e.occurredOn = time return e diff --git a/test/contact.go b/test/contact.go index 4952780b7..aa80fcccc 100644 --- a/test/contact.go +++ b/test/contact.go @@ -6,10 +6,10 @@ import ( ) type mockContact struct { - channel courier.Channel - urn urns.URN - auth string - uuid courier.ContactUUID + channel courier.Channel + urn urns.URN + authTokens map[string]string + uuid courier.ContactUUID } func (c *mockContact) UUID() courier.ContactUUID { return c.uuid } diff --git a/test/msg.go b/test/msg.go index 66ad6eea1..88a2229dc 100644 --- a/test/msg.go +++ b/test/msg.go @@ -15,6 +15,7 @@ type mockMsg struct { channel courier.Channel urn urns.URN urnAuth string + urnAuthTokens map[string]string text string attachments []string locale i18n.Locale @@ -64,31 +65,39 @@ func (m *mockMsg) FlowUUID() string { return m.flow.UUID } -func (m *mockMsg) Channel() courier.Channel { return m.channel } -func (m *mockMsg) ID() courier.MsgID { return m.id } -func (m *mockMsg) EventID() int64 { return int64(m.id) } -func (m *mockMsg) UUID() courier.MsgUUID { return m.uuid } -func (m *mockMsg) Text() string { return m.text } -func (m *mockMsg) Attachments() []string { return m.attachments } -func (m *mockMsg) Locale() i18n.Locale { return m.locale } -func (m *mockMsg) ExternalID() string { return m.externalID } -func (m *mockMsg) URN() urns.URN { return m.urn } -func (m *mockMsg) URNAuth() string { return m.urnAuth } -func (m *mockMsg) ContactName() string { return m.contactName } -func (m *mockMsg) HighPriority() bool { return m.highPriority } -func (m *mockMsg) QuickReplies() []string { return m.quickReplies } -func (m *mockMsg) Origin() courier.MsgOrigin { return m.origin } -func (m *mockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } -func (m *mockMsg) Topic() string { return m.topic } -func (m *mockMsg) ResponseToExternalID() string { return m.responseToExternalID } -func (m *mockMsg) Metadata() json.RawMessage { return m.metadata } -func (m *mockMsg) IsResend() bool { return m.isResend } -func (m *mockMsg) ReceivedOn() *time.Time { return m.receivedOn } -func (m *mockMsg) SentOn() *time.Time { return m.sentOn } -func (m *mockMsg) WiredOn() *time.Time { return m.wiredOn } +func (m *mockMsg) Channel() courier.Channel { return m.channel } +func (m *mockMsg) ID() courier.MsgID { return m.id } +func (m *mockMsg) EventID() int64 { return int64(m.id) } +func (m *mockMsg) UUID() courier.MsgUUID { return m.uuid } +func (m *mockMsg) Text() string { return m.text } +func (m *mockMsg) Attachments() []string { return m.attachments } +func (m *mockMsg) Locale() i18n.Locale { return m.locale } +func (m *mockMsg) ExternalID() string { return m.externalID } +func (m *mockMsg) URN() urns.URN { return m.urn } +func (m *mockMsg) URNAuth() string { return m.urnAuth } +func (m *mockMsg) URNAuthTokens() map[string]string { return m.urnAuthTokens } +func (m *mockMsg) ContactName() string { return m.contactName } +func (m *mockMsg) HighPriority() bool { return m.highPriority } +func (m *mockMsg) QuickReplies() []string { return m.quickReplies } +func (m *mockMsg) Origin() courier.MsgOrigin { return m.origin } +func (m *mockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } +func (m *mockMsg) Topic() string { return m.topic } +func (m *mockMsg) ResponseToExternalID() string { return m.responseToExternalID } +func (m *mockMsg) Metadata() json.RawMessage { return m.metadata } +func (m *mockMsg) IsResend() bool { return m.isResend } +func (m *mockMsg) ReceivedOn() *time.Time { return m.receivedOn } +func (m *mockMsg) SentOn() *time.Time { return m.sentOn } +func (m *mockMsg) WiredOn() *time.Time { return m.wiredOn } -func (m *mockMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } -func (m *mockMsg) WithURNAuth(auth string) courier.Msg { m.urnAuth = auth; return m } +func (m *mockMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } +func (m *mockMsg) WithURNAuth(token string) courier.Msg { + m.urnAuth = token + return m +} +func (m *mockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { + m.urnAuthTokens = tokens + return m +} func (m *mockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } func (m *mockMsg) WithID(id courier.MsgID) courier.Msg { m.id = id; return m } func (m *mockMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.uuid = uuid; return m } diff --git a/utils/misc.go b/utils/misc.go index 7b1c7f879..99c19cde3 100644 --- a/utils/misc.go +++ b/utils/misc.go @@ -133,3 +133,14 @@ func ChunkSlice[T any](slice []T, size int) [][]T { } return chunks } + +// MapContains returns whether m1 contains all the key value pairs in m2 +func MapContains[K comparable, V comparable, M ~map[K]V](m1 M, m2 M) bool { + for k, v2 := range m2 { + v1, ok := m1[k] + if !ok || v1 != v2 { + return false + } + } + return true +} diff --git a/utils/misc_test.go b/utils/misc_test.go index 9dc055266..50d16b548 100644 --- a/utils/misc_test.go +++ b/utils/misc_test.go @@ -103,3 +103,11 @@ func TestStringsToRows(t *testing.T) { assert.Equal(t, tc.expected, rows, "rows mismatch for replies %v", tc.replies) } } + +func TestMapContains(t *testing.T) { + assert.True(t, utils.MapContains(map[string]string{}, map[string]string{})) + assert.True(t, utils.MapContains(map[string]string{"a": "1", "b": "2", "c": "3"}, map[string]string{"a": "1"})) + assert.True(t, utils.MapContains(map[string]string{"a": "1", "b": "2", "c": "3"}, map[string]string{"b": "2", "c": "3"})) + assert.False(t, utils.MapContains(map[string]string{"a": "1", "b": "2"}, map[string]string{"c": "3"})) + assert.False(t, utils.MapContains(map[string]string{"a": "1", "b": "2"}, map[string]string{"a": "4"})) +} From e25d376dd34b3f31e47015794a59630d93b4fdc7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 10:19:35 -0500 Subject: [PATCH 087/170] Simplify Msg interface --- backends/rapidpro/backend_test.go | 4 --- backends/rapidpro/msg.go | 14 -------- handlers/firebase/firebase_test.go | 20 ++++++------ handlers/highconnection/highconnection.go | 9 +++-- handlers/test.go | 32 +++++++++--------- msg.go | 40 +++++++++++------------ test/msg.go | 19 ++--------- 7 files changed, 55 insertions(+), 83 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 73c23b835..b632029fd 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -140,8 +140,6 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { ts.True(msg.IsResend()) flow_ref := courier.FlowReference{UUID: "9de3663f-c5c5-4c92-9f45-ecbc09abcc85", Name: "Favorites"} ts.Equal(&flow_ref, msg.Flow()) - ts.Equal("Favorites", msg.FlowName()) - ts.Equal("9de3663f-c5c5-4c92-9f45-ecbc09abcc85", msg.FlowUUID()) msgJSONNoQR := `{ "text": "Test message 21", @@ -168,8 +166,6 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { ts.Equal("", msg.ResponseToExternalID()) ts.False(msg.IsResend()) ts.Nil(msg.Flow()) - ts.Equal("", msg.FlowName()) - ts.Equal("", msg.FlowUUID()) } func (ts *BackendTestSuite) TestDeleteMsgByExternalID() { diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index c9f13a527..45c2a7249 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -354,20 +354,6 @@ func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } -func (m *DBMsg) FlowName() string { - if m.Flow_ == nil { - return "" - } - return m.Flow_.Name -} - -func (m *DBMsg) FlowUUID() string { - if m.Flow_ == nil { - return "" - } - return m.Flow_.UUID -} - func (m *DBMsg) Topic() string { if m.Metadata_ == nil { return "" diff --git a/handlers/firebase/firebase_test.go b/handlers/firebase/firebase_test.go index 81c2ade13..cfdc82629 100644 --- a/handlers/firebase/firebase_test.go +++ b/handlers/firebase/firebase_test.go @@ -45,16 +45,16 @@ var testChannels = []courier.Channel{ var testCases = []IncomingTestCase{ { - Label: "Receive Valid Message", - URL: receiveURL, - Data: "from=12345&date=2017-01-01T08:50:00.000&fcm_token=token&name=fred&msg=hello+world", - ExpectedRespStatus: 200, - ExpectedBodyContains: "Accepted", - ExpectedMsgText: Sp("hello world"), - ExpectedURN: "fcm:12345", - ExpectedDate: time.Date(2017, 1, 1, 8, 50, 0, 0, time.UTC), - ExpectedURNAuth: "token", - ExpectedContactName: Sp("fred"), + Label: "Receive Valid Message", + URL: receiveURL, + Data: "from=12345&date=2017-01-01T08:50:00.000&fcm_token=token&name=fred&msg=hello+world", + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedMsgText: Sp("hello world"), + ExpectedURN: "fcm:12345", + ExpectedDate: time.Date(2017, 1, 1, 8, 50, 0, 0, time.UTC), + ExpectedURNAuthTokens: map[string]string{"default": "token"}, + ExpectedContactName: Sp("fred"), }, { Label: "Receive Invalid Date", diff --git a/handlers/highconnection/highconnection.go b/handlers/highconnection/highconnection.go index 5e5785b60..bb6c4ae8d 100644 --- a/handlers/highconnection/highconnection.go +++ b/handlers/highconnection/highconnection.go @@ -139,8 +139,13 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) parts := handlers.SplitMsgByChannel(msg.Channel(), handlers.GetTextAndAttachments(msg), maxMsgLength) - for _, part := range parts { + var flowName string + if msg.Flow() != nil { + flowName = msg.Flow().Name + } + + for _, part := range parts { form := url.Values{ "accountid": []string{username}, "password": []string{password}, @@ -148,7 +153,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann "to": []string{msg.URN().Path()}, "ret_id": []string{msg.ID().String()}, "datacoding": []string{"8"}, - "user_data": []string{msg.FlowName()}, + "user_data": []string{flowName}, "ret_url": []string{statusURL}, "ret_mo_url": []string{receiveURL}, } diff --git a/handlers/test.go b/handlers/test.go index 248aa3c6e..d9b16e1b5 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -40,21 +40,21 @@ type IncomingTestCase struct { Headers map[string]string MultipartForm map[string]string - ExpectedRespStatus int - ExpectedBodyContains string - ExpectedContactName *string - ExpectedMsgText *string - ExpectedURN urns.URN - ExpectedURNAuth string - ExpectedAttachments []string - ExpectedDate time.Time - ExpectedMsgStatus courier.MsgStatus - ExpectedExternalID string - ExpectedMsgID int64 - ExpectedEvent courier.ChannelEventType - ExpectedEventExtra map[string]string - ExpectedErrors []*courier.ChannelError - NoLogsExpected bool + ExpectedRespStatus int + ExpectedBodyContains string + ExpectedContactName *string + ExpectedMsgText *string + ExpectedURN urns.URN + ExpectedURNAuthTokens map[string]string + ExpectedAttachments []string + ExpectedDate time.Time + ExpectedMsgStatus courier.MsgStatus + ExpectedExternalID string + ExpectedMsgID int64 + ExpectedEvent courier.ChannelEventType + ExpectedEventExtra map[string]string + ExpectedErrors []*courier.ChannelError + NoLogsExpected bool } // MockedRequest is a fake HTTP request @@ -181,7 +181,7 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Equal(t, tc.ExpectedExternalID, msg.ExternalID()) } assert.Equal(t, tc.ExpectedURN, msg.URN()) - assert.Equal(t, tc.ExpectedURNAuth, msg.URNAuthTokens()["default"]) + assert.Equal(t, tc.ExpectedURNAuthTokens, msg.URNAuthTokens()) } else { assert.Empty(t, mb.WrittenMsgs(), "unexpected msg written") } diff --git a/msg.go b/msg.go index d79fb5a1d..0a7bbac4d 100644 --- a/msg.go +++ b/msg.go @@ -52,46 +52,46 @@ const ( // Msg is our interface to represent an incoming or outgoing message type Msg interface { + Event + ID() MsgID UUID() MsgUUID + ExternalID() string Text() string Attachments() []string - Locale() i18n.Locale - ExternalID() string URN() urns.URN - URNAuth() string - URNAuthTokens() map[string]string - ContactName() string + Channel() Channel + + // outgoing specific QuickReplies() []string + Locale() i18n.Locale + URNAuth() string Origin() MsgOrigin ContactLastSeenOn() *time.Time Topic() string Metadata() json.RawMessage ResponseToExternalID() string + SentOn() *time.Time IsResend() bool - Flow() *FlowReference - FlowName() string - FlowUUID() string - - Channel() Channel + SessionStatus() string + HighPriority() bool + // incoming specific + URNAuthTokens() map[string]string + ContactName() string ReceivedOn() *time.Time - SentOn() *time.Time - - HighPriority() bool + WithAttachment(url string) Msg WithContactName(name string) Msg + WithURNAuthTokens(tokens map[string]string) Msg WithReceivedOn(date time.Time) Msg + + // only used to create outgoing messages for testing WithID(id MsgID) Msg WithUUID(uuid MsgUUID) Msg - WithAttachment(url string) Msg - WithLocale(i18n.Locale) Msg - WithURNAuth(token string) Msg - WithURNAuthTokens(tokens map[string]string) Msg WithMetadata(metadata json.RawMessage) Msg WithFlow(flow *FlowReference) Msg - - EventID() int64 - SessionStatus() string + WithLocale(i18n.Locale) Msg + WithURNAuth(token string) Msg } diff --git a/test/msg.go b/test/msg.go index 88a2229dc..aad3ef39e 100644 --- a/test/msg.go +++ b/test/msg.go @@ -48,23 +48,8 @@ func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, } } -func (m *mockMsg) SessionStatus() string { return "" } -func (m *mockMsg) Flow() *courier.FlowReference { return m.flow } - -func (m *mockMsg) FlowName() string { - if m.flow == nil { - return "" - } - return m.flow.Name -} - -func (m *mockMsg) FlowUUID() string { - if m.flow == nil { - return "" - } - return m.flow.UUID -} - +func (m *mockMsg) SessionStatus() string { return "" } +func (m *mockMsg) Flow() *courier.FlowReference { return m.flow } func (m *mockMsg) Channel() courier.Channel { return m.channel } func (m *mockMsg) ID() courier.MsgID { return m.id } func (m *mockMsg) EventID() int64 { return int64(m.id) } From 0761293d590b216ee68d0942f5ed9992b841d8bc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 10:52:34 -0500 Subject: [PATCH 088/170] Remove test specific methods from Msg interface and RP Msg implementation --- backends/rapidpro/msg.go | 91 ++++++++++++++-------------------------- handlers/test.go | 4 +- msg.go | 11 ----- responses_test.go | 2 +- test/backend.go | 8 ++-- test/msg.go | 87 +++++++++++++++++++------------------- 6 files changed, 82 insertions(+), 121 deletions(-) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 45c2a7249..c30213c62 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -331,29 +331,21 @@ type DBMsg struct { alreadyWritten bool } -func (m *DBMsg) ID() courier.MsgID { return m.ID_ } -func (m *DBMsg) EventID() int64 { return int64(m.ID_) } -func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } -func (m *DBMsg) Text() string { return m.Text_ } -func (m *DBMsg) Attachments() []string { return m.Attachments_ } -func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } -func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } -func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } -func (m *DBMsg) URN() urns.URN { return m.URN_ } -func (m *DBMsg) URNAuth() string { return m.URNAuth_ } -func (m *DBMsg) URNAuthTokens() map[string]string { return m.URNAuthTokens_ } -func (m *DBMsg) ContactName() string { return m.ContactName_ } -func (m *DBMsg) HighPriority() bool { return m.HighPriority_ } -func (m *DBMsg) ReceivedOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) SentOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) ResponseToExternalID() string { return m.ResponseToExternalID_ } -func (m *DBMsg) IsResend() bool { return m.IsResend_ } -func (m *DBMsg) Channel() courier.Channel { return m.channel } -func (m *DBMsg) SessionStatus() string { return m.SessionStatus_ } -func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } -func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } -func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } - +func (m *DBMsg) EventID() int64 { return int64(m.ID_) } +func (m *DBMsg) ID() courier.MsgID { return m.ID_ } +func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } +func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } +func (m *DBMsg) Text() string { return m.Text_ } +func (m *DBMsg) Attachments() []string { return m.Attachments_ } +func (m *DBMsg) URN() urns.URN { return m.URN_ } +func (m *DBMsg) Channel() courier.Channel { return m.channel } + +// outgoing specific +func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } +func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } +func (m *DBMsg) URNAuth() string { return m.URNAuth_ } +func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } +func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } func (m *DBMsg) Topic() string { if m.Metadata_ == nil { return "" @@ -361,51 +353,30 @@ func (m *DBMsg) Topic() string { topic, _, _, _ := jsonparser.Get(m.Metadata_, "topic") return string(topic) } - -// Metadata returns the metadata for this message func (m *DBMsg) Metadata() json.RawMessage { return m.Metadata_ } - -func (m *DBMsg) hash() string { - hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) - return hex.EncodeToString(hash[:]) -} - -// WithContactName can be used to set the contact name on a msg -func (m *DBMsg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } - -// WithReceivedOn can be used to set sent_on on a msg in a chained call -func (m *DBMsg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } - -// WithID can be used to set the id on a msg in a chained call -func (m *DBMsg) WithID(id courier.MsgID) courier.Msg { m.ID_ = id; return m } - -// WithUUID can be used to set the id on a msg in a chained call -func (m *DBMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.UUID_ = uuid; return m } - -// WithMetadata can be used to add metadata to a Msg -func (m *DBMsg) WithMetadata(metadata json.RawMessage) courier.Msg { m.Metadata_ = metadata; return m } - -// WithFlow can be used to add flow to a Msg -func (m *DBMsg) WithFlow(flow *courier.FlowReference) courier.Msg { m.Flow_ = flow; return m } - -// WithAttachment can be used to append to the media urls for a message +func (m *DBMsg) ResponseToExternalID() string { return m.ResponseToExternalID_ } +func (m *DBMsg) SentOn() *time.Time { return m.SentOn_ } +func (m *DBMsg) IsResend() bool { return m.IsResend_ } +func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } +func (m *DBMsg) SessionStatus() string { return m.SessionStatus_ } +func (m *DBMsg) HighPriority() bool { return m.HighPriority_ } + +// incoming specific +func (m *DBMsg) ReceivedOn() *time.Time { return m.SentOn_ } func (m *DBMsg) WithAttachment(url string) courier.Msg { m.Attachments_ = append(m.Attachments_, url) return m } - -func (m *DBMsg) WithLocale(lc i18n.Locale) courier.Msg { m.Locale_ = null.String(lc); return m } - -// WithURNAuth sets the URN auth to be used for sending (only used to create messages in tests) -func (m *DBMsg) WithURNAuth(token string) courier.Msg { - m.URNAuth_ = token - return m -} - -// WithURNAuthTokens can be used to save URN auth tokens from an incoming message +func (m *DBMsg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } func (m *DBMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { m.URNAuthTokens_ = tokens return m } +func (m *DBMsg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } + +func (m *DBMsg) hash() string { + hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) + return hex.EncodeToString(hash[:]) +} diff --git a/handlers/test.go b/handlers/test.go index d9b16e1b5..f2a593ec1 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -166,7 +166,7 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour if tc.ExpectedMsgText != nil || tc.ExpectedAttachments != nil { require.Len(mb.WrittenMsgs(), 1, "expected a msg to be written") - msg := mb.WrittenMsgs()[0] + msg := mb.WrittenMsgs()[0].(*test.MockMsg) if tc.ExpectedMsgText != nil { assert.Equal(t, *tc.ExpectedMsgText, msg.Text()) @@ -314,7 +314,7 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier t.Run(tc.Label, func(t *testing.T) { require := require.New(t) - msg := mb.NewOutgoingMsg(channel, 10, urns.URN(tc.MsgURN), tc.MsgText, tc.MsgHighPriority, tc.MsgQuickReplies, tc.MsgTopic, tc.MsgResponseToExternalID, msgOrigin, tc.MsgContactLastSeenOn) + msg := mb.NewOutgoingMsg(channel, 10, urns.URN(tc.MsgURN), tc.MsgText, tc.MsgHighPriority, tc.MsgQuickReplies, tc.MsgTopic, tc.MsgResponseToExternalID, msgOrigin, tc.MsgContactLastSeenOn).(*test.MockMsg) msg.WithLocale(tc.MsgLocale) for _, a := range tc.MsgAttachments { diff --git a/msg.go b/msg.go index 0a7bbac4d..e1185394c 100644 --- a/msg.go +++ b/msg.go @@ -78,20 +78,9 @@ type Msg interface { HighPriority() bool // incoming specific - URNAuthTokens() map[string]string - ContactName() string ReceivedOn() *time.Time - WithAttachment(url string) Msg WithContactName(name string) Msg WithURNAuthTokens(tokens map[string]string) Msg WithReceivedOn(date time.Time) Msg - - // only used to create outgoing messages for testing - WithID(id MsgID) Msg - WithUUID(uuid MsgUUID) Msg - WithMetadata(metadata json.RawMessage) Msg - WithFlow(flow *FlowReference) Msg - WithLocale(i18n.Locale) Msg - WithURNAuth(token string) Msg } diff --git a/responses_test.go b/responses_test.go index 2dbe4b9f6..ffea312bb 100644 --- a/responses_test.go +++ b/responses_test.go @@ -43,7 +43,7 @@ func TestWriteAndLogUnauthorized(t *testing.T) { func TestWriteMsgSuccess(t *testing.T) { ch := test.NewMockChannel("5fccf4b6-48d7-4f5a-bce8-b0d1fd5342ec", "NX", "+1234567890", "US", nil) - msg := test.NewMockBackend().NewIncomingMsg(ch, "tel:+0987654321", "hi there", "", nil).WithUUID("588aafc4-ab5c-48ce-89e8-05c9fdeeafb7") + msg := test.NewMockBackend().NewIncomingMsg(ch, "tel:+0987654321", "hi there", "", nil).(*test.MockMsg).WithUUID("588aafc4-ab5c-48ce-89e8-05c9fdeeafb7") w := httptest.NewRecorder() err := courier.WriteMsgSuccess(w, []courier.Msg{msg}) diff --git a/test/backend.go b/test/backend.go index ce4054c2a..30421e20b 100644 --- a/test/backend.go +++ b/test/backend.go @@ -97,7 +97,7 @@ func (mb *MockBackend) DeleteMsgByExternalID(ctx context.Context, channel courie // NewIncomingMsg creates a new message from the given params func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) courier.Msg { - m := &mockMsg{ + m := &MockMsg{ channel: channel, urn: urn, text: text, externalID: extID, } @@ -114,7 +114,7 @@ func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, tex func (mb *MockBackend) NewOutgoingMsg(channel courier.Channel, id courier.MsgID, urn urns.URN, text string, highPriority bool, quickReplies []string, topic string, responseToExternalID string, origin courier.MsgOrigin, contactLastSeenOn *time.Time) courier.Msg { - return &mockMsg{ + return &MockMsg{ channel: channel, id: id, urn: urn, @@ -190,7 +190,7 @@ func (mb *MockBackend) SetErrorOnQueue(shouldError bool) { // WriteMsg queues the passed in message internally func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courier.ChannelLog) error { - mock := m.(*mockMsg) + mock := m.(*MockMsg) // this msg has already been written (we received it twice), we are a no op if mock.alreadyWritten { @@ -205,7 +205,7 @@ func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courie } mb.writtenMsgs = append(mb.writtenMsgs, m) - mb.lastContactName = m.(*mockMsg).contactName + mb.lastContactName = m.(*MockMsg).contactName if m.ExternalID() != "" { mb.seenExternalIDs[fmt.Sprintf("%s|%s", m.Channel().UUID(), m.ExternalID())] = m.UUID() diff --git a/test/msg.go b/test/msg.go index aad3ef39e..c3285bc4c 100644 --- a/test/msg.go +++ b/test/msg.go @@ -9,7 +9,7 @@ import ( "github.com/nyaruka/gocommon/urns" ) -type mockMsg struct { +type MockMsg struct { id courier.MsgID uuid courier.MsgUUID channel courier.Channel @@ -35,11 +35,10 @@ type mockMsg struct { receivedOn *time.Time sentOn *time.Time - wiredOn *time.Time } -func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, urn urns.URN, text string) courier.Msg { - return &mockMsg{ +func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, urn urns.URN, text string) *MockMsg { + return &MockMsg{ id: id, uuid: uuid, channel: channel, @@ -48,49 +47,51 @@ func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, } } -func (m *mockMsg) SessionStatus() string { return "" } -func (m *mockMsg) Flow() *courier.FlowReference { return m.flow } -func (m *mockMsg) Channel() courier.Channel { return m.channel } -func (m *mockMsg) ID() courier.MsgID { return m.id } -func (m *mockMsg) EventID() int64 { return int64(m.id) } -func (m *mockMsg) UUID() courier.MsgUUID { return m.uuid } -func (m *mockMsg) Text() string { return m.text } -func (m *mockMsg) Attachments() []string { return m.attachments } -func (m *mockMsg) Locale() i18n.Locale { return m.locale } -func (m *mockMsg) ExternalID() string { return m.externalID } -func (m *mockMsg) URN() urns.URN { return m.urn } -func (m *mockMsg) URNAuth() string { return m.urnAuth } -func (m *mockMsg) URNAuthTokens() map[string]string { return m.urnAuthTokens } -func (m *mockMsg) ContactName() string { return m.contactName } -func (m *mockMsg) HighPriority() bool { return m.highPriority } -func (m *mockMsg) QuickReplies() []string { return m.quickReplies } -func (m *mockMsg) Origin() courier.MsgOrigin { return m.origin } -func (m *mockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } -func (m *mockMsg) Topic() string { return m.topic } -func (m *mockMsg) ResponseToExternalID() string { return m.responseToExternalID } -func (m *mockMsg) Metadata() json.RawMessage { return m.metadata } -func (m *mockMsg) IsResend() bool { return m.isResend } -func (m *mockMsg) ReceivedOn() *time.Time { return m.receivedOn } -func (m *mockMsg) SentOn() *time.Time { return m.sentOn } -func (m *mockMsg) WiredOn() *time.Time { return m.wiredOn } +func (m *MockMsg) EventID() int64 { return int64(m.id) } +func (m *MockMsg) ID() courier.MsgID { return m.id } +func (m *MockMsg) UUID() courier.MsgUUID { return m.uuid } +func (m *MockMsg) ExternalID() string { return m.externalID } +func (m *MockMsg) Text() string { return m.text } +func (m *MockMsg) Attachments() []string { return m.attachments } +func (m *MockMsg) URN() urns.URN { return m.urn } +func (m *MockMsg) Channel() courier.Channel { return m.channel } -func (m *mockMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } -func (m *mockMsg) WithURNAuth(token string) courier.Msg { - m.urnAuth = token +// outgoing specific +func (m *MockMsg) QuickReplies() []string { return m.quickReplies } +func (m *MockMsg) Locale() i18n.Locale { return m.locale } +func (m *MockMsg) URNAuth() string { return m.urnAuth } +func (m *MockMsg) Origin() courier.MsgOrigin { return m.origin } +func (m *MockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } +func (m *MockMsg) Topic() string { return m.topic } +func (m *MockMsg) Metadata() json.RawMessage { return m.metadata } +func (m *MockMsg) ResponseToExternalID() string { return m.responseToExternalID } +func (m *MockMsg) SentOn() *time.Time { return m.sentOn } +func (m *MockMsg) IsResend() bool { return m.isResend } +func (m *MockMsg) Flow() *courier.FlowReference { return m.flow } +func (m *MockMsg) SessionStatus() string { return "" } +func (m *MockMsg) HighPriority() bool { return m.highPriority } + +// incoming specific +func (m *MockMsg) ReceivedOn() *time.Time { return m.receivedOn } +func (m *MockMsg) WithAttachment(url string) courier.Msg { + m.attachments = append(m.attachments, url) return m } -func (m *mockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { +func (m *MockMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } +func (m *MockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { m.urnAuthTokens = tokens return m } -func (m *mockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } -func (m *mockMsg) WithID(id courier.MsgID) courier.Msg { m.id = id; return m } -func (m *mockMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.uuid = uuid; return m } -func (m *mockMsg) WithAttachment(url string) courier.Msg { - m.attachments = append(m.attachments, url) - return m -} -func (m *mockMsg) WithLocale(lc i18n.Locale) courier.Msg { m.locale = lc; return m } -func (m *mockMsg) WithMetadata(metadata json.RawMessage) courier.Msg { m.metadata = metadata; return m } +func (m *MockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } + +// used for testing created incoming messages +func (m *MockMsg) URNAuthTokens() map[string]string { return m.urnAuthTokens } +func (m *MockMsg) ContactName() string { return m.contactName } -func (m *mockMsg) WithFlow(flow *courier.FlowReference) courier.Msg { m.flow = flow; return m } +// used to create outgoing messages for testing +func (m *MockMsg) WithID(id courier.MsgID) courier.Msg { m.id = id; return m } +func (m *MockMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.uuid = uuid; return m } +func (m *MockMsg) WithMetadata(metadata json.RawMessage) courier.Msg { m.metadata = metadata; return m } +func (m *MockMsg) WithFlow(flow *courier.FlowReference) courier.Msg { m.flow = flow; return m } +func (m *MockMsg) WithLocale(lc i18n.Locale) courier.Msg { m.locale = lc; return m } +func (m *MockMsg) WithURNAuth(token string) courier.Msg { m.urnAuth = token; return m } From c402ec6600280731da4c017e635ab7c3c860cd3d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 11:22:53 -0500 Subject: [PATCH 089/170] Simplify StatusUpdate interface --- backends/rapidpro/backend.go | 16 ++++--- backends/rapidpro/backend_test.go | 6 +-- backends/rapidpro/status.go | 68 ++++++++++------------------ channel_event.go | 4 +- handler_test.go | 6 +-- handlers/test.go | 4 +- handlers/whatsapp_legacy/whatsapp.go | 2 +- log.go | 2 +- responses.go | 2 +- status.go | 9 ++-- test/backend.go | 6 +-- test/status.go | 28 +++++------- 12 files changed, 65 insertions(+), 88 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 29a240893..49f8bfca2 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -529,27 +529,29 @@ func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalI // WriteStatusUpdate writes the passed in MsgStatus to our store func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { - log := logrus.WithFields(logrus.Fields{"msg_id": status.ID(), "msg_external_id": status.ExternalID(), "status": status.Status()}) + log := logrus.WithFields(logrus.Fields{"msg_id": status.MsgID(), "msg_external_id": status.ExternalID(), "status": status.Status()}) su := status.(*StatusUpdate) - if status.ID() == courier.NilMsgID && status.ExternalID() == "" { + if status.MsgID() == courier.NilMsgID && status.ExternalID() == "" { return errors.New("message status with no id or external id") } - if status.HasUpdatedURN() { + // if we have a URN update, do that + oldURN, newURN := status.URNUpdate() + if oldURN != urns.NilURN && newURN != urns.NilURN { err := b.updateContactURN(ctx, status) if err != nil { return errors.Wrap(err, "error updating contact URN") } } - if status.ID() != courier.NilMsgID { + if status.MsgID() != courier.NilMsgID { // this is a message we've just sent and were given an external id for if status.ExternalID() != "" { rc := b.redisPool.Get() defer rc.Close() - err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.ID())) + err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.MsgID())) if err != nil { log.WithError(err).Error("error recording external id") } @@ -557,7 +559,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp // we sent a message that errored so clear our sent flag to allow it to be retried if status.Status() == courier.MsgStatusErrored { - err := b.ClearMsgSent(ctx, status.ID()) + err := b.ClearMsgSent(ctx, status.MsgID()) if err != nil { log.WithError(err).Error("error clearing sent flags") } @@ -573,7 +575,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp // updateContactURN updates contact URN according to the old/new URNs from status func (b *backend) updateContactURN(ctx context.Context, status courier.StatusUpdate) error { - old, new := status.UpdatedURN() + old, new := status.URNUpdate() // retrieve channel channel, err := b.GetChannel(ctx, courier.AnyChannelType, status.ChannelUUID()) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index b632029fd..59edccff6 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -624,7 +624,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { newURN, _ := urns.NewWhatsAppURN("5588776655") status = ts.b.NewStatusUpdate(channel, courier.MsgID(10000), courier.MsgStatusSent, clog6) - status.SetUpdatedURN(oldURN, newURN) + status.SetURNUpdate(oldURN, newURN) ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) @@ -645,7 +645,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) status = ts.b.NewStatusUpdate(channel, courier.MsgID(10007), courier.MsgStatusSent, clog6) - status.SetUpdatedURN(oldURN, newURN) + status.SetURNUpdate(oldURN, newURN) ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) @@ -667,7 +667,7 @@ func (ts *BackendTestSuite) TestMsgStatus() { ts.NoError(tx.Commit()) status = ts.b.NewStatusUpdate(channel, courier.MsgID(10007), courier.MsgStatusSent, clog6) - status.SetUpdatedURN(oldURN, newURN) + status.SetURNUpdate(oldURN, newURN) ts.NoError(ts.b.WriteStatusUpdate(ctx, status)) diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 6411cffe1..b42dab2ee 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -17,6 +17,19 @@ import ( "github.com/sirupsen/logrus" ) +// StatusUpdate represents a status update on a message +type StatusUpdate struct { + ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"` + ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` + MsgID_ courier.MsgID `json:"msg_id,omitempty" db:"msg_id"` + OldURN_ urns.URN `json:"old_urn" db:"old_urn"` + NewURN_ urns.URN `json:"new_urn" db:"new_urn"` + ExternalID_ string `json:"external_id,omitempty" db:"external_id"` + Status_ courier.MsgStatus `json:"status" db:"status"` + ModifiedOn_ time.Time `json:"modified_on" db:"modified_on"` + LogUUID courier.ChannelLogUUID `json:"log_uuid" db:"log_uuid"` +} + // creates a new message status update func newStatusUpdate(channel courier.Channel, id courier.MsgID, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) *StatusUpdate { dbChannel := channel.(*DBChannel) @@ -24,7 +37,7 @@ func newStatusUpdate(channel courier.Channel, id courier.MsgID, externalID strin return &StatusUpdate{ ChannelUUID_: channel.UUID(), ChannelID_: dbChannel.ID(), - ID_: id, + MsgID_: id, OldURN_: urns.NilURN, NewURN_: urns.NilURN, ExternalID_: externalID, @@ -118,38 +131,11 @@ func (b *backend) flushStatusFile(filename string, contents []byte) error { return err } -//----------------------------------------------------------------------------- -// StatusUpdate implementation -//----------------------------------------------------------------------------- - -// StatusUpdate represents a status update on a message -type StatusUpdate struct { - ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"` - ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` - ID_ courier.MsgID `json:"msg_id,omitempty" db:"msg_id"` - OldURN_ urns.URN `json:"old_urn" db:"old_urn"` - NewURN_ urns.URN `json:"new_urn" db:"new_urn"` - ExternalID_ string `json:"external_id,omitempty" db:"external_id"` - Status_ courier.MsgStatus `json:"status" db:"status"` - ModifiedOn_ time.Time `json:"modified_on" db:"modified_on"` - LogUUID courier.ChannelLogUUID `json:"log_uuid" db:"log_uuid"` -} - -func (s *StatusUpdate) EventID() int64 { return int64(s.ID_) } - +func (s *StatusUpdate) EventID() int64 { return int64(s.MsgID_) } func (s *StatusUpdate) ChannelUUID() courier.ChannelUUID { return s.ChannelUUID_ } -func (s *StatusUpdate) ID() courier.MsgID { return s.ID_ } +func (s *StatusUpdate) MsgID() courier.MsgID { return s.MsgID_ } -func (s *StatusUpdate) RowID() string { - if s.ID_ != courier.NilMsgID { - return strconv.FormatInt(int64(s.ID_), 10) - } else if s.ExternalID_ != "" { - return s.ExternalID_ - } - return "" -} - -func (s *StatusUpdate) SetUpdatedURN(old, new urns.URN) error { +func (s *StatusUpdate) SetURNUpdate(old, new urns.URN) error { // check by nil URN if old == urns.NilURN || new == urns.NilURN { return errors.New("cannot update contact URN from/to nil URN") @@ -166,15 +152,9 @@ func (s *StatusUpdate) SetUpdatedURN(old, new urns.URN) error { s.NewURN_ = new return nil } -func (s *StatusUpdate) UpdatedURN() (urns.URN, urns.URN) { +func (s *StatusUpdate) URNUpdate() (urns.URN, urns.URN) { return s.OldURN_, s.NewURN_ } -func (s *StatusUpdate) HasUpdatedURN() bool { - if s.OldURN_ != urns.NilURN && s.NewURN_ != urns.NilURN { - return true - } - return false -} func (s *StatusUpdate) ExternalID() string { return s.ExternalID_ } func (s *StatusUpdate) SetExternalID(id string) { s.ExternalID_ = id } @@ -182,10 +162,12 @@ func (s *StatusUpdate) SetExternalID(id string) { s.ExternalID_ = id } func (s *StatusUpdate) Status() courier.MsgStatus { return s.Status_ } func (s *StatusUpdate) SetStatus(status courier.MsgStatus) { s.Status_ = status } +// StatusWriter handles batched writes of status updates to the database type StatusWriter struct { *syncx.Batcher[*StatusUpdate] } +// NewStatusWriter creates a new status update writer func NewStatusWriter(b *backend, spoolDir string, wg *sync.WaitGroup) *StatusWriter { return &StatusWriter{ Batcher: syncx.NewBatcher[*StatusUpdate](func(batch []*StatusUpdate) { @@ -209,7 +191,7 @@ func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batc for _, s := range batch { _, err = b.writeStatusUpdatesToDB(ctx, []*StatusUpdate{s}) if err != nil { - log := log.WithField("msg_id", s.ID()) + log := log.WithField("msg_id", s.MsgID()) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() @@ -237,7 +219,7 @@ func (b *backend) writeStatusUpdatesToDB(ctx context.Context, statuses []*Status // get the statuses which have external ID instead of a message ID missingID := make([]*StatusUpdate, 0, 500) for _, s := range statuses { - if s.ID_ == courier.NilMsgID { + if s.MsgID_ == courier.NilMsgID { missingID = append(missingID, s) } } @@ -253,7 +235,7 @@ func (b *backend) writeStatusUpdatesToDB(ctx context.Context, statuses []*Status unresolved := make([]*StatusUpdate, 0, len(statuses)) for _, s := range statuses { - if s.ID_ != courier.NilMsgID { + if s.MsgID_ != courier.NilMsgID { resolved = append(resolved, s) } else { unresolved = append(unresolved, s) @@ -296,7 +278,7 @@ func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*Sta if err != nil { notInCache = append(notInCache, statuses[i]) } else { - statuses[i].ID_ = courier.MsgID(id) + statuses[i].MsgID_ = courier.MsgID(id) } } @@ -336,7 +318,7 @@ func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*Sta // find the status with this channel ID and external ID and update its msg ID s := statusesByExt[ext{channelID, externalID}] - s.ID_ = msgID + s.MsgID_ = msgID } return rows.Err() diff --git a/channel_event.go b/channel_event.go index ee64860d4..261560735 100644 --- a/channel_event.go +++ b/channel_event.go @@ -23,6 +23,8 @@ const ( // ChannelEvent represents an event on a channel, such as a follow, new conversation or referral type ChannelEvent interface { + Event + ChannelUUID() ChannelUUID URN() urns.URN EventType() ChannelEventType @@ -34,6 +36,4 @@ type ChannelEvent interface { WithURNAuthTokens(tokens map[string]string) ChannelEvent WithExtra(extra map[string]string) ChannelEvent WithOccurredOn(time.Time) ChannelEvent - - EventID() int64 } diff --git a/handler_test.go b/handler_test.go index 857adc562..bfc5007e8 100644 --- a/handler_test.go +++ b/handler_test.go @@ -52,7 +52,7 @@ func TestHandling(t *testing.T) { // message should have failed because we don't have a registered handler assert.Equal(1, len(mb.WrittenMsgStatuses())) - assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].ID()) + assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].MsgID()) assert.Equal(courier.MsgStatusFailed, mb.WrittenMsgStatuses()[0].Status()) assert.Equal(1, len(mb.WrittenChannelLogs())) @@ -68,7 +68,7 @@ func TestHandling(t *testing.T) { // message should be marked as wired assert.Len(mb.WrittenMsgStatuses(), 1) status := mb.WrittenMsgStatuses()[0] - assert.Equal(msg.ID(), status.ID()) + assert.Equal(msg.ID(), status.MsgID()) assert.Equal(courier.MsgStatusSent, status.Status()) assert.Len(mb.WrittenChannelLogs(), 1) @@ -89,7 +89,7 @@ func TestHandling(t *testing.T) { // message should be marked as wired assert.Equal(1, len(mb.WrittenMsgStatuses())) - assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].ID()) + assert.Equal(msg.ID(), mb.WrittenMsgStatuses()[0].MsgID()) assert.Equal(courier.MsgStatusWired, mb.WrittenMsgStatuses()[0].Status()) // try to receive a message instead diff --git a/handlers/test.go b/handlers/test.go index f2a593ec1..f72d05cb5 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -197,7 +197,7 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Equal(t, tc.ExpectedExternalID, status.ExternalID()) } if tc.ExpectedMsgID != 0 { - assert.Equal(t, tc.ExpectedMsgID, int64(status.ID())) + assert.Equal(t, tc.ExpectedMsgID, int64(status.MsgID())) } } else { assert.Empty(t, mb.WrittenMsgStatuses(), "unexpected msg status written") @@ -445,7 +445,7 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier } if tc.ExpectedNewURN != "" { - old, new := status.UpdatedURN() + old, new := status.URNUpdate() require.Equal(urns.URN(tc.MsgURN), old) require.Equal(urns.URN(tc.ExpectedNewURN), new) } diff --git a/handlers/whatsapp_legacy/whatsapp.go b/handlers/whatsapp_legacy/whatsapp.go index debc33fe3..c2739edd0 100644 --- a/handlers/whatsapp_legacy/whatsapp.go +++ b/handlers/whatsapp_legacy/whatsapp.go @@ -539,7 +539,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann // so update contact URN if wppID != "" if wppID != "" { newURN, _ := urns.NewWhatsAppURN(wppID) - err = status.SetUpdatedURN(msg.URN(), newURN) + err = status.SetURNUpdate(msg.URN(), newURN) if err != nil { clog.RawError(err) diff --git a/log.go b/log.go index 8bdef851a..ac95e56ce 100644 --- a/log.go +++ b/log.go @@ -15,7 +15,7 @@ func LogMsgStatusReceived(r *http.Request, status StatusUpdate) { "url": r.Context().Value(contextRequestURL), "elapsed_ms": getElapsedMS(r), "status": status.Status(), - "msg_id": status.ID(), + "msg_id": status.MsgID(), "msg_external_id": status.ExternalID(), }).Debug("status updated") } diff --git a/responses.go b/responses.go index 9e4eb2b0c..c7e555a57 100644 --- a/responses.go +++ b/responses.go @@ -135,7 +135,7 @@ func NewStatusData(status StatusUpdate) StatusData { "status", status.ChannelUUID(), status.Status(), - status.ID(), + status.MsgID(), status.ExternalID(), } } diff --git a/status.go b/status.go index 47e299f35..b729b8ce7 100644 --- a/status.go +++ b/status.go @@ -23,14 +23,13 @@ const ( // StatusUpdate represents a status update on a message type StatusUpdate interface { - EventID() int64 + Event ChannelUUID() ChannelUUID - ID() MsgID + MsgID() MsgID - SetUpdatedURN(old, new urns.URN) error - UpdatedURN() (old, new urns.URN) - HasUpdatedURN() bool + SetURNUpdate(old, new urns.URN) error + URNUpdate() (old, new urns.URN) ExternalID() string SetExternalID(string) diff --git a/test/backend.go b/test/backend.go index 30421e20b..15719ed1d 100644 --- a/test/backend.go +++ b/test/backend.go @@ -216,9 +216,9 @@ func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courie // NewStatusUpdate creates a new Status object for the given message id func (mb *MockBackend) NewStatusUpdate(channel courier.Channel, id courier.MsgID, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { - return &mockMsgStatus{ + return &MockStatusUpdate{ channel: channel, - id: id, + msgID: id, status: status, createdOn: time.Now().In(time.UTC), } @@ -226,7 +226,7 @@ func (mb *MockBackend) NewStatusUpdate(channel courier.Channel, id courier.MsgID // NewStatusUpdateByExternalID creates a new Status object for the given external id func (mb *MockBackend) NewStatusUpdateByExternalID(channel courier.Channel, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) courier.StatusUpdate { - return &mockMsgStatus{ + return &MockStatusUpdate{ channel: channel, externalID: externalID, status: status, diff --git a/test/status.go b/test/status.go index ee1f195d4..12e6a9037 100644 --- a/test/status.go +++ b/test/status.go @@ -7,9 +7,9 @@ import ( "github.com/nyaruka/gocommon/urns" ) -type mockMsgStatus struct { +type MockStatusUpdate struct { channel courier.Channel - id courier.MsgID + msgID courier.MsgID oldURN urns.URN newURN urns.URN externalID string @@ -17,27 +17,21 @@ type mockMsgStatus struct { createdOn time.Time } -func (m *mockMsgStatus) ChannelUUID() courier.ChannelUUID { return m.channel.UUID() } -func (m *mockMsgStatus) ID() courier.MsgID { return m.id } -func (m *mockMsgStatus) EventID() int64 { return int64(m.id) } +func (m *MockStatusUpdate) EventID() int64 { return int64(m.msgID) } +func (m *MockStatusUpdate) ChannelUUID() courier.ChannelUUID { return m.channel.UUID() } +func (m *MockStatusUpdate) MsgID() courier.MsgID { return m.msgID } -func (m *mockMsgStatus) SetUpdatedURN(old, new urns.URN) error { +func (m *MockStatusUpdate) SetURNUpdate(old, new urns.URN) error { m.oldURN = old m.newURN = new return nil } -func (m *mockMsgStatus) UpdatedURN() (urns.URN, urns.URN) { +func (m *MockStatusUpdate) URNUpdate() (urns.URN, urns.URN) { return m.oldURN, m.newURN } -func (m *mockMsgStatus) HasUpdatedURN() bool { - if m.oldURN != urns.NilURN && m.newURN != urns.NilURN { - return true - } - return false -} -func (m *mockMsgStatus) ExternalID() string { return m.externalID } -func (m *mockMsgStatus) SetExternalID(id string) { m.externalID = id } +func (m *MockStatusUpdate) ExternalID() string { return m.externalID } +func (m *MockStatusUpdate) SetExternalID(id string) { m.externalID = id } -func (m *mockMsgStatus) Status() courier.MsgStatus { return m.status } -func (m *mockMsgStatus) SetStatus(status courier.MsgStatus) { m.status = status } +func (m *MockStatusUpdate) Status() courier.MsgStatus { return m.status } +func (m *MockStatusUpdate) SetStatus(status courier.MsgStatus) { m.status = status } From 1c60bb8c26111b6c7dcf333162a852079419ef69 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 11:42:20 -0500 Subject: [PATCH 090/170] More consistent naming and ordering within module for backend types --- backends/rapidpro/backend.go | 22 +-- backends/rapidpro/backend_test.go | 73 ++++--- backends/rapidpro/channel.go | 299 ++++++++++++++--------------- backends/rapidpro/channel_event.go | 139 ++++++-------- backends/rapidpro/channel_log.go | 2 +- backends/rapidpro/contact.go | 48 ++--- backends/rapidpro/media.go | 28 +-- backends/rapidpro/media_test.go | 6 +- backends/rapidpro/msg.go | 292 ++++++++++++++-------------- backends/rapidpro/status.go | 2 +- backends/rapidpro/task.go | 8 +- backends/rapidpro/urn.go | 4 +- 12 files changed, 447 insertions(+), 476 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 49f8bfca2..56ef8cccc 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -322,7 +322,7 @@ func (b *backend) GetChannelByAddress(ctx context.Context, ct courier.ChannelTyp // GetContact returns the contact for the passed in channel and URN func (b *backend) GetContact(ctx context.Context, c courier.Channel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (courier.Contact, error) { - dbChannel := c.(*DBChannel) + dbChannel := c.(*Channel) return contactForURN(ctx, b, dbChannel.OrgID_, dbChannel, urn, authTokens, name, clog) } @@ -332,8 +332,8 @@ func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contac if err != nil { return urns.NilURN, err } - dbChannel := c.(*DBChannel) - dbContact := contact.(*DBContact) + dbChannel := c.(*Channel) + dbContact := contact.(*Contact) _, err = getOrCreateContactURN(tx, dbChannel, dbContact.ID_, urn, authTokens) if err != nil { return urns.NilURN, err @@ -348,7 +348,7 @@ func (b *backend) AddURNtoContact(ctx context.Context, c courier.Channel, contac // RemoveURNFromcontact removes a URN from the passed in contact func (b *backend) RemoveURNfromContact(ctx context.Context, c courier.Channel, contact courier.Contact, urn urns.URN) (urns.URN, error) { - dbContact := contact.(*DBContact) + dbContact := contact.(*Contact) _, err := b.db.ExecContext(ctx, `UPDATE contacts_contacturn SET contact_id = NULL WHERE contact_id = $1 AND identity = $2`, dbContact.ID_, urn.Identity().String()) if err != nil { return urns.NilURN, err @@ -358,7 +358,7 @@ func (b *backend) RemoveURNfromContact(ctx context.Context, c courier.Channel, c // DeleteMsgByExternalID resolves a message external id and quees a task to mailroom to delete it func (b *backend) DeleteMsgByExternalID(ctx context.Context, channel courier.Channel, externalID string) error { - ch := channel.(*DBChannel) + ch := channel.(*Channel) row := b.db.QueryRowContext(ctx, `SELECT id, contact_id FROM msgs_msg WHERE channel_id = $1 AND external_id = $2 AND direction = 'I'`, ch.ID(), externalID) var msgID courier.MsgID @@ -417,7 +417,7 @@ func (b *backend) PopNextOutgoingMsg(ctx context.Context) (courier.Msg, error) { } if msgJSON != "" { - dbMsg := &DBMsg{} + dbMsg := &Msg{} err = json.Unmarshal([]byte(msgJSON), dbMsg) if err != nil { queue.MarkComplete(rc, msgQueueName, token) @@ -432,7 +432,7 @@ func (b *backend) PopNextOutgoingMsg(ctx context.Context) (courier.Msg, error) { } dbMsg.Direction_ = MsgOutgoing - dbMsg.channel = channel.(*DBChannel) + dbMsg.channel = channel.(*Channel) dbMsg.workerToken = token // clear out our seen incoming messages @@ -485,7 +485,7 @@ func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, rc := b.redisPool.Get() defer rc.Close() - dbMsg := msg.(*DBMsg) + dbMsg := msg.(*Msg) queue.MarkComplete(rc, msgQueueName, dbMsg.workerToken) @@ -582,7 +582,7 @@ func (b *backend) updateContactURN(ctx context.Context, status courier.StatusUpd if err != nil { return errors.Wrap(err, "error retrieving channel") } - dbChannel := channel.(*DBChannel) + dbChannel := channel.(*Channel) tx, err := b.db.BeginTxx(ctx, nil) if err != nil { return err @@ -662,7 +662,7 @@ func (b *backend) SaveAttachment(ctx context.Context, ch courier.Channel, conten filename = fmt.Sprintf("%s.%s", filename, extension) } - orgID := ch.(*DBChannel).OrgID() + orgID := ch.(*Channel).OrgID() path := filepath.Join(b.config.S3AttachmentsPrefix, strconv.FormatInt(int64(orgID), 10), filename[:4], filename[4:8], filename) @@ -694,7 +694,7 @@ func (b *backend) ResolveMedia(ctx context.Context, mediaUrl string) (courier.Me rc := b.redisPool.Get() defer rc.Close() - var media *DBMedia + var media *Media mediaJSON, err := b.mediaCache.Get(rc, mediaUUID) if err != nil { return nil, errors.Wrap(err, "error looking up cached media") diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 59edccff6..b9584a0b9 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -94,14 +94,14 @@ func (ts *BackendTestSuite) clearRedis() { ts.Require().NoError(err) } -func (ts *BackendTestSuite) getChannel(cType string, cUUID string) *DBChannel { +func (ts *BackendTestSuite) getChannel(cType string, cUUID string) *Channel { channelUUID := courier.ChannelUUID(cUUID) channel, err := ts.b.GetChannel(context.Background(), courier.ChannelType(cType), channelUUID) ts.Require().NoError(err, "error getting channel") ts.Require().NotNil(channel) - return channel.(*DBChannel) + return channel.(*Channel) } func (ts *BackendTestSuite) TestMsgUnmarshal() { @@ -126,7 +126,7 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { "metadata": {"topic": "event"} }` - msg := DBMsg{} + msg := Msg{} err := json.Unmarshal([]byte(msgJSON), &msg) ts.NoError(err) ts.Equal(courier.ChannelUUID("f3ad3eb6-d00d-4dc3-92e9-9f34f32940ba"), msg.ChannelUUID_) @@ -157,7 +157,7 @@ func (ts *BackendTestSuite) TestMsgUnmarshal() { "metadata": null }` - msg = DBMsg{} + msg = Msg{} err = json.Unmarshal([]byte(msgJSONNoQR), &msg) ts.NoError(err) ts.Nil(msg.Attachments()) @@ -250,7 +250,7 @@ func (ts *BackendTestSuite) TestContactRace() { ctx := context.Background() // create our contact twice - var contact1, contact2 *DBContact + var contact1, contact2 *Contact var err1, err2 error go func() { @@ -394,7 +394,7 @@ func (ts *BackendTestSuite) TestContactURN() { // try to create two contacts at the same time in goroutines, this tests our transaction rollbacks urn2, _ := urns.NewTelURNForCountry("12065551616", "US") var wait sync.WaitGroup - var contact2, contact3 *DBContact + var contact2, contact3 *Contact wait.Add(2) go func() { var err2 error @@ -740,9 +740,9 @@ func (ts *BackendTestSuite) TestCheckForDuplicate() { urn, _ := urns.NewTelURNForCountry("12065551215", knChannel.Country()) urn2, _ := urns.NewTelURNForCountry("12065551277", knChannel.Country()) - createAndWriteMsg := func(ch courier.Channel, u urns.URN, text, extID string) *DBMsg { + createAndWriteMsg := func(ch courier.Channel, u urns.URN, text, extID string) *Msg { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, knChannel, nil) - m := ts.b.NewIncomingMsg(ch, u, text, extID, clog).(*DBMsg) + m := ts.b.NewIncomingMsg(ch, u, text, extID, clog).(*Msg) err := ts.b.WriteMsg(ctx, m, clog) ts.NoError(err) return m @@ -1089,7 +1089,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { // create a new courier msg urn, _ := urns.NewTelURNForCountry("12065551212", knChannel.Country()) - msg := ts.b.NewIncomingMsg(knChannel, urn, "test123", "ext123", clog).WithReceivedOn(now).WithContactName("test contact").(*DBMsg) + msg := ts.b.NewIncomingMsg(knChannel, urn, "test123", "ext123", clog).WithReceivedOn(now).WithContactName("test contact").(*Msg) // try to write it to our db err := ts.b.WriteMsg(ctx, msg, clog) @@ -1097,7 +1097,7 @@ func (ts *BackendTestSuite) TestWriteMsg() { // creating the incoming msg again should give us the same UUID and have the msg set as not to write time.Sleep(1 * time.Second) - msg2 := ts.b.NewIncomingMsg(knChannel, urn, "test123", "ext123", clog).(*DBMsg) + msg2 := ts.b.NewIncomingMsg(knChannel, urn, "test123", "ext123", clog).(*Msg) ts.Equal(msg2.UUID(), msg.UUID()) ts.True(msg2.alreadyWritten) @@ -1146,24 +1146,24 @@ func (ts *BackendTestSuite) TestWriteMsg() { // waiting 5 seconds should let us write it successfully time.Sleep(5 * time.Second) - msg3 := ts.b.NewIncomingMsg(knChannel, urn, "test123", "", clog).(*DBMsg) + msg3 := ts.b.NewIncomingMsg(knChannel, urn, "test123", "", clog).(*Msg) ts.NotEqual(msg3.UUID(), msg.UUID()) // msg with null bytes in it, that's fine for a request body - msg = ts.b.NewIncomingMsg(knChannel, urn, "test456\x00456", "ext456", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, "test456\x00456", "ext456", clog).(*Msg) err = writeMsgToDB(ctx, ts.b, msg, clog) ts.NoError(err) // more null bytes text, _ := url.PathUnescape("%1C%00%00%00%00%00%07%E0%00") - msg = ts.b.NewIncomingMsg(knChannel, urn, text, "", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, text, "", clog).(*Msg) err = writeMsgToDB(ctx, ts.b, msg, clog) ts.NoError(err) ts.clearRedis() // check that our mailroom queue has an item - msg = ts.b.NewIncomingMsg(knChannel, urn, "hello 1 2 3", "", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, "hello 1 2 3", "", clog).(*Msg) err = writeMsgToDB(ctx, ts.b, msg, clog) ts.NoError(err) @@ -1192,7 +1192,7 @@ func (ts *BackendTestSuite) TestWriteMsgWithAttachments() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, knChannel, nil) urn, _ := urns.NewTelURNForCountry("12065551218", knChannel.Country()) - msg := ts.b.NewIncomingMsg(knChannel, urn, "two regular attachments", "", clog).(*DBMsg) + msg := ts.b.NewIncomingMsg(knChannel, urn, "two regular attachments", "", clog).(*Msg) msg.WithAttachment("http://example.com/test.jpg") msg.WithAttachment("http://example.com/test.m4a") @@ -1202,7 +1202,7 @@ func (ts *BackendTestSuite) TestWriteMsgWithAttachments() { ts.Equal([]string{"http://example.com/test.jpg", "http://example.com/test.m4a"}, msg.Attachments()) // try an embedded attachment - msg = ts.b.NewIncomingMsg(knChannel, urn, "embedded attachment data", "", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, "embedded attachment data", "", clog).(*Msg) msg.WithAttachment(fmt.Sprintf("data:%s", base64.StdEncoding.EncodeToString(test.ReadFile("../../test/testdata/test.jpg")))) // should have actually fetched and saved it to storage, with the correct content type @@ -1211,14 +1211,14 @@ func (ts *BackendTestSuite) TestWriteMsgWithAttachments() { ts.Equal([]string{"image/jpeg:_test_storage/attachments/media/1/9b95/5e36/9b955e36-ac16-4c6b-8ab6-9b9af5cd042a.jpg"}, msg.Attachments()) // try an invalid embedded attachment - msg = ts.b.NewIncomingMsg(knChannel, urn, "invalid embedded attachment data", "", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, "invalid embedded attachment data", "", clog).(*Msg) msg.WithAttachment("data:34564363576573573") err = ts.b.WriteMsg(ctx, msg, clog) ts.EqualError(err, "unable to decode attachment data: illegal base64 data at input byte 16") // try a geo attachment - msg = ts.b.NewIncomingMsg(knChannel, urn, "geo attachment", "", clog).(*DBMsg) + msg = ts.b.NewIncomingMsg(knChannel, urn, "geo attachment", "", clog).(*Msg) msg.WithAttachment("geo:123.234,-45.676") // should be saved as is @@ -1236,7 +1236,7 @@ func (ts *BackendTestSuite) TestPreferredChannelCheckRole() { now := time.Now().Round(time.Microsecond).In(time.UTC) urn, _ := urns.NewTelURNForCountry("12065552020", exChannel.Country()) - msg := ts.b.NewIncomingMsg(exChannel, urn, "test123", "ext123", clog).WithReceivedOn(now).WithContactName("test contact").(*DBMsg) + msg := ts.b.NewIncomingMsg(exChannel, urn, "test123", "ext123", clog).WithReceivedOn(now).WithContactName("test contact").(*Msg) // try to write it to our db err := ts.b.WriteMsg(ctx, msg, clog) @@ -1273,7 +1273,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { ts.NoError(err) ts.Equal(null.String("kermit frog"), contact.Name_) - dbE := event.(*DBChannelEvent) + dbE := event.(*ChannelEvent) dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) @@ -1315,7 +1315,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { ts.NoError(err) ts.Equal(null.String("kermit frog"), contact.Name_) - dbE := event.(*DBChannelEvent) + dbE := event.(*ChannelEvent) dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.Referral) @@ -1344,7 +1344,7 @@ func (ts *BackendTestSuite) TestResolveMedia() { }{ { // image upload that can be resolved url: "http://nyaruka.s3.com/orgs/1/media/ec69/ec6972be-809c-4c8d-be59-ba9dbd74c977/test.jpg", - media: &DBMedia{ + media: &Media{ UUID_: "ec6972be-809c-4c8d-be59-ba9dbd74c977", Path_: "/orgs/1/media/ec69/ec6972be-809c-4c8d-be59-ba9dbd74c977/test.jpg", ContentType_: "image/jpeg", @@ -1352,12 +1352,12 @@ func (ts *BackendTestSuite) TestResolveMedia() { Size_: 123, Width_: 1024, Height_: 768, - Alternates_: []*DBMedia{}, + Alternates_: []*Media{}, }, }, { // same image upload, this time from cache url: "http://nyaruka.s3.com/orgs/1/media/ec69/ec6972be-809c-4c8d-be59-ba9dbd74c977/test.jpg", - media: &DBMedia{ + media: &Media{ UUID_: "ec6972be-809c-4c8d-be59-ba9dbd74c977", Path_: "/orgs/1/media/ec69/ec6972be-809c-4c8d-be59-ba9dbd74c977/test.jpg", ContentType_: "image/jpeg", @@ -1365,7 +1365,7 @@ func (ts *BackendTestSuite) TestResolveMedia() { Size_: 123, Width_: 1024, Height_: 768, - Alternates_: []*DBMedia{}, + Alternates_: []*Media{}, }, }, { // image upload that can't be resolved @@ -1386,14 +1386,14 @@ func (ts *BackendTestSuite) TestResolveMedia() { }, { // audio upload url: "http://nyaruka.s3.com/orgs/1/media/5310/5310f50f-9c8e-4035-9150-be5a1f78f21a/test.mp3", - media: &DBMedia{ + media: &Media{ UUID_: "5310f50f-9c8e-4035-9150-be5a1f78f21a", Path_: "/orgs/1/media/5310/5310f50f-9c8e-4035-9150-be5a1f78f21a/test.mp3", ContentType_: "audio/mp3", URL_: "http://nyaruka.s3.com/orgs/1/media/5310/5310f50f-9c8e-4035-9150-be5a1f78f21a/test.mp3", Size_: 123, Duration_: 500, - Alternates_: []*DBMedia{ + Alternates_: []*Media{ { UUID_: "514c552c-e585-40e2-938a-fe9450172da8", Path_: "/orgs/1/media/514c/514c552c-e585-40e2-938a-fe9450172da8/test.m4a", @@ -1483,8 +1483,8 @@ type ServerTestSuite struct { } // for testing only, returned DBMsg object is not fully populated -func readMsgFromDB(b *backend, id courier.MsgID) *DBMsg { - m := &DBMsg{ +func readMsgFromDB(b *backend, id courier.MsgID) *Msg { + m := &Msg{ ID_: id, } err := b.db.Get(m, sqlSelectMsg, id) @@ -1492,7 +1492,7 @@ func readMsgFromDB(b *backend, id courier.MsgID) *DBMsg { panic(err) } - ch := &DBChannel{ + ch := &Channel{ ID_: m.ChannelID_, } err = b.db.Get(ch, selectChannelSQL, m.ChannelID_) @@ -1550,3 +1550,16 @@ FROM WHERE ch.id = $1 ` + +const sqlSelectEvent = ` +SELECT org_id, channel_id, contact_id, contact_urn_id, event_type, extra, occurred_on, created_on, log_uuids + FROM channels_channelevent + WHERE id = $1` + +func readChannelEventFromDB(b *backend, id ChannelEventID) (*ChannelEvent, error) { + e := &ChannelEvent{ + ID_: id, + } + err := b.db.Get(e, sqlSelectEvent, id) + return e, err +} diff --git a/backends/rapidpro/channel.go b/backends/rapidpro/channel.go index ab4ebbf50..39a4e51fd 100644 --- a/backends/rapidpro/channel.go +++ b/backends/rapidpro/channel.go @@ -23,9 +23,137 @@ const ( LogPolicyAll = "A" ) +// Channel is the RapidPro specific concrete type satisfying the courier.Channel interface +type Channel struct { + OrgID_ OrgID `db:"org_id"` + UUID_ courier.ChannelUUID `db:"uuid"` + ID_ courier.ChannelID `db:"id"` + ChannelType_ courier.ChannelType `db:"channel_type"` + Schemes_ pq.StringArray `db:"schemes"` + Name_ sql.NullString `db:"name"` + Address_ sql.NullString `db:"address"` + Country_ sql.NullString `db:"country"` + Config_ null.Map[any] `db:"config"` + Role_ string `db:"role"` + LogPolicy LogPolicy `db:"log_policy"` + + OrgConfig_ null.Map[any] `db:"org_config"` + OrgIsAnon_ bool `db:"org_is_anon"` + + expiration time.Time +} + +func (c *Channel) ID() courier.ChannelID { return c.ID_ } +func (c *Channel) UUID() courier.ChannelUUID { return c.UUID_ } +func (c *Channel) OrgID() OrgID { return c.OrgID_ } +func (c *Channel) OrgIsAnon() bool { return c.OrgIsAnon_ } +func (c *Channel) ChannelType() courier.ChannelType { return c.ChannelType_ } +func (c *Channel) Name() string { return c.Name_.String } +func (c *Channel) Schemes() []string { return []string(c.Schemes_) } +func (c *Channel) Address() string { return c.Address_.String } + +// ChannelAddress returns the address of this channel +func (c *Channel) ChannelAddress() courier.ChannelAddress { + if !c.Address_.Valid { + return courier.NilChannelAddress + } + + return courier.ChannelAddress(c.Address_.String) +} + +// Country returns the country code for this channel if any +func (c *Channel) Country() string { return c.Country_.String } + +// IsScheme returns whether this channel serves only the passed in scheme +func (c *Channel) IsScheme(scheme string) bool { + return len(c.Schemes_) == 1 && c.Schemes_[0] == scheme +} + +// Roles returns the roles of this channel +func (c *Channel) Roles() []courier.ChannelRole { + roles := []courier.ChannelRole{} + for _, char := range strings.Split(c.Role_, "") { + roles = append(roles, courier.ChannelRole(char)) + } + return roles +} + +// HasRole returns whether the passed in channel supports the passed role +func (c *Channel) HasRole(role courier.ChannelRole) bool { + for _, r := range c.Roles() { + if r == role { + return true + } + } + return false +} + +// ConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found +func (c *Channel) ConfigForKey(key string, defaultValue any) any { + value, found := c.Config_[key] + if !found { + return defaultValue + } + return value +} + +// OrgConfigForKey returns the org config value for the passed in key, or defaultValue if it isn't found +func (c *Channel) OrgConfigForKey(key string, defaultValue any) any { + value, found := c.OrgConfig_[key] + if !found { + return defaultValue + } + return value +} + +// StringConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found +func (c *Channel) StringConfigForKey(key string, defaultValue string) string { + val := c.ConfigForKey(key, defaultValue) + str, isStr := val.(string) + if !isStr { + return defaultValue + } + return str +} + +// BoolConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found +func (c *Channel) BoolConfigForKey(key string, defaultValue bool) bool { + val := c.ConfigForKey(key, defaultValue) + b, isBool := val.(bool) + if !isBool { + return defaultValue + } + return b +} + +// IntConfigForKey returns the config value for the passed in key +func (c *Channel) IntConfigForKey(key string, defaultValue int) int { + val := c.ConfigForKey(key, defaultValue) + + // golang unmarshals number literals in JSON into float64s by default + f, isFloat := val.(float64) + if isFloat { + return int(f) + } + + str, isStr := val.(string) + if isStr { + i, err := strconv.Atoi(str) + if err == nil { + return i + } + } + return defaultValue +} + +// CallbackDomain is convenience utility to get the callback domain configured for this channel +func (c *Channel) CallbackDomain(fallbackDomain string) string { + return c.StringConfigForKey(courier.ConfigCallbackDomain, fallbackDomain) +} + // getChannel will look up the channel with the passed in UUID and channel type. // It will return an error if the channel does not exist or is not active. -func getChannel(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, channelUUID courier.ChannelUUID) (*DBChannel, error) { +func getChannel(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, channelUUID courier.ChannelUUID) (*Channel, error) { // look for the channel locally cachedChannel, localErr := getCachedChannel(channelType, channelUUID) @@ -78,8 +206,8 @@ SELECT WHERE c.uuid = $1 AND c.is_active = TRUE AND c.org_id IS NOT NULL` // ChannelForUUID attempts to look up the channel with the passed in UUID, returning it -func loadChannelFromDB(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, uuid courier.ChannelUUID) (*DBChannel, error) { - channel := &DBChannel{UUID_: uuid} +func loadChannelFromDB(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, uuid courier.ChannelUUID) (*Channel, error) { + channel := &Channel{UUID_: uuid} // select just the fields we need err := db.GetContext(ctx, channel, sqlLookupChannelFromUUID, uuid) @@ -104,7 +232,7 @@ func loadChannelFromDB(ctx context.Context, db *sqlx.DB, channelType courier.Cha } // getCachedChannel returns a Channel object for the passed in type and UUID. -func getCachedChannel(channelType courier.ChannelType, uuid courier.ChannelUUID) (*DBChannel, error) { +func getCachedChannel(channelType courier.ChannelType, uuid courier.ChannelUUID) (*Channel, error) { // first see if the channel exists in our local cache cacheMutex.RLock() channel, found := channelCache[uuid] @@ -127,7 +255,7 @@ func getCachedChannel(channelType courier.ChannelType, uuid courier.ChannelUUID) return nil, courier.ErrChannelNotFound } -func cacheChannel(channel *DBChannel) { +func cacheChannel(channel *Channel) { channel.expiration = time.Now().Add(localTTL) cacheMutex.Lock() @@ -145,11 +273,11 @@ func clearLocalChannel(uuid courier.ChannelUUID) { const localTTL = 60 * time.Second var cacheMutex sync.RWMutex -var channelCache = make(map[courier.ChannelUUID]*DBChannel) +var channelCache = make(map[courier.ChannelUUID]*Channel) // getChannelByAddress will look up the channel with the passed in address and channel type. // It will return an error if the channel does not exist or is not active. -func getChannelByAddress(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, address courier.ChannelAddress) (*DBChannel, error) { +func getChannelByAddress(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, address courier.ChannelAddress) (*Channel, error) { // look for the channel locally cachedChannel, localErr := getCachedChannelByAddress(channelType, address) @@ -202,8 +330,8 @@ SELECT WHERE c.address = $1 AND c.is_active = TRUE AND c.org_id IS NOT NULL` // loadChannelByAddressFromDB get the channel with the passed in channel type and address from the DB, returning it -func loadChannelByAddressFromDB(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, address courier.ChannelAddress) (*DBChannel, error) { - channel := &DBChannel{Address_: sql.NullString{String: address.String(), Valid: address == courier.NilChannelAddress}} +func loadChannelByAddressFromDB(ctx context.Context, db *sqlx.DB, channelType courier.ChannelType, address courier.ChannelAddress) (*Channel, error) { + channel := &Channel{Address_: sql.NullString{String: address.String(), Valid: address == courier.NilChannelAddress}} // select just the fields we need err := db.GetContext(ctx, channel, sqlLookupChannelFromAddress, address) @@ -228,7 +356,7 @@ func loadChannelByAddressFromDB(ctx context.Context, db *sqlx.DB, channelType co } // getCachedChannelByAddress returns a Channel object for the passed in type and address. -func getCachedChannelByAddress(channelType courier.ChannelType, address courier.ChannelAddress) (*DBChannel, error) { +func getCachedChannelByAddress(channelType courier.ChannelType, address courier.ChannelAddress) (*Channel, error) { // first see if the channel exists in our local cache cacheByAddressMutex.RLock() channel, found := channelByAddressCache[address] @@ -252,7 +380,7 @@ func getCachedChannelByAddress(channelType courier.ChannelType, address courier. return nil, courier.ErrChannelNotFound } -func cacheChannelByAddress(channel *DBChannel) { +func cacheChannelByAddress(channel *Channel) { channel.expiration = time.Now().Add(localTTL) // never cache if the address is nil or empty @@ -272,151 +400,4 @@ func clearLocalChannelByAddress(address courier.ChannelAddress) { } var cacheByAddressMutex sync.RWMutex -var channelByAddressCache = make(map[courier.ChannelAddress]*DBChannel) - -//----------------------------------------------------------------------------- -// Channel Implementation -//----------------------------------------------------------------------------- - -// DBChannel is the RapidPro specific concrete type satisfying the courier.Channel interface -type DBChannel struct { - OrgID_ OrgID `db:"org_id"` - UUID_ courier.ChannelUUID `db:"uuid"` - ID_ courier.ChannelID `db:"id"` - ChannelType_ courier.ChannelType `db:"channel_type"` - Schemes_ pq.StringArray `db:"schemes"` - Name_ sql.NullString `db:"name"` - Address_ sql.NullString `db:"address"` - Country_ sql.NullString `db:"country"` - Config_ null.Map[any] `db:"config"` - Role_ string `db:"role"` - LogPolicy LogPolicy `db:"log_policy"` - - OrgConfig_ null.Map[any] `db:"org_config"` - OrgIsAnon_ bool `db:"org_is_anon"` - - expiration time.Time -} - -// OrgID returns the id of the org this channel is for -func (c *DBChannel) OrgID() OrgID { return c.OrgID_ } - -// OrgIsAnon returns the org for this channel is anonymous -func (c *DBChannel) OrgIsAnon() bool { return c.OrgIsAnon_ } - -// ChannelType returns the type of this channel -func (c *DBChannel) ChannelType() courier.ChannelType { return c.ChannelType_ } - -// Name returns the name of this channel -func (c *DBChannel) Name() string { return c.Name_.String } - -// Schemes returns the schemes this channels supports -func (c *DBChannel) Schemes() []string { return []string(c.Schemes_) } - -// ID returns the id of this channel -func (c *DBChannel) ID() courier.ChannelID { return c.ID_ } - -// UUID returns the UUID of this channel -func (c *DBChannel) UUID() courier.ChannelUUID { return c.UUID_ } - -// Address returns the address of this channel as a string -func (c *DBChannel) Address() string { return c.Address_.String } - -// ChannelAddress returns the address of this channel -func (c *DBChannel) ChannelAddress() courier.ChannelAddress { - if !c.Address_.Valid { - return courier.NilChannelAddress - } - - return courier.ChannelAddress(c.Address_.String) -} - -// Country returns the country code for this channel if any -func (c *DBChannel) Country() string { return c.Country_.String } - -// IsScheme returns whether this channel serves only the passed in scheme -func (c *DBChannel) IsScheme(scheme string) bool { - return len(c.Schemes_) == 1 && c.Schemes_[0] == scheme -} - -// Roles returns the roles of this channel -func (c *DBChannel) Roles() []courier.ChannelRole { - roles := []courier.ChannelRole{} - for _, char := range strings.Split(c.Role_, "") { - roles = append(roles, courier.ChannelRole(char)) - } - return roles -} - -// HasRole returns whether the passed in channel supports the passed role -func (c *DBChannel) HasRole(role courier.ChannelRole) bool { - for _, r := range c.Roles() { - if r == role { - return true - } - } - return false -} - -// ConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) ConfigForKey(key string, defaultValue any) any { - value, found := c.Config_[key] - if !found { - return defaultValue - } - return value -} - -// OrgConfigForKey returns the org config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) OrgConfigForKey(key string, defaultValue any) any { - value, found := c.OrgConfig_[key] - if !found { - return defaultValue - } - return value -} - -// StringConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) StringConfigForKey(key string, defaultValue string) string { - val := c.ConfigForKey(key, defaultValue) - str, isStr := val.(string) - if !isStr { - return defaultValue - } - return str -} - -// BoolConfigForKey returns the config value for the passed in key, or defaultValue if it isn't found -func (c *DBChannel) BoolConfigForKey(key string, defaultValue bool) bool { - val := c.ConfigForKey(key, defaultValue) - b, isBool := val.(bool) - if !isBool { - return defaultValue - } - return b -} - -// IntConfigForKey returns the config value for the passed in key -func (c *DBChannel) IntConfigForKey(key string, defaultValue int) int { - val := c.ConfigForKey(key, defaultValue) - - // golang unmarshals number literals in JSON into float64s by default - f, isFloat := val.(float64) - if isFloat { - return int(f) - } - - str, isStr := val.(string) - if isStr { - i, err := strconv.Atoi(str) - if err == nil { - return i - } - } - return defaultValue -} - -// CallbackDomain is convenience utility to get the callback domain configured for this channel -func (c *DBChannel) CallbackDomain(fallbackDomain string) string { - return c.StringConfigForKey(courier.ConfigCallbackDomain, fallbackDomain) -} +var channelByAddressCache = make(map[courier.ChannelAddress]*Channel) diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index f1bcb12e1..48f884081 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -35,12 +35,35 @@ func (i ChannelEventID) String() string { return "null" } +// ChannelEvent represents an event on a channel.. that isn't a new message or status update +type ChannelEvent struct { + ID_ ChannelEventID ` db:"id"` + OrgID_ OrgID `json:"org_id" db:"org_id"` + ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"` + ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` + URN_ urns.URN `json:"urn" db:"urn"` + EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` + Extra_ null.Map[string] `json:"extra" db:"extra"` + OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` + CreatedOn_ time.Time `json:"created_on" db:"created_on"` + LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` + + ContactID_ ContactID `json:"-" db:"contact_id"` + ContactURNID_ ContactURNID `json:"-" db:"contact_urn_id"` + + // used to update contact + ContactName_ string `json:"contact_name"` + URNAuthTokens_ map[string]string `json:"auth_tokens"` + + channel *Channel +} + // newChannelEvent creates a new channel event -func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType, urn urns.URN, clog *courier.ChannelLog) *DBChannelEvent { - dbChannel := channel.(*DBChannel) +func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType, urn urns.URN, clog *courier.ChannelLog) *ChannelEvent { + dbChannel := channel.(*Channel) now := time.Now().In(time.UTC) - return &DBChannelEvent{ + return &ChannelEvent{ ChannelUUID_: dbChannel.UUID_, OrgID_: dbChannel.OrgID_, ChannelID_: dbChannel.ID_, @@ -54,9 +77,39 @@ func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType } } +func (e *ChannelEvent) EventID() int64 { return int64(e.ID_) } +func (e *ChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID_ } +func (e *ChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } +func (e *ChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } +func (e *ChannelEvent) URN() urns.URN { return e.URN_ } +func (e *ChannelEvent) Extra() map[string]string { return e.Extra_ } +func (e *ChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } +func (e *ChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } +func (e *ChannelEvent) Channel() *Channel { return e.channel } + +func (e *ChannelEvent) WithContactName(name string) courier.ChannelEvent { + e.ContactName_ = name + return e +} + +func (e *ChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.ChannelEvent { + e.URNAuthTokens_ = tokens + return e +} + +func (e *ChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { + e.Extra_ = null.Map[string](extra) + return e +} + +func (e *ChannelEvent) WithOccurredOn(time time.Time) courier.ChannelEvent { + e.OccurredOn_ = time + return e +} + // writeChannelEvent writes the passed in event to the database, queueing it to our spool in case the database is down func writeChannelEvent(ctx context.Context, b *backend, event courier.ChannelEvent, clog *courier.ChannelLog) error { - dbEvent := event.(*DBChannelEvent) + dbEvent := event.(*ChannelEvent) err := writeChannelEventToDB(ctx, b, dbEvent, clog) @@ -79,7 +132,7 @@ INSERT INTO RETURNING id` // writeChannelEventToDB writes the passed in msg status to our db -func writeChannelEventToDB(ctx context.Context, b *backend, e *DBChannelEvent, clog *courier.ChannelLog) error { +func writeChannelEventToDB(ctx context.Context, b *backend, e *ChannelEvent, clog *courier.ChannelLog) error { // grab the contact for this event contact, err := contactForURN(ctx, b, e.OrgID_, e.channel, e.URN_, e.URNAuthTokens_, e.ContactName_, clog) if err != nil { @@ -119,7 +172,7 @@ func (b *backend) flushChannelEventFile(filename string, contents []byte) error ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - event := &DBChannelEvent{} + event := &ChannelEvent{} err := json.Unmarshal(contents, event) if err != nil { log.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) @@ -132,7 +185,7 @@ func (b *backend) flushChannelEventFile(filename string, contents []byte) error if err != nil { return err } - event.channel = channel.(*DBChannel) + event.channel = channel.(*Channel) // create log tho it won't be written clog := courier.NewChannelLog(courier.ChannelLogTypeMsgReceive, channel, nil) @@ -140,75 +193,3 @@ func (b *backend) flushChannelEventFile(filename string, contents []byte) error // try to flush to our database return writeChannelEventToDB(ctx, b, event, clog) } - -const sqlSelectEvent = ` -SELECT org_id, channel_id, contact_id, contact_urn_id, event_type, extra, occurred_on, created_on, log_uuids - FROM channels_channelevent - WHERE id = $1` - -func readChannelEventFromDB(b *backend, id ChannelEventID) (*DBChannelEvent, error) { - e := &DBChannelEvent{ - ID_: id, - } - err := b.db.Get(e, sqlSelectEvent, id) - return e, err -} - -//----------------------------------------------------------------------------- -// ChannelEvent implementation -//----------------------------------------------------------------------------- - -// DBChannelEvent represents an event on a channel -type DBChannelEvent struct { - ID_ ChannelEventID ` db:"id"` - OrgID_ OrgID `json:"org_id" db:"org_id"` - ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"` - ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` - URN_ urns.URN `json:"urn" db:"urn"` - EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` - Extra_ null.Map[string] `json:"extra" db:"extra"` - OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` - CreatedOn_ time.Time `json:"created_on" db:"created_on"` - LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` - - ContactID_ ContactID `json:"-" db:"contact_id"` - ContactURNID_ ContactURNID `json:"-" db:"contact_urn_id"` - - // used to update contact - ContactName_ string `json:"contact_name"` - URNAuthTokens_ map[string]string `json:"auth_tokens"` - - channel *DBChannel -} - -func (e *DBChannelEvent) EventID() int64 { return int64(e.ID_) } -func (e *DBChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID_ } -func (e *DBChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } -func (e *DBChannelEvent) ContactName() string { return e.ContactName_ } -func (e *DBChannelEvent) AuthTokens() map[string]string { return e.URNAuthTokens_ } -func (e *DBChannelEvent) URN() urns.URN { return e.URN_ } -func (e *DBChannelEvent) Extra() map[string]string { return e.Extra_ } -func (e *DBChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } -func (e *DBChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } -func (e *DBChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } -func (e *DBChannelEvent) Channel() *DBChannel { return e.channel } - -func (e *DBChannelEvent) WithContactName(name string) courier.ChannelEvent { - e.ContactName_ = name - return e -} - -func (e *DBChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.ChannelEvent { - e.URNAuthTokens_ = tokens - return e -} - -func (e *DBChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { - e.Extra_ = null.Map[string](extra) - return e -} - -func (e *DBChannelEvent) WithOccurredOn(time time.Time) courier.ChannelEvent { - e.OccurredOn_ = time - return e -} diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 47c1025c1..3aba20d34 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -58,7 +58,7 @@ type channelError struct { // queues the passed in channel log to a writer func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) { log := logrus.WithFields(logrus.Fields{"log_uuid": clog.UUID(), "log_type": clog.Type(), "channel_uuid": clog.Channel().UUID()}) - dbChan := clog.Channel().(*DBChannel) + dbChan := clog.Channel().(*Channel) // so that we don't save null logs := clog.HTTPLogs() diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 4ee3179c8..80f5cc208 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -41,6 +41,27 @@ func (i ContactID) String() string { return "null" } +// Contact is our struct for a contact in the database +type Contact struct { + OrgID_ OrgID `db:"org_id"` + ID_ ContactID `db:"id"` + UUID_ courier.ContactUUID `db:"uuid"` + Name_ null.String `db:"name"` + + URNID_ ContactURNID `db:"urn_id"` + + CreatedOn_ time.Time `db:"created_on"` + ModifiedOn_ time.Time `db:"modified_on"` + + CreatedBy_ int `db:"created_by_id"` + ModifiedBy_ int `db:"modified_by_id"` + + IsNew_ bool +} + +// UUID returns the UUID for this contact +func (c *Contact) UUID() courier.ContactUUID { return c.UUID_ } + const insertContactSQL = ` INSERT INTO contacts_contact(org_id, is_active, status, uuid, created_on, modified_on, created_by_id, modified_by_id, name, ticket_count) @@ -49,7 +70,7 @@ RETURNING id ` // insertContact inserts the passed in contact, the id field will be populated with the result on success -func insertContact(tx *sqlx.Tx, contact *DBContact) error { +func insertContact(tx *sqlx.Tx, contact *Contact) error { rows, err := tx.NamedQuery(insertContactSQL, contact) if err != nil { return err @@ -81,9 +102,9 @@ WHERE ` // contactForURN first tries to look up a contact for the passed in URN, if not finding one then creating one -func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChannel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (*DBContact, error) { +func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (*Contact, error) { // try to look up our contact by URN - contact := &DBContact{} + contact := &Contact{} err := b.db.GetContext(ctx, contact, lookupContactFromURNSQL, urn.Identity(), org) if err != nil && err != sql.ErrNoRows { logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") @@ -200,24 +221,3 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *DBChanne // and return it return contact, nil } - -// DBContact is our struct for a contact in the database -type DBContact struct { - OrgID_ OrgID `db:"org_id"` - ID_ ContactID `db:"id"` - UUID_ courier.ContactUUID `db:"uuid"` - Name_ null.String `db:"name"` - - URNID_ ContactURNID `db:"urn_id"` - - CreatedOn_ time.Time `db:"created_on"` - ModifiedOn_ time.Time `db:"modified_on"` - - CreatedBy_ int `db:"created_by_id"` - ModifiedBy_ int `db:"modified_by_id"` - - IsNew_ bool -} - -// UUID returns the UUID for this contact -func (c *DBContact) UUID() courier.ContactUUID { return c.UUID_ } diff --git a/backends/rapidpro/media.go b/backends/rapidpro/media.go index 7c2e15406..dd782262b 100644 --- a/backends/rapidpro/media.go +++ b/backends/rapidpro/media.go @@ -10,7 +10,7 @@ import ( "github.com/nyaruka/gocommon/uuids" ) -type DBMedia struct { +type Media struct { UUID_ uuids.UUID `db:"uuid" json:"uuid"` Path_ string `db:"path" json:"path"` ContentType_ string `db:"content_type" json:"content_type"` @@ -19,18 +19,18 @@ type DBMedia struct { Width_ int `db:"width" json:"width"` Height_ int `db:"height" json:"height"` Duration_ int `db:"duration" json:"duration"` - Alternates_ []*DBMedia ` json:"alternates"` + Alternates_ []*Media ` json:"alternates"` } -func (m *DBMedia) UUID() uuids.UUID { return m.UUID_ } -func (m *DBMedia) Name() string { return filepath.Base(m.Path_) } -func (m *DBMedia) ContentType() string { return m.ContentType_ } -func (m *DBMedia) URL() string { return m.URL_ } -func (m *DBMedia) Size() int { return m.Size_ } -func (m *DBMedia) Width() int { return m.Width_ } -func (m *DBMedia) Height() int { return m.Height_ } -func (m *DBMedia) Duration() int { return m.Duration_ } -func (m *DBMedia) Alternates() []courier.Media { +func (m *Media) UUID() uuids.UUID { return m.UUID_ } +func (m *Media) Name() string { return filepath.Base(m.Path_) } +func (m *Media) ContentType() string { return m.ContentType_ } +func (m *Media) URL() string { return m.URL_ } +func (m *Media) Size() int { return m.Size_ } +func (m *Media) Width() int { return m.Width_ } +func (m *Media) Height() int { return m.Height_ } +func (m *Media) Duration() int { return m.Duration_ } +func (m *Media) Alternates() []courier.Media { as := make([]courier.Media, len(m.Alternates_)) for i, alt := range m.Alternates_ { as[i] = alt @@ -38,7 +38,7 @@ func (m *DBMedia) Alternates() []courier.Media { return as } -var _ courier.Media = &DBMedia{} +var _ courier.Media = &Media{} var sqlLookupMediaFromUUID = ` SELECT m.uuid, m.path, m.content_type, m.url, m.size, m.width, m.height, m.duration @@ -47,8 +47,8 @@ INNER JOIN msgs_media m0 ON m0.id = m.id OR m0.id = m.original_id WHERE m0.uuid = $1 ORDER BY m.id` -func lookupMediaFromUUID(ctx context.Context, db *sqlx.DB, uuid uuids.UUID) (*DBMedia, error) { - var records []*DBMedia +func lookupMediaFromUUID(ctx context.Context, db *sqlx.DB, uuid uuids.UUID) (*Media, error) { + var records []*Media err := db.SelectContext(ctx, &records, sqlLookupMediaFromUUID, uuid) if err != nil && err != sql.ErrNoRows { return nil, err diff --git a/backends/rapidpro/media_test.go b/backends/rapidpro/media_test.go index 7178dfcd5..f49e3c640 100644 --- a/backends/rapidpro/media_test.go +++ b/backends/rapidpro/media_test.go @@ -9,14 +9,14 @@ import ( ) func TestDBMedia(t *testing.T) { - media1 := &rapidpro.DBMedia{ + media1 := &rapidpro.Media{ UUID_: "5310f50f-9c8e-4035-9150-be5a1f78f21a", Path_: "/orgs/1/media/5310/5310f50f-9c8e-4035-9150-be5a1f78f21a/test.mp3", ContentType_: "audio/mp3", URL_: "http://nyaruka.s3.com/orgs/1/media/5310/5310f50f-9c8e-4035-9150-be5a1f78f21a/test.mp3", Size_: 123, Duration_: 500, - Alternates_: []*rapidpro.DBMedia{ + Alternates_: []*rapidpro.Media{ { UUID_: "514c552c-e585-40e2-938a-fe9450172da8", Path_: "/orgs/1/media/514c/514c552c-e585-40e2-938a-fe9450172da8/test.m4a", @@ -32,7 +32,7 @@ func TestDBMedia(t *testing.T) { media1JSON, err := json.Marshal(media1) assert.NoError(t, err) - media2 := &rapidpro.DBMedia{} + media2 := &rapidpro.Media{} err = json.Unmarshal(media1JSON, media2) assert.NoError(t, err) assert.Equal(t, media1, media2) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index c30213c62..de54349f7 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -46,9 +46,146 @@ const ( MsgArchived MsgVisibility = "A" ) +// Msg is our base struct to represent msgs both in our JSON and db representations +type Msg struct { + OrgID_ OrgID `json:"org_id" db:"org_id"` + ID_ courier.MsgID `json:"id" db:"id"` + UUID_ courier.MsgUUID `json:"uuid" db:"uuid"` + Direction_ MsgDirection ` db:"direction"` + Status_ courier.MsgStatus ` db:"status"` + Visibility_ MsgVisibility ` db:"visibility"` + HighPriority_ bool `json:"high_priority" db:"high_priority"` + Text_ string `json:"text" db:"text"` + Attachments_ pq.StringArray `json:"attachments" db:"attachments"` + QuickReplies_ pq.StringArray `json:"quick_replies" db:"quick_replies"` + Locale_ null.String `json:"locale" db:"locale"` + ExternalID_ null.String ` db:"external_id"` + Metadata_ json.RawMessage `json:"metadata" db:"metadata"` + + ChannelID_ courier.ChannelID ` db:"channel_id"` + ContactID_ ContactID `json:"contact_id" db:"contact_id"` + ContactURNID_ ContactURNID `json:"contact_urn_id" db:"contact_urn_id"` + + MessageCount_ int ` db:"msg_count"` + ErrorCount_ int ` db:"error_count"` + FailedReason_ null.String ` db:"failed_reason"` + + NextAttempt_ time.Time ` db:"next_attempt"` + CreatedOn_ time.Time `json:"created_on" db:"created_on"` + ModifiedOn_ time.Time ` db:"modified_on"` + QueuedOn_ time.Time ` db:"queued_on"` + SentOn_ *time.Time ` db:"sent_on"` + LogUUIDs pq.StringArray ` db:"log_uuids"` + + // extra non-model fields that mailroom will include in queued payload + ChannelUUID_ courier.ChannelUUID `json:"channel_uuid"` + URN_ urns.URN `json:"urn"` + URNAuth_ string `json:"urn_auth"` + ResponseToExternalID_ string `json:"response_to_external_id"` + IsResend_ bool `json:"is_resend"` + Flow_ *courier.FlowReference `json:"flow"` + Origin_ courier.MsgOrigin `json:"origin"` + ContactLastSeenOn_ *time.Time `json:"contact_last_seen_on"` + + // extra fields used to allow courier to update a session's timeout to *after* the message has been sent + SessionID_ SessionID `json:"session_id"` + SessionTimeout_ int `json:"session_timeout"` + SessionWaitStartedOn_ *time.Time `json:"session_wait_started_on"` + SessionStatus_ string `json:"session_status"` + + ContactName_ string `json:"contact_name"` + URNAuthTokens_ map[string]string `json:"auth_tokens"` + channel *Channel + workerToken queue.WorkerToken + alreadyWritten bool +} + +// newMsg creates a new DBMsg object with the passed in parameters +func newMsg(direction MsgDirection, channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) *Msg { + now := time.Now() + dbChannel := channel.(*Channel) + + return &Msg{ + OrgID_: dbChannel.OrgID(), + UUID_: courier.MsgUUID(uuids.New()), + Direction_: direction, + Status_: courier.MsgStatusPending, + Visibility_: MsgVisible, + HighPriority_: false, + Text_: text, + ExternalID_: null.String(extID), + + ChannelID_: dbChannel.ID(), + ChannelUUID_: dbChannel.UUID(), + + URN_: urn, + MessageCount_: 1, + + NextAttempt_: now, + CreatedOn_: now, + ModifiedOn_: now, + QueuedOn_: now, + LogUUIDs: []string{string(clog.UUID())}, + + channel: dbChannel, + workerToken: "", + alreadyWritten: false, + } +} + +func (m *Msg) EventID() int64 { return int64(m.ID_) } +func (m *Msg) ID() courier.MsgID { return m.ID_ } +func (m *Msg) UUID() courier.MsgUUID { return m.UUID_ } +func (m *Msg) ExternalID() string { return string(m.ExternalID_) } +func (m *Msg) Text() string { return m.Text_ } +func (m *Msg) Attachments() []string { return m.Attachments_ } +func (m *Msg) URN() urns.URN { return m.URN_ } +func (m *Msg) Channel() courier.Channel { return m.channel } + +// outgoing specific +func (m *Msg) QuickReplies() []string { return m.QuickReplies_ } +func (m *Msg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } +func (m *Msg) URNAuth() string { return m.URNAuth_ } +func (m *Msg) Origin() courier.MsgOrigin { return m.Origin_ } +func (m *Msg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } +func (m *Msg) Topic() string { + if m.Metadata_ == nil { + return "" + } + topic, _, _, _ := jsonparser.Get(m.Metadata_, "topic") + return string(topic) +} +func (m *Msg) Metadata() json.RawMessage { + return m.Metadata_ +} +func (m *Msg) ResponseToExternalID() string { return m.ResponseToExternalID_ } +func (m *Msg) SentOn() *time.Time { return m.SentOn_ } +func (m *Msg) IsResend() bool { return m.IsResend_ } +func (m *Msg) Flow() *courier.FlowReference { return m.Flow_ } +func (m *Msg) SessionStatus() string { return m.SessionStatus_ } +func (m *Msg) HighPriority() bool { return m.HighPriority_ } + +// incoming specific +func (m *Msg) ReceivedOn() *time.Time { return m.SentOn_ } +func (m *Msg) WithAttachment(url string) courier.Msg { + m.Attachments_ = append(m.Attachments_, url) + return m +} +func (m *Msg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } +func (m *Msg) WithURNAuthTokens(tokens map[string]string) courier.Msg { + m.URNAuthTokens_ = tokens + return m +} +func (m *Msg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } + +func (m *Msg) hash() string { + hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) + return hex.EncodeToString(hash[:]) +} + // WriteMsg creates a message given the passed in arguments func writeMsg(ctx context.Context, b *backend, msg courier.Msg, clog *courier.ChannelLog) error { - m := msg.(*DBMsg) + m := msg.(*Msg) // this msg has already been written (we received it twice), we are a no op if m.alreadyWritten { @@ -104,39 +241,6 @@ func writeMsg(ctx context.Context, b *backend, msg courier.Msg, clog *courier.Ch return err } -// newMsg creates a new DBMsg object with the passed in parameters -func newMsg(direction MsgDirection, channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) *DBMsg { - now := time.Now() - dbChannel := channel.(*DBChannel) - - return &DBMsg{ - OrgID_: dbChannel.OrgID(), - UUID_: courier.MsgUUID(uuids.New()), - Direction_: direction, - Status_: courier.MsgStatusPending, - Visibility_: MsgVisible, - HighPriority_: false, - Text_: text, - ExternalID_: null.String(extID), - - ChannelID_: dbChannel.ID(), - ChannelUUID_: dbChannel.UUID(), - - URN_: urn, - MessageCount_: 1, - - NextAttempt_: now, - CreatedOn_: now, - ModifiedOn_: now, - QueuedOn_: now, - LogUUIDs: []string{string(clog.UUID())}, - - channel: dbChannel, - workerToken: "", - alreadyWritten: false, - } -} - const sqlInsertMsg = ` INSERT INTO msgs_msg(org_id, uuid, direction, text, attachments, msg_type, msg_count, error_count, high_priority, status, @@ -145,7 +249,7 @@ INSERT INTO :visibility, :external_id, :channel_id, :contact_id, :contact_urn_id, :created_on, :modified_on, :next_attempt, :queued_on, :sent_on, :log_uuids) RETURNING id` -func writeMsgToDB(ctx context.Context, b *backend, m *DBMsg, clog *courier.ChannelLog) error { +func writeMsgToDB(ctx context.Context, b *backend, m *Msg, clog *courier.ChannelLog) error { contact, err := contactForURN(ctx, b, m.OrgID_, m.channel, m.URN_, m.URNAuthTokens_, m.ContactName_, clog) // our db is down, write to the spool, we will write/queue this later @@ -191,7 +295,7 @@ func (b *backend) flushMsgFile(filename string, contents []byte) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() - msg := &DBMsg{} + msg := &Msg{} err := json.Unmarshal(contents, msg) if err != nil { log.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) @@ -204,7 +308,7 @@ func (b *backend) flushMsgFile(filename string, contents []byte) error { if err != nil { return err } - msg.channel = channel.(*DBChannel) + msg.channel = channel.(*Channel) // create log tho it won't be written clog := courier.NewChannelLog(courier.ChannelLogTypeMsgReceive, channel, nil) @@ -221,7 +325,7 @@ func (b *backend) flushMsgFile(filename string, contents []byte) error { //----------------------------------------------------------------------------- // checks to see if this message has already been received and if so returns its UUID -func (b *backend) checkMsgAlreadyReceived(msg *DBMsg) courier.MsgUUID { +func (b *backend) checkMsgAlreadyReceived(msg *Msg) courier.MsgUUID { rc := b.redisPool.Get() defer rc.Close() @@ -251,7 +355,7 @@ func (b *backend) checkMsgAlreadyReceived(msg *DBMsg) courier.MsgUUID { } // records that the given message has been received and written to the database -func (b *backend) recordMsgReceived(msg *DBMsg) { +func (b *backend) recordMsgReceived(msg *Msg) { rc := b.redisPool.Get() defer rc.Close() @@ -267,116 +371,8 @@ func (b *backend) recordMsgReceived(msg *DBMsg) { } // clearMsgSeen clears our seen incoming messages for the passed in channel and URN -func (b *backend) clearMsgSeen(rc redis.Conn, msg *DBMsg) { +func (b *backend) clearMsgSeen(rc redis.Conn, msg *Msg) { fingerprint := fmt.Sprintf("%s|%s", msg.Channel().UUID(), msg.URN().Identity()) b.receivedMsgs.Del(rc, fingerprint) } - -//----------------------------------------------------------------------------- -// Our implementation of Msg interface -//----------------------------------------------------------------------------- - -// DBMsg is our base struct to represent msgs both in our JSON and db representations -type DBMsg struct { - OrgID_ OrgID `json:"org_id" db:"org_id"` - ID_ courier.MsgID `json:"id" db:"id"` - UUID_ courier.MsgUUID `json:"uuid" db:"uuid"` - Direction_ MsgDirection ` db:"direction"` - Status_ courier.MsgStatus ` db:"status"` - Visibility_ MsgVisibility ` db:"visibility"` - HighPriority_ bool `json:"high_priority" db:"high_priority"` - Text_ string `json:"text" db:"text"` - Attachments_ pq.StringArray `json:"attachments" db:"attachments"` - QuickReplies_ pq.StringArray `json:"quick_replies" db:"quick_replies"` - Locale_ null.String `json:"locale" db:"locale"` - ExternalID_ null.String ` db:"external_id"` - Metadata_ json.RawMessage `json:"metadata" db:"metadata"` - - ChannelID_ courier.ChannelID ` db:"channel_id"` - ContactID_ ContactID `json:"contact_id" db:"contact_id"` - ContactURNID_ ContactURNID `json:"contact_urn_id" db:"contact_urn_id"` - - MessageCount_ int ` db:"msg_count"` - ErrorCount_ int ` db:"error_count"` - FailedReason_ null.String ` db:"failed_reason"` - - NextAttempt_ time.Time ` db:"next_attempt"` - CreatedOn_ time.Time `json:"created_on" db:"created_on"` - ModifiedOn_ time.Time ` db:"modified_on"` - QueuedOn_ time.Time ` db:"queued_on"` - SentOn_ *time.Time ` db:"sent_on"` - LogUUIDs pq.StringArray ` db:"log_uuids"` - - // extra non-model fields that mailroom will include in queued payload - ChannelUUID_ courier.ChannelUUID `json:"channel_uuid"` - URN_ urns.URN `json:"urn"` - URNAuth_ string `json:"urn_auth"` - ResponseToExternalID_ string `json:"response_to_external_id"` - IsResend_ bool `json:"is_resend"` - Flow_ *courier.FlowReference `json:"flow"` - Origin_ courier.MsgOrigin `json:"origin"` - ContactLastSeenOn_ *time.Time `json:"contact_last_seen_on"` - - // extra fields used to allow courier to update a session's timeout to *after* the message has been sent - SessionID_ SessionID `json:"session_id"` - SessionTimeout_ int `json:"session_timeout"` - SessionWaitStartedOn_ *time.Time `json:"session_wait_started_on"` - SessionStatus_ string `json:"session_status"` - - ContactName_ string `json:"contact_name"` - URNAuthTokens_ map[string]string `json:"auth_tokens"` - channel *DBChannel - workerToken queue.WorkerToken - alreadyWritten bool -} - -func (m *DBMsg) EventID() int64 { return int64(m.ID_) } -func (m *DBMsg) ID() courier.MsgID { return m.ID_ } -func (m *DBMsg) UUID() courier.MsgUUID { return m.UUID_ } -func (m *DBMsg) ExternalID() string { return string(m.ExternalID_) } -func (m *DBMsg) Text() string { return m.Text_ } -func (m *DBMsg) Attachments() []string { return m.Attachments_ } -func (m *DBMsg) URN() urns.URN { return m.URN_ } -func (m *DBMsg) Channel() courier.Channel { return m.channel } - -// outgoing specific -func (m *DBMsg) QuickReplies() []string { return m.QuickReplies_ } -func (m *DBMsg) Locale() i18n.Locale { return i18n.Locale(string(m.Locale_)) } -func (m *DBMsg) URNAuth() string { return m.URNAuth_ } -func (m *DBMsg) Origin() courier.MsgOrigin { return m.Origin_ } -func (m *DBMsg) ContactLastSeenOn() *time.Time { return m.ContactLastSeenOn_ } -func (m *DBMsg) Topic() string { - if m.Metadata_ == nil { - return "" - } - topic, _, _, _ := jsonparser.Get(m.Metadata_, "topic") - return string(topic) -} -func (m *DBMsg) Metadata() json.RawMessage { - return m.Metadata_ -} -func (m *DBMsg) ResponseToExternalID() string { return m.ResponseToExternalID_ } -func (m *DBMsg) SentOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) IsResend() bool { return m.IsResend_ } -func (m *DBMsg) Flow() *courier.FlowReference { return m.Flow_ } -func (m *DBMsg) SessionStatus() string { return m.SessionStatus_ } -func (m *DBMsg) HighPriority() bool { return m.HighPriority_ } - -// incoming specific -func (m *DBMsg) ReceivedOn() *time.Time { return m.SentOn_ } -func (m *DBMsg) WithAttachment(url string) courier.Msg { - m.Attachments_ = append(m.Attachments_, url) - return m -} -func (m *DBMsg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } -func (m *DBMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { - m.URNAuthTokens_ = tokens - return m -} -func (m *DBMsg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } - -func (m *DBMsg) hash() string { - hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) - return hex.EncodeToString(hash[:]) -} diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index b42dab2ee..16243e6e6 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -32,7 +32,7 @@ type StatusUpdate struct { // creates a new message status update func newStatusUpdate(channel courier.Channel, id courier.MsgID, externalID string, status courier.MsgStatus, clog *courier.ChannelLog) *StatusUpdate { - dbChannel := channel.(*DBChannel) + dbChannel := channel.(*Channel) return &StatusUpdate{ ChannelUUID_: channel.UUID(), diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index 964b46168..da9aeefab 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -9,8 +9,8 @@ import ( "github.com/nyaruka/gocommon/jsonx" ) -func queueMsgHandling(rc redis.Conn, c *DBContact, m *DBMsg) error { - channel := m.Channel().(*DBChannel) +func queueMsgHandling(rc redis.Conn, c *Contact, m *Msg) error { + channel := m.Channel().(*Channel) // queue to mailroom body := map[string]any{ @@ -30,7 +30,7 @@ func queueMsgHandling(rc redis.Conn, c *DBContact, m *DBMsg) error { return queueMailroomTask(rc, "msg_event", m.OrgID_, m.ContactID_, body) } -func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { +func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { // queue to mailroom switch e.EventType() { case courier.StopContact: @@ -81,7 +81,7 @@ func queueChannelEvent(rc redis.Conn, c *DBContact, e *DBChannelEvent) error { } } -func queueMsgDeleted(rc redis.Conn, ch *DBChannel, msgID courier.MsgID, contactID ContactID) error { +func queueMsgDeleted(rc redis.Conn, ch *Channel, msgID courier.MsgID, contactID ContactID) error { return queueMailroomTask(rc, "msg_deleted", ch.OrgID_, contactID, map[string]any{"org_id": ch.OrgID_, "msg_id": msgID}) } diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 0263880de..70f82a4dd 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -94,7 +94,7 @@ func getURNsForContact(db *sqlx.Tx, contactID ContactID) ([]*ContactURN, error) // that the passed in channel is the default one for that URN // // Note that the URN must be one of the contact's URN before calling this method -func setDefaultURN(db *sqlx.Tx, channel *DBChannel, contact *DBContact, urn urns.URN, authTokens map[string]string) error { +func setDefaultURN(db *sqlx.Tx, channel *Channel, contact *Contact, urn urns.URN, authTokens map[string]string) error { scheme := urn.Scheme() contactURNs, err := getURNsForContact(db, contact.ID_) if err != nil { @@ -175,7 +175,7 @@ func getContactURNByIdentity(db *sqlx.Tx, org OrgID, urn urns.URN) (*ContactURN, // getOrCreateContactURN returns the ContactURN for the passed in org and URN, creating and associating // it with the passed in contact if necessary -func getOrCreateContactURN(db *sqlx.Tx, channel *DBChannel, contactID ContactID, urn urns.URN, authTokens map[string]string) (*ContactURN, error) { +func getOrCreateContactURN(db *sqlx.Tx, channel *Channel, contactID ContactID, urn urns.URN, authTokens map[string]string) (*ContactURN, error) { contactURN := newContactURN(channel.OrgID(), courier.NilChannelID, contactID, urn, authTokens) if channel.HasRole(courier.ChannelRoleSend) { contactURN.ChannelID = channel.ID() From 0427cb40df6720fc6812ffab62dd734737fe22bb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 11:44:31 -0500 Subject: [PATCH 091/170] Update CHANGELOG.md for v8.3.16 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37378710..a067dddf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +v8.3.16 (2023-09-13) +------------------------- + * Simplify interfaces that handlers have access to + * Allow handlers to create arbitrary auth tokens with messages and channel events + * Rename legacy FB and WA handlers + * Refactor whatsapp handlers to be more DRY + v8.3.15 (2023-09-12) ------------------------- * Stop reading from ContactURN.auth and remove from model From 17d0f8468a12ade627cad01aedd34522488818b2 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 14:04:01 -0500 Subject: [PATCH 092/170] Add support for FB notificaiton messages optin and optout events --- backends/rapidpro/urn.go | 14 ++--- channel_event.go | 2 + handlers/firebase/firebase_test.go | 3 +- handlers/meta/meta.go | 51 +++++++++++++------ handlers/meta/meta_test.go | 26 ++++++++++ .../fba/notificationMessagesOptIn.json | 30 +++++++++++ .../fba/notificationMessagesOptOut.json | 30 +++++++++++ handlers/test.go | 5 +- test/backend.go | 45 ++++++++++++---- test/msg.go | 4 -- utils/misc.go | 12 +++++ utils/misc_test.go | 34 +++++++++++++ utils/whatsapp/api.go | 10 ++++ 13 files changed, 223 insertions(+), 43 deletions(-) create mode 100644 handlers/meta/testdata/fba/notificationMessagesOptIn.json create mode 100644 handlers/meta/testdata/fba/notificationMessagesOptOut.json diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 70f82a4dd..46d814d83 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -119,9 +119,7 @@ func setDefaultURN(db *sqlx.Tx, channel *Channel, contact *Contact, urn urns.URN contactURNs[0].ChannelID = channel.ID() } - for k, v := range authTokens { - contactURNs[0].AuthTokens[k] = v - } + utils.MapUpdate(contactURNs[0].AuthTokens, authTokens) return updateContactURN(db, contactURNs[0]) } @@ -142,9 +140,7 @@ func setDefaultURN(db *sqlx.Tx, channel *Channel, contact *Contact, urn urns.URN existing.ChannelID = channel.ID() } - for k, v := range authTokens { - contactURNs[0].AuthTokens[k] = v - } + utils.MapUpdate(contactURNs[0].AuthTokens, authTokens) } else { existing.Priority = currPriority @@ -209,11 +205,9 @@ func getOrCreateContactURN(db *sqlx.Tx, channel *Channel, contactID ContactID, u } } - // update our auth if we have a value set + // update our auth tokens if any provided if authTokens != nil { - for k, v := range authTokens { - contactURN.AuthTokens[k] = v - } + utils.MapUpdate(contactURN.AuthTokens, authTokens) err = updateContactURN(db, contactURN) } diff --git a/channel_event.go b/channel_event.go index 261560735..cabc8893a 100644 --- a/channel_event.go +++ b/channel_event.go @@ -15,6 +15,8 @@ const ( Referral ChannelEventType = "referral" StopContact ChannelEventType = "stop_contact" WelcomeMessage ChannelEventType = "welcome_message" + EventTypeOptIn ChannelEventType = "optin" + EventTypeOptOut ChannelEventType = "optout" ) //----------------------------------------------------------------------------- diff --git a/handlers/firebase/firebase_test.go b/handlers/firebase/firebase_test.go index cfdc82629..2f8bb07ba 100644 --- a/handlers/firebase/firebase_test.go +++ b/handlers/firebase/firebase_test.go @@ -8,6 +8,7 @@ import ( "github.com/nyaruka/courier" . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/urns" ) const ( @@ -53,7 +54,7 @@ var testCases = []IncomingTestCase{ ExpectedMsgText: Sp("hello world"), ExpectedURN: "fcm:12345", ExpectedDate: time.Date(2017, 1, 1, 8, 50, 0, 0, time.UTC), - ExpectedURNAuthTokens: map[string]string{"default": "token"}, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"fcm:12345": {"default": "token"}}, ExpectedContactName: Sp("fred"), }, { diff --git a/handlers/meta/meta.go b/handlers/meta/meta.go index ec90ed724..1e3fe87af 100644 --- a/handlers/meta/meta.go +++ b/handlers/meta/meta.go @@ -428,26 +428,45 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } if msg.OptIn != nil { - // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) - // TODO: - // We need to deal with the case of them responding and remapping the user_ref in that case: - // https://developers.facebook.com/docs/messenger-platform/discovery/checkbox-plugin - // Right now that we even support this isn't documented and I don't think anybody uses it, so leaving that out. - // (things will still work, we just will have dupe contacts, one with user_ref for the first contact, then with the real id when they reply) - if msg.OptIn.UserRef != "" { - urn, err = urns.NewFacebookURN(urns.FacebookRefPrefix + msg.OptIn.UserRef) - if err != nil { - return nil, nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + var event courier.ChannelEvent + + if msg.OptIn.Type == "notification_messages" { + eventType := courier.EventTypeOptIn + optInName := msg.OptIn.Title + authToken := msg.OptIn.NotificationMessagesToken + + if msg.OptIn.NotificationMessagesStatus == "STOP_NOTIFICATIONS" { + eventType = courier.EventTypeOptOut + authToken = "" // so that we remove it } - } - event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) + event = h.Backend().NewChannelEvent(channel, eventType, urn, clog). + WithOccurredOn(date). + WithExtra(map[string]string{ + "optin_name": optInName, + }). + WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInName): authToken}) + } else { - // build our extra - extra := map[string]string{ - referrerIDKey: msg.OptIn.Ref, + // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) + // TODO: + // We need to deal with the case of them responding and remapping the user_ref in that case: + // https://developers.facebook.com/docs/messenger-platform/discovery/checkbox-plugin + // Right now that we even support this isn't documented and I don't think anybody uses it, so leaving that out. + // (things will still work, we just will have dupe contacts, one with user_ref for the first contact, then with the real id when they reply) + if msg.OptIn.UserRef != "" { + urn, err = urns.NewFacebookURN(urns.FacebookRefPrefix + msg.OptIn.UserRef) + if err != nil { + return nil, nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) + } + } + + event = h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog). + WithOccurredOn(date). + WithExtra(map[string]string{ + referrerIDKey: msg.OptIn.Ref, + }) } - event = event.WithExtra(extra) err := h.Backend().WriteChannelEvent(ctx, event, clog) if err != nil { diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index 5adfe0d72..e408d1ec5 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -127,6 +127,32 @@ var testCasesFBA = []IncomingTestCase{ ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, PrepRequest: addValidSignature, }, + { + Label: "Receive Notification Messages OptIn", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/notificationMessagesOptIn.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedURN: "facebook:5678", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + ExpectedEvent: courier.EventTypeOptIn, + ExpectedEventExtra: map[string]string{"optin_name": "Bird Facts"}, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:Bird Facts": "12345678901234567890"}}, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Notification Messages OptOut", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/notificationMessagesOptOut.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedURN: "facebook:5678", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + ExpectedEvent: courier.EventTypeOptOut, + ExpectedEventExtra: map[string]string{"optin_name": "Bird Facts"}, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, + PrepRequest: addValidSignature, + }, { Label: "Receive Get Started", URL: "/c/fba/receive", diff --git a/handlers/meta/testdata/fba/notificationMessagesOptIn.json b/handlers/meta/testdata/fba/notificationMessagesOptIn.json new file mode 100644 index 000000000..278c681f8 --- /dev/null +++ b/handlers/meta/testdata/fba/notificationMessagesOptIn.json @@ -0,0 +1,30 @@ +{ + "object": "page", + "entry": [ + { + "id": "12345", + "time": 1459991487970, + "messaging": [ + { + "sender": { + "id": "5678" + }, + "recipient": { + "id": "12345" + }, + "timestamp": 1459991487970, + "optin": { + "type": "notification_messages", + "payload": "bird-facts", + "notification_messages_token": "12345678901234567890", + "notification_messages_frequency": "DAILY", + "token_expiry_timestamp": 2145916800000, + "user_token_status": "NOT_REFRESHED", + "notification_messages_timezone": "UTC", + "title": "Bird Facts" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/handlers/meta/testdata/fba/notificationMessagesOptOut.json b/handlers/meta/testdata/fba/notificationMessagesOptOut.json new file mode 100644 index 000000000..b2ba80116 --- /dev/null +++ b/handlers/meta/testdata/fba/notificationMessagesOptOut.json @@ -0,0 +1,30 @@ +{ + "object": "page", + "entry": [ + { + "id": "12345", + "time": 1459991487970, + "messaging": [ + { + "recipient": { + "id": "12345" + }, + "timestamp": 1459991487970, + "sender": { + "id": "5678" + }, + "optin": { + "type": "notification_messages", + "payload": "bird-facts", + "notification_messages_token": "12345678901234567890", + "notification_messages_frequency": "DAILY", + "token_expiry_timestamp": 2145916800000, + "user_token_status": "NOT_REFRESHED", + "notification_messages_status": "STOP_NOTIFICATIONS", + "title": "Bird Facts" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/handlers/test.go b/handlers/test.go index f72d05cb5..f9803f159 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -45,7 +45,7 @@ type IncomingTestCase struct { ExpectedContactName *string ExpectedMsgText *string ExpectedURN urns.URN - ExpectedURNAuthTokens map[string]string + ExpectedURNAuthTokens map[urns.URN]map[string]string ExpectedAttachments []string ExpectedDate time.Time ExpectedMsgStatus courier.MsgStatus @@ -181,7 +181,6 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Equal(t, tc.ExpectedExternalID, msg.ExternalID()) } assert.Equal(t, tc.ExpectedURN, msg.URN()) - assert.Equal(t, tc.ExpectedURNAuthTokens, msg.URNAuthTokens()) } else { assert.Empty(t, mb.WrittenMsgs(), "unexpected msg written") } @@ -222,6 +221,8 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour require.Equal(*tc.ExpectedContactName, mb.LastContactName()) } + assert.Equal(t, tc.ExpectedURNAuthTokens, mb.URNAuthTokens()) + // unless we know there won't be a log, check one was written if !tc.NoLogsExpected { if assert.Equal(t, 1, len(mb.WrittenChannelLogs()), "expected a channel log") { diff --git a/test/backend.go b/test/backend.go index 15719ed1d..5fa6526b6 100644 --- a/test/backend.go +++ b/test/backend.go @@ -10,6 +10,7 @@ import ( "github.com/gomodule/redigo/redis" _ "github.com/lib/pq" "github.com/nyaruka/courier" + "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/pkg/errors" @@ -51,6 +52,7 @@ type MockBackend struct { lastMsgID courier.MsgID lastContactName string + urnAuthTokens map[urns.URN]map[string]string sentMsgs map[courier.MsgID]bool seenExternalIDs map[string]courier.MsgUUID } @@ -190,22 +192,26 @@ func (mb *MockBackend) SetErrorOnQueue(shouldError bool) { // WriteMsg queues the passed in message internally func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courier.ChannelLog) error { - mock := m.(*MockMsg) + mm := m.(*MockMsg) // this msg has already been written (we received it twice), we are a no op - if mock.alreadyWritten { + if mm.alreadyWritten { return nil } mb.lastMsgID++ - mock.id = mb.lastMsgID + mm.id = mb.lastMsgID if mb.errorOnQueue { return errors.New("unable to queue message") } mb.writtenMsgs = append(mb.writtenMsgs, m) - mb.lastContactName = m.(*MockMsg).contactName + mb.lastContactName = mm.contactName + + if mm.urnAuthTokens != nil { + mb.recordURNAuthTokens(mm.urn, mm.urnAuthTokens) + } if m.ExternalID() != "" { mb.seenExternalIDs[fmt.Sprintf("%s|%s", m.Channel().UUID(), m.ExternalID())] = m.UUID() @@ -254,11 +260,18 @@ func (mb *MockBackend) NewChannelEvent(channel courier.Channel, eventType courie // WriteChannelEvent writes the channel event passed in func (mb *MockBackend) WriteChannelEvent(ctx context.Context, event courier.ChannelEvent, clog *courier.ChannelLog) error { + evt := event.(*mockChannelEvent) + mb.mutex.Lock() defer mb.mutex.Unlock() mb.writtenChannelEvents = append(mb.writtenChannelEvents, event) - mb.lastContactName = event.(*mockChannelEvent).contactName + mb.lastContactName = evt.contactName + + if evt.urnAuthTokens != nil { + mb.recordURNAuthTokens(evt.urn, evt.urnAuthTokens) + } + return nil } @@ -363,11 +376,12 @@ func (mb *MockBackend) RedisPool() *redis.Pool { // Methods not part of the backed interface but used in tests //////////////////////////////////////////////////////////////////////////////// -func (mb *MockBackend) WrittenMsgs() []courier.Msg { return mb.writtenMsgs } -func (mb *MockBackend) WrittenMsgStatuses() []courier.StatusUpdate { return mb.writtenMsgStatuses } -func (mb *MockBackend) WrittenChannelEvents() []courier.ChannelEvent { return mb.writtenChannelEvents } -func (mb *MockBackend) WrittenChannelLogs() []*courier.ChannelLog { return mb.writtenChannelLogs } -func (mb *MockBackend) SavedAttachments() []*SavedAttachment { return mb.savedAttachments } +func (mb *MockBackend) WrittenMsgs() []courier.Msg { return mb.writtenMsgs } +func (mb *MockBackend) WrittenMsgStatuses() []courier.StatusUpdate { return mb.writtenMsgStatuses } +func (mb *MockBackend) WrittenChannelEvents() []courier.ChannelEvent { return mb.writtenChannelEvents } +func (mb *MockBackend) WrittenChannelLogs() []*courier.ChannelLog { return mb.writtenChannelLogs } +func (mb *MockBackend) SavedAttachments() []*SavedAttachment { return mb.savedAttachments } +func (mb *MockBackend) URNAuthTokens() map[urns.URN]map[string]string { return mb.urnAuthTokens } // LastContactName returns the contact name set on the last msg or channel event written func (mb *MockBackend) LastContactName() string { @@ -400,9 +414,20 @@ func (mb *MockBackend) Reset() { mb.writtenMsgStatuses = nil mb.writtenChannelEvents = nil mb.writtenChannelLogs = nil + mb.urnAuthTokens = nil } // SetStorageError sets the error to return for operation that try to use storage func (mb *MockBackend) SetStorageError(err error) { mb.storageError = err } + +func (mb *MockBackend) recordURNAuthTokens(urn urns.URN, authTokens map[string]string) { + if mb.urnAuthTokens == nil { + mb.urnAuthTokens = make(map[urns.URN]map[string]string) + } + if mb.urnAuthTokens[urn] == nil { + mb.urnAuthTokens[urn] = map[string]string{} + } + utils.MapUpdate(mb.urnAuthTokens[urn], authTokens) +} diff --git a/test/msg.go b/test/msg.go index c3285bc4c..800f489eb 100644 --- a/test/msg.go +++ b/test/msg.go @@ -84,10 +84,6 @@ func (m *MockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { } func (m *MockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } -// used for testing created incoming messages -func (m *MockMsg) URNAuthTokens() map[string]string { return m.urnAuthTokens } -func (m *MockMsg) ContactName() string { return m.contactName } - // used to create outgoing messages for testing func (m *MockMsg) WithID(id courier.MsgID) courier.Msg { m.id = id; return m } func (m *MockMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.uuid = uuid; return m } diff --git a/utils/misc.go b/utils/misc.go index 99c19cde3..8312921f8 100644 --- a/utils/misc.go +++ b/utils/misc.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "net/url" "path" + "reflect" "unicode/utf8" validator "gopkg.in/go-playground/validator.v9" @@ -144,3 +145,14 @@ func MapContains[K comparable, V comparable, M ~map[K]V](m1 M, m2 M) bool { } return true } + +// MapUpdate updates map m1 to contain the key value pairs in m2 - deleting any pairs in m1 which have zero values in m2. +func MapUpdate[K comparable, V comparable, M ~map[K]V](m1 M, m2 M) { + for k, v := range m2 { + if reflect.ValueOf(v).IsZero() { + delete(m1, k) + } else { + m1[k] = v + } + } +} diff --git a/utils/misc_test.go b/utils/misc_test.go index 50d16b548..996abc332 100644 --- a/utils/misc_test.go +++ b/utils/misc_test.go @@ -111,3 +111,37 @@ func TestMapContains(t *testing.T) { assert.False(t, utils.MapContains(map[string]string{"a": "1", "b": "2"}, map[string]string{"c": "3"})) assert.False(t, utils.MapContains(map[string]string{"a": "1", "b": "2"}, map[string]string{"a": "4"})) } + +func TestMapUpdate(t *testing.T) { + tcs := []struct { + m1 map[string]any + m2 map[string]any + updated map[string]any + }{ + { + map[string]any{}, + map[string]any{}, + map[string]any{}, + }, + { + map[string]any{"a": "1", "b": "2"}, + map[string]any{"b": 5, "c": "3"}, + map[string]any{"a": "1", "b": 5, "c": "3"}, + }, + { + map[string]any{"a": "1", "b": "2", "c": "3"}, + map[string]any{"b": 0, "c": ""}, // delete by zero value + map[string]any{"a": "1"}, + }, + { + map[string]any{"a": "1"}, + map[string]any{"c": ""}, // delete but doesn't exist in m1 so noop + map[string]any{"a": "1"}, + }, + } + + for _, tc := range tcs { + utils.MapUpdate(tc.m1, tc.m2) + assert.Equal(t, tc.updated, tc.m1) + } +} diff --git a/utils/whatsapp/api.go b/utils/whatsapp/api.go index 08d78c3b7..fc71ee278 100644 --- a/utils/whatsapp/api.go +++ b/utils/whatsapp/api.go @@ -136,6 +136,16 @@ type MOPayload struct { Timestamp int64 `json:"timestamp"` OptIn *struct { + Type string `json:"type"` + Payload string `json:"payload"` + NotificationMessagesToken string `json:"notification_messages_token"` + NotificationMessagesTimezone string `json:"notification_messages_timezone"` + NotificationMessagesFrequency string `json:"notification_messages_frequency"` + NotificationMessagesStatus string `json:"notification_messages_status"` + TokenExpiryTimestamp int64 `json:"token_expiry_timestamp"` + UserTokenStatus string `json:"user_token_status"` + Title string `json:"title"` + Ref string `json:"ref"` UserRef string `json:"user_ref"` } `json:"optin"` From fc0450d1e75ff5ecb041d8073026aca5ec750b8a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 14:53:54 -0500 Subject: [PATCH 093/170] Allow incoming tests on handlers to test for multiple events --- handlers/external/external_test.go | 10 ++-- handlers/facebook_legacy/facebook_test.go | 42 +++++++-------- handlers/jiochat/jiochat_test.go | 5 +- handlers/messagebird/messagebird_test.go | 5 +- handlers/meta/meta_test.go | 63 ++++++++++------------- handlers/mtarget/mtarget_test.go | 12 ++++- handlers/telegram/telegram_test.go | 6 +-- handlers/test.go | 33 +++++++----- handlers/twiml/twiml_test.go | 36 +++++++------ handlers/viber/viber_test.go | 40 +++++++++++--- handlers/wechat/wechat_test.go | 12 ++++- 11 files changed, 156 insertions(+), 108 deletions(-) diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index 3c44972b4..ce2ac7ec6 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -166,8 +166,9 @@ var handleTestCases = []IncomingTestCase{ Data: "nothing", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+2349067554729", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+2349067554729"}, + }, }, { Label: "Stopped Event Post", @@ -175,8 +176,9 @@ var handleTestCases = []IncomingTestCase{ Data: "from=%2B2349067554729", ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+2349067554729", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+2349067554729"}, + }, }, { Label: "Stopped Event Invalid URN", diff --git a/handlers/facebook_legacy/facebook_test.go b/handlers/facebook_legacy/facebook_test.go index 15672be6c..e68596561 100644 --- a/handlers/facebook_legacy/facebook_test.go +++ b/handlers/facebook_legacy/facebook_test.go @@ -481,10 +481,9 @@ var testCases = []IncomingTestCase{ Data: optInUserRef, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:ref:optin_user_ref", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, }, { Label: "Receive OptIn", @@ -492,10 +491,9 @@ var testCases = []IncomingTestCase{ Data: optIn, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, }, { Label: "Receive Get Started", @@ -503,10 +501,9 @@ var testCases = []IncomingTestCase{ Data: postbackGetStarted, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + }, }, { Label: "Receive Referral Postback", @@ -514,10 +511,9 @@ var testCases = []IncomingTestCase{ Data: postback, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + }, }, { Label: "Receive Referral", @@ -525,10 +521,9 @@ var testCases = []IncomingTestCase{ Data: postbackReferral, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + }, }, { Label: "Receive Referral", @@ -536,10 +531,9 @@ var testCases = []IncomingTestCase{ Data: referral, ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + }, }, { Label: "Receive DLR", diff --git a/handlers/jiochat/jiochat_test.go b/handlers/jiochat/jiochat_test.go index 4f82f7b6b..2ef8f8679 100644 --- a/handlers/jiochat/jiochat_test.go +++ b/handlers/jiochat/jiochat_test.go @@ -192,8 +192,9 @@ var testCases = []IncomingTestCase{ Data: subscribeEvent, ExpectedRespStatus: 200, ExpectedBodyContains: "Event Accepted", - ExpectedEvent: courier.NewConversation, - ExpectedURN: "jiochat:1234", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "jiochat:1234"}, + }, }, { Label: "Unsubscribe Event", diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 31e6512da..09c2e2448 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -167,8 +167,9 @@ var defaultReceiveTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedMsgStatus: "F", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Contact has sent 'stop'")}, - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:188885551515", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:188885551515"}, + }, }, { Label: "Receive Invalid Status", diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index 5adfe0d72..4b1385e28 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -109,11 +109,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/optInUserRef.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:ref:optin_user_ref", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive OptIn", @@ -121,11 +120,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/optIn.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "optin_ref"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive Get Started", @@ -133,11 +131,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/postbackGetStarted.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive Referral Postback", @@ -145,11 +142,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/postback.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive Referral", @@ -157,11 +153,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/postbackReferral.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive Referral", @@ -169,11 +164,10 @@ var testCasesFBA = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/referral.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive DLR", @@ -320,11 +314,10 @@ var testCasesIG = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/ig/icebreakerGetStarted.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedURN: "instagram:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]string{"title": "icebreaker question", "payload": "get_started"}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, + }, + PrepRequest: addValidSignature, }, { Label: "Different Page", diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/mtarget_test.go index 566b64298..9a4e14fc2 100644 --- a/handlers/mtarget/mtarget_test.go +++ b/handlers/mtarget/mtarget_test.go @@ -35,8 +35,16 @@ var handleTestCases = []IncomingTestCase{ {Label: "Receive Valid Message", URL: receiveURL, Data: receiveValidMessage, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("hello world"), ExpectedURN: "tel:+923161909799", ExpectedExternalID: "foo"}, {Label: "Invalid URN", URL: receiveURL, Data: receiveInvalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, - {Label: "Receive Stop", URL: receiveURL, Data: receiveStop, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedURN: "tel:+923161909799", ExpectedEvent: courier.StopContact}, + { + Label: "Receive Stop", + URL: receiveURL, + Data: receiveStop, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+923161909799"}, + }, + }, {Label: "Receive Missing From", URL: receiveURL, Data: receiveMissingFrom, ExpectedRespStatus: 400, ExpectedBodyContains: "missing required field 'Msisdn'"}, {Label: "Receive Part 2", URL: receiveURL, Data: receivePart2, ExpectedRespStatus: 200, ExpectedBodyContains: "received"}, diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/telegram_test.go index c03cf61e9..73a080014 100644 --- a/handlers/telegram/telegram_test.go +++ b/handlers/telegram/telegram_test.go @@ -537,9 +537,9 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedContactName: Sp("Nic Pottier"), - ExpectedEvent: courier.NewConversation, - ExpectedURN: "telegram:3527065#nicpottier", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "telegram:3527065#nicpottier", Time: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)}, + }, }, { Label: "Receive No Params", diff --git a/handlers/test.go b/handlers/test.go index f72d05cb5..cbad99bc8 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -28,6 +28,14 @@ import ( // RequestPrepFunc is our type for a hook for tests to use before a request is fired in a test type RequestPrepFunc func(*http.Request) +// ExpectedEvent is an expected channel event +type ExpectedEvent struct { + Type courier.ChannelEventType + URN urns.URN + Time time.Time + Extra map[string]string +} + // IncomingTestCase defines the test values for a particular test case type IncomingTestCase struct { Label string @@ -51,8 +59,7 @@ type IncomingTestCase struct { ExpectedMsgStatus courier.MsgStatus ExpectedExternalID string ExpectedMsgID int64 - ExpectedEvent courier.ChannelEventType - ExpectedEventExtra map[string]string + ExpectedEvents []ExpectedEvent ExpectedErrors []*courier.ChannelError NoLogsExpected bool } @@ -203,19 +210,21 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Empty(t, mb.WrittenMsgStatuses(), "unexpected msg status written") } - if tc.ExpectedEvent != "" { - require.Len(mb.WrittenChannelEvents(), 1, "expected a channel event to be written") - event := mb.WrittenChannelEvents()[0] + actualEvents := mb.WrittenChannelEvents() + assert.Len(t, actualEvents, len(tc.ExpectedEvents), "unexpected number of events written") + for i, expectedEvent := range tc.ExpectedEvents { + if (len(actualEvents) - 1) < i { + break + } + actualEvent := actualEvents[i] - assert.Equal(t, tc.ExpectedEvent, event.EventType()) - assert.Equal(t, tc.ExpectedEventExtra, event.Extra()) - assert.Equal(t, tc.ExpectedURN, event.URN()) + assert.Equal(t, expectedEvent.Type, actualEvent.EventType(), "event type mismatch for event %d", i) + assert.Equal(t, expectedEvent.URN, actualEvent.URN(), "URN mismatch for event %d", i) + assert.Equal(t, expectedEvent.Extra, actualEvent.Extra(), "extra mismatch for event %d", i) - if !tc.ExpectedDate.IsZero() { - assert.Equal(t, tc.ExpectedDate, event.OccurredOn()) + if !expectedEvent.Time.IsZero() { + assert.Equal(t, expectedEvent.Time, actualEvent.OccurredOn()) } - } else { - assert.Empty(t, mb.WrittenChannelEvents(), "unexpected channel event written") } if tc.ExpectedContactName != nil { diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index 63a00a7c6..5ab350803 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -107,10 +107,11 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+12028831111", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+12028831111"}, + }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, + PrepRequest: addValidSignature, }, {Label: "Status No Params", URL: statusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring", PrepRequest: addValidSignature}, @@ -154,10 +155,11 @@ var tmsTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+12028831111", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+12028831111"}, + }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, + PrepRequest: addValidSignature, }, {Label: "Status TMS extra", URL: tmsStatusURL, Data: tmsStatusExtra, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent, ExpectedExternalID: "SM0b6e2697aae04182a9f5b5c7a8994c7f", PrepRequest: addValidSignature}, @@ -203,10 +205,11 @@ var twTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+12028831111", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+12028831111"}, + }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, + PrepRequest: addValidSignature, }, {Label: "Status No Params", URL: twStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring", PrepRequest: addValidSignature}, @@ -237,10 +240,11 @@ var swTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedEvent: "stop_contact", - ExpectedURN: "tel:+12028831111", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "tel:+12028831111"}, + }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, + PrepRequest: addValidSignature, }, {Label: "Status No Params", URL: swStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring"}, {Label: "Status Invalid Status", URL: swStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'"}, diff --git a/handlers/viber/viber_test.go b/handlers/viber/viber_test.go index 2a0fad59e..61357b154 100644 --- a/handlers/viber/viber_test.go +++ b/handlers/viber/viber_test.go @@ -508,9 +508,36 @@ var testCases = []IncomingTestCase{ {Label: "Webhook validation", URL: receiveURL, Data: webhookCheck, ExpectedRespStatus: 200, ExpectedBodyContains: "webhook valid", PrepRequest: addValidSignature}, {Label: "Failed Status Report", URL: receiveURL, Data: failedStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, PrepRequest: addValidSignature}, {Label: "Delivered Status Report", URL: receiveURL, Data: deliveredStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `Ignored`, PrepRequest: addValidSignature}, - {Label: "Subcribe", URL: receiveURL, Data: validSubscribed, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvent: "new_conversation", ExpectedURN: "viber:01234567890A=", PrepRequest: addValidSignature}, - {Label: "Subcribe Invalid URN", URL: receiveURL, Data: invalidURNSubscribed, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid viber id", PrepRequest: addValidSignature}, - {Label: "Unsubcribe", URL: receiveURL, Data: validUnsubscribed, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvent: courier.StopContact, ExpectedURN: "viber:01234567890A=", PrepRequest: addValidSignature}, + { + Label: "Subcribe", + URL: receiveURL, + Data: validSubscribed, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "viber:01234567890A="}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Subcribe Invalid URN", + URL: receiveURL, + Data: invalidURNSubscribed, + ExpectedRespStatus: 400, + ExpectedBodyContains: "invalid viber id", + PrepRequest: addValidSignature, + }, + { + Label: "Unsubcribe", + URL: receiveURL, + Data: validUnsubscribed, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.StopContact, URN: "viber:01234567890A="}, + }, + PrepRequest: addValidSignature, + }, {Label: "Unsubcribe Invalid URN", URL: receiveURL, Data: invalidURNUnsubscribed, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid viber id", PrepRequest: addValidSignature}, {Label: "Conversation Started", URL: receiveURL, Data: validConversationStarted, ExpectedRespStatus: 200, ExpectedBodyContains: "ignored conversation start", PrepRequest: addValidSignature}, {Label: "Unexpected event", URL: receiveURL, Data: unexpectedEvent, ExpectedRespStatus: 400, @@ -551,9 +578,10 @@ var testWelcomeMessageCases = []IncomingTestCase{ Data: validConversationStarted, ExpectedRespStatus: 200, ExpectedBodyContains: `{"auth_token":"Token","text":"Welcome to VP, Please subscribe here for more.","type":"text","tracking_data":"0"}`, - ExpectedEvent: "welcome_message", - ExpectedURN: "viber:xy5/5y6O81+/kbWHpLhBoA==", - PrepRequest: addValidSignature, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.WelcomeMessage, URN: "viber:xy5/5y6O81+/kbWHpLhBoA=="}, + }, + PrepRequest: addValidSignature, }, } diff --git a/handlers/wechat/wechat_test.go b/handlers/wechat/wechat_test.go index f3f6f357a..84e299f21 100644 --- a/handlers/wechat/wechat_test.go +++ b/handlers/wechat/wechat_test.go @@ -151,8 +151,16 @@ var testCases = []IncomingTestCase{ ExpectedAttachments: []string{"https://api.weixin.qq.com/cgi-bin/media/get?media_id=12"}, ExpectedDate: time.Date(2018, 2, 16, 9, 47, 4, 438000000, time.UTC)}, - {Label: "Subscribe Event", URL: receiveURL, Data: subscribeEvent, ExpectedRespStatus: 200, ExpectedBodyContains: "Event Accepted", - ExpectedEvent: courier.NewConversation, ExpectedURN: "wechat:1234"}, + { + Label: "Subscribe Event", + URL: receiveURL, + Data: subscribeEvent, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Event Accepted", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.NewConversation, URN: "wechat:1234"}, + }, + }, {Label: "Unsubscribe Event", URL: receiveURL, Data: unsubscribeEvent, ExpectedRespStatus: 200, ExpectedBodyContains: "unknown event"}, From d6f87ade22ee548ce349511e1c7c6bce4a95f8a2 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 15:07:31 -0500 Subject: [PATCH 094/170] Rename event type constants for clarity --- backends/rapidpro/backend_test.go | 8 ++++---- backends/rapidpro/task.go | 8 ++++---- channel_event.go | 8 ++++---- handlers/external/external.go | 2 +- handlers/external/external_test.go | 4 ++-- handlers/facebook_legacy/facebook.go | 10 +++++----- handlers/facebook_legacy/facebook_test.go | 12 ++++++------ handlers/jiochat/jiochat.go | 2 +- handlers/jiochat/jiochat_test.go | 2 +- handlers/messagebird/messagebird.go | 2 +- handlers/messagebird/messagebird_test.go | 2 +- handlers/meta/meta.go | 10 +++++----- handlers/meta/meta_test.go | 14 +++++++------- handlers/mtarget/mtarget.go | 2 +- handlers/mtarget/mtarget_test.go | 2 +- handlers/telegram/telegram.go | 12 ++++++------ handlers/telegram/telegram_test.go | 2 +- handlers/test.go | 2 +- handlers/twiml/twiml.go | 4 ++-- handlers/twiml/twiml_test.go | 8 ++++---- handlers/viber/viber.go | 6 +++--- handlers/viber/viber_test.go | 6 +++--- handlers/wechat/wechat.go | 2 +- handlers/wechat/wechat_test.go | 2 +- handlers/yo/yo.go | 2 +- responses_test.go | 2 +- 26 files changed, 68 insertions(+), 68 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index b9584a0b9..72b26bb1d 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1265,7 +1265,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}).WithContactName("kermit frog") + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}).WithContactName("kermit frog") err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) @@ -1276,7 +1276,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { dbE := event.(*ChannelEvent) dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) - ts.Equal(dbE.EventType_, courier.Referral) + ts.Equal(dbE.EventType_, courier.EventTypeReferral) ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) @@ -1305,7 +1305,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.Referral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1318,7 +1318,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { dbE := event.(*ChannelEvent) dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) - ts.Equal(dbE.EventType_, courier.Referral) + ts.Equal(dbE.EventType_, courier.EventTypeReferral) ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index da9aeefab..b3c3f45ab 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -33,7 +33,7 @@ func queueMsgHandling(rc redis.Conn, c *Contact, m *Msg) error { func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { // queue to mailroom switch e.EventType() { - case courier.StopContact: + case courier.EventTypeStopContact: body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, @@ -41,7 +41,7 @@ func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { } return queueMailroomTask(rc, "stop_event", e.OrgID_, e.ContactID_, body) - case courier.WelcomeMessage: + case courier.EventTypeWelcomeMessage: body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, @@ -52,7 +52,7 @@ func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { } return queueMailroomTask(rc, "welcome_message", e.OrgID_, e.ContactID_, body) - case courier.Referral: + case courier.EventTypeReferral: body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, @@ -64,7 +64,7 @@ func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { } return queueMailroomTask(rc, "referral", e.OrgID_, e.ContactID_, body) - case courier.NewConversation: + case courier.EventTypeNewConversation: body := map[string]any{ "org_id": e.OrgID_, "contact_id": e.ContactID_, diff --git a/channel_event.go b/channel_event.go index 261560735..d35033aeb 100644 --- a/channel_event.go +++ b/channel_event.go @@ -11,10 +11,10 @@ type ChannelEventType string // Possible values for ChannelEventTypes const ( - NewConversation ChannelEventType = "new_conversation" - Referral ChannelEventType = "referral" - StopContact ChannelEventType = "stop_contact" - WelcomeMessage ChannelEventType = "welcome_message" + EventTypeNewConversation ChannelEventType = "new_conversation" + EventTypeReferral ChannelEventType = "referral" + EventTypeStopContact ChannelEventType = "stop_contact" + EventTypeWelcomeMessage ChannelEventType = "welcome_message" ) //----------------------------------------------------------------------------- diff --git a/handlers/external/external.go b/handlers/external/external.go index 1cc170a44..820407ff9 100644 --- a/handlers/external/external.go +++ b/handlers/external/external.go @@ -111,7 +111,7 @@ func (h *handler) receiveStopContact(ctx context.Context, channel courier.Channe urn = urn.Normalize("") // create a stop channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.StopContact, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeStopContact, urn, clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { return nil, err diff --git a/handlers/external/external_test.go b/handlers/external/external_test.go index ce2ac7ec6..080295053 100644 --- a/handlers/external/external_test.go +++ b/handlers/external/external_test.go @@ -167,7 +167,7 @@ var handleTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+2349067554729"}, + {Type: courier.EventTypeStopContact, URN: "tel:+2349067554729"}, }, }, { @@ -177,7 +177,7 @@ var handleTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+2349067554729"}, + {Type: courier.EventTypeStopContact, URN: "tel:+2349067554729"}, }, }, { diff --git a/handlers/facebook_legacy/facebook.go b/handlers/facebook_legacy/facebook.go index 9e39bda86..f6867ee33 100644 --- a/handlers/facebook_legacy/facebook.go +++ b/handlers/facebook_legacy/facebook.go @@ -270,7 +270,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } } - event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) + event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra extra := map[string]string{ @@ -288,9 +288,9 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } else if msg.Postback != nil { // by default postbacks are treated as new conversations, unless we have referral information - eventType := courier.NewConversation + eventType := courier.EventTypeNewConversation if msg.Postback.Referral.Ref != "" { - eventType = courier.Referral + eventType = courier.EventTypeReferral } event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) @@ -301,7 +301,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } // add in referral information if we have it - if eventType == courier.Referral { + if eventType == courier.EventTypeReferral { extra[referrerIDKey] = msg.Postback.Referral.Ref extra[sourceKey] = msg.Postback.Referral.Source extra[typeKey] = msg.Postback.Referral.Type @@ -323,7 +323,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w } else if msg.Referral != nil { // this is an incoming referral - event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) + event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra extra := map[string]string{ diff --git a/handlers/facebook_legacy/facebook_test.go b/handlers/facebook_legacy/facebook_test.go index e68596561..eefee4c96 100644 --- a/handlers/facebook_legacy/facebook_test.go +++ b/handlers/facebook_legacy/facebook_test.go @@ -482,7 +482,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, }, { @@ -492,7 +492,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, }, { @@ -502,7 +502,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, }, }, { @@ -512,7 +512,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, }, { @@ -522,7 +522,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, }, { @@ -532,7 +532,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, }, { diff --git a/handlers/jiochat/jiochat.go b/handlers/jiochat/jiochat.go index e97c42bbb..1fd878f74 100644 --- a/handlers/jiochat/jiochat.go +++ b/handlers/jiochat/jiochat.go @@ -122,7 +122,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // subscribe event, trigger a new conversation if payload.MsgType == "event" && payload.Event == "subscribe" { - channelEvent := h.Backend().NewChannelEvent(channel, courier.NewConversation, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeNewConversation, urn, clog) err := h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { diff --git a/handlers/jiochat/jiochat_test.go b/handlers/jiochat/jiochat_test.go index 2ef8f8679..32f87080c 100644 --- a/handlers/jiochat/jiochat_test.go +++ b/handlers/jiochat/jiochat_test.go @@ -193,7 +193,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Event Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "jiochat:1234"}, + {Type: courier.EventTypeNewConversation, URN: "jiochat:1234"}, }, }, { diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/messagebird.go index 2b4df4353..6eb67ac16 100644 --- a/handlers/messagebird/messagebird.go +++ b/handlers/messagebird/messagebird.go @@ -130,7 +130,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } // create a stop channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.StopContact, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeStopContact, urn, clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { return nil, err diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/messagebird_test.go index 09c2e2448..1d3e6e173 100644 --- a/handlers/messagebird/messagebird_test.go +++ b/handlers/messagebird/messagebird_test.go @@ -168,7 +168,7 @@ var defaultReceiveTestCases = []IncomingTestCase{ ExpectedMsgStatus: "F", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Contact has sent 'stop'")}, ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:188885551515"}, + {Type: courier.EventTypeStopContact, URN: "tel:188885551515"}, }, }, { diff --git a/handlers/meta/meta.go b/handlers/meta/meta.go index ec90ed724..48bf4b4fb 100644 --- a/handlers/meta/meta.go +++ b/handlers/meta/meta.go @@ -441,7 +441,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } } - event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) + event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra extra := map[string]string{ @@ -459,9 +459,9 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } else if msg.Postback != nil { // by default postbacks are treated as new conversations, unless we have referral information - eventType := courier.NewConversation + eventType := courier.EventTypeNewConversation if msg.Postback.Referral.Ref != "" { - eventType = courier.Referral + eventType = courier.EventTypeReferral } event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) @@ -472,7 +472,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } // add in referral information if we have it - if eventType == courier.Referral { + if eventType == courier.EventTypeReferral { extra[referrerIDKey] = msg.Postback.Referral.Ref extra[sourceKey] = msg.Postback.Referral.Source extra[typeKey] = msg.Postback.Referral.Type @@ -494,7 +494,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c } else if msg.Referral != nil { // this is an incoming referral - event := h.Backend().NewChannelEvent(channel, courier.Referral, urn, clog).WithOccurredOn(date) + event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra extra := map[string]string{ diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index 4b1385e28..d65f01deb 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -110,7 +110,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -121,7 +121,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -132,7 +132,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, @@ -143,7 +143,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, PrepRequest: addValidSignature, }, @@ -154,7 +154,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, @@ -165,7 +165,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.Referral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, @@ -315,7 +315,7 @@ var testCasesIG = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, diff --git a/handlers/mtarget/mtarget.go b/handlers/mtarget/mtarget.go index fc2e802d3..e84e559a7 100644 --- a/handlers/mtarget/mtarget.go +++ b/handlers/mtarget/mtarget.go @@ -134,7 +134,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp // if this a stop command, shortcut stopping that contact if keyword == "Stop" { - stop := h.Backend().NewChannelEvent(c, courier.StopContact, urn, clog) + stop := h.Backend().NewChannelEvent(c, courier.EventTypeStopContact, urn, clog) err := h.Backend().WriteChannelEvent(ctx, stop, clog) if err != nil { return nil, err diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/mtarget_test.go index 9a4e14fc2..5f189d2f2 100644 --- a/handlers/mtarget/mtarget_test.go +++ b/handlers/mtarget/mtarget_test.go @@ -42,7 +42,7 @@ var handleTestCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+923161909799"}, + {Type: courier.EventTypeStopContact, URN: "tel:+923161909799"}, }, }, {Label: "Receive Missing From", URL: receiveURL, Data: receiveMissingFrom, ExpectedRespStatus: 400, ExpectedBodyContains: "missing required field 'Msisdn'"}, diff --git a/handlers/telegram/telegram.go b/handlers/telegram/telegram.go index af847fc69..4373caa9a 100644 --- a/handlers/telegram/telegram.go +++ b/handlers/telegram/telegram.go @@ -71,7 +71,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // this is a start command, trigger a new conversation if text == "/start" { - event := h.Backend().NewChannelEvent(channel, courier.NewConversation, urn, clog).WithContactName(name).WithOccurredOn(date) + event := h.Backend().NewChannelEvent(channel, courier.EventTypeNewConversation, urn, clog).WithContactName(name).WithOccurredOn(date) err = h.Backend().WriteChannelEvent(ctx, event, clog) if err != nil { return nil, err @@ -224,7 +224,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendMessage", form, msgKeyBoard, clog) if botBlocked { status.SetStatus(courier.MsgStatusFailed) - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err } @@ -250,7 +250,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendPhoto", form, attachmentKeyBoard, clog) if botBlocked { status.SetStatus(courier.MsgStatusFailed) - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err } @@ -266,7 +266,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendVideo", form, attachmentKeyBoard, clog) if botBlocked { status.SetStatus(courier.MsgStatusFailed) - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err } @@ -282,7 +282,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendAudio", form, attachmentKeyBoard, clog) if botBlocked { status.SetStatus(courier.MsgStatusFailed) - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err } @@ -298,7 +298,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann externalID, botBlocked, err := h.sendMsgPart(msg, authToken, "sendDocument", form, attachmentKeyBoard, clog) if botBlocked { status.SetStatus(courier.MsgStatusFailed) - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) return status, err } diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/telegram_test.go index 73a080014..8aa2b92ef 100644 --- a/handlers/telegram/telegram_test.go +++ b/handlers/telegram/telegram_test.go @@ -538,7 +538,7 @@ var testCases = []IncomingTestCase{ ExpectedBodyContains: "Accepted", ExpectedContactName: Sp("Nic Pottier"), ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "telegram:3527065#nicpottier", Time: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)}, + {Type: courier.EventTypeNewConversation, URN: "telegram:3527065#nicpottier", Time: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC)}, }, }, { diff --git a/handlers/test.go b/handlers/test.go index cbad99bc8..2faec3d89 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -434,7 +434,7 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier if tc.ExpectedStopEvent { require.Len(mb.WrittenChannelEvents(), 1) event := mb.WrittenChannelEvents()[0] - require.Equal(courier.StopContact, event.EventType()) + require.Equal(courier.EventTypeStopContact, event.EventType()) } if tc.ExpectedContactURNs != nil { diff --git a/handlers/twiml/twiml.go b/handlers/twiml/twiml.go index 5c18164ca..edbb2d765 100644 --- a/handlers/twiml/twiml.go +++ b/handlers/twiml/twiml.go @@ -201,7 +201,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // create a stop channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.StopContact, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeStopContact, urn, clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { return nil, err @@ -299,7 +299,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetStatus(courier.MsgStatusFailed) // create a stop channel event - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { return nil, err diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/twiml_test.go index 5ab350803..6856520da 100644 --- a/handlers/twiml/twiml_test.go +++ b/handlers/twiml/twiml_test.go @@ -108,7 +108,7 @@ var testCases = []IncomingTestCase{ ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+12028831111"}, + {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, @@ -156,7 +156,7 @@ var tmsTestCases = []IncomingTestCase{ ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+12028831111"}, + {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, @@ -206,7 +206,7 @@ var twTestCases = []IncomingTestCase{ ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+12028831111"}, + {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, @@ -241,7 +241,7 @@ var swTestCases = []IncomingTestCase{ ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "tel:+12028831111"}, + {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, diff --git a/handlers/viber/viber.go b/handlers/viber/viber.go index c65339022..acc2796d7 100644 --- a/handlers/viber/viber.go +++ b/handlers/viber/viber.go @@ -146,7 +146,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } // build the channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.WelcomeMessage, urn, clog).WithContactName(ContactName) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeWelcomeMessage, urn, clog).WithContactName(ContactName) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { @@ -168,7 +168,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h } // build the channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.NewConversation, urn, clog).WithContactName(ContactName) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeNewConversation, urn, clog).WithContactName(ContactName) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { @@ -188,7 +188,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } // build the channel event - channelEvent := h.Backend().NewChannelEvent(channel, courier.StopContact, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeStopContact, urn, clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { diff --git a/handlers/viber/viber_test.go b/handlers/viber/viber_test.go index 61357b154..fd72639eb 100644 --- a/handlers/viber/viber_test.go +++ b/handlers/viber/viber_test.go @@ -515,7 +515,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "viber:01234567890A="}, + {Type: courier.EventTypeNewConversation, URN: "viber:01234567890A="}, }, PrepRequest: addValidSignature, }, @@ -534,7 +534,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.StopContact, URN: "viber:01234567890A="}, + {Type: courier.EventTypeStopContact, URN: "viber:01234567890A="}, }, PrepRequest: addValidSignature, }, @@ -579,7 +579,7 @@ var testWelcomeMessageCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `{"auth_token":"Token","text":"Welcome to VP, Please subscribe here for more.","type":"text","tracking_data":"0"}`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.WelcomeMessage, URN: "viber:xy5/5y6O81+/kbWHpLhBoA=="}, + {Type: courier.EventTypeWelcomeMessage, URN: "viber:xy5/5y6O81+/kbWHpLhBoA=="}, }, PrepRequest: addValidSignature, }, diff --git a/handlers/wechat/wechat.go b/handlers/wechat/wechat.go index c0731bd84..9c64a99e0 100644 --- a/handlers/wechat/wechat.go +++ b/handlers/wechat/wechat.go @@ -127,7 +127,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w if payload.MsgType == "event" && payload.Event == "subscribe" { clog.SetType(courier.ChannelLogTypeEventReceive) - channelEvent := h.Backend().NewChannelEvent(channel, courier.NewConversation, urn, clog) + channelEvent := h.Backend().NewChannelEvent(channel, courier.EventTypeNewConversation, urn, clog) err := h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { diff --git a/handlers/wechat/wechat_test.go b/handlers/wechat/wechat_test.go index 84e299f21..96c66abda 100644 --- a/handlers/wechat/wechat_test.go +++ b/handlers/wechat/wechat_test.go @@ -158,7 +158,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Event Accepted", ExpectedEvents: []ExpectedEvent{ - {Type: courier.NewConversation, URN: "wechat:1234"}, + {Type: courier.EventTypeNewConversation, URN: "wechat:1234"}, }, }, diff --git a/handlers/yo/yo.go b/handlers/yo/yo.go index 7a24bd4c3..175b01281 100644 --- a/handlers/yo/yo.go +++ b/handlers/yo/yo.go @@ -143,7 +143,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann status.SetStatus(courier.MsgStatusFailed) // create a stop channel event - channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.StopContact, msg.URN(), clog) + channelEvent := h.Backend().NewChannelEvent(msg.Channel(), courier.EventTypeStopContact, msg.URN(), clog) err = h.Backend().WriteChannelEvent(ctx, channelEvent, clog) if err != nil { return nil, err diff --git a/responses_test.go b/responses_test.go index ffea312bb..7d27d183a 100644 --- a/responses_test.go +++ b/responses_test.go @@ -54,7 +54,7 @@ func TestWriteMsgSuccess(t *testing.T) { func TestWriteChannelEventSuccess(t *testing.T) { ch := test.NewMockChannel("5fccf4b6-48d7-4f5a-bce8-b0d1fd5342ec", "NX", "+1234567890", "US", nil) - evt := test.NewMockBackend().NewChannelEvent(ch, courier.StopContact, "tel:+0987654321", nil).WithOccurredOn(time.Date(2022, 9, 15, 12, 7, 30, 0, time.UTC)) + evt := test.NewMockBackend().NewChannelEvent(ch, courier.EventTypeStopContact, "tel:+0987654321", nil).WithOccurredOn(time.Date(2022, 9, 15, 12, 7, 30, 0, time.UTC)) w := httptest.NewRecorder() err := courier.WriteChannelEventSuccess(w, evt) From f17563e47f5f0fcaf245ee68ca6f57c6b39006bf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Sep 2023 15:16:40 -0500 Subject: [PATCH 095/170] Fix some linter issues --- handlers/meta/meta.go | 8 -------- handlers/meta/meta_test.go | 4 ++-- utils/whatsapp/languages_test.go | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/handlers/meta/meta.go b/handlers/meta/meta.go index 48bf4b4fb..16a0cc7cf 100644 --- a/handlers/meta/meta.go +++ b/handlers/meta/meta.go @@ -101,14 +101,6 @@ func (h *handler) Initialize(s courier.Server) error { // }] // } -type wacMedia struct { - Caption string `json:"caption"` - Filename string `json:"filename"` - ID string `json:"id"` - Mimetype string `json:"mime_type"` - SHA256 string `json:"sha256"` -} - func (h *handler) RedactValues(ch courier.Channel) []string { vals := h.BaseHandler.RedactValues(ch) vals = append(vals, h.Server().Config().FacebookApplicationSecret, h.Server().Config().FacebookWebhookSecret, h.Server().Config().WhatsappAdminSystemUserToken) diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index d65f01deb..4e89be4b4 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -422,7 +422,7 @@ func buildMockFBGraphFBA(testCases []IncomingTestCase) *httptest.Server { // invalid auth token if accessToken != "a123" { - http.Error(w, "invalid auth token", 403) + http.Error(w, "invalid auth token", http.StatusForbidden) } // user has a name @@ -446,7 +446,7 @@ func buildMockFBGraphIG(testCases []IncomingTestCase) *httptest.Server { // invalid auth token if accessToken != "a123" { - http.Error(w, "invalid auth token", 403) + http.Error(w, "invalid auth token", http.StatusForbidden) } // user has a name diff --git a/utils/whatsapp/languages_test.go b/utils/whatsapp/languages_test.go index 38d496785..ee5c9a5fa 100644 --- a/utils/whatsapp/languages_test.go +++ b/utils/whatsapp/languages_test.go @@ -5,7 +5,7 @@ import ( "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/i18n" - "gopkg.in/go-playground/assert.v1" + "github.com/stretchr/testify/assert" ) func TestGetSupportedLanguage(t *testing.T) { From c641cb0794e9fff7f4d86251033272473de0e184 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 14 Sep 2023 08:49:42 -0500 Subject: [PATCH 096/170] Use optin payload to hold UUID --- handlers/meta/meta.go | 7 +++---- handlers/meta/meta_test.go | 6 +++--- handlers/meta/testdata/fba/notificationMessagesOptIn.json | 2 +- handlers/meta/testdata/fba/notificationMessagesOptOut.json | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/handlers/meta/meta.go b/handlers/meta/meta.go index 65c0fd49d..75ad9280b 100644 --- a/handlers/meta/meta.go +++ b/handlers/meta/meta.go @@ -425,6 +425,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if msg.OptIn.Type == "notification_messages" { eventType := courier.EventTypeOptIn optInName := msg.OptIn.Title + optInUUID := msg.OptIn.Payload authToken := msg.OptIn.NotificationMessagesToken if msg.OptIn.NotificationMessagesStatus == "STOP_NOTIFICATIONS" { @@ -434,10 +435,8 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, eventType, urn, clog). WithOccurredOn(date). - WithExtra(map[string]string{ - "optin_name": optInName, - }). - WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInName): authToken}) + WithExtra(map[string]string{"optin_uuid": optInUUID, "optin_name": optInName}). + WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInUUID): authToken}) } else { // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index bf4f82b02..e153c5cc7 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -132,9 +132,9 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, }, - ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:Bird Facts": "12345678901234567890"}}, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:2fad015d-2126-4ac2-a008-b5ac95c3906b": "12345678901234567890"}}, PrepRequest: addValidSignature, }, { @@ -144,7 +144,7 @@ var testCasesFBA = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, PrepRequest: addValidSignature, diff --git a/handlers/meta/testdata/fba/notificationMessagesOptIn.json b/handlers/meta/testdata/fba/notificationMessagesOptIn.json index 278c681f8..112bb0fb1 100644 --- a/handlers/meta/testdata/fba/notificationMessagesOptIn.json +++ b/handlers/meta/testdata/fba/notificationMessagesOptIn.json @@ -15,7 +15,7 @@ "timestamp": 1459991487970, "optin": { "type": "notification_messages", - "payload": "bird-facts", + "payload": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "notification_messages_token": "12345678901234567890", "notification_messages_frequency": "DAILY", "token_expiry_timestamp": 2145916800000, diff --git a/handlers/meta/testdata/fba/notificationMessagesOptOut.json b/handlers/meta/testdata/fba/notificationMessagesOptOut.json index b2ba80116..14746b875 100644 --- a/handlers/meta/testdata/fba/notificationMessagesOptOut.json +++ b/handlers/meta/testdata/fba/notificationMessagesOptOut.json @@ -15,7 +15,7 @@ }, "optin": { "type": "notification_messages", - "payload": "bird-facts", + "payload": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "notification_messages_token": "12345678901234567890", "notification_messages_frequency": "DAILY", "token_expiry_timestamp": 2145916800000, From 6350950f080829f952df8fc531b97bd7840e2edc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 14 Sep 2023 10:56:39 -0500 Subject: [PATCH 097/170] Fix stop contact event task names --- backends/rapidpro/task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index b3c3f45ab..2b84453cd 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -39,7 +39,7 @@ func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { "contact_id": e.ContactID_, "occurred_on": e.OccurredOn_, } - return queueMailroomTask(rc, "stop_event", e.OrgID_, e.ContactID_, body) + return queueMailroomTask(rc, "stop_contact", e.OrgID_, e.ContactID_, body) case courier.EventTypeWelcomeMessage: body := map[string]any{ From c4209a9a016f9ace9992481cc5f563301f17a1ee Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 14 Sep 2023 11:17:53 -0500 Subject: [PATCH 098/170] Update CHANGELOG.md for v8.3.17 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a067dddf3..fc2c3df54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v8.3.17 (2023-09-14) +------------------------- + * Fix stop contact event task names + * Add support for FB notificaiton messages optin and optout events + v8.3.16 (2023-09-13) ------------------------- * Simplify interfaces that handlers have access to From 2d061667dd4881bc781937d3460e5972b79767cd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 10:02:45 -0500 Subject: [PATCH 099/170] Cleanup WAC/FBA/IG/D3C test files --- handlers/dialog360/dialog360_test.go | 38 +++---- .../wac/{audioWAC.json => audio.json} | 0 .../wac/{buttonWAC.json => button.json} | 0 ...{buttonReplyWAC.json => button_reply.json} | 0 .../wac/{documentWAC.json => document.json} | 0 .../wac/{duplicateWAC.json => duplicate.json} | 0 .../{errorErrors.json => error_errors.json} | 0 .../wac/{errorMsg.json => error_msg.json} | 0 .../{errorStatus.json => error_status.json} | 0 .../wac/{helloWAC.json => hello.json} | 0 ...gnoreStatusWAC.json => ignore_status.json} | 0 .../wac/{imageWAC.json => image.json} | 0 .../{invalidFrom.json => invalid_from.json} | 0 ...alidStatusWAC.json => invalid_status.json} | 0 ...dTimestamp.json => invalid_timestamp.json} | 0 .../{listReplyWAC.json => list_reply.json} | 0 .../wac/{locationWAC.json => location.json} | 0 ...{validStatusWAC.json => valid_status.json} | 0 .../wac/{videoWAC.json => video.json} | 0 .../wac/{voiceWAC.json => voice.json} | 0 handlers/meta/meta_test.go | 106 +++++++++--------- .../{attachmentFBA.json => attachment.json} | 0 ...ferentPageFBA.json => different_page.json} | 0 ...uplicateMsgFBA.json => duplicate_msg.json} | 0 .../testdata/fba/{echoFBA.json => echo.json} | 0 .../fba/{helloMsgFBA.json => hello_msg.json} | 0 .../{invalidURNFBA.json => invalid_urn.json} | 0 ...tachment.json => location_attachment.json} | 0 .../{noEntriesFBA.json => no_entries.json} | 0 ...riesFBA.json => no_messaging_entries.json} | 0 .../fba/{notPage.json => not_page.json} | 0 ....json => notification_messages_optin.json} | 0 ...json => notification_messages_optout.json} | 0 ...Started.json => postback_get_started.json} | 0 ...ckReferral.json => postback_referral.json} | 0 .../fba/{optIn.json => referral_optin.json} | 0 ...rRef.json => referral_optin_user_ref.json} | 0 .../fba/{thumbsUp.json => thumbs_up.json} | 0 ...yFBA.json => unknown_messaging_entry.json} | 0 .../ig/{attachmentIG.json => attachment.json} | 0 ...fferentPageIG.json => different_page.json} | 0 ...duplicateMsgIG.json => duplicate_msg.json} | 0 .../testdata/ig/{echoIG.json => echo.json} | 0 .../ig/{helloMsgIG.json => hello_msg.json} | 0 ...arted.json => icebreaker_get_started.json} | 0 .../{invalidURNIG.json => invalid_urn.json} | 0 .../ig/{noEntriesIG.json => no_entries.json} | 0 ...triesIG.json => no_messaging_entries.json} | 0 .../{notInstagram.json => not_instagram.json} | 0 ...storyMentionIG.json => story_mention.json} | 0 ...ryIG.json => unknown_messaging_entry.json} | 0 .../ig/{unsentMsgIG.json => unsent_msg.json} | 0 .../wac/{audioWAC.json => audio.json} | 0 .../wac/{buttonWAC.json => button.json} | 0 ...{buttonReplyWAC.json => button_reply.json} | 0 .../wac/{documentWAC.json => document.json} | 0 .../wac/{duplicateWAC.json => duplicate.json} | 0 .../{errorErrors.json => error_errors.json} | 0 .../wac/{errorMsg.json => error_msg.json} | 0 .../{errorStatus.json => error_status.json} | 0 .../wac/{helloWAC.json => hello.json} | 0 ...gnoreStatusWAC.json => ignore_status.json} | 0 .../wac/{imageWAC.json => image.json} | 0 .../{invalidFrom.json => invalid_from.json} | 0 ...alidStatusWAC.json => invalid_status.json} | 0 ...dTimestamp.json => invalid_timestamp.json} | 0 .../{listReplyWAC.json => list_reply.json} | 0 .../wac/{locationWAC.json => location.json} | 0 ...{validStatusWAC.json => valid_status.json} | 0 .../wac/{videoWAC.json => video.json} | 0 .../wac/{voiceWAC.json => voice.json} | 0 71 files changed, 72 insertions(+), 72 deletions(-) rename handlers/dialog360/testdata/wac/{audioWAC.json => audio.json} (100%) rename handlers/dialog360/testdata/wac/{buttonWAC.json => button.json} (100%) rename handlers/dialog360/testdata/wac/{buttonReplyWAC.json => button_reply.json} (100%) rename handlers/dialog360/testdata/wac/{documentWAC.json => document.json} (100%) rename handlers/dialog360/testdata/wac/{duplicateWAC.json => duplicate.json} (100%) rename handlers/dialog360/testdata/wac/{errorErrors.json => error_errors.json} (100%) rename handlers/dialog360/testdata/wac/{errorMsg.json => error_msg.json} (100%) rename handlers/dialog360/testdata/wac/{errorStatus.json => error_status.json} (100%) rename handlers/dialog360/testdata/wac/{helloWAC.json => hello.json} (100%) rename handlers/dialog360/testdata/wac/{ignoreStatusWAC.json => ignore_status.json} (100%) rename handlers/dialog360/testdata/wac/{imageWAC.json => image.json} (100%) rename handlers/dialog360/testdata/wac/{invalidFrom.json => invalid_from.json} (100%) rename handlers/dialog360/testdata/wac/{invalidStatusWAC.json => invalid_status.json} (100%) rename handlers/dialog360/testdata/wac/{invalidTimestamp.json => invalid_timestamp.json} (100%) rename handlers/dialog360/testdata/wac/{listReplyWAC.json => list_reply.json} (100%) rename handlers/dialog360/testdata/wac/{locationWAC.json => location.json} (100%) rename handlers/dialog360/testdata/wac/{validStatusWAC.json => valid_status.json} (100%) rename handlers/dialog360/testdata/wac/{videoWAC.json => video.json} (100%) rename handlers/dialog360/testdata/wac/{voiceWAC.json => voice.json} (100%) rename handlers/meta/testdata/fba/{attachmentFBA.json => attachment.json} (100%) rename handlers/meta/testdata/fba/{differentPageFBA.json => different_page.json} (100%) rename handlers/meta/testdata/fba/{duplicateMsgFBA.json => duplicate_msg.json} (100%) rename handlers/meta/testdata/fba/{echoFBA.json => echo.json} (100%) rename handlers/meta/testdata/fba/{helloMsgFBA.json => hello_msg.json} (100%) rename handlers/meta/testdata/fba/{invalidURNFBA.json => invalid_urn.json} (100%) rename handlers/meta/testdata/fba/{locationAttachment.json => location_attachment.json} (100%) rename handlers/meta/testdata/fba/{noEntriesFBA.json => no_entries.json} (100%) rename handlers/meta/testdata/fba/{noMessagingEntriesFBA.json => no_messaging_entries.json} (100%) rename handlers/meta/testdata/fba/{notPage.json => not_page.json} (100%) rename handlers/meta/testdata/fba/{notificationMessagesOptIn.json => notification_messages_optin.json} (100%) rename handlers/meta/testdata/fba/{notificationMessagesOptOut.json => notification_messages_optout.json} (100%) rename handlers/meta/testdata/fba/{postbackGetStarted.json => postback_get_started.json} (100%) rename handlers/meta/testdata/fba/{postbackReferral.json => postback_referral.json} (100%) rename handlers/meta/testdata/fba/{optIn.json => referral_optin.json} (100%) rename handlers/meta/testdata/fba/{optInUserRef.json => referral_optin_user_ref.json} (100%) rename handlers/meta/testdata/fba/{thumbsUp.json => thumbs_up.json} (100%) rename handlers/meta/testdata/fba/{unknownMessagingEntryFBA.json => unknown_messaging_entry.json} (100%) rename handlers/meta/testdata/ig/{attachmentIG.json => attachment.json} (100%) rename handlers/meta/testdata/ig/{differentPageIG.json => different_page.json} (100%) rename handlers/meta/testdata/ig/{duplicateMsgIG.json => duplicate_msg.json} (100%) rename handlers/meta/testdata/ig/{echoIG.json => echo.json} (100%) rename handlers/meta/testdata/ig/{helloMsgIG.json => hello_msg.json} (100%) rename handlers/meta/testdata/ig/{icebreakerGetStarted.json => icebreaker_get_started.json} (100%) rename handlers/meta/testdata/ig/{invalidURNIG.json => invalid_urn.json} (100%) rename handlers/meta/testdata/ig/{noEntriesIG.json => no_entries.json} (100%) rename handlers/meta/testdata/ig/{noMessagingEntriesIG.json => no_messaging_entries.json} (100%) rename handlers/meta/testdata/ig/{notInstagram.json => not_instagram.json} (100%) rename handlers/meta/testdata/ig/{storyMentionIG.json => story_mention.json} (100%) rename handlers/meta/testdata/ig/{unknownMessagingEntryIG.json => unknown_messaging_entry.json} (100%) rename handlers/meta/testdata/ig/{unsentMsgIG.json => unsent_msg.json} (100%) rename handlers/meta/testdata/wac/{audioWAC.json => audio.json} (100%) rename handlers/meta/testdata/wac/{buttonWAC.json => button.json} (100%) rename handlers/meta/testdata/wac/{buttonReplyWAC.json => button_reply.json} (100%) rename handlers/meta/testdata/wac/{documentWAC.json => document.json} (100%) rename handlers/meta/testdata/wac/{duplicateWAC.json => duplicate.json} (100%) rename handlers/meta/testdata/wac/{errorErrors.json => error_errors.json} (100%) rename handlers/meta/testdata/wac/{errorMsg.json => error_msg.json} (100%) rename handlers/meta/testdata/wac/{errorStatus.json => error_status.json} (100%) rename handlers/meta/testdata/wac/{helloWAC.json => hello.json} (100%) rename handlers/meta/testdata/wac/{ignoreStatusWAC.json => ignore_status.json} (100%) rename handlers/meta/testdata/wac/{imageWAC.json => image.json} (100%) rename handlers/meta/testdata/wac/{invalidFrom.json => invalid_from.json} (100%) rename handlers/meta/testdata/wac/{invalidStatusWAC.json => invalid_status.json} (100%) rename handlers/meta/testdata/wac/{invalidTimestamp.json => invalid_timestamp.json} (100%) rename handlers/meta/testdata/wac/{listReplyWAC.json => list_reply.json} (100%) rename handlers/meta/testdata/wac/{locationWAC.json => location.json} (100%) rename handlers/meta/testdata/wac/{validStatusWAC.json => valid_status.json} (100%) rename handlers/meta/testdata/wac/{videoWAC.json => video.json} (100%) rename handlers/meta/testdata/wac/{voiceWAC.json => voice.json} (100%) diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index e807a7911..8d85d25c0 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -37,7 +37,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Message WAC", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/helloWAC.json")), + Data: string(test.ReadFile("./testdata/wac/hello.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -50,7 +50,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Duplicate Valid Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/duplicateWAC.json")), + Data: string(test.ReadFile("./testdata/wac/duplicate.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -63,7 +63,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Voice Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/voiceWAC.json")), + Data: string(test.ReadFile("./testdata/wac/voice.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -77,7 +77,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Button Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonWAC.json")), + Data: string(test.ReadFile("./testdata/wac/button.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -90,7 +90,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Document Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/documentWAC.json")), + Data: string(test.ReadFile("./testdata/wac/document.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -104,7 +104,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Image Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/imageWAC.json")), + Data: string(test.ReadFile("./testdata/wac/image.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -118,7 +118,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Video Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/videoWAC.json")), + Data: string(test.ReadFile("./testdata/wac/video.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -132,7 +132,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Audio Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/audioWAC.json")), + Data: string(test.ReadFile("./testdata/wac/audio.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -146,7 +146,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Location Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/locationWAC.json")), + Data: string(test.ReadFile("./testdata/wac/location.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"msg"`, ExpectedMsgText: Sp(""), @@ -165,21 +165,21 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Invalid FROM", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidFrom.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_from.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid whatsapp id", }, { Label: "Receive Invalid timestamp JSON", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidTimestamp.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_timestamp.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid timestamp", }, { Label: "Receive Message WAC with error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorMsg.json")), + Data: string(test.ReadFile("./testdata/wac/error_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, @@ -188,7 +188,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorErrors.json")), + Data: string(test.ReadFile("./testdata/wac/error_errors.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, @@ -197,7 +197,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/validStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/valid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "S", @@ -206,7 +206,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Status with error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorStatus.json")), + Data: string(test.ReadFile("./testdata/wac/error_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "F", @@ -216,21 +216,21 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Invalid Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"unknown status: in_orbit"`, }, { Label: "Receive Ignore Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/ignoreStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/ignore_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"ignoring status: deleted"`, }, { Label: "Receive Valid Interactive Button Reply Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonReplyWAC.json")), + Data: string(test.ReadFile("./testdata/wac/button_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -243,7 +243,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Interactive List Reply Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/listReplyWAC.json")), + Data: string(test.ReadFile("./testdata/wac/list_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, diff --git a/handlers/dialog360/testdata/wac/audioWAC.json b/handlers/dialog360/testdata/wac/audio.json similarity index 100% rename from handlers/dialog360/testdata/wac/audioWAC.json rename to handlers/dialog360/testdata/wac/audio.json diff --git a/handlers/dialog360/testdata/wac/buttonWAC.json b/handlers/dialog360/testdata/wac/button.json similarity index 100% rename from handlers/dialog360/testdata/wac/buttonWAC.json rename to handlers/dialog360/testdata/wac/button.json diff --git a/handlers/dialog360/testdata/wac/buttonReplyWAC.json b/handlers/dialog360/testdata/wac/button_reply.json similarity index 100% rename from handlers/dialog360/testdata/wac/buttonReplyWAC.json rename to handlers/dialog360/testdata/wac/button_reply.json diff --git a/handlers/dialog360/testdata/wac/documentWAC.json b/handlers/dialog360/testdata/wac/document.json similarity index 100% rename from handlers/dialog360/testdata/wac/documentWAC.json rename to handlers/dialog360/testdata/wac/document.json diff --git a/handlers/dialog360/testdata/wac/duplicateWAC.json b/handlers/dialog360/testdata/wac/duplicate.json similarity index 100% rename from handlers/dialog360/testdata/wac/duplicateWAC.json rename to handlers/dialog360/testdata/wac/duplicate.json diff --git a/handlers/dialog360/testdata/wac/errorErrors.json b/handlers/dialog360/testdata/wac/error_errors.json similarity index 100% rename from handlers/dialog360/testdata/wac/errorErrors.json rename to handlers/dialog360/testdata/wac/error_errors.json diff --git a/handlers/dialog360/testdata/wac/errorMsg.json b/handlers/dialog360/testdata/wac/error_msg.json similarity index 100% rename from handlers/dialog360/testdata/wac/errorMsg.json rename to handlers/dialog360/testdata/wac/error_msg.json diff --git a/handlers/dialog360/testdata/wac/errorStatus.json b/handlers/dialog360/testdata/wac/error_status.json similarity index 100% rename from handlers/dialog360/testdata/wac/errorStatus.json rename to handlers/dialog360/testdata/wac/error_status.json diff --git a/handlers/dialog360/testdata/wac/helloWAC.json b/handlers/dialog360/testdata/wac/hello.json similarity index 100% rename from handlers/dialog360/testdata/wac/helloWAC.json rename to handlers/dialog360/testdata/wac/hello.json diff --git a/handlers/dialog360/testdata/wac/ignoreStatusWAC.json b/handlers/dialog360/testdata/wac/ignore_status.json similarity index 100% rename from handlers/dialog360/testdata/wac/ignoreStatusWAC.json rename to handlers/dialog360/testdata/wac/ignore_status.json diff --git a/handlers/dialog360/testdata/wac/imageWAC.json b/handlers/dialog360/testdata/wac/image.json similarity index 100% rename from handlers/dialog360/testdata/wac/imageWAC.json rename to handlers/dialog360/testdata/wac/image.json diff --git a/handlers/dialog360/testdata/wac/invalidFrom.json b/handlers/dialog360/testdata/wac/invalid_from.json similarity index 100% rename from handlers/dialog360/testdata/wac/invalidFrom.json rename to handlers/dialog360/testdata/wac/invalid_from.json diff --git a/handlers/dialog360/testdata/wac/invalidStatusWAC.json b/handlers/dialog360/testdata/wac/invalid_status.json similarity index 100% rename from handlers/dialog360/testdata/wac/invalidStatusWAC.json rename to handlers/dialog360/testdata/wac/invalid_status.json diff --git a/handlers/dialog360/testdata/wac/invalidTimestamp.json b/handlers/dialog360/testdata/wac/invalid_timestamp.json similarity index 100% rename from handlers/dialog360/testdata/wac/invalidTimestamp.json rename to handlers/dialog360/testdata/wac/invalid_timestamp.json diff --git a/handlers/dialog360/testdata/wac/listReplyWAC.json b/handlers/dialog360/testdata/wac/list_reply.json similarity index 100% rename from handlers/dialog360/testdata/wac/listReplyWAC.json rename to handlers/dialog360/testdata/wac/list_reply.json diff --git a/handlers/dialog360/testdata/wac/locationWAC.json b/handlers/dialog360/testdata/wac/location.json similarity index 100% rename from handlers/dialog360/testdata/wac/locationWAC.json rename to handlers/dialog360/testdata/wac/location.json diff --git a/handlers/dialog360/testdata/wac/validStatusWAC.json b/handlers/dialog360/testdata/wac/valid_status.json similarity index 100% rename from handlers/dialog360/testdata/wac/validStatusWAC.json rename to handlers/dialog360/testdata/wac/valid_status.json diff --git a/handlers/dialog360/testdata/wac/videoWAC.json b/handlers/dialog360/testdata/wac/video.json similarity index 100% rename from handlers/dialog360/testdata/wac/videoWAC.json rename to handlers/dialog360/testdata/wac/video.json diff --git a/handlers/dialog360/testdata/wac/voiceWAC.json b/handlers/dialog360/testdata/wac/voice.json similarity index 100% rename from handlers/dialog360/testdata/wac/voiceWAC.json rename to handlers/dialog360/testdata/wac/voice.json diff --git a/handlers/meta/meta_test.go b/handlers/meta/meta_test.go index e153c5cc7..cc9ffad00 100644 --- a/handlers/meta/meta_test.go +++ b/handlers/meta/meta_test.go @@ -34,7 +34,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Message FBA", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/helloMsgFBA.json")), + Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -48,7 +48,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Invalid Signature", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/helloMsgFBA.json")), + Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid request signature", PrepRequest: addInvalidSignature, @@ -56,7 +56,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "No Duplicate Receive Message", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/duplicateMsgFBA.json")), + Data: string(test.ReadFile("./testdata/fba/duplicate_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp("Hello World"), @@ -68,7 +68,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Attachment", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/attachmentFBA.json")), + Data: string(test.ReadFile("./testdata/fba/attachment.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp(""), @@ -81,7 +81,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Location", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/locationAttachment.json")), + Data: string(test.ReadFile("./testdata/fba/location_attachment.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp(""), @@ -94,7 +94,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Thumbs Up", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/thumbsUp.json")), + Data: string(test.ReadFile("./testdata/fba/thumbs_up.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp("👍"), @@ -106,7 +106,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive OptIn UserRef", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/optInUserRef.json")), + Data: string(test.ReadFile("./testdata/fba/referral_optin_user_ref.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -117,7 +117,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive OptIn", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/optIn.json")), + Data: string(test.ReadFile("./testdata/fba/referral_optin.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -128,7 +128,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Notification Messages OptIn", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notificationMessagesOptIn.json")), + Data: string(test.ReadFile("./testdata/fba/notification_messages_optin.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -140,7 +140,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Notification Messages OptOut", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notificationMessagesOptOut.json")), + Data: string(test.ReadFile("./testdata/fba/notification_messages_optout.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -152,7 +152,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Get Started", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postbackGetStarted.json")), + Data: string(test.ReadFile("./testdata/fba/postback_get_started.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -174,7 +174,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Receive Referral", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postbackReferral.json")), + Data: string(test.ReadFile("./testdata/fba/postback_referral.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -206,7 +206,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Different Page", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/differentPageFBA.json")), + Data: string(test.ReadFile("./testdata/fba/different_page.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"data":[]`, PrepRequest: addValidSignature, @@ -214,7 +214,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Echo", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/echoFBA.json")), + Data: string(test.ReadFile("./testdata/fba/echo.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring echo`, PrepRequest: addValidSignature, @@ -222,7 +222,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Not Page", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notPage.json")), + Data: string(test.ReadFile("./testdata/fba/not_page.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notpage", NoLogsExpected: true, @@ -231,7 +231,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "No Entries", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/noEntriesFBA.json")), + Data: string(test.ReadFile("./testdata/fba/no_entries.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "no entries found", NoLogsExpected: true, @@ -240,7 +240,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "No Messaging Entries", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/noMessagingEntriesFBA.json")), + Data: string(test.ReadFile("./testdata/fba/no_messaging_entries.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", PrepRequest: addValidSignature, @@ -248,7 +248,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Unknown Messaging Entry", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/unknownMessagingEntryFBA.json")), + Data: string(test.ReadFile("./testdata/fba/unknown_messaging_entry.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", PrepRequest: addValidSignature, @@ -265,7 +265,7 @@ var testCasesFBA = []IncomingTestCase{ { Label: "Invalid URN", URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/invalidURNFBA.json")), + Data: string(test.ReadFile("./testdata/fba/invalid_urn.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid facebook id", PrepRequest: addValidSignature, @@ -276,7 +276,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Receive Message", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/helloMsgIG.json")), + Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -290,7 +290,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Receive Invalid Signature", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/helloMsgIG.json")), + Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid request signature", PrepRequest: addInvalidSignature, @@ -298,7 +298,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "No Duplicate Receive Message", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/duplicateMsgIG.json")), + Data: string(test.ReadFile("./testdata/ig/duplicate_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp("Hello World"), @@ -310,7 +310,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Receive Attachment", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/attachmentIG.json")), + Data: string(test.ReadFile("./testdata/ig/attachment.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedMsgText: Sp(""), @@ -335,7 +335,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Receive Icebreaker Get Started", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/icebreakerGetStarted.json")), + Data: string(test.ReadFile("./testdata/ig/icebreaker_get_started.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ @@ -346,7 +346,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Different Page", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/differentPageIG.json")), + Data: string(test.ReadFile("./testdata/ig/different_page.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"data":[]`, PrepRequest: addValidSignature, @@ -354,7 +354,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Echo", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/echoIG.json")), + Data: string(test.ReadFile("./testdata/ig/echo.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring echo`, PrepRequest: addValidSignature, @@ -362,7 +362,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "No Entries", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/noEntriesIG.json")), + Data: string(test.ReadFile("./testdata/ig/no_entries.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "no entries found", NoLogsExpected: true, @@ -371,7 +371,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Not Instagram", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/notInstagram.json")), + Data: string(test.ReadFile("./testdata/ig/not_instagram.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notinstagram", NoLogsExpected: true, @@ -380,7 +380,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "No Messaging Entries", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/noMessagingEntriesIG.json")), + Data: string(test.ReadFile("./testdata/ig/no_messaging_entries.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", PrepRequest: addValidSignature, @@ -388,7 +388,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Unknown Messaging Entry", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unknownMessagingEntryIG.json")), + Data: string(test.ReadFile("./testdata/ig/unknown_messaging_entry.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", PrepRequest: addValidSignature, @@ -405,7 +405,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Invalid URN", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/invalidURNIG.json")), + Data: string(test.ReadFile("./testdata/ig/invalid_urn.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid instagram id", PrepRequest: addValidSignature, @@ -413,7 +413,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Story Mention", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/storyMentionIG.json")), + Data: string(test.ReadFile("./testdata/ig/story_mention.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring story_mention`, PrepRequest: addValidSignature, @@ -421,7 +421,7 @@ var testCasesIG = []IncomingTestCase{ { Label: "Message unsent", URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unsentMsgIG.json")), + Data: string(test.ReadFile("./testdata/ig/unsent_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `msg deleted`, PrepRequest: addValidSignature, @@ -566,7 +566,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Message WAC", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/helloWAC.json")), + Data: string(test.ReadFile("./testdata/wac/hello.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -580,7 +580,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Duplicate Valid Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/duplicateWAC.json")), + Data: string(test.ReadFile("./testdata/wac/duplicate.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -594,7 +594,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Voice Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/voiceWAC.json")), + Data: string(test.ReadFile("./testdata/wac/voice.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -609,7 +609,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Button Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonWAC.json")), + Data: string(test.ReadFile("./testdata/wac/button.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -623,7 +623,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Document Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/documentWAC.json")), + Data: string(test.ReadFile("./testdata/wac/document.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -638,7 +638,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Image Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/imageWAC.json")), + Data: string(test.ReadFile("./testdata/wac/image.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -653,7 +653,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Video Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/videoWAC.json")), + Data: string(test.ReadFile("./testdata/wac/video.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -668,7 +668,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Audio Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/audioWAC.json")), + Data: string(test.ReadFile("./testdata/wac/audio.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -683,7 +683,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Location Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/locationWAC.json")), + Data: string(test.ReadFile("./testdata/wac/location.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"msg"`, ExpectedMsgText: Sp(""), @@ -705,7 +705,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Invalid From", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidFrom.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_from.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid whatsapp id", PrepRequest: addValidSignature, @@ -713,7 +713,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Invalid Timestamp", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidTimestamp.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_timestamp.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid timestamp", PrepRequest: addValidSignature, @@ -721,7 +721,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Message WAC invalid signature", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/helloWAC.json")), + Data: string(test.ReadFile("./testdata/wac/hello.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid request signature", NoQueueErrorCheck: true, @@ -731,7 +731,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Message WAC with error message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorMsg.json")), + Data: string(test.ReadFile("./testdata/wac/error_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, @@ -741,7 +741,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive error message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorErrors.json")), + Data: string(test.ReadFile("./testdata/wac/error_errors.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, @@ -751,7 +751,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Status", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/validStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/valid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "S", @@ -761,7 +761,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Status with error message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorStatus.json")), + Data: string(test.ReadFile("./testdata/wac/error_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "F", @@ -772,7 +772,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Invalid Status", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/invalid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"unknown status: in_orbit"`, PrepRequest: addValidSignature, @@ -780,7 +780,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Ignore Status", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/ignoreStatusWAC.json")), + Data: string(test.ReadFile("./testdata/wac/ignore_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"ignoring status: deleted"`, PrepRequest: addValidSignature, @@ -788,7 +788,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Interactive Button Reply Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonReplyWAC.json")), + Data: string(test.ReadFile("./testdata/wac/button_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -802,7 +802,7 @@ var testCasesWAC = []IncomingTestCase{ { Label: "Receive Valid Interactive List Reply Message", URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/listReplyWAC.json")), + Data: string(test.ReadFile("./testdata/wac/list_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, diff --git a/handlers/meta/testdata/fba/attachmentFBA.json b/handlers/meta/testdata/fba/attachment.json similarity index 100% rename from handlers/meta/testdata/fba/attachmentFBA.json rename to handlers/meta/testdata/fba/attachment.json diff --git a/handlers/meta/testdata/fba/differentPageFBA.json b/handlers/meta/testdata/fba/different_page.json similarity index 100% rename from handlers/meta/testdata/fba/differentPageFBA.json rename to handlers/meta/testdata/fba/different_page.json diff --git a/handlers/meta/testdata/fba/duplicateMsgFBA.json b/handlers/meta/testdata/fba/duplicate_msg.json similarity index 100% rename from handlers/meta/testdata/fba/duplicateMsgFBA.json rename to handlers/meta/testdata/fba/duplicate_msg.json diff --git a/handlers/meta/testdata/fba/echoFBA.json b/handlers/meta/testdata/fba/echo.json similarity index 100% rename from handlers/meta/testdata/fba/echoFBA.json rename to handlers/meta/testdata/fba/echo.json diff --git a/handlers/meta/testdata/fba/helloMsgFBA.json b/handlers/meta/testdata/fba/hello_msg.json similarity index 100% rename from handlers/meta/testdata/fba/helloMsgFBA.json rename to handlers/meta/testdata/fba/hello_msg.json diff --git a/handlers/meta/testdata/fba/invalidURNFBA.json b/handlers/meta/testdata/fba/invalid_urn.json similarity index 100% rename from handlers/meta/testdata/fba/invalidURNFBA.json rename to handlers/meta/testdata/fba/invalid_urn.json diff --git a/handlers/meta/testdata/fba/locationAttachment.json b/handlers/meta/testdata/fba/location_attachment.json similarity index 100% rename from handlers/meta/testdata/fba/locationAttachment.json rename to handlers/meta/testdata/fba/location_attachment.json diff --git a/handlers/meta/testdata/fba/noEntriesFBA.json b/handlers/meta/testdata/fba/no_entries.json similarity index 100% rename from handlers/meta/testdata/fba/noEntriesFBA.json rename to handlers/meta/testdata/fba/no_entries.json diff --git a/handlers/meta/testdata/fba/noMessagingEntriesFBA.json b/handlers/meta/testdata/fba/no_messaging_entries.json similarity index 100% rename from handlers/meta/testdata/fba/noMessagingEntriesFBA.json rename to handlers/meta/testdata/fba/no_messaging_entries.json diff --git a/handlers/meta/testdata/fba/notPage.json b/handlers/meta/testdata/fba/not_page.json similarity index 100% rename from handlers/meta/testdata/fba/notPage.json rename to handlers/meta/testdata/fba/not_page.json diff --git a/handlers/meta/testdata/fba/notificationMessagesOptIn.json b/handlers/meta/testdata/fba/notification_messages_optin.json similarity index 100% rename from handlers/meta/testdata/fba/notificationMessagesOptIn.json rename to handlers/meta/testdata/fba/notification_messages_optin.json diff --git a/handlers/meta/testdata/fba/notificationMessagesOptOut.json b/handlers/meta/testdata/fba/notification_messages_optout.json similarity index 100% rename from handlers/meta/testdata/fba/notificationMessagesOptOut.json rename to handlers/meta/testdata/fba/notification_messages_optout.json diff --git a/handlers/meta/testdata/fba/postbackGetStarted.json b/handlers/meta/testdata/fba/postback_get_started.json similarity index 100% rename from handlers/meta/testdata/fba/postbackGetStarted.json rename to handlers/meta/testdata/fba/postback_get_started.json diff --git a/handlers/meta/testdata/fba/postbackReferral.json b/handlers/meta/testdata/fba/postback_referral.json similarity index 100% rename from handlers/meta/testdata/fba/postbackReferral.json rename to handlers/meta/testdata/fba/postback_referral.json diff --git a/handlers/meta/testdata/fba/optIn.json b/handlers/meta/testdata/fba/referral_optin.json similarity index 100% rename from handlers/meta/testdata/fba/optIn.json rename to handlers/meta/testdata/fba/referral_optin.json diff --git a/handlers/meta/testdata/fba/optInUserRef.json b/handlers/meta/testdata/fba/referral_optin_user_ref.json similarity index 100% rename from handlers/meta/testdata/fba/optInUserRef.json rename to handlers/meta/testdata/fba/referral_optin_user_ref.json diff --git a/handlers/meta/testdata/fba/thumbsUp.json b/handlers/meta/testdata/fba/thumbs_up.json similarity index 100% rename from handlers/meta/testdata/fba/thumbsUp.json rename to handlers/meta/testdata/fba/thumbs_up.json diff --git a/handlers/meta/testdata/fba/unknownMessagingEntryFBA.json b/handlers/meta/testdata/fba/unknown_messaging_entry.json similarity index 100% rename from handlers/meta/testdata/fba/unknownMessagingEntryFBA.json rename to handlers/meta/testdata/fba/unknown_messaging_entry.json diff --git a/handlers/meta/testdata/ig/attachmentIG.json b/handlers/meta/testdata/ig/attachment.json similarity index 100% rename from handlers/meta/testdata/ig/attachmentIG.json rename to handlers/meta/testdata/ig/attachment.json diff --git a/handlers/meta/testdata/ig/differentPageIG.json b/handlers/meta/testdata/ig/different_page.json similarity index 100% rename from handlers/meta/testdata/ig/differentPageIG.json rename to handlers/meta/testdata/ig/different_page.json diff --git a/handlers/meta/testdata/ig/duplicateMsgIG.json b/handlers/meta/testdata/ig/duplicate_msg.json similarity index 100% rename from handlers/meta/testdata/ig/duplicateMsgIG.json rename to handlers/meta/testdata/ig/duplicate_msg.json diff --git a/handlers/meta/testdata/ig/echoIG.json b/handlers/meta/testdata/ig/echo.json similarity index 100% rename from handlers/meta/testdata/ig/echoIG.json rename to handlers/meta/testdata/ig/echo.json diff --git a/handlers/meta/testdata/ig/helloMsgIG.json b/handlers/meta/testdata/ig/hello_msg.json similarity index 100% rename from handlers/meta/testdata/ig/helloMsgIG.json rename to handlers/meta/testdata/ig/hello_msg.json diff --git a/handlers/meta/testdata/ig/icebreakerGetStarted.json b/handlers/meta/testdata/ig/icebreaker_get_started.json similarity index 100% rename from handlers/meta/testdata/ig/icebreakerGetStarted.json rename to handlers/meta/testdata/ig/icebreaker_get_started.json diff --git a/handlers/meta/testdata/ig/invalidURNIG.json b/handlers/meta/testdata/ig/invalid_urn.json similarity index 100% rename from handlers/meta/testdata/ig/invalidURNIG.json rename to handlers/meta/testdata/ig/invalid_urn.json diff --git a/handlers/meta/testdata/ig/noEntriesIG.json b/handlers/meta/testdata/ig/no_entries.json similarity index 100% rename from handlers/meta/testdata/ig/noEntriesIG.json rename to handlers/meta/testdata/ig/no_entries.json diff --git a/handlers/meta/testdata/ig/noMessagingEntriesIG.json b/handlers/meta/testdata/ig/no_messaging_entries.json similarity index 100% rename from handlers/meta/testdata/ig/noMessagingEntriesIG.json rename to handlers/meta/testdata/ig/no_messaging_entries.json diff --git a/handlers/meta/testdata/ig/notInstagram.json b/handlers/meta/testdata/ig/not_instagram.json similarity index 100% rename from handlers/meta/testdata/ig/notInstagram.json rename to handlers/meta/testdata/ig/not_instagram.json diff --git a/handlers/meta/testdata/ig/storyMentionIG.json b/handlers/meta/testdata/ig/story_mention.json similarity index 100% rename from handlers/meta/testdata/ig/storyMentionIG.json rename to handlers/meta/testdata/ig/story_mention.json diff --git a/handlers/meta/testdata/ig/unknownMessagingEntryIG.json b/handlers/meta/testdata/ig/unknown_messaging_entry.json similarity index 100% rename from handlers/meta/testdata/ig/unknownMessagingEntryIG.json rename to handlers/meta/testdata/ig/unknown_messaging_entry.json diff --git a/handlers/meta/testdata/ig/unsentMsgIG.json b/handlers/meta/testdata/ig/unsent_msg.json similarity index 100% rename from handlers/meta/testdata/ig/unsentMsgIG.json rename to handlers/meta/testdata/ig/unsent_msg.json diff --git a/handlers/meta/testdata/wac/audioWAC.json b/handlers/meta/testdata/wac/audio.json similarity index 100% rename from handlers/meta/testdata/wac/audioWAC.json rename to handlers/meta/testdata/wac/audio.json diff --git a/handlers/meta/testdata/wac/buttonWAC.json b/handlers/meta/testdata/wac/button.json similarity index 100% rename from handlers/meta/testdata/wac/buttonWAC.json rename to handlers/meta/testdata/wac/button.json diff --git a/handlers/meta/testdata/wac/buttonReplyWAC.json b/handlers/meta/testdata/wac/button_reply.json similarity index 100% rename from handlers/meta/testdata/wac/buttonReplyWAC.json rename to handlers/meta/testdata/wac/button_reply.json diff --git a/handlers/meta/testdata/wac/documentWAC.json b/handlers/meta/testdata/wac/document.json similarity index 100% rename from handlers/meta/testdata/wac/documentWAC.json rename to handlers/meta/testdata/wac/document.json diff --git a/handlers/meta/testdata/wac/duplicateWAC.json b/handlers/meta/testdata/wac/duplicate.json similarity index 100% rename from handlers/meta/testdata/wac/duplicateWAC.json rename to handlers/meta/testdata/wac/duplicate.json diff --git a/handlers/meta/testdata/wac/errorErrors.json b/handlers/meta/testdata/wac/error_errors.json similarity index 100% rename from handlers/meta/testdata/wac/errorErrors.json rename to handlers/meta/testdata/wac/error_errors.json diff --git a/handlers/meta/testdata/wac/errorMsg.json b/handlers/meta/testdata/wac/error_msg.json similarity index 100% rename from handlers/meta/testdata/wac/errorMsg.json rename to handlers/meta/testdata/wac/error_msg.json diff --git a/handlers/meta/testdata/wac/errorStatus.json b/handlers/meta/testdata/wac/error_status.json similarity index 100% rename from handlers/meta/testdata/wac/errorStatus.json rename to handlers/meta/testdata/wac/error_status.json diff --git a/handlers/meta/testdata/wac/helloWAC.json b/handlers/meta/testdata/wac/hello.json similarity index 100% rename from handlers/meta/testdata/wac/helloWAC.json rename to handlers/meta/testdata/wac/hello.json diff --git a/handlers/meta/testdata/wac/ignoreStatusWAC.json b/handlers/meta/testdata/wac/ignore_status.json similarity index 100% rename from handlers/meta/testdata/wac/ignoreStatusWAC.json rename to handlers/meta/testdata/wac/ignore_status.json diff --git a/handlers/meta/testdata/wac/imageWAC.json b/handlers/meta/testdata/wac/image.json similarity index 100% rename from handlers/meta/testdata/wac/imageWAC.json rename to handlers/meta/testdata/wac/image.json diff --git a/handlers/meta/testdata/wac/invalidFrom.json b/handlers/meta/testdata/wac/invalid_from.json similarity index 100% rename from handlers/meta/testdata/wac/invalidFrom.json rename to handlers/meta/testdata/wac/invalid_from.json diff --git a/handlers/meta/testdata/wac/invalidStatusWAC.json b/handlers/meta/testdata/wac/invalid_status.json similarity index 100% rename from handlers/meta/testdata/wac/invalidStatusWAC.json rename to handlers/meta/testdata/wac/invalid_status.json diff --git a/handlers/meta/testdata/wac/invalidTimestamp.json b/handlers/meta/testdata/wac/invalid_timestamp.json similarity index 100% rename from handlers/meta/testdata/wac/invalidTimestamp.json rename to handlers/meta/testdata/wac/invalid_timestamp.json diff --git a/handlers/meta/testdata/wac/listReplyWAC.json b/handlers/meta/testdata/wac/list_reply.json similarity index 100% rename from handlers/meta/testdata/wac/listReplyWAC.json rename to handlers/meta/testdata/wac/list_reply.json diff --git a/handlers/meta/testdata/wac/locationWAC.json b/handlers/meta/testdata/wac/location.json similarity index 100% rename from handlers/meta/testdata/wac/locationWAC.json rename to handlers/meta/testdata/wac/location.json diff --git a/handlers/meta/testdata/wac/validStatusWAC.json b/handlers/meta/testdata/wac/valid_status.json similarity index 100% rename from handlers/meta/testdata/wac/validStatusWAC.json rename to handlers/meta/testdata/wac/valid_status.json diff --git a/handlers/meta/testdata/wac/videoWAC.json b/handlers/meta/testdata/wac/video.json similarity index 100% rename from handlers/meta/testdata/wac/videoWAC.json rename to handlers/meta/testdata/wac/video.json diff --git a/handlers/meta/testdata/wac/voiceWAC.json b/handlers/meta/testdata/wac/voice.json similarity index 100% rename from handlers/meta/testdata/wac/voiceWAC.json rename to handlers/meta/testdata/wac/voice.json From c7f38e7489c9b3e17f57f6b56a80f2ca8860baeb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 10:06:44 -0500 Subject: [PATCH 100/170] Use meta handler test files for dialog360 handler --- handlers/dialog360/dialog360_test.go | 38 ++++++------- handlers/dialog360/testdata/wac/audio.json | 43 --------------- handlers/dialog360/testdata/wac/button.json | 44 --------------- .../dialog360/testdata/wac/button_reply.json | 43 --------------- handlers/dialog360/testdata/wac/document.json | 43 --------------- .../dialog360/testdata/wac/duplicate.json | 48 ---------------- .../dialog360/testdata/wac/error_errors.json | 34 ------------ .../dialog360/testdata/wac/error_msg.json | 46 ---------------- .../dialog360/testdata/wac/error_status.json | 55 ------------------- handlers/dialog360/testdata/wac/hello.json | 39 ------------- .../dialog360/testdata/wac/ignore_status.json | 49 ----------------- handlers/dialog360/testdata/wac/image.json | 43 --------------- .../dialog360/testdata/wac/invalid_from.json | 39 ------------- .../testdata/wac/invalid_status.json | 49 ----------------- .../testdata/wac/invalid_timestamp.json | 39 ------------- .../dialog360/testdata/wac/list_reply.json | 43 --------------- handlers/dialog360/testdata/wac/location.json | 43 --------------- .../dialog360/testdata/wac/valid_status.json | 49 ----------------- handlers/dialog360/testdata/wac/video.json | 43 --------------- handlers/dialog360/testdata/wac/voice.json | 42 -------------- 20 files changed, 19 insertions(+), 853 deletions(-) delete mode 100644 handlers/dialog360/testdata/wac/audio.json delete mode 100644 handlers/dialog360/testdata/wac/button.json delete mode 100644 handlers/dialog360/testdata/wac/button_reply.json delete mode 100644 handlers/dialog360/testdata/wac/document.json delete mode 100644 handlers/dialog360/testdata/wac/duplicate.json delete mode 100644 handlers/dialog360/testdata/wac/error_errors.json delete mode 100644 handlers/dialog360/testdata/wac/error_msg.json delete mode 100644 handlers/dialog360/testdata/wac/error_status.json delete mode 100644 handlers/dialog360/testdata/wac/hello.json delete mode 100644 handlers/dialog360/testdata/wac/ignore_status.json delete mode 100644 handlers/dialog360/testdata/wac/image.json delete mode 100644 handlers/dialog360/testdata/wac/invalid_from.json delete mode 100644 handlers/dialog360/testdata/wac/invalid_status.json delete mode 100644 handlers/dialog360/testdata/wac/invalid_timestamp.json delete mode 100644 handlers/dialog360/testdata/wac/list_reply.json delete mode 100644 handlers/dialog360/testdata/wac/location.json delete mode 100644 handlers/dialog360/testdata/wac/valid_status.json delete mode 100644 handlers/dialog360/testdata/wac/video.json delete mode 100644 handlers/dialog360/testdata/wac/voice.json diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/dialog360_test.go index 8d85d25c0..3cd7872fc 100644 --- a/handlers/dialog360/dialog360_test.go +++ b/handlers/dialog360/dialog360_test.go @@ -37,7 +37,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Message WAC", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/hello.json")), + Data: string(test.ReadFile("../meta/testdata/wac/hello.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -50,7 +50,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Duplicate Valid Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/duplicate.json")), + Data: string(test.ReadFile("../meta/testdata/wac/duplicate.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -63,7 +63,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Voice Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/voice.json")), + Data: string(test.ReadFile("../meta/testdata/wac/voice.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -77,7 +77,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Button Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/button.json")), + Data: string(test.ReadFile("../meta/testdata/wac/button.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -90,7 +90,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Document Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/document.json")), + Data: string(test.ReadFile("../meta/testdata/wac/document.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -104,7 +104,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Image Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/image.json")), + Data: string(test.ReadFile("../meta/testdata/wac/image.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -118,7 +118,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Video Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/video.json")), + Data: string(test.ReadFile("../meta/testdata/wac/video.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -132,7 +132,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Audio Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/audio.json")), + Data: string(test.ReadFile("../meta/testdata/wac/audio.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -146,7 +146,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Location Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/location.json")), + Data: string(test.ReadFile("../meta/testdata/wac/location.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"msg"`, ExpectedMsgText: Sp(""), @@ -165,21 +165,21 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Invalid FROM", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_from.json")), + Data: string(test.ReadFile("../meta/testdata/wac/invalid_from.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid whatsapp id", }, { Label: "Receive Invalid timestamp JSON", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_timestamp.json")), + Data: string(test.ReadFile("../meta/testdata/wac/invalid_timestamp.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "invalid timestamp", }, { Label: "Receive Message WAC with error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_msg.json")), + Data: string(test.ReadFile("../meta/testdata/wac/error_msg.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, @@ -188,7 +188,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_errors.json")), + Data: string(test.ReadFile("../meta/testdata/wac/error_errors.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, @@ -197,7 +197,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/valid_status.json")), + Data: string(test.ReadFile("../meta/testdata/wac/valid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "S", @@ -206,7 +206,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Status with error message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_status.json")), + Data: string(test.ReadFile("../meta/testdata/wac/error_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, ExpectedMsgStatus: "F", @@ -216,21 +216,21 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Invalid Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_status.json")), + Data: string(test.ReadFile("../meta/testdata/wac/invalid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"unknown status: in_orbit"`, }, { Label: "Receive Ignore Status", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/ignore_status.json")), + Data: string(test.ReadFile("../meta/testdata/wac/ignore_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"ignoring status: deleted"`, }, { Label: "Receive Valid Interactive Button Reply Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/button_reply.json")), + Data: string(test.ReadFile("../meta/testdata/wac/button_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, @@ -243,7 +243,7 @@ var testCasesD3C = []IncomingTestCase{ { Label: "Receive Valid Interactive List Reply Message", URL: d3CReceiveURL, - Data: string(test.ReadFile("./testdata/wac/list_reply.json")), + Data: string(test.ReadFile("../meta/testdata/wac/list_reply.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", NoQueueErrorCheck: true, diff --git a/handlers/dialog360/testdata/wac/audio.json b/handlers/dialog360/testdata/wac/audio.json deleted file mode 100644 index f578e5fc9..000000000 --- a/handlers/dialog360/testdata/wac/audio.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "audio": { - "file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683", - "id": "id_audio", - "mime_type": "image/jpeg", - "sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db", - "caption": "Check out my new phone!" - }, - "timestamp": "1454119029", - "type": "audio" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/button.json b/handlers/dialog360/testdata/wac/button.json deleted file mode 100644 index 10f592773..000000000 --- a/handlers/dialog360/testdata/wac/button.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "button": { - "payload": "No-Button-Payload", - "text": "No" - }, - "context": { - "from": "5678", - "id": "gBGGFmkiWVVPAgkgQkwi7IORac0" - }, - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "type": "button" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/button_reply.json b/handlers/dialog360/testdata/wac/button_reply.json deleted file mode 100644 index e44859b0d..000000000 --- a/handlers/dialog360/testdata/wac/button_reply.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "interactive": { - "type": "button_reply", - "button_reply": { - "id": "id_button_reply", - "title": "Yes" - } - }, - "timestamp": "1454119029", - "type": "interactive" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/document.json b/handlers/dialog360/testdata/wac/document.json deleted file mode 100644 index 1c5f08eab..000000000 --- a/handlers/dialog360/testdata/wac/document.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "type": "document", - "document": { - "caption": "80skaraokesonglistartist", - "file": "/usr/local/wamedia/shared/fc233119-733f-49c-bcbd-b2f68f798e33", - "id": "id_document", - "mime_type": "application/pdf", - "sha256": "3b11fa6ef2bde1dd14726e09d3edaf782120919d06f6484f32d5d5caa4b8e" - } - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/duplicate.json b/handlers/dialog360/testdata/wac/duplicate.json deleted file mode 100644 index 69463fb0f..000000000 --- a/handlers/dialog360/testdata/wac/duplicate.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "text": { - "body": "Hello World" - }, - "type": "text" - }, - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "text": { - "body": "Hello World" - }, - "type": "text" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/error_errors.json b/handlers/dialog360/testdata/wac/error_errors.json deleted file mode 100644 index 42e86216f..000000000 --- a/handlers/dialog360/testdata/wac/error_errors.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "errors": [ - { - "code": 0, - "title": "We were unable to authenticate the app user" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/error_msg.json b/handlers/dialog360/testdata/wac/error_msg.json deleted file mode 100644 index 099dc62aa..000000000 --- a/handlers/dialog360/testdata/wac/error_msg.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "text": { - "body": "Hello World" - }, - "type": "unsupported", - "errors": [ - { - "code": 131051, - "details": "Message type is not currently supported", - "title": "Unsupported message type" - } - ] - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/error_status.json b/handlers/dialog360/testdata/wac/error_status.json deleted file mode 100644 index 95f03649d..000000000 --- a/handlers/dialog360/testdata/wac/error_status.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "statuses": [ - { - "id": "external_id", - "recipient_id": "5678", - "status": "failed", - "timestamp": "1454119029", - "type": "message", - "conversation": { - "id": "CONVERSATION_ID", - "expiration_timestamp": 1454119029, - "origin": { - "type": "referral_conversion" - } - }, - "pricing": { - "pricing_model": "CBP", - "billable": false, - "category": "referral_conversion" - }, - "errors": [ - { - "code": 131014, - "title": "Request for url https://URL.jpg failed with error: 404 (Not Found)" - } - ] - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/hello.json b/handlers/dialog360/testdata/wac/hello.json deleted file mode 100644 index d7cf38ee8..000000000 --- a/handlers/dialog360/testdata/wac/hello.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "text": { - "body": "Hello World" - }, - "type": "text" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/ignore_status.json b/handlers/dialog360/testdata/wac/ignore_status.json deleted file mode 100644 index 2b2e583a1..000000000 --- a/handlers/dialog360/testdata/wac/ignore_status.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "statuses": [ - { - "id": "external_id", - "recipient_id": "5678", - "status": "deleted", - "timestamp": "1454119029", - "type": "message", - "conversation": { - "id": "CONVERSATION_ID", - "expiration_timestamp": 1454119029, - "origin": { - "type": "referral_conversion" - } - }, - "pricing": { - "pricing_model": "CBP", - "billable": false, - "category": "referral_conversion" - } - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/image.json b/handlers/dialog360/testdata/wac/image.json deleted file mode 100644 index 7d3728e5b..000000000 --- a/handlers/dialog360/testdata/wac/image.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "image": { - "file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683", - "id": "id_image", - "mime_type": "image/jpeg", - "sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db", - "caption": "Check out my new phone!" - }, - "timestamp": "1454119029", - "type": "image" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/invalid_from.json b/handlers/dialog360/testdata/wac/invalid_from.json deleted file mode 100644 index 12a28cc54..000000000 --- a/handlers/dialog360/testdata/wac/invalid_from.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "bla" - } - ], - "messages": [ - { - "from": "bla", - "id": "external_id", - "timestamp": "1454119029", - "text": { - "body": "Hello World" - }, - "type": "text" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/invalid_status.json b/handlers/dialog360/testdata/wac/invalid_status.json deleted file mode 100644 index 6a3a4fbcc..000000000 --- a/handlers/dialog360/testdata/wac/invalid_status.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "statuses": [ - { - "id": "external_id", - "recipient_id": "5678", - "status": "in_orbit", - "timestamp": "1454119029", - "type": "message", - "conversation": { - "id": "CONVERSATION_ID", - "expiration_timestamp": 1454119029, - "origin": { - "type": "referral_conversion" - } - }, - "pricing": { - "pricing_model": "CBP", - "billable": false, - "category": "referral_conversion" - } - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/invalid_timestamp.json b/handlers/dialog360/testdata/wac/invalid_timestamp.json deleted file mode 100644 index dc0dd66d5..000000000 --- a/handlers/dialog360/testdata/wac/invalid_timestamp.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "bla" - } - ], - "messages": [ - { - "from": "bla", - "id": "external_id", - "timestamp": "asdf", - "text": { - "body": "Hello World" - }, - "type": "text" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/list_reply.json b/handlers/dialog360/testdata/wac/list_reply.json deleted file mode 100644 index 1f3d2981c..000000000 --- a/handlers/dialog360/testdata/wac/list_reply.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "interactive": { - "type": "list_reply", - "list_reply": { - "id": "id_list_reply", - "title": "Yes" - } - }, - "timestamp": "1454119029", - "type": "interactive" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/location.json b/handlers/dialog360/testdata/wac/location.json deleted file mode 100644 index 09a721c8d..000000000 --- a/handlers/dialog360/testdata/wac/location.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "location": { - "address": "Main Street Beach, Santa Cruz, CA", - "latitude": 0.000000, - "longitude": 1.000000, - "name": "Main Street Beach", - "url": "https://foursquare.com/v/4d7031d35b5df7744" - }, - "timestamp": "1454119029", - "type": "location" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/valid_status.json b/handlers/dialog360/testdata/wac/valid_status.json deleted file mode 100644 index 8a3360787..000000000 --- a/handlers/dialog360/testdata/wac/valid_status.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "statuses": [ - { - "id": "external_id", - "recipient_id": "5678", - "status": "sent", - "timestamp": "1454119029", - "type": "message", - "conversation": { - "id": "CONVERSATION_ID", - "expiration_timestamp": 1454119029, - "origin": { - "type": "referral_conversion" - } - }, - "pricing": { - "pricing_model": "CBP", - "billable": false, - "category": "referral_conversion" - } - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/video.json b/handlers/dialog360/testdata/wac/video.json deleted file mode 100644 index 234422efe..000000000 --- a/handlers/dialog360/testdata/wac/video.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "video": { - "file": "/usr/local/wamedia/shared/b1cf38-8734-4ad3-b4a1-ef0c10d0d683", - "id": "id_video", - "mime_type": "image/jpeg", - "sha256": "29ed500fa64eb55fc19dc4124acb300e5dcc54a0f822a301ae99944db", - "caption": "Check out my new phone!" - }, - "timestamp": "1454119029", - "type": "video" - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file diff --git a/handlers/dialog360/testdata/wac/voice.json b/handlers/dialog360/testdata/wac/voice.json deleted file mode 100644 index 03e03375f..000000000 --- a/handlers/dialog360/testdata/wac/voice.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "object": "whatsapp_business_account", - "entry": [ - { - "id": "8856996819413533", - "changes": [ - { - "value": { - "messaging_product": "whatsapp", - "metadata": { - "display_phone_number": "+250 788 123 200", - "phone_number_id": "12345" - }, - "contacts": [ - { - "profile": { - "name": "Kerry Fisher" - }, - "wa_id": "5678" - } - ], - "messages": [ - { - "from": "5678", - "id": "external_id", - "timestamp": "1454119029", - "type": "voice", - "voice": { - "file": "/usr/local/wamedia/shared/463e/b7ec/ff4e4d9bb1101879cbd411b2", - "id": "id_voice", - "mime_type": "audio/ogg; codecs=opus", - "sha256": "fa9e1807d936b7cebe63654ea3a7912b1fa9479220258d823590521ef53b0710" - } - } - ] - }, - "field": "messages" - } - ] - } - ] -} \ No newline at end of file From 5930fb462b82120a0cc41667d980f07f41f4d71e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 10:17:50 -0500 Subject: [PATCH 101/170] Rename handler modules to handler.go --- handlers/africastalking/{africastalking.go => handler.go} | 0 .../africastalking/{africastalking_test.go => handler_test.go} | 0 handlers/arabiacell/{arabiacell.go => handler.go} | 0 handlers/arabiacell/{arabiacell_test.go => handler_test.go} | 0 handlers/bandwidth/{bandwidth.go => handler.go} | 0 handlers/bandwidth/{bandwidth_test.go => handler_test.go} | 0 handlers/bongolive/{bongolive.go => handler.go} | 0 handlers/bongolive/{bongolive_test.go => handler_test.go} | 0 handlers/burstsms/{burstsms.go => handler.go} | 0 handlers/burstsms/{burstsms_test.go => handler_test.go} | 0 handlers/clickatell/{clickatell.go => handler.go} | 0 handlers/clickatell/{clickatell_test.go => handler_test.go} | 0 handlers/clickmobile/{clickmobile.go => handler.go} | 0 handlers/clickmobile/{clickmobile_test.go => handler_test.go} | 0 handlers/clicksend/{clicksend.go => handler.go} | 0 handlers/clicksend/{clicksend_test.go => handler_test.go} | 0 handlers/dart/{dart.go => handler.go} | 0 handlers/dart/{dart_test.go => handler_test.go} | 0 handlers/dialog360/{dialog360.go => handler.go} | 0 handlers/dialog360/{dialog360_test.go => handler_test.go} | 0 handlers/discord/{discord.go => handler.go} | 0 handlers/discord/{discord_test.go => handler_test.go} | 0 handlers/dmark/{dmark.go => handler.go} | 0 handlers/dmark/{dmark_test.go => handler_test.go} | 0 handlers/external/{external.go => handler.go} | 0 handlers/external/{external_test.go => handler_test.go} | 0 handlers/facebook_legacy/{facebook.go => handler.go} | 0 handlers/facebook_legacy/{facebook_test.go => handler_test.go} | 0 handlers/firebase/{firebase.go => handler.go} | 0 handlers/firebase/{firebase_test.go => handler_test.go} | 0 handlers/freshchat/{freshchat.go => handler.go} | 0 handlers/freshchat/{freshchat_test.go => handler_test.go} | 0 handlers/globe/{globe.go => handler.go} | 0 handlers/globe/{globe_test.go => handler_test.go} | 0 handlers/highconnection/{highconnection.go => handler.go} | 0 .../highconnection/{highconnection_test.go => handler_test.go} | 0 handlers/hormuud/{hormuud.go => handler.go} | 0 handlers/hormuud/{hormuud_test.go => handler_test.go} | 0 handlers/hub9/{hub9.go => handler.go} | 0 handlers/i2sms/{i2sms.go => handler.go} | 0 handlers/i2sms/{i2sms_test.go => handler_test.go} | 0 handlers/infobip/{infobip.go => handler.go} | 0 handlers/infobip/{infobip_test.go => handler_test.go} | 0 handlers/jasmin/{jasmin.go => handler.go} | 0 handlers/jasmin/{jasmin_test.go => handler_test.go} | 0 handlers/jiochat/{jiochat.go => handler.go} | 0 handlers/jiochat/{jiochat_test.go => handler_test.go} | 0 handlers/justcall/{justcall.go => handler.go} | 0 handlers/justcall/{justcall_test.go => handler_test.go} | 0 handlers/kaleyra/{kaleyra.go => handler.go} | 0 handlers/kaleyra/{kaleyra_test.go => handler_test.go} | 0 handlers/kannel/{kannel.go => handler.go} | 0 handlers/kannel/{kannel_test.go => handler_test.go} | 0 handlers/line/{line.go => handler.go} | 0 handlers/line/{line_test.go => handler_test.go} | 0 handlers/m3tech/{m3tech.go => handler.go} | 0 handlers/m3tech/{m3tech_test.go => handler_test.go} | 0 handlers/macrokiosk/{macrokiosk.go => handler.go} | 0 handlers/macrokiosk/{macrokiosk_test.go => handler_test.go} | 0 handlers/mblox/{mblox.go => handler.go} | 0 handlers/mblox/{mblox_test.go => handler_test.go} | 0 handlers/messagebird/{messagebird.go => handler.go} | 0 handlers/messagebird/{messagebird_test.go => handler_test.go} | 0 handlers/messangi/{messangi.go => handler.go} | 0 handlers/messangi/{messangi_test.go => handler_test.go} | 0 handlers/meta/{meta.go => handlers.go} | 0 handlers/meta/{meta_test.go => handlers_test.go} | 0 handlers/mtarget/{mtarget.go => handler.go} | 0 handlers/mtarget/{mtarget_test.go => handler_test.go} | 0 handlers/mtn/{mtn.go => handler.go} | 0 handlers/mtn/{mtn_test.go => handler_test.go} | 0 handlers/nexmo/{nexmo.go => handler.go} | 0 handlers/nexmo/{nexmo_test.go => handler_test.go} | 0 handlers/novo/{novo.go => handler.go} | 0 handlers/novo/{novo_test.go => handler_test.go} | 0 handlers/playmobile/{playmobile.go => handler.go} | 0 handlers/playmobile/{playmobile_test.go => handler_test.go} | 0 handlers/plivo/{plivo.go => handler.go} | 0 handlers/plivo/{plivo_test.go => handler_test.go} | 0 handlers/redrabbit/{redrabbit.go => handler.go} | 0 handlers/redrabbit/{redrabbit_test.go => handler_test.go} | 0 handlers/rocketchat/{rocketchat.go => handler.go} | 0 handlers/rocketchat/{rocketchat_test.go => handler_test.go} | 0 handlers/shaqodoon/{shaqodoon.go => handler.go} | 0 handlers/shaqodoon/{shaqodoon_test.go => handler_test.go} | 0 handlers/slack/{slack.go => handler.go} | 0 handlers/slack/{slack_test.go => handler_test.go} | 0 handlers/smscentral/{smscentral.go => handler.go} | 0 handlers/smscentral/{smscentral_test.go => handler_test.go} | 0 handlers/start/{start.go => handler.go} | 0 handlers/start/{start_test.go => handler_test.go} | 0 handlers/telegram/{telegram.go => handler.go} | 0 handlers/telegram/{telegram_test.go => handler_test.go} | 0 handlers/telesom/{telesom.go => handler.go} | 0 handlers/telesom/{telesom_test.go => handler_test.go} | 0 handlers/thinq/{thinq.go => handler.go} | 0 handlers/thinq/{thinq_test.go => handler_test.go} | 0 handlers/twiml/{twiml.go => handlers.go} | 0 handlers/twiml/{twiml_test.go => handlers_test.go} | 0 handlers/twitter/{twitter.go => handler.go} | 0 handlers/twitter/{twitter_test.go => handler_test.go} | 0 handlers/viber/{viber.go => handler.go} | 0 handlers/viber/{viber_test.go => handler_test.go} | 0 handlers/vk/{vk.go => handler.go} | 0 handlers/vk/{vk_test.go => handler_test.go} | 0 handlers/wavy/{wavy.go => handler.go} | 0 handlers/wavy/{wavy_test.go => handler_test.go} | 0 handlers/wechat/{wechat.go => handler.go} | 0 handlers/wechat/{wechat_test.go => handler_test.go} | 0 handlers/whatsapp_legacy/{whatsapp.go => handler.go} | 0 handlers/whatsapp_legacy/{whatsapp_test.go => handler_test.go} | 0 handlers/yo/{yo.go => handler.go} | 0 handlers/yo/{yo_test.go => handler_test.go} | 0 handlers/zenvia/{zenvia.go => handlers.go} | 0 handlers/zenvia/{zenvia_test.go => handlers_test.go} | 0 115 files changed, 0 insertions(+), 0 deletions(-) rename handlers/africastalking/{africastalking.go => handler.go} (100%) rename handlers/africastalking/{africastalking_test.go => handler_test.go} (100%) rename handlers/arabiacell/{arabiacell.go => handler.go} (100%) rename handlers/arabiacell/{arabiacell_test.go => handler_test.go} (100%) rename handlers/bandwidth/{bandwidth.go => handler.go} (100%) rename handlers/bandwidth/{bandwidth_test.go => handler_test.go} (100%) rename handlers/bongolive/{bongolive.go => handler.go} (100%) rename handlers/bongolive/{bongolive_test.go => handler_test.go} (100%) rename handlers/burstsms/{burstsms.go => handler.go} (100%) rename handlers/burstsms/{burstsms_test.go => handler_test.go} (100%) rename handlers/clickatell/{clickatell.go => handler.go} (100%) rename handlers/clickatell/{clickatell_test.go => handler_test.go} (100%) rename handlers/clickmobile/{clickmobile.go => handler.go} (100%) rename handlers/clickmobile/{clickmobile_test.go => handler_test.go} (100%) rename handlers/clicksend/{clicksend.go => handler.go} (100%) rename handlers/clicksend/{clicksend_test.go => handler_test.go} (100%) rename handlers/dart/{dart.go => handler.go} (100%) rename handlers/dart/{dart_test.go => handler_test.go} (100%) rename handlers/dialog360/{dialog360.go => handler.go} (100%) rename handlers/dialog360/{dialog360_test.go => handler_test.go} (100%) rename handlers/discord/{discord.go => handler.go} (100%) rename handlers/discord/{discord_test.go => handler_test.go} (100%) rename handlers/dmark/{dmark.go => handler.go} (100%) rename handlers/dmark/{dmark_test.go => handler_test.go} (100%) rename handlers/external/{external.go => handler.go} (100%) rename handlers/external/{external_test.go => handler_test.go} (100%) rename handlers/facebook_legacy/{facebook.go => handler.go} (100%) rename handlers/facebook_legacy/{facebook_test.go => handler_test.go} (100%) rename handlers/firebase/{firebase.go => handler.go} (100%) rename handlers/firebase/{firebase_test.go => handler_test.go} (100%) rename handlers/freshchat/{freshchat.go => handler.go} (100%) rename handlers/freshchat/{freshchat_test.go => handler_test.go} (100%) rename handlers/globe/{globe.go => handler.go} (100%) rename handlers/globe/{globe_test.go => handler_test.go} (100%) rename handlers/highconnection/{highconnection.go => handler.go} (100%) rename handlers/highconnection/{highconnection_test.go => handler_test.go} (100%) rename handlers/hormuud/{hormuud.go => handler.go} (100%) rename handlers/hormuud/{hormuud_test.go => handler_test.go} (100%) rename handlers/hub9/{hub9.go => handler.go} (100%) rename handlers/i2sms/{i2sms.go => handler.go} (100%) rename handlers/i2sms/{i2sms_test.go => handler_test.go} (100%) rename handlers/infobip/{infobip.go => handler.go} (100%) rename handlers/infobip/{infobip_test.go => handler_test.go} (100%) rename handlers/jasmin/{jasmin.go => handler.go} (100%) rename handlers/jasmin/{jasmin_test.go => handler_test.go} (100%) rename handlers/jiochat/{jiochat.go => handler.go} (100%) rename handlers/jiochat/{jiochat_test.go => handler_test.go} (100%) rename handlers/justcall/{justcall.go => handler.go} (100%) rename handlers/justcall/{justcall_test.go => handler_test.go} (100%) rename handlers/kaleyra/{kaleyra.go => handler.go} (100%) rename handlers/kaleyra/{kaleyra_test.go => handler_test.go} (100%) rename handlers/kannel/{kannel.go => handler.go} (100%) rename handlers/kannel/{kannel_test.go => handler_test.go} (100%) rename handlers/line/{line.go => handler.go} (100%) rename handlers/line/{line_test.go => handler_test.go} (100%) rename handlers/m3tech/{m3tech.go => handler.go} (100%) rename handlers/m3tech/{m3tech_test.go => handler_test.go} (100%) rename handlers/macrokiosk/{macrokiosk.go => handler.go} (100%) rename handlers/macrokiosk/{macrokiosk_test.go => handler_test.go} (100%) rename handlers/mblox/{mblox.go => handler.go} (100%) rename handlers/mblox/{mblox_test.go => handler_test.go} (100%) rename handlers/messagebird/{messagebird.go => handler.go} (100%) rename handlers/messagebird/{messagebird_test.go => handler_test.go} (100%) rename handlers/messangi/{messangi.go => handler.go} (100%) rename handlers/messangi/{messangi_test.go => handler_test.go} (100%) rename handlers/meta/{meta.go => handlers.go} (100%) rename handlers/meta/{meta_test.go => handlers_test.go} (100%) rename handlers/mtarget/{mtarget.go => handler.go} (100%) rename handlers/mtarget/{mtarget_test.go => handler_test.go} (100%) rename handlers/mtn/{mtn.go => handler.go} (100%) rename handlers/mtn/{mtn_test.go => handler_test.go} (100%) rename handlers/nexmo/{nexmo.go => handler.go} (100%) rename handlers/nexmo/{nexmo_test.go => handler_test.go} (100%) rename handlers/novo/{novo.go => handler.go} (100%) rename handlers/novo/{novo_test.go => handler_test.go} (100%) rename handlers/playmobile/{playmobile.go => handler.go} (100%) rename handlers/playmobile/{playmobile_test.go => handler_test.go} (100%) rename handlers/plivo/{plivo.go => handler.go} (100%) rename handlers/plivo/{plivo_test.go => handler_test.go} (100%) rename handlers/redrabbit/{redrabbit.go => handler.go} (100%) rename handlers/redrabbit/{redrabbit_test.go => handler_test.go} (100%) rename handlers/rocketchat/{rocketchat.go => handler.go} (100%) rename handlers/rocketchat/{rocketchat_test.go => handler_test.go} (100%) rename handlers/shaqodoon/{shaqodoon.go => handler.go} (100%) rename handlers/shaqodoon/{shaqodoon_test.go => handler_test.go} (100%) rename handlers/slack/{slack.go => handler.go} (100%) rename handlers/slack/{slack_test.go => handler_test.go} (100%) rename handlers/smscentral/{smscentral.go => handler.go} (100%) rename handlers/smscentral/{smscentral_test.go => handler_test.go} (100%) rename handlers/start/{start.go => handler.go} (100%) rename handlers/start/{start_test.go => handler_test.go} (100%) rename handlers/telegram/{telegram.go => handler.go} (100%) rename handlers/telegram/{telegram_test.go => handler_test.go} (100%) rename handlers/telesom/{telesom.go => handler.go} (100%) rename handlers/telesom/{telesom_test.go => handler_test.go} (100%) rename handlers/thinq/{thinq.go => handler.go} (100%) rename handlers/thinq/{thinq_test.go => handler_test.go} (100%) rename handlers/twiml/{twiml.go => handlers.go} (100%) rename handlers/twiml/{twiml_test.go => handlers_test.go} (100%) rename handlers/twitter/{twitter.go => handler.go} (100%) rename handlers/twitter/{twitter_test.go => handler_test.go} (100%) rename handlers/viber/{viber.go => handler.go} (100%) rename handlers/viber/{viber_test.go => handler_test.go} (100%) rename handlers/vk/{vk.go => handler.go} (100%) rename handlers/vk/{vk_test.go => handler_test.go} (100%) rename handlers/wavy/{wavy.go => handler.go} (100%) rename handlers/wavy/{wavy_test.go => handler_test.go} (100%) rename handlers/wechat/{wechat.go => handler.go} (100%) rename handlers/wechat/{wechat_test.go => handler_test.go} (100%) rename handlers/whatsapp_legacy/{whatsapp.go => handler.go} (100%) rename handlers/whatsapp_legacy/{whatsapp_test.go => handler_test.go} (100%) rename handlers/yo/{yo.go => handler.go} (100%) rename handlers/yo/{yo_test.go => handler_test.go} (100%) rename handlers/zenvia/{zenvia.go => handlers.go} (100%) rename handlers/zenvia/{zenvia_test.go => handlers_test.go} (100%) diff --git a/handlers/africastalking/africastalking.go b/handlers/africastalking/handler.go similarity index 100% rename from handlers/africastalking/africastalking.go rename to handlers/africastalking/handler.go diff --git a/handlers/africastalking/africastalking_test.go b/handlers/africastalking/handler_test.go similarity index 100% rename from handlers/africastalking/africastalking_test.go rename to handlers/africastalking/handler_test.go diff --git a/handlers/arabiacell/arabiacell.go b/handlers/arabiacell/handler.go similarity index 100% rename from handlers/arabiacell/arabiacell.go rename to handlers/arabiacell/handler.go diff --git a/handlers/arabiacell/arabiacell_test.go b/handlers/arabiacell/handler_test.go similarity index 100% rename from handlers/arabiacell/arabiacell_test.go rename to handlers/arabiacell/handler_test.go diff --git a/handlers/bandwidth/bandwidth.go b/handlers/bandwidth/handler.go similarity index 100% rename from handlers/bandwidth/bandwidth.go rename to handlers/bandwidth/handler.go diff --git a/handlers/bandwidth/bandwidth_test.go b/handlers/bandwidth/handler_test.go similarity index 100% rename from handlers/bandwidth/bandwidth_test.go rename to handlers/bandwidth/handler_test.go diff --git a/handlers/bongolive/bongolive.go b/handlers/bongolive/handler.go similarity index 100% rename from handlers/bongolive/bongolive.go rename to handlers/bongolive/handler.go diff --git a/handlers/bongolive/bongolive_test.go b/handlers/bongolive/handler_test.go similarity index 100% rename from handlers/bongolive/bongolive_test.go rename to handlers/bongolive/handler_test.go diff --git a/handlers/burstsms/burstsms.go b/handlers/burstsms/handler.go similarity index 100% rename from handlers/burstsms/burstsms.go rename to handlers/burstsms/handler.go diff --git a/handlers/burstsms/burstsms_test.go b/handlers/burstsms/handler_test.go similarity index 100% rename from handlers/burstsms/burstsms_test.go rename to handlers/burstsms/handler_test.go diff --git a/handlers/clickatell/clickatell.go b/handlers/clickatell/handler.go similarity index 100% rename from handlers/clickatell/clickatell.go rename to handlers/clickatell/handler.go diff --git a/handlers/clickatell/clickatell_test.go b/handlers/clickatell/handler_test.go similarity index 100% rename from handlers/clickatell/clickatell_test.go rename to handlers/clickatell/handler_test.go diff --git a/handlers/clickmobile/clickmobile.go b/handlers/clickmobile/handler.go similarity index 100% rename from handlers/clickmobile/clickmobile.go rename to handlers/clickmobile/handler.go diff --git a/handlers/clickmobile/clickmobile_test.go b/handlers/clickmobile/handler_test.go similarity index 100% rename from handlers/clickmobile/clickmobile_test.go rename to handlers/clickmobile/handler_test.go diff --git a/handlers/clicksend/clicksend.go b/handlers/clicksend/handler.go similarity index 100% rename from handlers/clicksend/clicksend.go rename to handlers/clicksend/handler.go diff --git a/handlers/clicksend/clicksend_test.go b/handlers/clicksend/handler_test.go similarity index 100% rename from handlers/clicksend/clicksend_test.go rename to handlers/clicksend/handler_test.go diff --git a/handlers/dart/dart.go b/handlers/dart/handler.go similarity index 100% rename from handlers/dart/dart.go rename to handlers/dart/handler.go diff --git a/handlers/dart/dart_test.go b/handlers/dart/handler_test.go similarity index 100% rename from handlers/dart/dart_test.go rename to handlers/dart/handler_test.go diff --git a/handlers/dialog360/dialog360.go b/handlers/dialog360/handler.go similarity index 100% rename from handlers/dialog360/dialog360.go rename to handlers/dialog360/handler.go diff --git a/handlers/dialog360/dialog360_test.go b/handlers/dialog360/handler_test.go similarity index 100% rename from handlers/dialog360/dialog360_test.go rename to handlers/dialog360/handler_test.go diff --git a/handlers/discord/discord.go b/handlers/discord/handler.go similarity index 100% rename from handlers/discord/discord.go rename to handlers/discord/handler.go diff --git a/handlers/discord/discord_test.go b/handlers/discord/handler_test.go similarity index 100% rename from handlers/discord/discord_test.go rename to handlers/discord/handler_test.go diff --git a/handlers/dmark/dmark.go b/handlers/dmark/handler.go similarity index 100% rename from handlers/dmark/dmark.go rename to handlers/dmark/handler.go diff --git a/handlers/dmark/dmark_test.go b/handlers/dmark/handler_test.go similarity index 100% rename from handlers/dmark/dmark_test.go rename to handlers/dmark/handler_test.go diff --git a/handlers/external/external.go b/handlers/external/handler.go similarity index 100% rename from handlers/external/external.go rename to handlers/external/handler.go diff --git a/handlers/external/external_test.go b/handlers/external/handler_test.go similarity index 100% rename from handlers/external/external_test.go rename to handlers/external/handler_test.go diff --git a/handlers/facebook_legacy/facebook.go b/handlers/facebook_legacy/handler.go similarity index 100% rename from handlers/facebook_legacy/facebook.go rename to handlers/facebook_legacy/handler.go diff --git a/handlers/facebook_legacy/facebook_test.go b/handlers/facebook_legacy/handler_test.go similarity index 100% rename from handlers/facebook_legacy/facebook_test.go rename to handlers/facebook_legacy/handler_test.go diff --git a/handlers/firebase/firebase.go b/handlers/firebase/handler.go similarity index 100% rename from handlers/firebase/firebase.go rename to handlers/firebase/handler.go diff --git a/handlers/firebase/firebase_test.go b/handlers/firebase/handler_test.go similarity index 100% rename from handlers/firebase/firebase_test.go rename to handlers/firebase/handler_test.go diff --git a/handlers/freshchat/freshchat.go b/handlers/freshchat/handler.go similarity index 100% rename from handlers/freshchat/freshchat.go rename to handlers/freshchat/handler.go diff --git a/handlers/freshchat/freshchat_test.go b/handlers/freshchat/handler_test.go similarity index 100% rename from handlers/freshchat/freshchat_test.go rename to handlers/freshchat/handler_test.go diff --git a/handlers/globe/globe.go b/handlers/globe/handler.go similarity index 100% rename from handlers/globe/globe.go rename to handlers/globe/handler.go diff --git a/handlers/globe/globe_test.go b/handlers/globe/handler_test.go similarity index 100% rename from handlers/globe/globe_test.go rename to handlers/globe/handler_test.go diff --git a/handlers/highconnection/highconnection.go b/handlers/highconnection/handler.go similarity index 100% rename from handlers/highconnection/highconnection.go rename to handlers/highconnection/handler.go diff --git a/handlers/highconnection/highconnection_test.go b/handlers/highconnection/handler_test.go similarity index 100% rename from handlers/highconnection/highconnection_test.go rename to handlers/highconnection/handler_test.go diff --git a/handlers/hormuud/hormuud.go b/handlers/hormuud/handler.go similarity index 100% rename from handlers/hormuud/hormuud.go rename to handlers/hormuud/handler.go diff --git a/handlers/hormuud/hormuud_test.go b/handlers/hormuud/handler_test.go similarity index 100% rename from handlers/hormuud/hormuud_test.go rename to handlers/hormuud/handler_test.go diff --git a/handlers/hub9/hub9.go b/handlers/hub9/handler.go similarity index 100% rename from handlers/hub9/hub9.go rename to handlers/hub9/handler.go diff --git a/handlers/i2sms/i2sms.go b/handlers/i2sms/handler.go similarity index 100% rename from handlers/i2sms/i2sms.go rename to handlers/i2sms/handler.go diff --git a/handlers/i2sms/i2sms_test.go b/handlers/i2sms/handler_test.go similarity index 100% rename from handlers/i2sms/i2sms_test.go rename to handlers/i2sms/handler_test.go diff --git a/handlers/infobip/infobip.go b/handlers/infobip/handler.go similarity index 100% rename from handlers/infobip/infobip.go rename to handlers/infobip/handler.go diff --git a/handlers/infobip/infobip_test.go b/handlers/infobip/handler_test.go similarity index 100% rename from handlers/infobip/infobip_test.go rename to handlers/infobip/handler_test.go diff --git a/handlers/jasmin/jasmin.go b/handlers/jasmin/handler.go similarity index 100% rename from handlers/jasmin/jasmin.go rename to handlers/jasmin/handler.go diff --git a/handlers/jasmin/jasmin_test.go b/handlers/jasmin/handler_test.go similarity index 100% rename from handlers/jasmin/jasmin_test.go rename to handlers/jasmin/handler_test.go diff --git a/handlers/jiochat/jiochat.go b/handlers/jiochat/handler.go similarity index 100% rename from handlers/jiochat/jiochat.go rename to handlers/jiochat/handler.go diff --git a/handlers/jiochat/jiochat_test.go b/handlers/jiochat/handler_test.go similarity index 100% rename from handlers/jiochat/jiochat_test.go rename to handlers/jiochat/handler_test.go diff --git a/handlers/justcall/justcall.go b/handlers/justcall/handler.go similarity index 100% rename from handlers/justcall/justcall.go rename to handlers/justcall/handler.go diff --git a/handlers/justcall/justcall_test.go b/handlers/justcall/handler_test.go similarity index 100% rename from handlers/justcall/justcall_test.go rename to handlers/justcall/handler_test.go diff --git a/handlers/kaleyra/kaleyra.go b/handlers/kaleyra/handler.go similarity index 100% rename from handlers/kaleyra/kaleyra.go rename to handlers/kaleyra/handler.go diff --git a/handlers/kaleyra/kaleyra_test.go b/handlers/kaleyra/handler_test.go similarity index 100% rename from handlers/kaleyra/kaleyra_test.go rename to handlers/kaleyra/handler_test.go diff --git a/handlers/kannel/kannel.go b/handlers/kannel/handler.go similarity index 100% rename from handlers/kannel/kannel.go rename to handlers/kannel/handler.go diff --git a/handlers/kannel/kannel_test.go b/handlers/kannel/handler_test.go similarity index 100% rename from handlers/kannel/kannel_test.go rename to handlers/kannel/handler_test.go diff --git a/handlers/line/line.go b/handlers/line/handler.go similarity index 100% rename from handlers/line/line.go rename to handlers/line/handler.go diff --git a/handlers/line/line_test.go b/handlers/line/handler_test.go similarity index 100% rename from handlers/line/line_test.go rename to handlers/line/handler_test.go diff --git a/handlers/m3tech/m3tech.go b/handlers/m3tech/handler.go similarity index 100% rename from handlers/m3tech/m3tech.go rename to handlers/m3tech/handler.go diff --git a/handlers/m3tech/m3tech_test.go b/handlers/m3tech/handler_test.go similarity index 100% rename from handlers/m3tech/m3tech_test.go rename to handlers/m3tech/handler_test.go diff --git a/handlers/macrokiosk/macrokiosk.go b/handlers/macrokiosk/handler.go similarity index 100% rename from handlers/macrokiosk/macrokiosk.go rename to handlers/macrokiosk/handler.go diff --git a/handlers/macrokiosk/macrokiosk_test.go b/handlers/macrokiosk/handler_test.go similarity index 100% rename from handlers/macrokiosk/macrokiosk_test.go rename to handlers/macrokiosk/handler_test.go diff --git a/handlers/mblox/mblox.go b/handlers/mblox/handler.go similarity index 100% rename from handlers/mblox/mblox.go rename to handlers/mblox/handler.go diff --git a/handlers/mblox/mblox_test.go b/handlers/mblox/handler_test.go similarity index 100% rename from handlers/mblox/mblox_test.go rename to handlers/mblox/handler_test.go diff --git a/handlers/messagebird/messagebird.go b/handlers/messagebird/handler.go similarity index 100% rename from handlers/messagebird/messagebird.go rename to handlers/messagebird/handler.go diff --git a/handlers/messagebird/messagebird_test.go b/handlers/messagebird/handler_test.go similarity index 100% rename from handlers/messagebird/messagebird_test.go rename to handlers/messagebird/handler_test.go diff --git a/handlers/messangi/messangi.go b/handlers/messangi/handler.go similarity index 100% rename from handlers/messangi/messangi.go rename to handlers/messangi/handler.go diff --git a/handlers/messangi/messangi_test.go b/handlers/messangi/handler_test.go similarity index 100% rename from handlers/messangi/messangi_test.go rename to handlers/messangi/handler_test.go diff --git a/handlers/meta/meta.go b/handlers/meta/handlers.go similarity index 100% rename from handlers/meta/meta.go rename to handlers/meta/handlers.go diff --git a/handlers/meta/meta_test.go b/handlers/meta/handlers_test.go similarity index 100% rename from handlers/meta/meta_test.go rename to handlers/meta/handlers_test.go diff --git a/handlers/mtarget/mtarget.go b/handlers/mtarget/handler.go similarity index 100% rename from handlers/mtarget/mtarget.go rename to handlers/mtarget/handler.go diff --git a/handlers/mtarget/mtarget_test.go b/handlers/mtarget/handler_test.go similarity index 100% rename from handlers/mtarget/mtarget_test.go rename to handlers/mtarget/handler_test.go diff --git a/handlers/mtn/mtn.go b/handlers/mtn/handler.go similarity index 100% rename from handlers/mtn/mtn.go rename to handlers/mtn/handler.go diff --git a/handlers/mtn/mtn_test.go b/handlers/mtn/handler_test.go similarity index 100% rename from handlers/mtn/mtn_test.go rename to handlers/mtn/handler_test.go diff --git a/handlers/nexmo/nexmo.go b/handlers/nexmo/handler.go similarity index 100% rename from handlers/nexmo/nexmo.go rename to handlers/nexmo/handler.go diff --git a/handlers/nexmo/nexmo_test.go b/handlers/nexmo/handler_test.go similarity index 100% rename from handlers/nexmo/nexmo_test.go rename to handlers/nexmo/handler_test.go diff --git a/handlers/novo/novo.go b/handlers/novo/handler.go similarity index 100% rename from handlers/novo/novo.go rename to handlers/novo/handler.go diff --git a/handlers/novo/novo_test.go b/handlers/novo/handler_test.go similarity index 100% rename from handlers/novo/novo_test.go rename to handlers/novo/handler_test.go diff --git a/handlers/playmobile/playmobile.go b/handlers/playmobile/handler.go similarity index 100% rename from handlers/playmobile/playmobile.go rename to handlers/playmobile/handler.go diff --git a/handlers/playmobile/playmobile_test.go b/handlers/playmobile/handler_test.go similarity index 100% rename from handlers/playmobile/playmobile_test.go rename to handlers/playmobile/handler_test.go diff --git a/handlers/plivo/plivo.go b/handlers/plivo/handler.go similarity index 100% rename from handlers/plivo/plivo.go rename to handlers/plivo/handler.go diff --git a/handlers/plivo/plivo_test.go b/handlers/plivo/handler_test.go similarity index 100% rename from handlers/plivo/plivo_test.go rename to handlers/plivo/handler_test.go diff --git a/handlers/redrabbit/redrabbit.go b/handlers/redrabbit/handler.go similarity index 100% rename from handlers/redrabbit/redrabbit.go rename to handlers/redrabbit/handler.go diff --git a/handlers/redrabbit/redrabbit_test.go b/handlers/redrabbit/handler_test.go similarity index 100% rename from handlers/redrabbit/redrabbit_test.go rename to handlers/redrabbit/handler_test.go diff --git a/handlers/rocketchat/rocketchat.go b/handlers/rocketchat/handler.go similarity index 100% rename from handlers/rocketchat/rocketchat.go rename to handlers/rocketchat/handler.go diff --git a/handlers/rocketchat/rocketchat_test.go b/handlers/rocketchat/handler_test.go similarity index 100% rename from handlers/rocketchat/rocketchat_test.go rename to handlers/rocketchat/handler_test.go diff --git a/handlers/shaqodoon/shaqodoon.go b/handlers/shaqodoon/handler.go similarity index 100% rename from handlers/shaqodoon/shaqodoon.go rename to handlers/shaqodoon/handler.go diff --git a/handlers/shaqodoon/shaqodoon_test.go b/handlers/shaqodoon/handler_test.go similarity index 100% rename from handlers/shaqodoon/shaqodoon_test.go rename to handlers/shaqodoon/handler_test.go diff --git a/handlers/slack/slack.go b/handlers/slack/handler.go similarity index 100% rename from handlers/slack/slack.go rename to handlers/slack/handler.go diff --git a/handlers/slack/slack_test.go b/handlers/slack/handler_test.go similarity index 100% rename from handlers/slack/slack_test.go rename to handlers/slack/handler_test.go diff --git a/handlers/smscentral/smscentral.go b/handlers/smscentral/handler.go similarity index 100% rename from handlers/smscentral/smscentral.go rename to handlers/smscentral/handler.go diff --git a/handlers/smscentral/smscentral_test.go b/handlers/smscentral/handler_test.go similarity index 100% rename from handlers/smscentral/smscentral_test.go rename to handlers/smscentral/handler_test.go diff --git a/handlers/start/start.go b/handlers/start/handler.go similarity index 100% rename from handlers/start/start.go rename to handlers/start/handler.go diff --git a/handlers/start/start_test.go b/handlers/start/handler_test.go similarity index 100% rename from handlers/start/start_test.go rename to handlers/start/handler_test.go diff --git a/handlers/telegram/telegram.go b/handlers/telegram/handler.go similarity index 100% rename from handlers/telegram/telegram.go rename to handlers/telegram/handler.go diff --git a/handlers/telegram/telegram_test.go b/handlers/telegram/handler_test.go similarity index 100% rename from handlers/telegram/telegram_test.go rename to handlers/telegram/handler_test.go diff --git a/handlers/telesom/telesom.go b/handlers/telesom/handler.go similarity index 100% rename from handlers/telesom/telesom.go rename to handlers/telesom/handler.go diff --git a/handlers/telesom/telesom_test.go b/handlers/telesom/handler_test.go similarity index 100% rename from handlers/telesom/telesom_test.go rename to handlers/telesom/handler_test.go diff --git a/handlers/thinq/thinq.go b/handlers/thinq/handler.go similarity index 100% rename from handlers/thinq/thinq.go rename to handlers/thinq/handler.go diff --git a/handlers/thinq/thinq_test.go b/handlers/thinq/handler_test.go similarity index 100% rename from handlers/thinq/thinq_test.go rename to handlers/thinq/handler_test.go diff --git a/handlers/twiml/twiml.go b/handlers/twiml/handlers.go similarity index 100% rename from handlers/twiml/twiml.go rename to handlers/twiml/handlers.go diff --git a/handlers/twiml/twiml_test.go b/handlers/twiml/handlers_test.go similarity index 100% rename from handlers/twiml/twiml_test.go rename to handlers/twiml/handlers_test.go diff --git a/handlers/twitter/twitter.go b/handlers/twitter/handler.go similarity index 100% rename from handlers/twitter/twitter.go rename to handlers/twitter/handler.go diff --git a/handlers/twitter/twitter_test.go b/handlers/twitter/handler_test.go similarity index 100% rename from handlers/twitter/twitter_test.go rename to handlers/twitter/handler_test.go diff --git a/handlers/viber/viber.go b/handlers/viber/handler.go similarity index 100% rename from handlers/viber/viber.go rename to handlers/viber/handler.go diff --git a/handlers/viber/viber_test.go b/handlers/viber/handler_test.go similarity index 100% rename from handlers/viber/viber_test.go rename to handlers/viber/handler_test.go diff --git a/handlers/vk/vk.go b/handlers/vk/handler.go similarity index 100% rename from handlers/vk/vk.go rename to handlers/vk/handler.go diff --git a/handlers/vk/vk_test.go b/handlers/vk/handler_test.go similarity index 100% rename from handlers/vk/vk_test.go rename to handlers/vk/handler_test.go diff --git a/handlers/wavy/wavy.go b/handlers/wavy/handler.go similarity index 100% rename from handlers/wavy/wavy.go rename to handlers/wavy/handler.go diff --git a/handlers/wavy/wavy_test.go b/handlers/wavy/handler_test.go similarity index 100% rename from handlers/wavy/wavy_test.go rename to handlers/wavy/handler_test.go diff --git a/handlers/wechat/wechat.go b/handlers/wechat/handler.go similarity index 100% rename from handlers/wechat/wechat.go rename to handlers/wechat/handler.go diff --git a/handlers/wechat/wechat_test.go b/handlers/wechat/handler_test.go similarity index 100% rename from handlers/wechat/wechat_test.go rename to handlers/wechat/handler_test.go diff --git a/handlers/whatsapp_legacy/whatsapp.go b/handlers/whatsapp_legacy/handler.go similarity index 100% rename from handlers/whatsapp_legacy/whatsapp.go rename to handlers/whatsapp_legacy/handler.go diff --git a/handlers/whatsapp_legacy/whatsapp_test.go b/handlers/whatsapp_legacy/handler_test.go similarity index 100% rename from handlers/whatsapp_legacy/whatsapp_test.go rename to handlers/whatsapp_legacy/handler_test.go diff --git a/handlers/yo/yo.go b/handlers/yo/handler.go similarity index 100% rename from handlers/yo/yo.go rename to handlers/yo/handler.go diff --git a/handlers/yo/yo_test.go b/handlers/yo/handler_test.go similarity index 100% rename from handlers/yo/yo_test.go rename to handlers/yo/handler_test.go diff --git a/handlers/zenvia/zenvia.go b/handlers/zenvia/handlers.go similarity index 100% rename from handlers/zenvia/zenvia.go rename to handlers/zenvia/handlers.go diff --git a/handlers/zenvia/zenvia_test.go b/handlers/zenvia/handlers_test.go similarity index 100% rename from handlers/zenvia/zenvia_test.go rename to handlers/zenvia/handlers_test.go From 2ea452e838b8e91f2845aca1da77f599588ac0bf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 11:04:05 -0500 Subject: [PATCH 102/170] Split IG/FBA/WAC tests into separate modules --- handlers/meta/facebook_test.go | 619 ++++++++++++ handlers/meta/handlers.go | 10 +- handlers/meta/handlers_test.go | 1620 ------------------------------- handlers/meta/instagram_test.go | 450 +++++++++ handlers/meta/whataspp_test.go | 610 ++++++++++++ 5 files changed, 1684 insertions(+), 1625 deletions(-) create mode 100644 handlers/meta/facebook_test.go delete mode 100644 handlers/meta/handlers_test.go create mode 100644 handlers/meta/instagram_test.go create mode 100644 handlers/meta/whataspp_test.go diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go new file mode 100644 index 000000000..169b8a6c8 --- /dev/null +++ b/handlers/meta/facebook_test.go @@ -0,0 +1,619 @@ +package meta + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/nyaruka/courier" + . "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/urns" + "github.com/stretchr/testify/assert" +) + +var facebookTestChannels = []courier.Channel{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), +} + +var facebookIncomingTests = []IncomingTestCase{ + { + Label: "Receive Message FBA", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "facebook:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid Signature", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid request signature", + PrepRequest: addInvalidSignature, + }, + { + Label: "No Duplicate Receive Message", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/duplicate_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "facebook:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Attachment", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/attachment.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp(""), + ExpectedAttachments: []string{"https://image-url/foo.png"}, + ExpectedURN: "facebook:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Location", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/location_attachment.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp(""), + ExpectedAttachments: []string{"geo:1.200000,-1.300000"}, + ExpectedURN: "facebook:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Thumbs Up", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/thumbs_up.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp("👍"), + ExpectedURN: "facebook:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive OptIn UserRef", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/referral_optin_user_ref.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive OptIn", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/referral_optin.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Notification Messages OptIn", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/notification_messages_optin.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, + }, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:2fad015d-2126-4ac2-a008-b5ac95c3906b": "12345678901234567890"}}, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Notification Messages OptOut", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/notification_messages_optout.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, + }, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Get Started", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/postback_get_started.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Referral Postback", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/postback.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Referral", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/postback_referral.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Referral", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/referral.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"referrer_id":"referral id"`, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Receive DLR", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/dlr.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", + PrepRequest: addValidSignature, + }, + { + Label: "Different Page", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/different_page.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"data":[]`, + PrepRequest: addValidSignature, + }, + { + Label: "Echo", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/echo.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `ignoring echo`, + PrepRequest: addValidSignature, + }, + { + Label: "Not Page", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/not_page.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notpage", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "No Entries", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/no_entries.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "no entries found", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "No Messaging Entries", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/no_messaging_entries.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + PrepRequest: addValidSignature, + }, + { + Label: "Unknown Messaging Entry", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/unknown_messaging_entry.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + PrepRequest: addValidSignature, + }, + { + Label: "Not JSON", + URL: "/c/fba/receive", + Data: "not JSON", + ExpectedRespStatus: 200, + ExpectedBodyContains: "unable to parse request JSON", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "Invalid URN", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/invalid_urn.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid facebook id", + PrepRequest: addValidSignature, + }, +} + +func TestFacebookIncoming(t *testing.T) { + graphURL = createMockGraphAPI().URL + + RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), facebookIncomingTests) +} + +func TestFacebookDescribeURN(t *testing.T) { + fbGraph := buildMockFBGraphFBA(facebookIncomingTests) + defer fbGraph.Close() + + channel := facebookTestChannels[0] + handler := newHandler("FBA", "Facebook") + handler.Initialize(courier.NewServer(courier.NewConfig(), nil)) + clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) + + tcs := []struct { + urn urns.URN + expectedMetadata map[string]string + }{ + {"facebook:1337", map[string]string{"name": "John Doe"}}, + {"facebook:4567", map[string]string{"name": ""}}, + {"facebook:ref:1337", map[string]string{}}, + } + + for _, tc := range tcs { + metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) + assert.Equal(t, metadata, tc.expectedMetadata) + } + + AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) +} + +func TestFacebookVerify(t *testing.T) { + RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), []IncomingTestCase{ + { + Label: "Valid Secret", + URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", + ExpectedRespStatus: 200, + ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + }, + { + Label: "Verify No Mode", + URL: "/c/fba/receive", + ExpectedRespStatus: 200, + ExpectedBodyContains: "unknown request", + NoLogsExpected: true, + }, + { + Label: "Verify No Secret", + URL: "/c/fba/receive?hub.mode=subscribe", + ExpectedRespStatus: 200, + ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, + }, + { + Label: "Invalid Secret", + URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=blah", + ExpectedRespStatus: 200, + ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, + }, + { + Label: "Valid Secret", + URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", + ExpectedRespStatus: 200, + ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, + }, + }) +} + +// 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.Msg) { + 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", + ExpectedExternalID: "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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + Label: "Text only flow response", + MsgText: "Simple Message", + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + Label: "Text only flow response using referal URN", + MsgText: "Simple Message", + 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", + ExpectedExternalID: "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", + ExpectedExternalID: "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", + ExpectedExternalID: "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", + ExpectedExternalID: "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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + 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", + ExpectedExternalID: "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", + ExpectedErrors: []*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", + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, + SendPrep: setSendURL, + }, + { + Label: "Response is invalid JSON", + MsgText: "Error", + MsgURN: "facebook:12345", + MockResponseBody: `bad json`, + MockResponseStatus: 200, + ExpectedErrors: []*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, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, + }, +} + +func TestFacebookOutgoing(t *testing.T) { + // shorter max msg length for testing + maxMsgLength = 100 + + var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) + + checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} + + RunOutgoingTestCases(t, channel, newHandler("FBA", "Facebook"), facebookOutgoingTests, checkRedacted, nil) +} + +func TestSigning(t *testing.T) { + tcs := []struct { + Body string + Signature string + }{ + { + "hello world", + "f39034b29165ec6a5104d9aef27266484ab26c8caa7bca8bcb2dd02e8be61b17", + }, + { + "hello world2", + "60905fdf409d0b4f721e99f6f25b31567a68a6b45e933d814e17a246be4c5a53", + }, + } + + for i, tc := range tcs { + sig, err := fbCalculateSignature("sesame", []byte(tc.Body)) + assert.NoError(t, err) + assert.Equal(t, tc.Signature, sig, "%d: mismatched signature", i) + } +} + +func TestFacebookBuildAttachmentRequest(t *testing.T) { + mb := test.NewMockBackend() + s := courier.NewServer(courier.NewConfig(), mb) + + handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("FBA"), "Facebook", false, nil)} + handler.Initialize(s) + req, _ := handler.BuildAttachmentRequest(context.Background(), mb, facebookTestChannels[0], "https://example.org/v1/media/41", nil) + assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) + assert.Equal(t, http.Header{}, req.Header) +} + +func createMockGraphAPI() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + accessToken := r.Header.Get("Authorization") + defer r.Body.Close() + + // invalid auth token + if accessToken != "Bearer a123" && accessToken != "Bearer wac_admin_system_user_token" { + fmt.Printf("Access token: %s\n", accessToken) + http.Error(w, "invalid auth token", http.StatusForbidden) + return + } + + if strings.HasSuffix(r.URL.Path, "image") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Image"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "audio") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Audio"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "voice") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Voice"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "video") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Video"}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "document") { + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Document"}`)) + return + } + + // valid token + w.Write([]byte(`{"url": "https://foo.bar/attachmentURL"}`)) + })) +} + +func addValidSignature(r *http.Request) { + body, _ := ReadBody(r, maxRequestBodyBytes) + sig, _ := fbCalculateSignature("fb_app_secret", body) + r.Header.Set(signatureHeader, fmt.Sprintf("sha256=%s", string(sig))) +} + +func addInvalidSignature(r *http.Request) { + r.Header.Set(signatureHeader, "invalidsig") +} + +// mocks the call to the Facebook graph API +func buildMockFBGraphFBA(testCases []IncomingTestCase) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + accessToken := r.URL.Query().Get("access_token") + defer r.Body.Close() + + // invalid auth token + if accessToken != "a123" { + http.Error(w, "invalid auth token", http.StatusForbidden) + } + + // user has a name + if strings.HasSuffix(r.URL.Path, "1337") { + w.Write([]byte(`{ "first_name": "John", "last_name": "Doe"}`)) + return + } + // no name + w.Write([]byte(`{ "first_name": "", "last_name": ""}`)) + })) + graphURL = server.URL + + return server +} diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 75ad9280b..c8f65ce62 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -60,14 +60,14 @@ const ( payloadKey = "payload" ) -func newHandler(channelType courier.ChannelType, name string, useUUIDRoutes bool) courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(channelType, name, useUUIDRoutes, []string{courier.ConfigAuthToken})} +func newHandler(channelType courier.ChannelType, name string) courier.ChannelHandler { + return &handler{handlers.NewBaseHandlerWithParams(channelType, name, false, []string{courier.ConfigAuthToken})} } func init() { - courier.RegisterHandler(newHandler("IG", "Instagram", false)) - courier.RegisterHandler(newHandler("FBA", "Facebook", false)) - courier.RegisterHandler(newHandler("WAC", "WhatsApp Cloud", false)) + courier.RegisterHandler(newHandler("IG", "Instagram")) + courier.RegisterHandler(newHandler("FBA", "Facebook")) + courier.RegisterHandler(newHandler("WAC", "WhatsApp Cloud")) } diff --git a/handlers/meta/handlers_test.go b/handlers/meta/handlers_test.go deleted file mode 100644 index cc9ffad00..000000000 --- a/handlers/meta/handlers_test.go +++ /dev/null @@ -1,1620 +0,0 @@ -package meta - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "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" -) - -var testChannelsFBA = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), -} - -var testChannelsIG = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), -} - -var testChannelsWAC = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), -} - -var testCasesFBA = []IncomingTestCase{ - { - Label: "Receive Message FBA", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Signature", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/hello_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - PrepRequest: addInvalidSignature, - }, - { - Label: "No Duplicate Receive Message", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/duplicate_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Attachment", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/attachment.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"https://image-url/foo.png"}, - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Location", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/location_attachment.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"geo:1.200000,-1.300000"}, - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Thumbs Up", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/thumbs_up.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("👍"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive OptIn UserRef", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/referral_optin_user_ref.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive OptIn", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/referral_optin.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Notification Messages OptIn", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notification_messages_optin.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, - }, - ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:2fad015d-2126-4ac2-a008-b5ac95c3906b": "12345678901234567890"}}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Notification Messages OptOut", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notification_messages_optout.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, - }, - ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Get Started", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postback_get_started.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral Postback", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postback.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postback_referral.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/referral.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"referrer_id":"referral id"`, - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Receive DLR", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/dlr.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", - PrepRequest: addValidSignature, - }, - { - Label: "Different Page", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/different_page.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"data":[]`, - PrepRequest: addValidSignature, - }, - { - Label: "Echo", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/echo.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring echo`, - PrepRequest: addValidSignature, - }, - { - Label: "Not Page", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/not_page.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notpage", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "No Entries", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/no_entries.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "no entries found", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "No Messaging Entries", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/no_messaging_entries.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Unknown Messaging Entry", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/unknown_messaging_entry.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Not JSON", - URL: "/c/fba/receive", - Data: "not JSON", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse request JSON", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "Invalid URN", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/invalid_urn.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid facebook id", - PrepRequest: addValidSignature, - }, -} - -var testCasesIG = []IncomingTestCase{ - { - Label: "Receive Message", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Signature", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - PrepRequest: addInvalidSignature, - }, - { - Label: "No Duplicate Receive Message", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/duplicate_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Attachment", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/attachment.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"https://image-url/foo.png"}, - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Like Heart", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/like_heart.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Icebreaker Get Started", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/icebreaker_get_started.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, - }, - PrepRequest: addValidSignature, - }, - { - Label: "Different Page", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/different_page.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"data":[]`, - PrepRequest: addValidSignature, - }, - { - Label: "Echo", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/echo.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring echo`, - PrepRequest: addValidSignature, - }, - { - Label: "No Entries", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/no_entries.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "no entries found", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "Not Instagram", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/not_instagram.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notinstagram", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "No Messaging Entries", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/no_messaging_entries.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Unknown Messaging Entry", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unknown_messaging_entry.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Not JSON", - URL: "/c/ig/receive", - Data: "not JSON", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse request JSON", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "Invalid URN", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/invalid_urn.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid instagram id", - PrepRequest: addValidSignature, - }, - { - Label: "Story Mention", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/story_mention.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring story_mention`, - PrepRequest: addValidSignature, - }, - { - Label: "Message unsent", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unsent_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `msg deleted`, - PrepRequest: addValidSignature, - }, -} - -func addValidSignature(r *http.Request) { - body, _ := ReadBody(r, maxRequestBodyBytes) - sig, _ := fbCalculateSignature("fb_app_secret", body) - r.Header.Set(signatureHeader, fmt.Sprintf("sha256=%s", string(sig))) -} - -func addInvalidSignature(r *http.Request) { - r.Header.Set(signatureHeader, "invalidsig") -} - -// mocks the call to the Facebook graph API -func buildMockFBGraphFBA(testCases []IncomingTestCase) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.URL.Query().Get("access_token") - defer r.Body.Close() - - // invalid auth token - if accessToken != "a123" { - http.Error(w, "invalid auth token", http.StatusForbidden) - } - - // user has a name - if strings.HasSuffix(r.URL.Path, "1337") { - w.Write([]byte(`{ "first_name": "John", "last_name": "Doe"}`)) - return - } - // no name - w.Write([]byte(`{ "first_name": "", "last_name": ""}`)) - })) - graphURL = server.URL - - return server -} - -// mocks the call to the Facebook graph API -func buildMockFBGraphIG(testCases []IncomingTestCase) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.URL.Query().Get("access_token") - defer r.Body.Close() - - // invalid auth token - if accessToken != "a123" { - http.Error(w, "invalid auth token", http.StatusForbidden) - } - - // user has a name - if strings.HasSuffix(r.URL.Path, "1337") { - w.Write([]byte(`{ "name": "John Doe"}`)) - return - } - - // no name - w.Write([]byte(`{ "name": ""}`)) - })) - graphURL = server.URL - - return server -} - -func TestDescribeURNForFBA(t *testing.T) { - fbGraph := buildMockFBGraphFBA(testCasesFBA) - defer fbGraph.Close() - - channel := testChannelsFBA[0] - handler := newHandler("FBA", "Facebook", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"facebook:1337", map[string]string{"name": "John Doe"}}, - {"facebook:4567", map[string]string{"name": ""}}, - {"facebook:ref:1337", map[string]string{}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -func TestDescribeURNForIG(t *testing.T) { - fbGraph := buildMockFBGraphIG(testCasesIG) - defer fbGraph.Close() - - channel := testChannelsIG[0] - handler := newHandler("IG", "Instagram", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"instagram:1337", map[string]string{"name": "John Doe"}}, - {"instagram:4567", map[string]string{"name": ""}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -func TestDescribeURNForWAC(t *testing.T) { - channel := testChannelsWAC[0] - handler := newHandler("WAC", "Cloud API WhatsApp", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"whatsapp:1337", map[string]string{}}, - {"whatsapp:4567", map[string]string{}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), testChannelsWAC[0], tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -var wacReceiveURL = "/c/wac/receive" - -var testCasesWAC = []IncomingTestCase{ - { - Label: "Receive Message WAC", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/hello.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Duplicate Valid Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/duplicate.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Voice Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/voice.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp(""), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Voice"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Button Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/button.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("No"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Document Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/document.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("80skaraokesonglistartist"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Document"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Image Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/image.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Image"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Video Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/video.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Video"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Audio Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/audio.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Audio"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Location Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/location.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"msg"`, - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"geo:0.000000,1.000000"}, - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid JSON", - URL: wacReceiveURL, - Data: "not json", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse", - NoLogsExpected: true, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid From", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_from.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid whatsapp id", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Timestamp", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_timestamp.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid timestamp", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Message WAC invalid signature", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/hello.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - PrepRequest: addInvalidSignature, - }, - { - Label: "Receive Message WAC with error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_msg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, - NoInvalidChannelCheck: true, - PrepRequest: addValidSignature, - }, - { - Label: "Receive error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_errors.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, - NoInvalidChannelCheck: true, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/valid_status.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "external_id", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Status with error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/error_status.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "F", - ExpectedExternalID: "external_id", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)")}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalid_status.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"unknown status: in_orbit"`, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Ignore Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/ignore_status.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"ignoring status: deleted"`, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Interactive Button Reply Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/button_reply.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Yes"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Interactive List Reply Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/list_reply.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Yes"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, -} - -func TestIncoming(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.Header.Get("Authorization") - defer r.Body.Close() - - // invalid auth token - if accessToken != "Bearer a123" && accessToken != "Bearer wac_admin_system_user_token" { - fmt.Printf("Access token: %s\n", accessToken) - http.Error(w, "invalid auth token", http.StatusForbidden) - return - } - - if strings.HasSuffix(r.URL.Path, "image") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Image"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "audio") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Audio"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "voice") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Voice"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "video") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Video"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "document") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Document"}`)) - return - } - - // valid token - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL"}`)) - - })) - graphURL = server.URL - - RunIncomingTestCases(t, testChannelsWAC, newHandler("WAC", "Cloud API WhatsApp", false), testCasesWAC) - RunIncomingTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) - RunIncomingTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) -} - -func BenchmarkHandler(b *testing.B) { - fbService := buildMockFBGraphFBA(testCasesFBA) - - RunChannelBenchmarks(b, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) - fbService.Close() - - fbServiceIG := buildMockFBGraphIG(testCasesIG) - - RunChannelBenchmarks(b, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) - fbServiceIG.Close() -} - -func TestVerify(t *testing.T) { - RunIncomingTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), []IncomingTestCase{ - { - Label: "Valid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoLogsExpected: true, - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - }, - { - Label: "Verify No Mode", - URL: "/c/fba/receive", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unknown request", - NoLogsExpected: true, - }, - { - Label: "Verify No Secret", - URL: "/c/fba/receive?hub.mode=subscribe", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - NoLogsExpected: true, - }, - { - Label: "Invalid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=blah", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - NoLogsExpected: true, - }, - { - Label: "Valid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoLogsExpected: true, - }, - }) - - RunIncomingTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), []IncomingTestCase{ - { - Label: "Valid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoLogsExpected: true, - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - }, - { - Label: "Verify No Mode", - URL: "/c/ig/receive", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unknown request", - NoLogsExpected: true, - }, - { - Label: "Verify No Secret", - URL: "/c/ig/receive?hub.mode=subscribe", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - NoLogsExpected: true, - }, - { - Label: "Invalid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=blah", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - NoLogsExpected: true, - }, - { - Label: "Valid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoLogsExpected: true, - }, - }) -} - -// 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.Msg) { - sendURL = s.URL - graphURL = s.URL -} - -var SendTestCasesFBA = []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", - ExpectedExternalID: "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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response using referal URN", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "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", - ExpectedErrors: []*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", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "facebook:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedErrors: []*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, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -var SendTestCasesIG = []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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "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"}, - 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", - ExpectedExternalID: "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", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "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", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Response doesn't contain message id", - MsgText: "ID Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 200, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response status code is non-200", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, - { - Label: "Response is channel specific error", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "error": {"message": "The image size is too large.","code": 36000 }}`, - MockResponseStatus: 400, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -var SendTestCasesWAC = []OutgoingTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Simple Message","preview_url":false}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Unicode Send", - MsgText: "☺", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"☺","preview_url":false}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Audio Send", - MsgText: "audio caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"audio caption","preview_url":false}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Document Send", - MsgText: "document caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Image Send", - MsgText: "image caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Video Send", - MsgText: "video caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "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" }, "variables": ["Chef", "tomorrow"]}}`), - ExpectedMsgStatus: "W", - ExpectedExternalID: "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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - SendPrep: setSendURL, - }, - { - Label: "Template Country Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), - 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"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Template Invalid Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "bnt", - MsgMetadata: json.RawMessage(`{"templating": { "template": { "name": "revive_issue", "uuid": "8ca114b4-bee2-4d3b-aaf1-9aa6b48d41e8" }, "variables": ["Chef", "tomorrow"]}}`), - 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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - 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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - 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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - 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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Interactive Button Message Send with audio attachment", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"}, - MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - 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"}}]}}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Interactive List Message Send with attachment", - MsgText: "Interactive List Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - 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"}]}]}}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Link Sending", - MsgText: "Link Sending https://link.com", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Link Sending https://link.com","preview_url":true}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Error Bad JSON", - MsgText: "Error", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `bad json`, - MockResponseStatus: 403, - ExpectedErrors: []*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, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("130429", "(#130429) Rate limit hit")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -func TestOutgoing(t *testing.T) { - // shorter max msg length for testing - maxMsgLength = 100 - - var ChannelFBA = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FBA", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) - var ChannelIG = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) - var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WAC", "12345_ID", "", map[string]any{courier.ConfigAuthToken: "a123"}) - - checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} - - RunOutgoingTestCases(t, ChannelFBA, newHandler("FBA", "Facebook", false), SendTestCasesFBA, checkRedacted, nil) - RunOutgoingTestCases(t, ChannelIG, newHandler("IG", "Instagram", false), SendTestCasesIG, checkRedacted, nil) - RunOutgoingTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, nil) -} - -func TestSigning(t *testing.T) { - tcs := []struct { - Body string - Signature string - }{ - { - "hello world", - "f39034b29165ec6a5104d9aef27266484ab26c8caa7bca8bcb2dd02e8be61b17", - }, - { - "hello world2", - "60905fdf409d0b4f721e99f6f25b31567a68a6b45e933d814e17a246be4c5a53", - }, - } - - for i, tc := range tcs { - sig, err := fbCalculateSignature("sesame", []byte(tc.Body)) - assert.NoError(t, err) - assert.Equal(t, tc.Signature, sig, "%d: mismatched signature", i) - } -} - -func newServer(backend courier.Backend) courier.Server { - config := courier.NewConfig() - config.WhatsappAdminSystemUserToken = "wac_admin_system_user_token" - return courier.NewServer(config, backend) -} - -func TestBuildAttachmentRequest(t *testing.T) { - mb := test.NewMockBackend() - s := newServer(mb) - wacHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("WAC"), "WhatsApp Cloud", false, nil)} - wacHandler.Initialize(s) - req, _ := wacHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsWAC[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, "Bearer wac_admin_system_user_token", req.Header.Get("Authorization")) - - fbaHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("FBA"), "Facebook", false, nil)} - fbaHandler.Initialize(s) - req, _ = fbaHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsFBA[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, http.Header{}, req.Header) - - igHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("IG"), "Instagram", false, nil)} - igHandler.Initialize(s) - req, _ = igHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsFBA[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, http.Header{}, req.Header) -} diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go new file mode 100644 index 000000000..8e94da732 --- /dev/null +++ b/handlers/meta/instagram_test.go @@ -0,0 +1,450 @@ +package meta + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/nyaruka/courier" + . "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/test" + "github.com/nyaruka/gocommon/urns" + "github.com/stretchr/testify/assert" +) + +var instgramTestChannels = []courier.Channel{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), +} + +var instagramIncomingTests = []IncomingTestCase{ + { + Label: "Receive Message", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "instagram:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid Signature", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/hello_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid request signature", + PrepRequest: addInvalidSignature, + }, + { + Label: "No Duplicate Receive Message", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/duplicate_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "instagram:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Attachment", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/attachment.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp(""), + ExpectedAttachments: []string{"https://image-url/foo.png"}, + ExpectedURN: "instagram:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Like Heart", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/like_heart.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedMsgText: Sp(""), + ExpectedURN: "instagram:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Icebreaker Get Started", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/icebreaker_get_started.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Different Page", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/different_page.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"data":[]`, + PrepRequest: addValidSignature, + }, + { + Label: "Echo", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/echo.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `ignoring echo`, + PrepRequest: addValidSignature, + }, + { + Label: "No Entries", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/no_entries.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "no entries found", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "Not Instagram", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/not_instagram.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notinstagram", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "No Messaging Entries", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/no_messaging_entries.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + PrepRequest: addValidSignature, + }, + { + Label: "Unknown Messaging Entry", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/unknown_messaging_entry.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + PrepRequest: addValidSignature, + }, + { + Label: "Not JSON", + URL: "/c/ig/receive", + Data: "not JSON", + ExpectedRespStatus: 200, + ExpectedBodyContains: "unable to parse request JSON", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "Invalid URN", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/invalid_urn.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid instagram id", + PrepRequest: addValidSignature, + }, + { + Label: "Story Mention", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/story_mention.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `ignoring story_mention`, + PrepRequest: addValidSignature, + }, + { + Label: "Message unsent", + URL: "/c/ig/receive", + Data: string(test.ReadFile("./testdata/ig/unsent_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `msg deleted`, + PrepRequest: addValidSignature, + }, +} + +func TestInstagramIncoming(t *testing.T) { + graphURL = createMockGraphAPI().URL + + RunIncomingTestCases(t, instgramTestChannels, newHandler("IG", "Instagram"), instagramIncomingTests) +} + +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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + Label: "Text only flow response", + MsgText: "Simple Message", + 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", + ExpectedExternalID: "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"}, + 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", + ExpectedExternalID: "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", + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + 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", + ExpectedExternalID: "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", + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + 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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, + { + Label: "Response doesn't contain message id", + MsgText: "ID Error", + MsgURN: "instagram:12345", + MockResponseBody: `{ "is_error": true }`, + MockResponseStatus: 200, + ExpectedMsgStatus: "E", + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, + SendPrep: setSendURL, + }, + { + Label: "Response status code is non-200", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponseBody: `{ "is_error": true }`, + MockResponseStatus: 403, + ExpectedMsgStatus: "E", + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, + SendPrep: setSendURL, + }, + { + Label: "Response is invalid JSON", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponseBody: `bad json`, + MockResponseStatus: 200, + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, + }, + { + Label: "Response is channel specific error", + MsgText: "Error", + MsgURN: "instagram:12345", + MockResponseBody: `{ "error": {"message": "The image size is too large.","code": 36000 }}`, + MockResponseStatus: 400, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, + }, +} + +func TestInstagramOutgoing(t *testing.T) { + // shorter max msg length for testing + maxMsgLength = 100 + + var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IG", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}) + + checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} + + RunOutgoingTestCases(t, channel, newHandler("IG", "Instagram"), instagramOutgoingTests, checkRedacted, nil) +} + +func TestInstgramVerify(t *testing.T) { + RunIncomingTestCases(t, instgramTestChannels, newHandler("IG", "Instagram"), []IncomingTestCase{ + { + Label: "Valid Secret", + URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", + ExpectedRespStatus: 200, + ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + }, + { + Label: "Verify No Mode", + URL: "/c/ig/receive", + ExpectedRespStatus: 200, + ExpectedBodyContains: "unknown request", + NoLogsExpected: true, + }, + { + Label: "Verify No Secret", + URL: "/c/ig/receive?hub.mode=subscribe", + ExpectedRespStatus: 200, + ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, + }, + { + Label: "Invalid Secret", + URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=blah", + ExpectedRespStatus: 200, + ExpectedBodyContains: "token does not match secret", + NoLogsExpected: true, + }, + { + Label: "Valid Secret", + URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", + ExpectedRespStatus: 200, + ExpectedBodyContains: "yarchallenge", + NoLogsExpected: true, + }, + }) +} + +func TestInstagramDescribeURN(t *testing.T) { + fbGraph := buildMockFBGraphIG(instagramIncomingTests) + defer fbGraph.Close() + + channel := instgramTestChannels[0] + handler := newHandler("IG", "Instagram") + handler.Initialize(courier.NewServer(courier.NewConfig(), nil)) + clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) + + tcs := []struct { + urn urns.URN + expectedMetadata map[string]string + }{ + {"instagram:1337", map[string]string{"name": "John Doe"}}, + {"instagram:4567", map[string]string{"name": ""}}, + } + + for _, tc := range tcs { + metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) + assert.Equal(t, metadata, tc.expectedMetadata) + } + + AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) +} + +func TestInstagramBuildAttachmentRequest(t *testing.T) { + mb := test.NewMockBackend() + s := courier.NewServer(courier.NewConfig(), mb) + + handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("IG"), "Instagram", false, nil)} + handler.Initialize(s) + req, _ := handler.BuildAttachmentRequest(context.Background(), mb, facebookTestChannels[0], "https://example.org/v1/media/41", nil) + assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) + assert.Equal(t, http.Header{}, req.Header) +} + +// mocks the call to the Facebook graph API +func buildMockFBGraphIG(testCases []IncomingTestCase) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + accessToken := r.URL.Query().Get("access_token") + defer r.Body.Close() + + // invalid auth token + if accessToken != "a123" { + http.Error(w, "invalid auth token", http.StatusForbidden) + } + + // user has a name + if strings.HasSuffix(r.URL.Path, "1337") { + w.Write([]byte(`{ "name": "John Doe"}`)) + return + } + + // no name + w.Write([]byte(`{ "name": ""}`)) + })) + graphURL = server.URL + + return server +} diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go new file mode 100644 index 000000000..2170f29b2 --- /dev/null +++ b/handlers/meta/whataspp_test.go @@ -0,0 +1,610 @@ +package meta + +import ( + "context" + "encoding/json" + "testing" + "time" + + "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" +) + +var whatsappTestChannels = []courier.Channel{ + test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]any{courier.ConfigAuthToken: "a123"}), +} + +var whatappReceiveURL = "/c/wac/receive" + +var whatsappIncomingTests = []IncomingTestCase{ + { + Label: "Receive Message WAC", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/hello.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Duplicate Valid Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/duplicate.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Hello World"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Voice Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/voice.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp(""), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Voice"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Button Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/button.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("No"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Document Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/document.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("80skaraokesonglistartist"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Document"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Image Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/image.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Check out my new phone!"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Image"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Video Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/video.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Check out my new phone!"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Video"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Audio Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/audio.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Check out my new phone!"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Audio"}, + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Location Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/location.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"type":"msg"`, + ExpectedMsgText: Sp(""), + ExpectedAttachments: []string{"geo:0.000000,1.000000"}, + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid JSON", + URL: whatappReceiveURL, + Data: "not json", + ExpectedRespStatus: 200, + ExpectedBodyContains: "unable to parse", + NoLogsExpected: true, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid From", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/invalid_from.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid whatsapp id", + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid Timestamp", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/invalid_timestamp.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid timestamp", + PrepRequest: addValidSignature, + }, + { + Label: "Receive Message WAC invalid signature", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/hello.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "invalid request signature", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + PrepRequest: addInvalidSignature, + }, + { + Label: "Receive Message WAC with error message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/error_msg.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, + NoInvalidChannelCheck: true, + PrepRequest: addValidSignature, + }, + { + Label: "Receive error message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/error_errors.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, + NoInvalidChannelCheck: true, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Status", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/valid_status.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"type":"status"`, + ExpectedMsgStatus: "S", + ExpectedExternalID: "external_id", + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Status with error message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/error_status.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"type":"status"`, + ExpectedMsgStatus: "F", + ExpectedExternalID: "external_id", + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)")}, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid Status", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/invalid_status.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"unknown status: in_orbit"`, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Ignore Status", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/ignore_status.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"ignoring status: deleted"`, + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Interactive Button Reply Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/button_reply.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Yes"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, + { + Label: "Receive Valid Interactive List Reply Message", + URL: whatappReceiveURL, + Data: string(test.ReadFile("./testdata/wac/list_reply.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: "Handled", + NoQueueErrorCheck: true, + NoInvalidChannelCheck: true, + ExpectedMsgText: Sp("Yes"), + ExpectedURN: "whatsapp:5678", + ExpectedExternalID: "external_id", + ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), + PrepRequest: addValidSignature, + }, +} + +func TestWhatsAppIncoming(t *testing.T) { + graphURL = createMockGraphAPI().URL + + RunIncomingTestCases(t, whatsappTestChannels, newHandler("WAC", "Cloud API WhatsApp"), whatsappIncomingTests) +} + +var whatsappOutgoingTests = []OutgoingTestCase{ + { + Label: "Plain Send", + MsgText: "Simple Message", + MsgURN: "whatsapp:250788123123", + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Simple Message","preview_url":false}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Unicode Send", + MsgText: "☺", + MsgURN: "whatsapp:250788123123", + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"☺","preview_url":false}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Audio Send", + MsgText: "audio caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, + MockResponses: map[MockedRequest]*httpx.MockResponse{ + { + Method: "POST", + Path: "/12345_ID/messages", + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + { + Method: "POST", + Path: "/12345_ID/messages", + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"audio caption","preview_url":false}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Document Send", + MsgText: "document caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Image Send", + MsgText: "image caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Video Send", + MsgText: "video caption", + MsgURN: "whatsapp:250788123123", + MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "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" }, "variables": ["Chef", "tomorrow"]}}`), + ExpectedMsgStatus: "W", + ExpectedExternalID: "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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, + SendPrep: setSendURL, + }, + { + Label: "Template Country Language", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "eng-US", + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), + 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"}]}]}}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Template Invalid Language", + MsgText: "templated message", + MsgURN: "whatsapp:250788123123", + MsgLocale: "bnt", + MsgMetadata: json.RawMessage(`{"templating": { "template": { "name": "revive_issue", "uuid": "8ca114b4-bee2-4d3b-aaf1-9aa6b48d41e8" }, "variables": ["Chef", "tomorrow"]}}`), + 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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + 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", + ExpectedExternalID: "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", + ExpectedExternalID: "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", + ExpectedExternalID: "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, + ExpectedRequestBody: `{"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"}}]}}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + 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, + ExpectedRequestBody: `{"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"}}]}}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + 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, + ExpectedRequestBody: `{"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"}}]}}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Interactive Button Message Send with audio attachment", + MsgText: "Interactive Button Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"}, + MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, + MockResponses: map[MockedRequest]*httpx.MockResponse{ + { + Method: "POST", + Path: "/12345_ID/messages", + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + { + Method: "POST", + Path: "/12345_ID/messages", + 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"}}]}}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Interactive List Message Send with attachment", + MsgText: "Interactive List Msg", + MsgURN: "whatsapp:250788123123", + MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponses: map[MockedRequest]*httpx.MockResponse{ + { + Method: "POST", + Path: "/12345_ID/messages", + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + { + Method: "POST", + Path: "/12345_ID/messages", + 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"}]}]}}}`, + }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), + }, + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Link Sending", + MsgText: "Link Sending https://link.com", + MsgURN: "whatsapp:250788123123", + MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, + MockResponseStatus: 201, + ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Link Sending https://link.com","preview_url":true}}`, + ExpectedRequestPath: "/12345_ID/messages", + ExpectedMsgStatus: "W", + ExpectedExternalID: "157b5e14568e8", + SendPrep: setSendURL, + }, + { + Label: "Error Bad JSON", + MsgText: "Error", + MsgURN: "whatsapp:250788123123", + MockResponseBody: `bad json`, + MockResponseStatus: 403, + ExpectedErrors: []*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, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("130429", "(#130429) Rate limit hit")}, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, + }, +} + +func TestWhatsAppOutgoing(t *testing.T) { + // shorter max msg length for testing + maxMsgLength = 100 + + var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WAC", "12345_ID", "", map[string]any{courier.ConfigAuthToken: "a123"}) + + checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} + + RunOutgoingTestCases(t, channel, newHandler("WAC", "Cloud API WhatsApp"), whatsappOutgoingTests, checkRedacted, nil) +} + +func TestWhatsAppDescribeURN(t *testing.T) { + channel := whatsappTestChannels[0] + handler := newHandler("WAC", "Cloud API WhatsApp") + handler.Initialize(newServerWithWAC(nil)) + clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) + + tcs := []struct { + urn urns.URN + expectedMetadata map[string]string + }{ + {"whatsapp:1337", map[string]string{}}, + {"whatsapp:4567", map[string]string{}}, + } + + for _, tc := range tcs { + metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), whatsappTestChannels[0], tc.urn, clog) + assert.Equal(t, metadata, tc.expectedMetadata) + } + + AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) +} + +func TestWhatsAppBuildAttachmentRequest(t *testing.T) { + mb := test.NewMockBackend() + s := newServerWithWAC(mb) + handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("WAC"), "WhatsApp Cloud", false, nil)} + handler.Initialize(s) + req, _ := handler.BuildAttachmentRequest(context.Background(), mb, whatsappTestChannels[0], "https://example.org/v1/media/41", nil) + assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) + assert.Equal(t, "Bearer wac_admin_system_user_token", req.Header.Get("Authorization")) +} + +func newServerWithWAC(backend courier.Backend) courier.Server { + config := courier.NewConfig() + config.WhatsappAdminSystemUserToken = "wac_admin_system_user_token" + return courier.NewServer(config, backend) +} From 3821e931c4b64cb4c9397ed940f8d64ef6656f53 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 12:05:07 -0500 Subject: [PATCH 103/170] Revert recent changes to legacy whatsapp and move whatsapp package into meta package --- handlers/dialog360/handler.go | 81 ++++------ handlers/meta/handlers.go | 149 +++++------------- handlers/meta/messenger/api.go | 54 +++++++ {utils => handlers/meta}/whatsapp/api.go | 57 +++---- .../meta}/whatsapp/languages.go | 0 .../meta}/whatsapp/languages_test.go | 2 +- handlers/meta/whatsapp/templates.go | 41 +++++ handlers/whatsapp_legacy/handler.go | 97 +++++++++++- handlers/whatsapp_legacy/handler_test.go | 13 ++ 9 files changed, 294 insertions(+), 200 deletions(-) create mode 100644 handlers/meta/messenger/api.go rename {utils => handlers/meta}/whatsapp/api.go (80%) rename {utils => handlers/meta}/whatsapp/languages.go (100%) rename {utils => handlers/meta}/whatsapp/languages_test.go (94%) create mode 100644 handlers/meta/whatsapp/templates.go diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index 977887ecd..72285aa95 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -14,8 +14,8 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" - "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -67,7 +67,7 @@ func (h *handler) Initialize(s courier.Server) error { // } // receiveEvent is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.MOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { // is not a 'whatsapp_business_account' object? ignore it if payload.Object != "whatsapp_business_account" { @@ -82,7 +82,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h var events []courier.Event var data []any - events, data, err := h.processCloudWhatsAppPayload(ctx, channel, payload, w, r, clog) + events, data, err := h.processWhatsAppPayload(ctx, channel, payload, w, r, clog) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -312,14 +312,14 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio whatsapp.MTPayload + var payloadAudio whatsapp.SendRequest for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? - templating, err := h.getTemplating(msg) + 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()) } @@ -417,7 +417,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } payload.Type = attType - media := whatsapp.MTMedia{Link: attURL} + media := whatsapp.Media{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -457,49 +457,49 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann attType = "document" } if attType == "image" { - image := whatsapp.MTMedia{ + image := whatsapp.Media{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := whatsapp.MTMedia{ + video := whatsapp.Media{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := whatsapp.MTMedia{ + document := whatsapp.Media{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.MTMedia{Link: attURL}} + payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} status, err := requestD3C(payloadAudio, accessToken, status, sendURL, zeroIndex, clog) if err != nil { return status, nil @@ -581,7 +581,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return status, nil } -func requestD3C(payload whatsapp.MTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestD3C(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -597,7 +597,7 @@ func requestD3C(payload whatsapp.MTPayload, accessToken string, status courier.S req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &whatsapp.MTResponse{} + respPayload := &whatsapp.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -617,26 +617,3 @@ func requestD3C(payload whatsapp.MTPayload, accessToken string, status courier.S status.SetStatus(courier.MsgStatusWired) return status, nil } - -func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.MsgTemplating, error) { - if len(msg.Metadata()) == 0 { - return nil, nil - } - - metadata := &struct { - Templating *whatsapp.MsgTemplating `json:"templating"` - }{} - if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { - return nil, err - } - - if metadata.Templating == nil { - return nil, nil - } - - if err := utils.Validate(metadata.Templating); err != nil { - return nil, errors.Wrapf(err, "invalid templating definition") - } - - return metadata.Templating, nil -} diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index c8f65ce62..636910e37 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -17,8 +17,9 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/handlers/meta/messenger" + "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" - "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -118,7 +119,7 @@ func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Chan return nil, nil } - payload := &whatsapp.MOPayload{} + payload := &whatsapp.Notifications{} err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, err @@ -199,7 +200,7 @@ func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (st } // receiveEvents is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.MOPayload, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -232,7 +233,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -370,7 +371,7 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri return events, data, nil } -func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.MOPayload, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { var err error // the list of events we deal with @@ -620,69 +621,16 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c return events, data, nil } -// { -// "messaging_type": "" -// "recipient": { -// "id":"" -// }, -// "message": { -// "text":"hello, world!" -// "attachment":{ -// "type":"image", -// "payload":{ -// "url":"http://www.messenger-rocks.com/image.jpg", -// "is_reusable":true -// } -// } -// } -// } -type mtPayload struct { - MessagingType string `json:"messaging_type"` - Tag string `json:"tag,omitempty"` - Recipient struct { - UserRef string `json:"user_ref,omitempty"` - ID string `json:"id,omitempty"` - } `json:"recipient"` - Message struct { - Text string `json:"text,omitempty"` - QuickReplies []mtQuickReply `json:"quick_replies,omitempty"` - Attachment *mtAttachment `json:"attachment,omitempty"` - } `json:"message"` -} - -type mtAttachment struct { - Type string `json:"type"` - Payload struct { - URL string `json:"url"` - IsReusable bool `json:"is_reusable"` - } `json:"payload"` -} - -type mtQuickReply struct { - Title string `json:"title"` - Payload string `json:"payload"` - ContentType string `json:"content_type"` -} - func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { if msg.Channel().ChannelType() == "FBA" || msg.Channel().ChannelType() == "IG" { return h.sendFacebookInstagramMsg(ctx, msg, clog) } else if msg.Channel().ChannelType() == "WAC" { - return h.sendCloudAPIWhatsappMsg(ctx, msg, clog) + return h.sendWhatsAppMsg(ctx, msg, clog) } return nil, fmt.Errorf("unssuported channel type") } -type fbaMTResponse struct { - ExternalID string `json:"message_id"` - RecipientID string `json:"recipient_id"` - Error struct { - Message string `json:"message"` - Code int `json:"code"` - } `json:"error"` -} - func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") @@ -691,7 +639,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, } isHuman := msg.Origin() == courier.MsgOriginChat || msg.Origin() == courier.MsgOriginTicket - payload := mtPayload{} + payload := &messenger.SendRequest{} if msg.Topic() != "" || isHuman { payload.MessagingType = "MESSAGE_TAG" @@ -734,7 +682,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { if i < len(msg.Attachments()) { // this is an attachment - payload.Message.Attachment = &mtAttachment{} + payload.Message.Attachment = &messenger.Attachment{} attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) attType = strings.Split(attType, "/")[0] if attType == "application" { @@ -753,7 +701,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, // include any quick replies on the last piece we send if i == (len(msgParts)+len(msg.Attachments()))-1 { for _, qr := range msg.QuickReplies() { - payload.Message.QuickReplies = append(payload.Message.QuickReplies, mtQuickReply{qr, qr, "text"}) + payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{qr, qr, "text"}) } } else { payload.Message.QuickReplies = nil @@ -772,7 +720,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &fbaMTResponse{} + respPayload := &messenger.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -839,7 +787,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, return status, nil } -func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := h.Server().Config().WhatsappAdminSystemUserToken @@ -859,14 +807,14 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, lang := whatsapp.GetSupportedLanguage(msg.Locale()) menuButton := whatsapp.GetMenuButton(lang) - var payloadAudio whatsapp.MTPayload + var payloadAudio whatsapp.SendRequest for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - payload := whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} + payload := whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path()} if len(msg.Attachments()) == 0 { // do we have a template? - templating, err := h.getTemplating(msg) + 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()) } @@ -964,7 +912,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } payload.Type = attType - media := whatsapp.MTMedia{Link: attURL} + media := whatsapp.Media{Link: attURL} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] @@ -1004,49 +952,49 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, attType = "document" } if attType == "image" { - image := whatsapp.MTMedia{ + image := whatsapp.Media{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "image", Image: &image} } else if attType == "video" { - video := whatsapp.MTMedia{ + video := whatsapp.Media{ Link: attURL, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "video", Video: &video} } else if attType == "document" { filename, err := utils.BasePathForURL(attURL) if err != nil { return nil, err } - document := whatsapp.MTMedia{ + document := whatsapp.Media{ Link: attURL, Filename: filename, } interactive.Header = &struct { - Type string "json:\"type\"" - Text string "json:\"text,omitempty\"" - Video *whatsapp.MTMedia "json:\"video,omitempty\"" - Image *whatsapp.MTMedia "json:\"image,omitempty\"" - Document *whatsapp.MTMedia "json:\"document,omitempty\"" + Type string "json:\"type\"" + Text string "json:\"text,omitempty\"" + Video *whatsapp.Media "json:\"video,omitempty\"" + Image *whatsapp.Media "json:\"image,omitempty\"" + Document *whatsapp.Media "json:\"document,omitempty\"" }{Type: "document", Document: &document} } else if attType == "audio" { var zeroIndex bool if i == 0 { zeroIndex = true } - payloadAudio = whatsapp.MTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.MTMedia{Link: attURL}} + payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} status, err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil @@ -1128,7 +1076,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg, return status, nil } -func requestWAC(payload whatsapp.MTPayload, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody, err := json.Marshal(payload) if err != nil { return status, err @@ -1144,7 +1092,7 @@ func requestWAC(payload whatsapp.MTPayload, accessToken string, status courier.S req.Header.Set("Accept", "application/json") _, respBody, _ := handlers.RequestHTTP(req, clog) - respPayload := &whatsapp.MTResponse{} + respPayload := &whatsapp.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) @@ -1256,29 +1204,6 @@ func fbCalculateSignature(appSecret string, body []byte) (string, error) { return hex.EncodeToString(mac.Sum(nil)), nil } -func (h *handler) getTemplating(msg courier.Msg) (*whatsapp.MsgTemplating, error) { - if len(msg.Metadata()) == 0 { - return nil, nil - } - - metadata := &struct { - Templating *whatsapp.MsgTemplating `json:"templating"` - }{} - if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { - return nil, err - } - - if metadata.Templating == nil { - return nil, nil - } - - if err := utils.Validate(metadata.Templating); err != nil { - return nil, errors.Wrapf(err, "invalid templating definition") - } - - return metadata.Templating, nil -} - // BuildAttachmentRequest to download media for message attachment with Bearer token set func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, channel courier.Channel, attachmentURL string, clog *courier.ChannelLog) (*http.Request, error) { token := h.Server().Config().WhatsappAdminSystemUserToken diff --git a/handlers/meta/messenger/api.go b/handlers/meta/messenger/api.go new file mode 100644 index 000000000..fcce73d2d --- /dev/null +++ b/handlers/meta/messenger/api.go @@ -0,0 +1,54 @@ +package messenger + +// { +// "messaging_type": "" +// "recipient": { +// "id":"" +// }, +// "message": { +// "text":"hello, world!" +// "attachment":{ +// "type":"image", +// "payload":{ +// "url":"http://www.messenger-rocks.com/image.jpg", +// "is_reusable":true +// } +// } +// } +// } +type SendRequest struct { + MessagingType string `json:"messaging_type"` + Tag string `json:"tag,omitempty"` + Recipient struct { + UserRef string `json:"user_ref,omitempty"` + ID string `json:"id,omitempty"` + } `json:"recipient"` + Message struct { + Text string `json:"text,omitempty"` + QuickReplies []QuickReply `json:"quick_replies,omitempty"` + Attachment *Attachment `json:"attachment,omitempty"` + } `json:"message"` +} + +type Attachment struct { + Type string `json:"type"` + Payload struct { + URL string `json:"url"` + IsReusable bool `json:"is_reusable"` + } `json:"payload"` +} + +type QuickReply struct { + Title string `json:"title"` + Payload string `json:"payload"` + ContentType string `json:"content_type"` +} + +type SendResponse struct { + ExternalID string `json:"message_id"` + RecipientID string `json:"recipient_id"` + Error struct { + Message string `json:"message"` + Code int `json:"code"` + } `json:"error"` +} diff --git a/utils/whatsapp/api.go b/handlers/meta/whatsapp/api.go similarity index 80% rename from utils/whatsapp/api.go rename to handlers/meta/whatsapp/api.go index fc71ee278..890d401c6 100644 --- a/utils/whatsapp/api.go +++ b/handlers/meta/whatsapp/api.go @@ -2,7 +2,7 @@ package whatsapp import "github.com/nyaruka/courier" -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#message-status-updates +// see https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#message-status-updates var StatusMapping = map[string]courier.MsgStatus{ "sent": courier.MsgStatusSent, "delivered": courier.MsgStatusDelivered, @@ -14,16 +14,7 @@ var IgnoreStatuses = map[string]bool{ "deleted": true, } -type MsgTemplating struct { - Template struct { - Name string `json:"name" validate:"required"` - UUID string `json:"uuid" validate:"required"` - } `json:"template" validate:"required,dive"` - Namespace string `json:"namespace"` - Variables []string `json:"variables"` -} - -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#example-2 +// see https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#example-2 type MOMedia struct { Caption string `json:"caption"` Filename string `json:"filename"` @@ -33,7 +24,7 @@ type MOMedia struct { } // API docs https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#notification-payload-object -type MOPayload struct { +type Notifications struct { Object string `json:"object"` Entry []struct { ID string `json:"id"` @@ -195,8 +186,8 @@ type MOPayload struct { } `json:"entry"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#media-messages -type MTMedia struct { +// see https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#media-messages +type Media struct { ID string `json:"id,omitempty"` Link string `json:"link,omitempty"` Caption string `json:"caption,omitempty"` @@ -244,24 +235,24 @@ type Language struct { Code string `json:"code"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-object -// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-messages +// see https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-object +// e.g. https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-messages type Template struct { Name string `json:"name"` Language *Language `json:"language"` Components []*Component `json:"components"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-object -// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-messages +// see https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-object +// e.g. https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#interactive-messages type Interactive struct { Type string `json:"type"` Header *struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - Video *MTMedia `json:"video,omitempty"` - Image *MTMedia `json:"image,omitempty"` - Document *MTMedia `json:"document,omitempty"` + Type string `json:"type"` + Text string `json:"text,omitempty"` + Video *Media `json:"video,omitempty"` + Image *Media `json:"image,omitempty"` + Document *Media `json:"document,omitempty"` } `json:"header,omitempty"` Body struct { Text string `json:"text"` @@ -276,9 +267,9 @@ type Interactive struct { } `json:"action,omitempty"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#request-syntax -// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#message-object -type MTPayload struct { +// see https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#request-syntax +// e.g. https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#message-object +type SendRequest struct { MessagingProduct string `json:"messaging_product"` RecipientType string `json:"recipient_type"` To string `json:"to"` @@ -286,19 +277,19 @@ type MTPayload struct { Text *Text `json:"text,omitempty"` - Document *MTMedia `json:"document,omitempty"` - Image *MTMedia `json:"image,omitempty"` - Audio *MTMedia `json:"audio,omitempty"` - Video *MTMedia `json:"video,omitempty"` + Document *Media `json:"document,omitempty"` + Image *Media `json:"image,omitempty"` + Audio *Media `json:"audio,omitempty"` + Video *Media `json:"video,omitempty"` Interactive *Interactive `json:"interactive,omitempty"` Template *Template `json:"template,omitempty"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#response-syntax -// Example https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#successful-response -type MTResponse struct { +// see https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#response-syntax +// e.g. https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#successful-response +type SendResponse struct { Messages []*struct { ID string `json:"id"` } `json:"messages"` diff --git a/utils/whatsapp/languages.go b/handlers/meta/whatsapp/languages.go similarity index 100% rename from utils/whatsapp/languages.go rename to handlers/meta/whatsapp/languages.go diff --git a/utils/whatsapp/languages_test.go b/handlers/meta/whatsapp/languages_test.go similarity index 94% rename from utils/whatsapp/languages_test.go rename to handlers/meta/whatsapp/languages_test.go index ee5c9a5fa..981a5ec73 100644 --- a/utils/whatsapp/languages_test.go +++ b/handlers/meta/whatsapp/languages_test.go @@ -3,7 +3,7 @@ package whatsapp_test import ( "testing" - "github.com/nyaruka/courier/utils/whatsapp" + "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) diff --git a/handlers/meta/whatsapp/templates.go b/handlers/meta/whatsapp/templates.go new file mode 100644 index 000000000..25a0e588b --- /dev/null +++ b/handlers/meta/whatsapp/templates.go @@ -0,0 +1,41 @@ +package whatsapp + +import ( + "encoding/json" + + "github.com/nyaruka/courier" + "github.com/nyaruka/courier/utils" + "github.com/pkg/errors" +) + +type MsgTemplating struct { + Template struct { + Name string `json:"name" validate:"required"` + UUID string `json:"uuid" validate:"required"` + } `json:"template" validate:"required,dive"` + Namespace string `json:"namespace"` + Variables []string `json:"variables"` +} + +func GetTemplating(msg courier.Msg) (*MsgTemplating, error) { + if len(msg.Metadata()) == 0 { + return nil, nil + } + + metadata := &struct { + Templating *MsgTemplating `json:"templating"` + }{} + if err := json.Unmarshal(msg.Metadata(), metadata); err != nil { + return nil, err + } + + if metadata.Templating == nil { + return nil, nil + } + + if err := utils.Validate(metadata.Templating); err != nil { + return nil, errors.Wrapf(err, "invalid templating definition") + } + + return metadata.Templating, nil +} diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index c2739edd0..28fc20a8c 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -16,8 +16,8 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" - "github.com/nyaruka/courier/utils/whatsapp" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" @@ -563,7 +563,7 @@ func buildPayloads(msg courier.Msg, h *handler, clog *courier.ChannelLog) ([]any parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) qrs := msg.QuickReplies() - langCode := whatsapp.GetSupportedLanguage(msg.Locale()) + langCode := getSupportedLanguage(msg.Locale()) wppVersion := msg.Channel().ConfigForKey("version", "0").(string) isInteractiveMsgCompatible := semver.Compare(wppVersion, interactiveMsgMinSupVersion) isInteractiveMsg := (isInteractiveMsgCompatible >= 0) && (len(qrs) > 0) @@ -1147,3 +1147,96 @@ type MsgTemplating struct { Namespace string `json:"namespace"` Variables []string `json:"variables"` } + +func getSupportedLanguage(lc i18n.Locale) string { + // look for exact match + if lang := supportedLanguages[lc]; lang != "" { + return lang + } + + // if we have a country, strip that off and look again for a match + l, c := lc.Split() + if c != "" { + if lang := supportedLanguages[i18n.Locale(l)]; lang != "" { + return lang + } + } + return "en" // fallback to English +} + +// Mapping from engine locales to supported languages, see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ +var supportedLanguages = map[i18n.Locale]string{ + "afr": "af", // Afrikaans + "sqi": "sq", // Albanian + "ara": "ar", // Arabic + "aze": "az", // Azerbaijani + "ben": "bn", // Bengali + "bul": "bg", // Bulgarian + "cat": "ca", // Catalan + "zho": "zh_CN", // Chinese + "zho-CN": "zh_CN", // Chinese (CHN) + "zho-HK": "zh_HK", // Chinese (HKG) + "zho-TW": "zh_TW", // Chinese (TAI) + "hrv": "hr", // Croatian + "ces": "cs", // Czech + "dah": "da", // Danish + "nld": "nl", // Dutch + "eng": "en", // English + "eng-GB": "en_GB", // English (UK) + "eng-US": "en_US", // English (US) + "est": "et", // Estonian + "fil": "fil", // Filipino + "fin": "fi", // Finnish + "fra": "fr", // French + "kat": "ka", // Georgian + "deu": "de", // German + "ell": "el", // Greek + "guj": "gu", // Gujarati + "hau": "ha", // Hausa + "enb": "he", // Hebrew + "hin": "hi", // Hindi + "hun": "hu", // Hungarian + "ind": "id", // Indonesian + "gle": "ga", // Irish + "ita": "it", // Italian + "jpn": "ja", // Japanese + "kan": "kn", // Kannada + "kaz": "kk", // Kazakh + "kin": "rw_RW", // Kinyarwanda + "kor": "ko", // Korean + "kir": "ky_KG", // Kyrgyzstan + "lao": "lo", // Lao + "lav": "lv", // Latvian + "lit": "lt", // Lithuanian + "mal": "ml", // Malayalam + "mkd": "mk", // Macedonian + "msa": "ms", // Malay + "mar": "mr", // Marathi + "nob": "nb", // Norwegian + "fas": "fa", // Persian + "pol": "pl", // Polish + "por": "pt_PT", // Portuguese + "por-BR": "pt_BR", // Portuguese (BR) + "por-PT": "pt_PT", // Portuguese (POR) + "pan": "pa", // Punjabi + "ron": "ro", // Romanian + "rus": "ru", // Russian + "srp": "sr", // Serbian + "slk": "sk", // Slovak + "slv": "sl", // Slovenian + "spa": "es", // Spanish + "spa-AR": "es_AR", // Spanish (ARG) + "spa-ES": "es_ES", // Spanish (SPA) + "spa-MX": "es_MX", // Spanish (MEX) + "swa": "sw", // Swahili + "swe": "sv", // Swedish + "tam": "ta", // Tamil + "tel": "te", // Telugu + "tha": "th", // Thai + "tur": "tr", // Turkish + "ukr": "uk", // Ukrainian + "urd": "ur", // Urdu + "uzb": "uz", // Uzbek + "vie": "vi", // Vietnamese + "zul": "zu", // Zulu +} diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index 2bfd1584d..2d39a6fba 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -13,6 +13,7 @@ import ( . "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/stretchr/testify/assert" ) @@ -1146,3 +1147,15 @@ func TestOutgoing(t *testing.T) { RunOutgoingTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), mediaCacheSendTestCases, []string{"token123"}, nil) } + +func TestGetSupportedLanguage(t *testing.T) { + assert.Equal(t, "en", getSupportedLanguage(i18n.NilLocale)) + assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("eng"))) + assert.Equal(t, "en_US", getSupportedLanguage(i18n.Locale("eng-US"))) + assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por"))) + assert.Equal(t, "pt_PT", getSupportedLanguage(i18n.Locale("por-PT"))) + assert.Equal(t, "pt_BR", getSupportedLanguage(i18n.Locale("por-BR"))) + assert.Equal(t, "fil", getSupportedLanguage(i18n.Locale("fil"))) + assert.Equal(t, "fr", getSupportedLanguage(i18n.Locale("fra-CA"))) + assert.Equal(t, "en", getSupportedLanguage(i18n.Locale("run"))) +} From 4c31737d15393404225a4bd884f7f7026b1fa7c1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 12:25:29 -0500 Subject: [PATCH 104/170] Split up notification payload into whatsapp and messenger specific parts --- handlers/dialog360/handler.go | 46 ++++--- handlers/meta/handlers.go | 55 +++++--- handlers/meta/messenger/api.go | 69 ++++++++++ handlers/meta/whatsapp/api.go | 245 ++++++++++++--------------------- 4 files changed, 214 insertions(+), 201 deletions(-) diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index 72285aa95..dad093ab3 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -48,26 +48,34 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -// { -// "object":"page", -// "entry":[{ -// "id":"180005062406476", -// "time":1514924367082, -// "messaging":[{ -// "sender": {"id":"1630934236957797"}, -// "recipient":{"id":"180005062406476"}, -// "timestamp":1514924366807, -// "message":{ -// "mid":"mid.$cAAD5QiNHkz1m6cyj11guxokwkhi2", -// "seq":33116, -// "text":"65863634" -// } -// }] -// }] -// } +// { +// "object":"page", +// "entry":[{ +// "id":"180005062406476", +// "time":1514924367082, +// "messaging":[{ +// "sender": {"id":"1630934236957797"}, +// "recipient":{"id":"180005062406476"}, +// "timestamp":1514924366807, +// "message":{ +// "mid":"mid.$cAAD5QiNHkz1m6cyj11guxokwkhi2", +// "seq":33116, +// "text":"65863634" +// } +// }] +// }] +// } +type Notifications struct { + Object string `json:"object"` + Entry []struct { + ID string `json:"id"` + Time int64 `json:"time"` + Changes []whatsapp.Change `json:"changes"` // used by WhatsApp + } `json:"entry"` +} // receiveEvent is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { // is not a 'whatsapp_business_account' object? ignore it if payload.Object != "whatsapp_business_account" { @@ -90,7 +98,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 636910e37..73236505f 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -84,23 +84,34 @@ func (h *handler) Initialize(s courier.Server) error { return nil } -// { -// "object":"page", -// "entry":[{ -// "id":"180005062406476", -// "time":1514924367082, -// "messaging":[{ -// "sender": {"id":"1630934236957797"}, -// "recipient":{"id":"180005062406476"}, -// "timestamp":1514924366807, -// "message":{ -// "mid":"mid.$cAAD5QiNHkz1m6cyj11guxokwkhi2", -// "seq":33116, -// "text":"65863634" -// } -// }] -// }] -// } +// https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#notification-payload-object +// +// { +// "object":"page", +// "entry":[{ +// "id":"180005062406476", +// "time":1514924367082, +// "messaging":[{ +// "sender": {"id":"1630934236957797"}, +// "recipient":{"id":"180005062406476"}, +// "timestamp":1514924366807, +// "message":{ +// "mid":"mid.$cAAD5QiNHkz1m6cyj11guxokwkhi2", +// "seq":33116, +// "text":"65863634" +// } +// }] +// }] +// } +type Notifications struct { + Object string `json:"object"` + Entry []struct { + ID string `json:"id"` + Time int64 `json:"time"` + Changes []whatsapp.Change `json:"changes"` // used by WhatsApp + Messaging []messenger.Messaging `json:"messaging"` // used by Facebook and Instgram + } `json:"entry"` +} func (h *handler) RedactValues(ch courier.Channel) []string { vals := h.BaseHandler.RedactValues(ch) @@ -119,7 +130,7 @@ func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Chan return nil, nil } - payload := &whatsapp.Notifications{} + payload := &Notifications{} err := handlers.DecodeAndValidateJSON(payload, r) if err != nil { return nil, err @@ -200,7 +211,7 @@ func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (st } // receiveEvents is our HTTP handler function for incoming messages and status updates -func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *whatsapp.Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { +func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *Notifications, clog *courier.ChannelLog) ([]courier.Event, error) { err := h.validateSignature(r) if err != nil { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) @@ -222,7 +233,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w if channel.ChannelType() == "FBA" || channel.ChannelType() == "IG" { events, data, err = h.processFacebookInstagramPayload(ctx, channel, payload, w, r, clog) } else { - events, data, err = h.processCloudWhatsAppPayload(ctx, channel, payload, w, r, clog) + events, data, err = h.processWhatsAppPayload(ctx, channel, payload, w, r, clog) } @@ -233,7 +244,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) } -func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Channel, payload *Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { // the list of events we deal with events := make([]courier.Event, 0, 2) @@ -371,7 +382,7 @@ func (h *handler) processCloudWhatsAppPayload(ctx context.Context, channel couri return events, data, nil } -func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *whatsapp.Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { +func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel courier.Channel, payload *Notifications, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, []any, error) { var err error // the list of events we deal with diff --git a/handlers/meta/messenger/api.go b/handlers/meta/messenger/api.go index fcce73d2d..b6510c643 100644 --- a/handlers/meta/messenger/api.go +++ b/handlers/meta/messenger/api.go @@ -52,3 +52,72 @@ type SendResponse struct { Code int `json:"code"` } `json:"error"` } + +// see https://developers.facebook.com/docs/messenger-platform/webhooks/#event-notifications +type Messaging struct { + Sender *struct { + ID string `json:"id"` + UserRef string `json:"user_ref,omitempty"` + } `json:"sender"` + Recipient *struct { + ID string `json:"id"` + } `json:"recipient"` + Timestamp int64 `json:"timestamp"` + + OptIn *struct { + Type string `json:"type"` + Payload string `json:"payload"` + NotificationMessagesToken string `json:"notification_messages_token"` + NotificationMessagesTimezone string `json:"notification_messages_timezone"` + NotificationMessagesFrequency string `json:"notification_messages_frequency"` + NotificationMessagesStatus string `json:"notification_messages_status"` + TokenExpiryTimestamp int64 `json:"token_expiry_timestamp"` + UserTokenStatus string `json:"user_token_status"` + Title string `json:"title"` + + Ref string `json:"ref"` + UserRef string `json:"user_ref"` + } `json:"optin"` + + Referral *struct { + Ref string `json:"ref"` + Source string `json:"source"` + Type string `json:"type"` + AdID string `json:"ad_id"` + } `json:"referral"` + + Postback *struct { + MID string `json:"mid"` + Title string `json:"title"` + Payload string `json:"payload"` + Referral struct { + Ref string `json:"ref"` + Source string `json:"source"` + Type string `json:"type"` + AdID string `json:"ad_id"` + } `json:"referral"` + } `json:"postback"` + + Message *struct { + IsEcho bool `json:"is_echo"` + MID string `json:"mid"` + Text string `json:"text"` + IsDeleted bool `json:"is_deleted"` + Attachments []struct { + Type string `json:"type"` + Payload *struct { + URL string `json:"url"` + StickerID int64 `json:"sticker_id"` + Coordinates *struct { + Lat float64 `json:"lat"` + Long float64 `json:"long"` + } `json:"coordinates"` + } + } `json:"attachments"` + } `json:"message"` + + Delivery *struct { + MIDs []string `json:"mids"` + Watermark int64 `json:"watermark"` + } `json:"delivery"` +} diff --git a/handlers/meta/whatsapp/api.go b/handlers/meta/whatsapp/api.go index 890d401c6..8937c05d6 100644 --- a/handlers/meta/whatsapp/api.go +++ b/handlers/meta/whatsapp/api.go @@ -23,167 +23,92 @@ type MOMedia struct { SHA256 string `json:"sha256"` } -// API docs https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#notification-payload-object -type Notifications struct { - Object string `json:"object"` - Entry []struct { - ID string `json:"id"` - Time int64 `json:"time"` - Changes []struct { - Field string `json:"field"` - Value struct { - MessagingProduct string `json:"messaging_product"` - Metadata *struct { - DisplayPhoneNumber string `json:"display_phone_number"` - PhoneNumberID string `json:"phone_number_id"` - } `json:"metadata"` - Contacts []struct { - Profile struct { - Name string `json:"name"` - } `json:"profile"` - WaID string `json:"wa_id"` - } `json:"contacts"` - Messages []struct { - ID string `json:"id"` - From string `json:"from"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Context *struct { - Forwarded bool `json:"forwarded"` - FrequentlyForwarded bool `json:"frequently_forwarded"` - From string `json:"from"` - ID string `json:"id"` - } `json:"context"` - Text struct { - Body string `json:"body"` - } `json:"text"` - Image *MOMedia `json:"image"` - Audio *MOMedia `json:"audio"` - Video *MOMedia `json:"video"` - Document *MOMedia `json:"document"` - Voice *MOMedia `json:"voice"` - Location *struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Name string `json:"name"` - Address string `json:"address"` - } `json:"location"` - Button *struct { - Text string `json:"text"` - Payload string `json:"payload"` - } `json:"button"` - Interactive struct { - Type string `json:"type"` - ButtonReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"button_reply,omitempty"` - ListReply struct { - ID string `json:"id"` - Title string `json:"title"` - } `json:"list_reply,omitempty"` - } `json:"interactive,omitempty"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"messages"` - Statuses []struct { - ID string `json:"id"` - RecipientID string `json:"recipient_id"` - Status string `json:"status"` - Timestamp string `json:"timestamp"` - Type string `json:"type"` - Conversation *struct { - ID string `json:"id"` - Origin *struct { - Type string `json:"type"` - } `json:"origin"` - } `json:"conversation"` - Pricing *struct { - PricingModel string `json:"pricing_model"` - Billable bool `json:"billable"` - Category string `json:"category"` - } `json:"pricing"` - Errors []struct { - Code int `json:"code"` - Title string `json:"title"` - } `json:"errors"` - } `json:"statuses"` - Errors []struct { - Code int `json:"code"` +type Change struct { + Field string `json:"field"` + Value struct { + MessagingProduct string `json:"messaging_product"` + Metadata *struct { + DisplayPhoneNumber string `json:"display_phone_number"` + PhoneNumberID string `json:"phone_number_id"` + } `json:"metadata"` + Contacts []struct { + Profile struct { + Name string `json:"name"` + } `json:"profile"` + WaID string `json:"wa_id"` + } `json:"contacts"` + Messages []struct { + ID string `json:"id"` + From string `json:"from"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` + Context *struct { + Forwarded bool `json:"forwarded"` + FrequentlyForwarded bool `json:"frequently_forwarded"` + From string `json:"from"` + ID string `json:"id"` + } `json:"context"` + Text struct { + Body string `json:"body"` + } `json:"text"` + Image *MOMedia `json:"image"` + Audio *MOMedia `json:"audio"` + Video *MOMedia `json:"video"` + Document *MOMedia `json:"document"` + Voice *MOMedia `json:"voice"` + Location *struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Name string `json:"name"` + Address string `json:"address"` + } `json:"location"` + Button *struct { + Text string `json:"text"` + Payload string `json:"payload"` + } `json:"button"` + Interactive struct { + Type string `json:"type"` + ButtonReply struct { + ID string `json:"id"` Title string `json:"title"` - } `json:"errors"` - } `json:"value"` - } `json:"changes"` - Messaging []struct { - Sender *struct { - ID string `json:"id"` - UserRef string `json:"user_ref,omitempty"` - } `json:"sender"` - Recipient *struct { - ID string `json:"id"` - } `json:"recipient"` - Timestamp int64 `json:"timestamp"` - - OptIn *struct { - Type string `json:"type"` - Payload string `json:"payload"` - NotificationMessagesToken string `json:"notification_messages_token"` - NotificationMessagesTimezone string `json:"notification_messages_timezone"` - NotificationMessagesFrequency string `json:"notification_messages_frequency"` - NotificationMessagesStatus string `json:"notification_messages_status"` - TokenExpiryTimestamp int64 `json:"token_expiry_timestamp"` - UserTokenStatus string `json:"user_token_status"` - Title string `json:"title"` - - Ref string `json:"ref"` - UserRef string `json:"user_ref"` - } `json:"optin"` - - Referral *struct { - Ref string `json:"ref"` - Source string `json:"source"` - Type string `json:"type"` - AdID string `json:"ad_id"` - } `json:"referral"` - - Postback *struct { - MID string `json:"mid"` - Title string `json:"title"` - Payload string `json:"payload"` - Referral struct { - Ref string `json:"ref"` - Source string `json:"source"` - Type string `json:"type"` - AdID string `json:"ad_id"` - } `json:"referral"` - } `json:"postback"` - - Message *struct { - IsEcho bool `json:"is_echo"` - MID string `json:"mid"` - Text string `json:"text"` - IsDeleted bool `json:"is_deleted"` - Attachments []struct { - Type string `json:"type"` - Payload *struct { - URL string `json:"url"` - StickerID int64 `json:"sticker_id"` - Coordinates *struct { - Lat float64 `json:"lat"` - Long float64 `json:"long"` - } `json:"coordinates"` - } - } `json:"attachments"` - } `json:"message"` - - Delivery *struct { - MIDs []string `json:"mids"` - Watermark int64 `json:"watermark"` - } `json:"delivery"` - } `json:"messaging"` - } `json:"entry"` + } `json:"button_reply,omitempty"` + ListReply struct { + ID string `json:"id"` + Title string `json:"title"` + } `json:"list_reply,omitempty"` + } `json:"interactive,omitempty"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"messages"` + Statuses []struct { + ID string `json:"id"` + RecipientID string `json:"recipient_id"` + Status string `json:"status"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` + Conversation *struct { + ID string `json:"id"` + Origin *struct { + Type string `json:"type"` + } `json:"origin"` + } `json:"conversation"` + Pricing *struct { + PricingModel string `json:"pricing_model"` + Billable bool `json:"billable"` + Category string `json:"category"` + } `json:"pricing"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"statuses"` + Errors []struct { + Code int `json:"code"` + Title string `json:"title"` + } `json:"errors"` + } `json:"value"` } // see https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#media-messages From e6432ddaa91588cb00938639c4b6b1cac8138a6d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 12:36:11 -0500 Subject: [PATCH 105/170] Coverage --- handlers/meta/whatsapp/templates_test.go | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 handlers/meta/whatsapp/templates_test.go diff --git a/handlers/meta/whatsapp/templates_test.go b/handlers/meta/whatsapp/templates_test.go new file mode 100644 index 000000000..7272341c9 --- /dev/null +++ b/handlers/meta/whatsapp/templates_test.go @@ -0,0 +1,41 @@ +package whatsapp_test + +import ( + "encoding/json" + "testing" + + "github.com/nyaruka/courier/handlers/meta/whatsapp" + "github.com/nyaruka/courier/test" + "github.com/stretchr/testify/assert" +) + +func TestGetTemplating(t *testing.T) { + msg := test.NewMockMsg(1, "87995844-2017-4ba0-bc73-f3da75b32f9b", nil, "tel:+1234567890", "hi") + + // no metadata, no templating + tpl, err := whatsapp.GetTemplating(msg) + assert.NoError(t, err) + assert.Nil(t, tpl) + + msg.WithMetadata(json.RawMessage(`{}`)) + + // no templating in metadata, no templating + tpl, err = whatsapp.GetTemplating(msg) + assert.NoError(t, err) + assert.Nil(t, tpl) + + msg.WithMetadata(json.RawMessage(`{"templating": {"foo": "bar"}}`)) + + // invalid templating in metadata, error + tpl, err = whatsapp.GetTemplating(msg) + assert.Error(t, err, "x") + assert.Nil(t, tpl) + + msg.WithMetadata(json.RawMessage(`{"templating": {"template": {"uuid": "4ed5000f-5c94-4143-9697-b7cbd230a381", "name": "Update"}}}`)) + + // invalid templating in metadata, error + tpl, err = whatsapp.GetTemplating(msg) + assert.NoError(t, err) + assert.Equal(t, "4ed5000f-5c94-4143-9697-b7cbd230a381", tpl.Template.UUID) + assert.Equal(t, "Update", tpl.Template.Name) +} From b5f9a11c80bbdda8b380c5c4acbf3b02ae63e2dc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 15:24:51 -0500 Subject: [PATCH 106/170] Improve testing of status updates from handlers --- handlers/africastalking/handler_test.go | 4 +- handlers/bandwidth/handler_test.go | 8 +- handlers/bongolive/handler_test.go | 2 +- handlers/burstsms/handler_test.go | 3 +- handlers/clickatell/handler_test.go | 4 +- handlers/dart/handler_test.go | 6 +- handlers/dialog360/handler_test.go | 6 +- handlers/discord/handler_test.go | 2 +- handlers/dmark/handler_test.go | 2 +- handlers/external/handler_test.go | 8 +- handlers/facebook_legacy/handler_test.go | 3 +- handlers/highconnection/handler_test.go | 2 +- handlers/infobip/handler_test.go | 10 +- handlers/jasmin/handler_test.go | 6 +- handlers/justcall/handler_test.go | 3 +- handlers/kaleyra/handler_test.go | 3 +- handlers/kannel/handler_test.go | 4 +- handlers/macrokiosk/handler_test.go | 22 +- handlers/mblox/handler_test.go | 9 +- handlers/messagebird/handler_test.go | 6 +- handlers/meta/facebook_test.go | 3 +- handlers/meta/whataspp_test.go | 18 +- handlers/mtarget/handler_test.go | 24 +- handlers/mtn/handler_test.go | 18 +- handlers/nexmo/handler_test.go | 27 +- handlers/plivo/handler_test.go | 22 +- handlers/test.go | 33 +-- handlers/thinq/handler_test.go | 5 +- handlers/twiml/handlers_test.go | 320 +++++++++++++++++++---- handlers/viber/handler_test.go | 10 +- handlers/wavy/handler_test.go | 4 +- handlers/whatsapp_legacy/handler_test.go | 5 +- handlers/zenvia/handlers_test.go | 36 ++- 33 files changed, 476 insertions(+), 162 deletions(-) diff --git a/handlers/africastalking/handler_test.go b/handlers/africastalking/handler_test.go index 623bfe087..cf2e663da 100644 --- a/handlers/africastalking/handler_test.go +++ b/handlers/africastalking/handler_test.go @@ -90,7 +90,7 @@ var incomingTestCases = []IncomingTestCase{ Data: "id=ATXid_dda018a640edfcc5d2ce455de3e4a6e7&status=Success", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "ATXid_dda018a640edfcc5d2ce455de3e4a6e7", Status: courier.MsgStatusDelivered}}, }, { Label: "Status Expired", @@ -98,7 +98,7 @@ var incomingTestCases = []IncomingTestCase{ Data: "id=ATXid_dda018a640edfcc5d2ce455de3e4a6e7&status=Expired", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "ATXid_dda018a640edfcc5d2ce455de3e4a6e7", Status: courier.MsgStatusFailed}}, }, } diff --git a/handlers/bandwidth/handler_test.go b/handlers/bandwidth/handler_test.go index 418c7e34a..26b02a096 100644 --- a/handlers/bandwidth/handler_test.go +++ b/handlers/bandwidth/handler_test.go @@ -217,7 +217,7 @@ var testCases = []IncomingTestCase{ Data: validStatusSent, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusSent}}, }, { Label: "Status delivered", @@ -225,16 +225,16 @@ var testCases = []IncomingTestCase{ Data: validStatusDelivered, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, }, { - Label: "Status delivered", + Label: "Status failed", URL: statusURL, Data: validStatusFailed, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "14762070468292kw2fuqty55yp2b2", Status: courier.MsgStatusFailed}}, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("4432", "forbidden to country")}, - ExpectedMsgStatus: courier.MsgStatusFailed, }, } diff --git a/handlers/bongolive/handler_test.go b/handlers/bongolive/handler_test.go index cfab36f41..132717c56 100644 --- a/handlers/bongolive/handler_test.go +++ b/handlers/bongolive/handler_test.go @@ -63,7 +63,7 @@ var testCases = []IncomingTestCase{ Data: "msgtype=5&dlrid=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: "", - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, }, { Label: "Invalid Msg Type", diff --git a/handlers/burstsms/handler_test.go b/handlers/burstsms/handler_test.go index 0e5565ee8..bdccff161 100644 --- a/handlers/burstsms/handler_test.go +++ b/handlers/burstsms/handler_test.go @@ -39,8 +39,7 @@ var testCases = []IncomingTestCase{ URL: statusURL + "?message_id=12345&status=pending", ExpectedRespStatus: 200, ExpectedBodyContains: "Status Update Accepted", - ExpectedExternalID: "12345", - ExpectedMsgStatus: "S", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusSent}}, }, { Label: "Receive Invalid Status", diff --git a/handlers/clickatell/handler_test.go b/handlers/clickatell/handler_test.go index d848ed9b9..a9d09449b 100644 --- a/handlers/clickatell/handler_test.go +++ b/handlers/clickatell/handler_test.go @@ -191,7 +191,7 @@ var testCases = []IncomingTestCase{ Data: `{"messageId": "msg1", "statusCode": 5}`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "msg1", Status: courier.MsgStatusFailed}}, }, { Label: "Valid Delivered status report", @@ -199,7 +199,7 @@ var testCases = []IncomingTestCase{ Data: `{"messageId": "msg1", "statusCode": 4}`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "msg1", Status: courier.MsgStatusSent}}, }, { Label: "Unexpected status report", diff --git a/handlers/dart/handler_test.go b/handlers/dart/handler_test.go index b0a2a1ed2..d080dae0a 100644 --- a/handlers/dart/handler_test.go +++ b/handlers/dart/handler_test.go @@ -49,21 +49,21 @@ var daTestCases = []IncomingTestCase{ URL: statusURL + "?status=10&messageid=12345", ExpectedRespStatus: 200, ExpectedBodyContains: "000", - ExpectedMsgStatus: "D", + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, { Label: "Valid Status", URL: statusURL + "?status=10&messageid=12345.2", ExpectedRespStatus: 200, ExpectedBodyContains: "000", - ExpectedMsgStatus: "D", + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, { Label: "Failed Status", URL: statusURL + "?status=30&messageid=12345", ExpectedRespStatus: 200, ExpectedBodyContains: "000", - ExpectedMsgStatus: "F", + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusFailed}}, }, { Label: "Missing Status", diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index 3cd7872fc..b71e17167 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -200,8 +200,7 @@ var testCasesD3C = []IncomingTestCase{ Data: string(test.ReadFile("../meta/testdata/wac/valid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "external_id", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "external_id", Status: courier.MsgStatusSent}}, }, { Label: "Receive Valid Status with error message", @@ -209,8 +208,7 @@ var testCasesD3C = []IncomingTestCase{ Data: string(test.ReadFile("../meta/testdata/wac/error_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "F", - ExpectedExternalID: "external_id", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "external_id", Status: courier.MsgStatusFailed}}, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)")}, }, { diff --git a/handlers/discord/handler_test.go b/handlers/discord/handler_test.go index 3a150860d..7fc490cad 100644 --- a/handlers/discord/handler_test.go +++ b/handlers/discord/handler_test.go @@ -67,7 +67,7 @@ var testCases = []IncomingTestCase{ Data: `id=12345`, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusSent}}, }, { Label: "Message Sent Handler Garbage", diff --git a/handlers/dmark/handler_test.go b/handlers/dmark/handler_test.go index 6511f3ef2..2f5d867eb 100644 --- a/handlers/dmark/handler_test.go +++ b/handlers/dmark/handler_test.go @@ -78,7 +78,7 @@ var testCases = []IncomingTestCase{ Data: "id=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, }, } diff --git a/handlers/external/handler_test.go b/handlers/external/handler_test.go index 080295053..75dfe2af6 100644 --- a/handlers/external/handler_test.go +++ b/handlers/external/handler_test.go @@ -128,7 +128,7 @@ var handleTestCases = []IncomingTestCase{ URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/failed/?id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusFailed}}, }, { Label: "Invalid Status", @@ -142,7 +142,7 @@ var handleTestCases = []IncomingTestCase{ URL: "/c/ex/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/sent/?id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusSent}}, }, { Label: "Delivered Valid", @@ -150,7 +150,7 @@ var handleTestCases = []IncomingTestCase{ Data: "nothing", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, { Label: "Delivered Valid Post", @@ -158,7 +158,7 @@ var handleTestCases = []IncomingTestCase{ Data: "id=12345", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, { Label: "Stopped Event", diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go index eefee4c96..8be072f83 100644 --- a/handlers/facebook_legacy/handler_test.go +++ b/handlers/facebook_legacy/handler_test.go @@ -541,8 +541,7 @@ var testCases = []IncomingTestCase{ Data: dlr, ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "mid.1458668856218:ed81099e15d3f4f233", Status: courier.MsgStatusDelivered}}, }, { Label: "Different Page", diff --git a/handlers/highconnection/handler_test.go b/handlers/highconnection/handler_test.go index e8976e657..3e4200227 100644 --- a/handlers/highconnection/handler_test.go +++ b/handlers/highconnection/handler_test.go @@ -74,7 +74,7 @@ var testCases = []IncomingTestCase{ URL: statusURL + "?ret_id=12345&status=6", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, } diff --git a/handlers/infobip/handler_test.go b/handlers/infobip/handler_test.go index 7260cd0b3..a2ddc6c15 100644 --- a/handlers/infobip/handler_test.go +++ b/handlers/infobip/handler_test.go @@ -246,7 +246,7 @@ var testCases = []IncomingTestCase{ Data: validStatusDelivered, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, }, { Label: "Status rejected", @@ -254,7 +254,7 @@ var testCases = []IncomingTestCase{ Data: validStatusRejected, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusFailed}}, }, { Label: "Status undeliverable", @@ -262,7 +262,7 @@ var testCases = []IncomingTestCase{ Data: validStatusUndeliverable, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusFailed}}, }, { Label: "Status pending", @@ -270,7 +270,7 @@ var testCases = []IncomingTestCase{ Data: validStatusPending, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusSent}, {ExternalID: "12347", Status: courier.MsgStatusSent}}, }, { Label: "Status expired", @@ -278,7 +278,7 @@ var testCases = []IncomingTestCase{ Data: validStatusExpired, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusSent}}, }, { Label: "Status group name unexpected", diff --git a/handlers/jasmin/handler_test.go b/handlers/jasmin/handler_test.go index 6c0afe3fd..89b72a88c 100644 --- a/handlers/jasmin/handler_test.go +++ b/handlers/jasmin/handler_test.go @@ -50,8 +50,7 @@ var handleTestCases = []IncomingTestCase{ Data: "id=external1&dlvrd=1", ExpectedRespStatus: 200, ExpectedBodyContains: "ACK/Jasmin", - ExpectedMsgStatus: "D", - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "external1", Status: courier.MsgStatusDelivered}}, }, { Label: "Status Failed", @@ -59,8 +58,7 @@ var handleTestCases = []IncomingTestCase{ Data: "id=external1&err=1", ExpectedRespStatus: 200, ExpectedBodyContains: "ACK/Jasmin", - ExpectedMsgStatus: "F", - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "external1", Status: courier.MsgStatusFailed}}, }, { Label: "Status Missing", diff --git a/handlers/justcall/handler_test.go b/handlers/justcall/handler_test.go index 49b3b0e28..4f6817f2b 100644 --- a/handlers/justcall/handler_test.go +++ b/handlers/justcall/handler_test.go @@ -241,8 +241,7 @@ var testCases = []IncomingTestCase{ Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "26523491", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "26523491", Status: courier.MsgStatusSent}}, }, { Label: "Receive invalid status direction", diff --git a/handlers/kaleyra/handler_test.go b/handlers/kaleyra/handler_test.go index adfc8aedd..18441e4ae 100644 --- a/handlers/kaleyra/handler_test.go +++ b/handlers/kaleyra/handler_test.go @@ -83,10 +83,9 @@ var testCases = []IncomingTestCase{ { Label: "Receive Valid Status", URL: receiveStatusURL + "?id=58f86fab-85c5-4f7c-9b68-9c323248afc4%3A0&status=read", - ExpectedExternalID: "58f86fab-85c5-4f7c-9b68-9c323248afc4:0", - ExpectedMsgStatus: "D", ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "58f86fab-85c5-4f7c-9b68-9c323248afc4:0", Status: courier.MsgStatusDelivered}}, }, { Label: "Receive Invalid Status", diff --git a/handlers/kannel/handler_test.go b/handlers/kannel/handler_test.go index aed0d898f..005fd50a9 100644 --- a/handlers/kannel/handler_test.go +++ b/handlers/kannel/handler_test.go @@ -82,7 +82,7 @@ var handleTestCases = []IncomingTestCase{ URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/?id=12345&status=4", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusSent}}, }, } @@ -103,7 +103,7 @@ var ignoreTestCases = []IncomingTestCase{ URL: "/c/kn/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/?id=12345&status=1", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{MsgID: 12345, Status: courier.MsgStatusDelivered}}, }, { Label: "Ignore Status Wired", diff --git a/handlers/macrokiosk/handler_test.go b/handlers/macrokiosk/handler_test.go index 74c01b62a..c36d8f666 100644 --- a/handlers/macrokiosk/handler_test.go +++ b/handlers/macrokiosk/handler_test.go @@ -45,8 +45,26 @@ var incomingTestCases = []IncomingTestCase{ {Label: "Invalid Params", URL: receiveURL, Data: invalidParamsReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "missing shortcode, longcode, from or msisdn parameters"}, {Label: "Invalid Address Params", URL: receiveURL, Data: invalidAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, - {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent}, - {Label: "Wired Status", URL: statusURL, Data: processingStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"W"`, ExpectedMsgStatus: courier.MsgStatusWired}, + { + Label: "Valid Status", + URL: statusURL, + Data: validStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"S"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12345", Status: courier.MsgStatusSent}, + }, + }, + { + Label: "Wired Status", + URL: statusURL, + Data: processingStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"W"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12345", Status: courier.MsgStatusWired}, + }, + }, {Label: "Unknown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } diff --git a/handlers/mblox/handler_test.go b/handlers/mblox/handler_test.go index 7bccba680..1a7da4cce 100644 --- a/handlers/mblox/handler_test.go +++ b/handlers/mblox/handler_test.go @@ -69,7 +69,14 @@ var testCases = []IncomingTestCase{ {Label: "Receive Missing Params", URL: receiveURL, Data: missingParamsRecieve, ExpectedRespStatus: 400, ExpectedBodyContains: "missing one of 'id', 'from', 'to', 'body' or 'received_at' in request body"}, {Label: "Invalid URN", URL: receiveURL, Data: invalidURN, ExpectedRespStatus: 400, ExpectedBodyContains: "phone number supplied is not a number"}, - {Label: "Status Valid", URL: receiveURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered}, + { + Label: "Status Valid", + URL: receiveURL, + Data: validStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, + }, {Label: "Status Unknown", URL: receiveURL, Data: unknownStatus, ExpectedRespStatus: 400, ExpectedBodyContains: `unknown status 'INVALID'`}, {Label: "Status Missing Batch ID", URL: receiveURL, Data: missingBatchID, ExpectedRespStatus: 400, ExpectedBodyContains: "missing one of 'batch_id' or 'status' in request body"}, } diff --git a/handlers/messagebird/handler_test.go b/handlers/messagebird/handler_test.go index 1d3e6e173..2e75a0c28 100644 --- a/handlers/messagebird/handler_test.go +++ b/handlers/messagebird/handler_test.go @@ -159,17 +159,17 @@ var defaultReceiveTestCases = []IncomingTestCase{ Label: "Status Valid", URL: statusBaseURL + "&status=sent", ExpectedRespStatus: 200, - ExpectedMsgStatus: "S", + ExpectedStatuses: []ExpectedStatus{{MsgID: 26, Status: courier.MsgStatusSent}}, }, { Label: "Status- Stop Received", URL: statusBaseURL + "&status=delivery_failed&statusErrorCode=103", ExpectedRespStatus: 200, - ExpectedMsgStatus: "F", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Contact has sent 'stop'")}, + ExpectedStatuses: []ExpectedStatus{{MsgID: 26, Status: courier.MsgStatusFailed}}, ExpectedEvents: []ExpectedEvent{ {Type: courier.EventTypeStopContact, URN: "tel:188885551515"}, }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("103", "Contact has sent 'stop'")}, }, { Label: "Receive Invalid Status", diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 169b8a6c8..a54ed5df7 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -189,8 +189,7 @@ var facebookIncomingTests = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/fba/dlr.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "mid.1458668856218:ed81099e15d3f4f233", Status: courier.MsgStatusDelivered}}, PrepRequest: addValidSignature, }, { diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index 2170f29b2..8c209c8ce 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -212,9 +212,10 @@ var whatsappIncomingTests = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/wac/valid_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "external_id", - PrepRequest: addValidSignature, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external_id", Status: courier.MsgStatusSent}, + }, + PrepRequest: addValidSignature, }, { Label: "Receive Valid Status with error message", @@ -222,10 +223,13 @@ var whatsappIncomingTests = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/wac/error_status.json")), ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "F", - ExpectedExternalID: "external_id", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)")}, - PrepRequest: addValidSignature, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external_id", Status: courier.MsgStatusFailed}, + }, + ExpectedErrors: []*courier.ChannelError{ + courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)"), + }, + PrepRequest: addValidSignature, }, { Label: "Receive Invalid Status", diff --git a/handlers/mtarget/handler_test.go b/handlers/mtarget/handler_test.go index 5f189d2f2..8808b34fe 100644 --- a/handlers/mtarget/handler_test.go +++ b/handlers/mtarget/handler_test.go @@ -51,10 +51,26 @@ var handleTestCases = []IncomingTestCase{ {Label: "Receive Part 1", URL: receiveURL, Data: receivePart1, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgText: Sp("hello world"), ExpectedURN: "tel:+923161909799"}, - {Label: "Status Delivered", URL: statusURL, Data: statusDelivered, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedExternalID: "12a7ee90-50ce-11e7-80ae-00000a0a643c", ExpectedMsgStatus: "D"}, - {Label: "Status Failed", URL: statusURL, Data: statusFailed, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", - ExpectedExternalID: "12a7ee90-50ce-11e7-80ae-00000a0a643c", ExpectedMsgStatus: "F"}, + { + Label: "Status Delivered", + URL: statusURL, + Data: statusDelivered, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12a7ee90-50ce-11e7-80ae-00000a0a643c", Status: courier.MsgStatusDelivered}, + }, + }, + { + Label: "Status Failed", + URL: statusURL, + Data: statusFailed, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12a7ee90-50ce-11e7-80ae-00000a0a643c", Status: courier.MsgStatusFailed}, + }, + }, {Label: "Status Missing ID", URL: statusURL, Data: statusMissingID, ExpectedRespStatus: 400, ExpectedBodyContains: "missing required field 'MsgId'"}, } diff --git a/handlers/mtn/handler_test.go b/handlers/mtn/handler_test.go index 2c990ac6e..e0c376681 100644 --- a/handlers/mtn/handler_test.go +++ b/handlers/mtn/handler_test.go @@ -103,8 +103,9 @@ var testCases = []IncomingTestCase{ Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "rrt-58503", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "rrt-58503", Status: courier.MsgStatusDelivered}, + }, }, { Label: "Receive Valid delivered Status", @@ -112,8 +113,9 @@ var testCases = []IncomingTestCase{ Data: validDeliveredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "rrt-58503", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "rrt-58503", Status: courier.MsgStatusDelivered}, + }, }, { Label: "Receive ignored Status", @@ -121,8 +123,6 @@ var testCases = []IncomingTestCase{ Data: ignoredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `Ignored`, - ExpectedMsgStatus: "", - ExpectedExternalID: "rrt-58503", }, { Label: "Receive ignored Status, missing transaction ID", @@ -130,7 +130,6 @@ var testCases = []IncomingTestCase{ Data: missingTransactionID, ExpectedRespStatus: 200, ExpectedBodyContains: `Ignored`, - ExpectedMsgStatus: "", }, { Label: "Receive expired Status", @@ -138,8 +137,9 @@ var testCases = []IncomingTestCase{ Data: expiredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedExternalID: "rrt-58503", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "rrt-58503", Status: courier.MsgStatusFailed}, + }, }, { Label: "Receive uknown Status", diff --git a/handlers/nexmo/handler_test.go b/handlers/nexmo/handler_test.go index be0d9f087..51079338c 100644 --- a/handlers/nexmo/handler_test.go +++ b/handlers/nexmo/handler_test.go @@ -61,41 +61,46 @@ var testCases = []IncomingTestCase{ URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=delivered&err-code=0", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external1", Status: courier.MsgStatusDelivered}, + }, }, { Label: "Status expired", URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=expired&err-code=0", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external1", Status: courier.MsgStatusFailed}, + }, }, { Label: "Status failed", URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=failed&err-code=6", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, - ExpectedExternalID: "external1", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("dlr:6", "Anti-Spam Rejection")}, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external1", Status: courier.MsgStatusFailed}, + }, + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("dlr:6", "Anti-Spam Rejection")}, }, { Label: "Status accepted", URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=accepted", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external1", Status: courier.MsgStatusSent}, + }, }, { Label: "Status buffered", URL: "/c/nx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?to=2020&messageId=external1&status=buffered", ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, - ExpectedMsgStatus: courier.MsgStatusSent, - ExpectedExternalID: "external1", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "external1", Status: courier.MsgStatusSent}, + }, }, { Label: "Status unexpected", diff --git a/handlers/plivo/handler_test.go b/handlers/plivo/handler_test.go index 44f7eadd1..15ed7c10f 100644 --- a/handlers/plivo/handler_test.go +++ b/handlers/plivo/handler_test.go @@ -36,8 +36,26 @@ var testCases = []IncomingTestCase{ {Label: "Invalid Address Params", URL: receiveURL, Data: invalidAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, {Label: "Missing Params", URL: receiveURL, Data: missingParams, ExpectedRespStatus: 400, ExpectedBodyContains: "Field validation for 'To' failed"}, - {Label: "Valid Status", URL: statusURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered}, - {Label: "Sent Status", URL: statusURL, Data: validSentStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent}, + { + Label: "Valid Status", + URL: statusURL, + Data: validStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12345", Status: courier.MsgStatusDelivered}, + }, + }, + { + Label: "Sent Status", + URL: statusURL, + Data: validSentStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"S"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "12345", Status: courier.MsgStatusSent}, + }, + }, {Label: "Invalid Status Address", URL: statusURL, Data: invalidStatusAddress, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid to number [1515], expecting [2020]"}, {Label: "Unkown Status", URL: statusURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `ignoring unknown status 'UNKNOWN'`}, } diff --git a/handlers/test.go b/handlers/test.go index ef74e0004..159024660 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -28,6 +28,13 @@ import ( // RequestPrepFunc is our type for a hook for tests to use before a request is fired in a test type RequestPrepFunc func(*http.Request) +// ExpectedStatus is an expected status update +type ExpectedStatus struct { + MsgID courier.MsgID + ExternalID string + Status courier.MsgStatus +} + // ExpectedEvent is an expected channel event type ExpectedEvent struct { Type courier.ChannelEventType @@ -56,9 +63,9 @@ type IncomingTestCase struct { ExpectedURNAuthTokens map[urns.URN]map[string]string ExpectedAttachments []string ExpectedDate time.Time - ExpectedMsgStatus courier.MsgStatus ExpectedExternalID string ExpectedMsgID int64 + ExpectedStatuses []ExpectedStatus ExpectedEvents []ExpectedEvent ExpectedErrors []*courier.ChannelError NoLogsExpected bool @@ -192,21 +199,17 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour assert.Empty(t, mb.WrittenMsgs(), "unexpected msg written") } - if tc.ExpectedMsgStatus != "" { - // TODO find better way to test statuses because some channels (e.g. infobip) can create multiple statuses in one call - require.Greater(len(mb.WrittenMsgStatuses()), 0, "expected a msg status to be written") - status := mb.WrittenMsgStatuses()[len(mb.WrittenMsgStatuses())-1] - - assert.Equal(t, tc.ExpectedMsgStatus, status.Status()) - - if tc.ExpectedExternalID != "" { - assert.Equal(t, tc.ExpectedExternalID, status.ExternalID()) - } - if tc.ExpectedMsgID != 0 { - assert.Equal(t, tc.ExpectedMsgID, int64(status.MsgID())) + actualStatuses := mb.WrittenMsgStatuses() + assert.Len(t, actualStatuses, len(tc.ExpectedStatuses), "unexpected number of status updates written") + for i, expectedStatus := range tc.ExpectedStatuses { + if (len(actualStatuses) - 1) < i { + break } - } else { - assert.Empty(t, mb.WrittenMsgStatuses(), "unexpected msg status written") + actualStatus := actualStatuses[i] + + assert.Equal(t, expectedStatus.MsgID, actualStatus.MsgID(), "msg id mismatch for update %d", i) + assert.Equal(t, expectedStatus.ExternalID, actualStatus.ExternalID(), "external id mismatch for update %d", i) + assert.Equal(t, expectedStatus.Status, actualStatus.Status(), "status value mismatch for update %d", i) } actualEvents := mb.WrittenChannelEvents() diff --git a/handlers/thinq/handler_test.go b/handlers/thinq/handler_test.go index 75e34b9b0..c5e82edd6 100644 --- a/handlers/thinq/handler_test.go +++ b/handlers/thinq/handler_test.go @@ -64,9 +64,10 @@ var testCases = []IncomingTestCase{ URL: statusURL, Data: "guid=1234&status=DELIVRD", ExpectedRespStatus: 200, - ExpectedExternalID: "1234", ExpectedBodyContains: `"status":"D"`, - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "1234", Status: courier.MsgStatusDelivered}, + }, }, { Label: "Status Invalid", diff --git a/handlers/twiml/handlers_test.go b/handlers/twiml/handlers_test.go index 6856520da..f8ab6f1ea 100644 --- a/handlers/twiml/handlers_test.go +++ b/handlers/twiml/handlers_test.go @@ -80,16 +80,44 @@ var ( ) var testCases = []IncomingTestCase{ - {Label: "Receive Valid", URL: receiveURL, Data: receiveValid, ExpectedRespStatus: 200, ExpectedBodyContains: "", - ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Receive Button Ignored", URL: receiveURL, Data: receiveButtonIgnored, ExpectedRespStatus: 200, ExpectedBodyContains: "", - ExpectedMsgText: Sp("Msg"), ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Receive Invalid Signature", URL: receiveURL, Data: receiveValid, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid request signature", - PrepRequest: addInvalidSignature}, - {Label: "Receive Missing Signature", URL: receiveURL, Data: receiveValid, ExpectedRespStatus: 400, ExpectedBodyContains: "missing request signature"}, - {Label: "Receive No Params", URL: receiveURL, Data: " ", ExpectedRespStatus: 400, ExpectedBodyContains: "field 'messagesid' required", + { + Label: "Receive Valid", + URL: receiveURL, + Data: receiveValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: "", + ExpectedMsgText: Sp("Msg"), + ExpectedURN: "tel:+14133881111", + ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + PrepRequest: addValidSignature, + }, + { + Label: "Receive Button Ignored", + URL: receiveURL, + Data: receiveButtonIgnored, + ExpectedRespStatus: 200, + ExpectedBodyContains: "", + ExpectedMsgText: Sp("Msg"), + ExpectedURN: "tel:+14133881111", + ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", + PrepRequest: addValidSignature, + }, + { + Label: "Receive Invalid Signature", + URL: receiveURL, + Data: receiveValid, + ExpectedRespStatus: 400, + ExpectedBodyContains: "invalid request signature", + PrepRequest: addInvalidSignature, + }, + { + Label: "Receive Missing Signature", + URL: receiveURL, + Data: receiveValid, + ExpectedRespStatus: 400, + ExpectedBodyContains: "missing request signature"}, + { + Label: "Receive No Params", URL: receiveURL, Data: " ", ExpectedRespStatus: 400, ExpectedBodyContains: "field 'messagesid' required", PrepRequest: addValidSignature}, {Label: "Receive Media", URL: receiveURL, Data: receiveMedia, ExpectedRespStatus: 200, ExpectedBodyContains: "", ExpectedURN: "tel:+14133881111", ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", ExpectedAttachments: []string{"cat.jpg", "dog.jpg"}, @@ -106,25 +134,75 @@ var testCases = []IncomingTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusFailed}, + }, ExpectedEvents: []ExpectedEvent{ {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, }, - {Label: "Status No Params", URL: statusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring", - PrepRequest: addValidSignature}, - {Label: "Status Invalid Status", URL: statusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", - PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: statusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Status Read", URL: statusURL, Data: statusRead, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: statusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, - PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: statusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, + { + Label: "Status No Params", + URL: statusURL, + Data: " ", + ExpectedRespStatus: 200, + ExpectedBodyContains: "no msg status, ignoring", + PrepRequest: addValidSignature, + }, + { + Label: "Status Invalid Status", + URL: statusURL, + Data: statusInvalid, + ExpectedRespStatus: 400, + ExpectedBodyContains: "unknown status 'huh'", + PrepRequest: addValidSignature, + }, + { + Label: "Status Valid", + URL: statusURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status Read", + URL: statusURL, + Data: statusRead, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Valid", + URL: statusIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {MsgID: 12345, Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Invalid", + URL: statusInvalidIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, } var tmsTestCases = []IncomingTestCase{ @@ -154,25 +232,75 @@ var tmsTestCases = []IncomingTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusFailed}, + }, ExpectedEvents: []ExpectedEvent{ {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("21610", "Attempt to send to unsubscribed recipient")}, PrepRequest: addValidSignature, }, - {Label: "Status TMS extra", URL: tmsStatusURL, Data: tmsStatusExtra, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"S"`, ExpectedMsgStatus: courier.MsgStatusSent, - ExpectedExternalID: "SM0b6e2697aae04182a9f5b5c7a8994c7f", PrepRequest: addValidSignature}, - {Label: "Status No Params", URL: tmsStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring", - PrepRequest: addValidSignature}, - {Label: "Status Invalid Status", URL: tmsStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", - PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: tmsStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: tmsStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, - PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: tmsStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, + { + Label: "Status TMS extra", + URL: tmsStatusURL, + Data: tmsStatusExtra, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"S"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SM0b6e2697aae04182a9f5b5c7a8994c7f", Status: courier.MsgStatusSent}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status No Params", + URL: tmsStatusURL, + Data: " ", + ExpectedRespStatus: 200, + ExpectedBodyContains: "no msg status, ignoring", + PrepRequest: addValidSignature, + }, + { + Label: "Status Invalid Status", + URL: tmsStatusURL, + Data: statusInvalid, + ExpectedRespStatus: 400, + ExpectedBodyContains: "unknown status 'huh'", + PrepRequest: addValidSignature, + }, + { + Label: "Status Valid", + URL: tmsStatusURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Valid", + URL: tmsStatusIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {MsgID: 12345, Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Invalid", + URL: tmsStatusInvalidIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, } var twTestCases = []IncomingTestCase{ @@ -204,7 +332,9 @@ var twTestCases = []IncomingTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusFailed}, + }, ExpectedEvents: []ExpectedEvent{ {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, @@ -215,12 +345,39 @@ var twTestCases = []IncomingTestCase{ PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: twStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: twStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: twStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, - PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: twStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, + { + Label: "Status Valid", + URL: twStatusURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Valid", + URL: twStatusIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {MsgID: 12345, Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Invalid", + URL: twStatusInvalidIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, } var swTestCases = []IncomingTestCase{ @@ -239,7 +396,9 @@ var swTestCases = []IncomingTestCase{ Data: statusStop, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, - ExpectedMsgStatus: courier.MsgStatusFailed, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusFailed}, + }, ExpectedEvents: []ExpectedEvent{ {Type: courier.EventTypeStopContact, URN: "tel:+12028831111"}, }, @@ -248,9 +407,37 @@ var swTestCases = []IncomingTestCase{ }, {Label: "Status No Params", URL: swStatusURL, Data: " ", ExpectedRespStatus: 200, ExpectedBodyContains: "no msg status, ignoring"}, {Label: "Status Invalid Status", URL: swStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'"}, - {Label: "Status Valid", URL: swStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, - {Label: "Status ID Valid", URL: swStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345}, - {Label: "Status ID Invalid", URL: swStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b"}, + { + Label: "Status Valid", + URL: swStatusURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Valid", + URL: swStatusIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {MsgID: 12345, Status: courier.MsgStatusDelivered}, + }, + }, + { + Label: "Status ID Invalid", + URL: swStatusInvalidIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + }, } var waTestCases = []IncomingTestCase{ @@ -273,12 +460,39 @@ var twaTestCases = []IncomingTestCase{ PrepRequest: addValidSignature}, {Label: "Status Invalid Status", URL: twaStatusURL, Data: statusInvalid, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown status 'huh'", PrepRequest: addValidSignature}, - {Label: "Status Valid", URL: twaStatusURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, - {Label: "Status ID Valid", URL: twaStatusIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedMsgID: 12345, - PrepRequest: addValidSignature}, - {Label: "Status ID Invalid", URL: twaStatusInvalidIDURL, Data: statusValid, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"D"`, ExpectedMsgStatus: courier.MsgStatusDelivered, ExpectedExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", - PrepRequest: addValidSignature}, + { + Label: "Status Valid", + URL: twaStatusURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Valid", + URL: twaStatusIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {MsgID: 12345, Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, + { + Label: "Status ID Invalid", + URL: twaStatusInvalidIDURL, + Data: statusValid, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"D"`, + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "SMe287d7109a5a925f182f0e07fe5b223b", Status: courier.MsgStatusDelivered}, + }, + PrepRequest: addValidSignature, + }, } func addValidSignature(r *http.Request) { diff --git a/handlers/viber/handler_test.go b/handlers/viber/handler_test.go index fd72639eb..6d4fbf6c7 100644 --- a/handlers/viber/handler_test.go +++ b/handlers/viber/handler_test.go @@ -506,7 +506,15 @@ var testCases = []IncomingTestCase{ {Label: "Receive invalid Message Type", URL: receiveURL, Data: receiveInvalidMessageType, ExpectedRespStatus: 400, ExpectedBodyContains: "unknown message type", PrepRequest: addValidSignature}, {Label: "Webhook validation", URL: receiveURL, Data: webhookCheck, ExpectedRespStatus: 200, ExpectedBodyContains: "webhook valid", PrepRequest: addValidSignature}, - {Label: "Failed Status Report", URL: receiveURL, Data: failedStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `"status":"F"`, ExpectedMsgStatus: courier.MsgStatusFailed, PrepRequest: addValidSignature}, + { + Label: "Failed Status Report", + URL: receiveURL, + Data: failedStatusReport, + ExpectedRespStatus: 200, + ExpectedBodyContains: `"status":"F"`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "4912661846655238145", Status: courier.MsgStatusFailed}}, + PrepRequest: addValidSignature, + }, {Label: "Delivered Status Report", URL: receiveURL, Data: deliveredStatusReport, ExpectedRespStatus: 200, ExpectedBodyContains: `Ignored`, PrepRequest: addValidSignature}, { Label: "Subcribe", diff --git a/handlers/wavy/handler_test.go b/handlers/wavy/handler_test.go index 902bf7a53..057051351 100644 --- a/handlers/wavy/handler_test.go +++ b/handlers/wavy/handler_test.go @@ -106,7 +106,7 @@ var testCases = []IncomingTestCase{ Data: validSentStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Status Update Accepted", - ExpectedMsgStatus: courier.MsgStatusSent, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusSent}}, }, { Label: "Unknown Sent Status Valid", @@ -135,7 +135,7 @@ var testCases = []IncomingTestCase{ Data: validDeliveredStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Status Update Accepted", - ExpectedMsgStatus: courier.MsgStatusDelivered, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "12345", Status: courier.MsgStatusDelivered}}, }, { Label: "Unknown Delivered Status Valid", diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index 2d39a6fba..4af9bbe14 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -474,8 +474,9 @@ var waTestCases = []IncomingTestCase{ Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "9712A34B4A8B6AD50F", + ExpectedStatuses: []ExpectedStatus{ + {ExternalID: "9712A34B4A8B6AD50F", Status: courier.MsgStatusSent}, + }, }, { Label: "Receive invalid JSON", diff --git a/handlers/zenvia/handlers_test.go b/handlers/zenvia/handlers_test.go index a60fa0253..ad25d3bea 100644 --- a/handlers/zenvia/handlers_test.go +++ b/handlers/zenvia/handlers_test.go @@ -177,8 +177,22 @@ var testWhatappCases = []IncomingTestCase{ {Label: "Missing field", URL: receiveWhatsappURL, Data: missingFieldsReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "validation for 'ID' failed on the 'required'"}, {Label: "Bad Date", URL: receiveWhatsappURL, Data: invalidDateReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid date format"}, - {Label: "Valid Status", URL: statusWhatsppURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `Accepted`, ExpectedMsgStatus: "S"}, - {Label: "Unkown Status", URL: statusWhatsppURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgStatus: "E"}, + { + Label: "Valid Status", + URL: statusWhatsppURL, + Data: validStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `Accepted`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "hs765939216", Status: courier.MsgStatusSent}}, + }, + { + Label: "Unkown Status", + URL: statusWhatsppURL, + Data: unknownStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "hs765939216", Status: courier.MsgStatusErrored}}, + }, {Label: "Not JSON body", URL: statusWhatsppURL, Data: notJSON, ExpectedRespStatus: 400, ExpectedBodyContains: "unable to parse request JSON"}, {Label: "Wrong JSON schema", URL: statusWhatsppURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, } @@ -198,8 +212,22 @@ var testSMSCases = []IncomingTestCase{ {Label: "Missing field", URL: receiveSMSURL, Data: missingFieldsReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "validation for 'ID' failed on the 'required'"}, {Label: "Bad Date", URL: receiveSMSURL, Data: invalidDateReceive, ExpectedRespStatus: 400, ExpectedBodyContains: "invalid date format"}, - {Label: "Valid Status", URL: statusSMSURL, Data: validStatus, ExpectedRespStatus: 200, ExpectedBodyContains: `Accepted`, ExpectedMsgStatus: "S"}, - {Label: "Unkown Status", URL: statusSMSURL, Data: unknownStatus, ExpectedRespStatus: 200, ExpectedBodyContains: "Accepted", ExpectedMsgStatus: "E"}, + { + Label: "Valid Status", + URL: statusSMSURL, + Data: validStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: `Accepted`, + ExpectedStatuses: []ExpectedStatus{{ExternalID: "hs765939216", Status: courier.MsgStatusSent}}, + }, + { + Label: "Unknown Status", + URL: statusSMSURL, + Data: unknownStatus, + ExpectedRespStatus: 200, + ExpectedBodyContains: "Accepted", + ExpectedStatuses: []ExpectedStatus{{ExternalID: "hs765939216", Status: courier.MsgStatusErrored}}, + }, {Label: "Not JSON body", URL: statusSMSURL, Data: notJSON, ExpectedRespStatus: 400, ExpectedBodyContains: "unable to parse request JSON"}, {Label: "Wrong JSON schema", URL: statusSMSURL, Data: wrongJSONSchema, ExpectedRespStatus: 400, ExpectedBodyContains: "request JSON doesn't match required schema"}, } From bc50d885da3496d682305d66e36f199f51251115 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Sep 2023 15:56:52 -0500 Subject: [PATCH 107/170] Use functional options pattern to create base handlers --- handlers/base.go | 43 +++++++++++++++++++++------------ handlers/firebase/handler.go | 2 +- handlers/globe/handler.go | 2 +- handlers/i2sms/handler.go | 2 +- handlers/meta/facebook_test.go | 2 +- handlers/meta/handlers.go | 2 +- handlers/meta/instagram_test.go | 2 +- handlers/meta/whataspp_test.go | 2 +- handlers/nexmo/handler.go | 2 +- handlers/slack/handler.go | 2 +- 10 files changed, 36 insertions(+), 25 deletions(-) diff --git a/handlers/base.go b/handlers/base.go index f7eb81a4b..998610776 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -12,26 +12,37 @@ var defaultRedactConfigKeys = []string{courier.ConfigAuthToken, courier.ConfigAP // BaseHandler is the base class for most handlers, it just stored the server, name and channel type for the handler type BaseHandler struct { - channelType courier.ChannelType - name string - server courier.Server - backend courier.Backend - useChannelRouteUUID bool - redactConfigKeys []string + channelType courier.ChannelType + name string + server courier.Server + backend courier.Backend + uuidChannelRouting bool + redactConfigKeys []string } // NewBaseHandler returns a newly constructed BaseHandler with the passed in parameters -func NewBaseHandler(channelType courier.ChannelType, name string) BaseHandler { - return NewBaseHandlerWithParams(channelType, name, true, defaultRedactConfigKeys) +func NewBaseHandler(channelType courier.ChannelType, name string, options ...func(*BaseHandler)) BaseHandler { + h := &BaseHandler{ + channelType: channelType, + name: name, + uuidChannelRouting: true, + redactConfigKeys: defaultRedactConfigKeys, + } + for _, o := range options { + o(h) + } + return *h +} + +func DisableUUIDRouting() func(*BaseHandler) { + return func(s *BaseHandler) { + s.uuidChannelRouting = false + } } -// NewBaseHandlerWithParams returns a newly constructed BaseHandler with the passed in parameters -func NewBaseHandlerWithParams(channelType courier.ChannelType, name string, useChannelRouteUUID bool, redactConfigKeys []string) BaseHandler { - return BaseHandler{ - channelType: channelType, - name: name, - useChannelRouteUUID: useChannelRouteUUID, - redactConfigKeys: redactConfigKeys, +func WithRedactConfigKeys(keys ...string) func(*BaseHandler) { + return func(s *BaseHandler) { + s.redactConfigKeys = keys } } @@ -63,7 +74,7 @@ func (h *BaseHandler) ChannelName() string { // UseChannelRouteUUID returns whether the router should use the channel UUID in the URL path func (h *BaseHandler) UseChannelRouteUUID() bool { - return h.useChannelRouteUUID + return h.uuidChannelRouting } func (h *BaseHandler) RedactValues(ch courier.Channel) []string { diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go index 553cb9c0a..8001e39c9 100644 --- a/handlers/firebase/handler.go +++ b/handlers/firebase/handler.go @@ -34,7 +34,7 @@ type handler struct { } func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(courier.ChannelType("FCM"), "Firebase", true, []string{configKey})} + return &handler{handlers.NewBaseHandler(courier.ChannelType("FCM"), "Firebase", handlers.WithRedactConfigKeys(configKey))} } func (h *handler) Initialize(s courier.Server) error { diff --git a/handlers/globe/handler.go b/handlers/globe/handler.go index 74b5d970c..0b3f62315 100644 --- a/handlers/globe/handler.go +++ b/handlers/globe/handler.go @@ -33,7 +33,7 @@ type handler struct { } func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(courier.ChannelType("GL"), "Globe Labs", true, []string{configPassphrase, configAppSecret})} + return &handler{handlers.NewBaseHandler(courier.ChannelType("GL"), "Globe Labs", handlers.WithRedactConfigKeys(configPassphrase, configAppSecret))} } // Initialize is called by the engine once everything is loaded diff --git a/handlers/i2sms/handler.go b/handlers/i2sms/handler.go index ee4d56193..07725734e 100644 --- a/handlers/i2sms/handler.go +++ b/handlers/i2sms/handler.go @@ -32,7 +32,7 @@ type handler struct { } func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(courier.ChannelType("I2"), "I2SMS", true, []string{courier.ConfigPassword, configChannelHash})} + return &handler{handlers.NewBaseHandler(courier.ChannelType("I2"), "I2SMS", handlers.WithRedactConfigKeys(courier.ConfigPassword, configChannelHash))} } // Initialize is called by the engine once everything is loaded diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index a54ed5df7..483d1bef3 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -534,7 +534,7 @@ func TestFacebookBuildAttachmentRequest(t *testing.T) { mb := test.NewMockBackend() s := courier.NewServer(courier.NewConfig(), mb) - handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("FBA"), "Facebook", false, nil)} + handler := &handler{NewBaseHandler(courier.ChannelType("FBA"), "Facebook", DisableUUIDRouting())} handler.Initialize(s) req, _ := handler.BuildAttachmentRequest(context.Background(), mb, facebookTestChannels[0], "https://example.org/v1/media/41", nil) assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 73236505f..5776e94e5 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -62,7 +62,7 @@ const ( ) func newHandler(channelType courier.ChannelType, name string) courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(channelType, name, false, []string{courier.ConfigAuthToken})} + return &handler{handlers.NewBaseHandler(channelType, name, handlers.DisableUUIDRouting(), handlers.WithRedactConfigKeys(courier.ConfigAuthToken))} } func init() { diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 8e94da732..24b829bcd 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -417,7 +417,7 @@ func TestInstagramBuildAttachmentRequest(t *testing.T) { mb := test.NewMockBackend() s := courier.NewServer(courier.NewConfig(), mb) - handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("IG"), "Instagram", false, nil)} + handler := &handler{NewBaseHandler(courier.ChannelType("IG"), "Instagram", DisableUUIDRouting())} handler.Initialize(s) req, _ := handler.BuildAttachmentRequest(context.Background(), mb, facebookTestChannels[0], "https://example.org/v1/media/41", nil) assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index 8c209c8ce..f62585b70 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -600,7 +600,7 @@ func TestWhatsAppDescribeURN(t *testing.T) { func TestWhatsAppBuildAttachmentRequest(t *testing.T) { mb := test.NewMockBackend() s := newServerWithWAC(mb) - handler := &handler{NewBaseHandlerWithParams(courier.ChannelType("WAC"), "WhatsApp Cloud", false, nil)} + handler := &handler{NewBaseHandler(courier.ChannelType("WAC"), "WhatsApp Cloud", DisableUUIDRouting())} handler.Initialize(s) req, _ := handler.BuildAttachmentRequest(context.Background(), mb, whatsappTestChannels[0], "https://example.org/v1/media/41", nil) assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) diff --git a/handlers/nexmo/handler.go b/handlers/nexmo/handler.go index 7b722b9b4..a1d26af06 100644 --- a/handlers/nexmo/handler.go +++ b/handlers/nexmo/handler.go @@ -88,7 +88,7 @@ type handler struct { } func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(courier.ChannelType("NX"), "Nexmo", true, []string{configNexmoAPISecret, configNexmoAppPrivateKey})} + return &handler{handlers.NewBaseHandler(courier.ChannelType("NX"), "Nexmo", handlers.WithRedactConfigKeys(configNexmoAPISecret, configNexmoAppPrivateKey))} } // Initialize is called by the engine once everything is loaded diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index 1ad3af556..fd86738ba 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -41,7 +41,7 @@ type handler struct { } func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandlerWithParams(courier.ChannelType("SL"), "Slack", true, []string{configBotToken, configUserToken, configValidationToken})} + return &handler{handlers.NewBaseHandler(courier.ChannelType("SL"), "Slack", handlers.WithRedactConfigKeys(configBotToken, configUserToken, configValidationToken))} } func (h *handler) Initialize(s courier.Server) error { From dde622c937ae1a418c720a931299036f5c442319 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Mon, 18 Sep 2023 13:37:48 +0200 Subject: [PATCH 108/170] Add a separate MsgOut interface type --- backend.go | 2 +- backends/rapidpro/backend.go | 2 +- handler.go | 2 +- handlers/africastalking/handler.go | 2 +- handlers/arabiacell/handler.go | 2 +- handlers/bandwidth/handler.go | 2 +- handlers/bongolive/handler.go | 2 +- handlers/burstsms/handler.go | 2 +- handlers/clickatell/handler.go | 2 +- handlers/clickmobile/handler.go | 2 +- handlers/clicksend/handler.go | 2 +- handlers/dart/handler.go | 2 +- handlers/dialog360/handler.go | 2 +- handlers/discord/handler.go | 2 +- handlers/dmark/handler.go | 2 +- handlers/external/handler.go | 2 +- handlers/facebook_legacy/handler.go | 2 +- handlers/firebase/handler.go | 2 +- handlers/freshchat/handler.go | 2 +- handlers/globe/handler.go | 2 +- handlers/highconnection/handler.go | 2 +- handlers/hormuud/handler.go | 2 +- handlers/i2sms/handler.go | 2 +- handlers/infobip/handler.go | 2 +- handlers/jasmin/handler.go | 2 +- handlers/jiochat/handler.go | 2 +- handlers/justcall/handler.go | 2 +- handlers/kaleyra/handler.go | 2 +- handlers/kannel/handler.go | 2 +- handlers/line/handler.go | 2 +- handlers/m3tech/handler.go | 2 +- handlers/macrokiosk/handler.go | 2 +- handlers/mblox/handler.go | 2 +- handlers/messagebird/handler.go | 2 +- handlers/messangi/handler.go | 2 +- handlers/meta/handlers.go | 6 +++--- handlers/meta/whatsapp/templates.go | 2 +- handlers/mtarget/handler.go | 2 +- handlers/mtn/handler.go | 2 +- handlers/nexmo/handler.go | 2 +- handlers/novo/handler.go | 2 +- handlers/playmobile/handler.go | 2 +- handlers/plivo/handler.go | 2 +- handlers/redrabbit/handler.go | 2 +- handlers/rocketchat/handler.go | 2 +- handlers/shaqodoon/handler.go | 2 +- handlers/slack/handler.go | 2 +- handlers/smscentral/handler.go | 2 +- handlers/start/handler.go | 2 +- handlers/telegram/handler.go | 2 +- handlers/telesom/handler.go | 2 +- handlers/thinq/handler.go | 2 +- handlers/twiml/handlers.go | 2 +- handlers/twitter/handler.go | 2 +- handlers/viber/handler.go | 2 +- handlers/vk/handler.go | 2 +- handlers/wavy/handler.go | 2 +- handlers/wechat/handler.go | 2 +- handlers/whatsapp_legacy/handler.go | 6 +++--- handlers/yo/handler.go | 2 +- handlers/zenvia/handlers.go | 2 +- msg.go | 19 ++++++++++++------- sender.go | 6 +++--- test/backend.go | 8 ++++---- test/handler.go | 2 +- 65 files changed, 85 insertions(+), 80 deletions(-) diff --git a/backend.go b/backend.go index 1a569f0d3..4d42e87eb 100644 --- a/backend.go +++ b/backend.go @@ -67,7 +67,7 @@ type Backend interface { // PopNextOutgoingMsg returns the next message that needs to be sent, callers should call MarkOutgoingMsgComplete with the // returned message when they have dealt with the message (regardless of whether it was sent or not) - PopNextOutgoingMsg(context.Context) (Msg, error) + PopNextOutgoingMsg(context.Context) (MsgOut, error) // WasMsgSent returns whether the backend thinks the passed in message was already sent. This can be used in cases where // a backend wants to implement a failsafe against double sending messages (say if they were double queued) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 56ef8cccc..ff593f456 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -399,7 +399,7 @@ func (b *backend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text str } // PopNextOutgoingMsg pops the next message that needs to be sent -func (b *backend) PopNextOutgoingMsg(ctx context.Context) (courier.Msg, error) { +func (b *backend) PopNextOutgoingMsg(ctx context.Context) (courier.MsgOut, error) { // pop the next message off our queue rc := b.redisPool.Get() defer rc.Close() diff --git a/handler.go b/handler.go index 670ca7073..fa47709e2 100644 --- a/handler.go +++ b/handler.go @@ -27,7 +27,7 @@ type ChannelHandler interface { UseChannelRouteUUID() bool RedactValues(Channel) []string GetChannel(context.Context, *http.Request) (Channel, error) - Send(context.Context, Msg, *ChannelLog) (StatusUpdate, error) + Send(context.Context, MsgOut, *ChannelLog) (StatusUpdate, error) WriteStatusSuccessResponse(context.Context, http.ResponseWriter, []StatusUpdate) error WriteMsgSuccessResponse(context.Context, http.ResponseWriter, []Msg) error diff --git a/handlers/africastalking/handler.go b/handlers/africastalking/handler.go index b73a8e381..f08ed27ca 100644 --- a/handlers/africastalking/handler.go +++ b/handlers/africastalking/handler.go @@ -114,7 +114,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { isSharedStr := msg.Channel().ConfigForKey(configIsShared, false) isShared, _ := isSharedStr.(bool) diff --git a/handlers/arabiacell/handler.go b/handlers/arabiacell/handler.go index 96c052fb8..5b708fde4 100644 --- a/handlers/arabiacell/handler.go +++ b/handlers/arabiacell/handler.go @@ -56,7 +56,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 AC channel") diff --git a/handlers/bandwidth/handler.go b/handlers/bandwidth/handler.go index d84c8d435..0c9a690ff 100644 --- a/handlers/bandwidth/handler.go +++ b/handlers/bandwidth/handler.go @@ -172,7 +172,7 @@ type mtResponse struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 BW channel") diff --git a/handlers/bongolive/handler.go b/handlers/bongolive/handler.go index b769a55b7..3c96cabf6 100644 --- a/handlers/bongolive/handler.go +++ b/handlers/bongolive/handler.go @@ -126,7 +126,7 @@ func writeBongoLiveResponse(w http.ResponseWriter) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 %s channel", msg.Channel().ChannelType()) diff --git a/handlers/burstsms/handler.go b/handlers/burstsms/handler.go index 3f152f4fc..8a4fe17c3 100644 --- a/handlers/burstsms/handler.go +++ b/handlers/burstsms/handler.go @@ -57,7 +57,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 BS channel") diff --git a/handlers/clickatell/handler.go b/handlers/clickatell/handler.go index 4a41290a4..b605febc7 100644 --- a/handlers/clickatell/handler.go +++ b/handlers/clickatell/handler.go @@ -154,7 +154,7 @@ func decodeUTF16BE(b []byte) (string, error) { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(courier.ConfigAPIKey, "") if apiKey == "" { return nil, fmt.Errorf("no api_key set for CT channel") diff --git a/handlers/clickmobile/handler.go b/handlers/clickmobile/handler.go index bf63da600..b91a4f6f6 100644 --- a/handlers/clickmobile/handler.go +++ b/handlers/clickmobile/handler.go @@ -98,7 +98,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 CM channel") diff --git a/handlers/clicksend/handler.go b/handlers/clicksend/handler.go index 77883d17e..a238232e0 100644 --- a/handlers/clicksend/handler.go +++ b/handlers/clicksend/handler.go @@ -61,7 +61,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if username == "" { return nil, fmt.Errorf("Missing 'username' config for CS channel") diff --git a/handlers/dart/handler.go b/handlers/dart/handler.go index 8984166b1..2ba4fcf97 100644 --- a/handlers/dart/handler.go +++ b/handlers/dart/handler.go @@ -146,7 +146,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 %s channel", msg.Channel().ChannelType()) diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index dad093ab3..9cad1494a 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -290,7 +290,7 @@ func resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.Chan } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { conn := h.Backend().RedisPool().Get() defer conn.Close() diff --git a/handlers/discord/handler.go b/handlers/discord/handler.go index a750d3331..5ea84f0bd 100644 --- a/handlers/discord/handler.go +++ b/handlers/discord/handler.go @@ -143,7 +143,7 @@ func (h *handler) receiveStatus(ctx context.Context, statusString string, channe } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { sendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, "") if sendURL == "" { return nil, fmt.Errorf("no send url set for DS channel") diff --git a/handlers/dmark/handler.go b/handlers/dmark/handler.go index 53d8d09df..648317d55 100644 --- a/handlers/dmark/handler.go +++ b/handlers/dmark/handler.go @@ -106,7 +106,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // get our authentication token auth := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if auth == "" { diff --git a/handlers/external/handler.go b/handlers/external/handler.go index 820407ff9..179163c7e 100644 --- a/handlers/external/handler.go +++ b/handlers/external/handler.go @@ -271,7 +271,7 @@ func (h *handler) receiveStatus(ctx context.Context, statusString string, channe } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { channel := msg.Channel() sendURL := channel.StringConfigForKey(courier.ConfigSendURL, "") diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index f6867ee33..6ca628e7d 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -471,7 +471,7 @@ type mtQuickReply struct { ContentType string `json:"content_type"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if accessToken == "" { diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go index 8001e39c9..07d9445f3 100644 --- a/handlers/firebase/handler.go +++ b/handlers/firebase/handler.go @@ -140,7 +140,7 @@ type mtNotification struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { title := msg.Channel().StringConfigForKey(configTitle, "") if title == "" { return nil, fmt.Errorf("no FCM_TITLE set for FCM channel") diff --git a/handlers/freshchat/handler.go b/handlers/freshchat/handler.go index 0b65f60e0..bdb6cd0a3 100644 --- a/handlers/freshchat/handler.go +++ b/handlers/freshchat/handler.go @@ -96,7 +96,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { agentID := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") if agentID == "" { diff --git a/handlers/globe/handler.go b/handlers/globe/handler.go index 0b3f62315..6ca0a3dc8 100644 --- a/handlers/globe/handler.go +++ b/handlers/globe/handler.go @@ -120,7 +120,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { appID := msg.Channel().StringConfigForKey(configAppID, "") if appID == "" { return nil, fmt.Errorf("Missing 'app_id' config for GL channel") diff --git a/handlers/highconnection/handler.go b/handlers/highconnection/handler.go index bb6c4ae8d..b0d480756 100644 --- a/handlers/highconnection/handler.go +++ b/handlers/highconnection/handler.go @@ -122,7 +122,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 HX channel") diff --git a/handlers/hormuud/handler.go b/handlers/hormuud/handler.go index 8d283b840..d6b583d7f 100644 --- a/handlers/hormuud/handler.go +++ b/handlers/hormuud/handler.go @@ -76,7 +76,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) token, err := h.FetchToken(ctx, msg.Channel(), msg, clog) diff --git a/handlers/i2sms/handler.go b/handlers/i2sms/handler.go index 07725734e..861619882 100644 --- a/handlers/i2sms/handler.go +++ b/handlers/i2sms/handler.go @@ -84,7 +84,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 I2 channel") diff --git a/handlers/infobip/handler.go b/handlers/infobip/handler.go index 2efd502f9..23cc3f0b5 100644 --- a/handlers/infobip/handler.go +++ b/handlers/infobip/handler.go @@ -159,7 +159,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 IB channel") diff --git a/handlers/jasmin/handler.go b/handlers/jasmin/handler.go index 70f79a6ae..c8f4334eb 100644 --- a/handlers/jasmin/handler.go +++ b/handlers/jasmin/handler.go @@ -119,7 +119,7 @@ func writeJasminACK(w http.ResponseWriter) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 JS channel") diff --git a/handlers/jiochat/handler.go b/handlers/jiochat/handler.go index 1fd878f74..59e827938 100644 --- a/handlers/jiochat/handler.go +++ b/handlers/jiochat/handler.go @@ -163,7 +163,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 diff --git a/handlers/justcall/handler.go b/handlers/justcall/handler.go index 805d6041f..bb4042817 100644 --- a/handlers/justcall/handler.go +++ b/handlers/justcall/handler.go @@ -153,7 +153,7 @@ type mtPayload struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(courier.ConfigAPIKey, "") if apiKey == "" { return nil, fmt.Errorf("no API key set for JCL channel") diff --git a/handlers/kaleyra/handler.go b/handlers/kaleyra/handler.go index fc47b9ba3..23ef50e8a 100644 --- a/handlers/kaleyra/handler.go +++ b/handlers/kaleyra/handler.go @@ -135,7 +135,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accountSID := msg.Channel().StringConfigForKey(configAccountSID, "") apiKey := msg.Channel().StringConfigForKey(configApiKey, "") diff --git a/handlers/kannel/handler.go b/handlers/kannel/handler.go index b209aea18..c9616f215 100644 --- a/handlers/kannel/handler.go +++ b/handlers/kannel/handler.go @@ -118,7 +118,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 KN channel") diff --git a/handlers/line/handler.go b/handlers/line/handler.go index fe73f68c5..0ccd4c623 100644 --- a/handlers/line/handler.go +++ b/handlers/line/handler.go @@ -283,7 +283,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { return nil, fmt.Errorf("no auth token set for LN channel: %s", msg.Channel().UUID()) diff --git a/handlers/m3tech/handler.go b/handlers/m3tech/handler.go index 90bc1a435..f539c8fa3 100644 --- a/handlers/m3tech/handler.go +++ b/handlers/m3tech/handler.go @@ -69,7 +69,7 @@ func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWr } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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") diff --git a/handlers/macrokiosk/handler.go b/handlers/macrokiosk/handler.go index 999a6e7e6..89873cbed 100644 --- a/handlers/macrokiosk/handler.go +++ b/handlers/macrokiosk/handler.go @@ -150,7 +150,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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, "") diff --git a/handlers/mblox/handler.go b/handlers/mblox/handler.go index 20c6330f5..df26bf03d 100644 --- a/handlers/mblox/handler.go +++ b/handlers/mblox/handler.go @@ -113,7 +113,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 == "" { diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 6eb67ac16..1026e5ac7 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -184,7 +184,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if authToken == "" { diff --git a/handlers/messangi/handler.go b/handlers/messangi/handler.go index ff2724c28..9fb69961b 100644 --- a/handlers/messangi/handler.go +++ b/handlers/messangi/handler.go @@ -60,7 +60,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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") diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 5776e94e5..8f9890170 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -632,7 +632,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c return events, data, nil } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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) } else if msg.Channel().ChannelType() == "WAC" { @@ -642,7 +642,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann return nil, fmt.Errorf("unssuported channel type") } -func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") if accessToken == "" { @@ -798,7 +798,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.Msg, return status, nil } -func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // can't do anything without an access token accessToken := h.Server().Config().WhatsappAdminSystemUserToken diff --git a/handlers/meta/whatsapp/templates.go b/handlers/meta/whatsapp/templates.go index 25a0e588b..c4638c779 100644 --- a/handlers/meta/whatsapp/templates.go +++ b/handlers/meta/whatsapp/templates.go @@ -17,7 +17,7 @@ type MsgTemplating struct { Variables []string `json:"variables"` } -func GetTemplating(msg courier.Msg) (*MsgTemplating, error) { +func GetTemplating(msg courier.MsgOut) (*MsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } diff --git a/handlers/mtarget/handler.go b/handlers/mtarget/handler.go index e84e559a7..e907f5ba6 100644 --- a/handlers/mtarget/handler.go +++ b/handlers/mtarget/handler.go @@ -148,7 +148,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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") diff --git a/handlers/mtn/handler.go b/handlers/mtn/handler.go index 32ab90ce0..c5e7b72bb 100644 --- a/handlers/mtn/handler.go +++ b/handlers/mtn/handler.go @@ -121,7 +121,7 @@ type mtPayload struct { } // Send implements courier.ChannelHandler -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 diff --git a/handlers/nexmo/handler.go b/handlers/nexmo/handler.go index a1d26af06..c9176544c 100644 --- a/handlers/nexmo/handler.go +++ b/handlers/nexmo/handler.go @@ -170,7 +170,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { nexmoAPIKey := msg.Channel().StringConfigForKey(configNexmoAPIKey, "") if nexmoAPIKey == "" { return nil, fmt.Errorf("no nexmo API key set for NX channel") diff --git a/handlers/novo/handler.go b/handlers/novo/handler.go index a9b65118a..f5ac00d30 100644 --- a/handlers/novo/handler.go +++ b/handlers/novo/handler.go @@ -78,7 +78,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { merchantID := msg.Channel().StringConfigForKey(configMerchantId, "") if merchantID == "" { return nil, fmt.Errorf("no merchant_id set for NV channel") diff --git a/handlers/playmobile/handler.go b/handlers/playmobile/handler.go index 4b47bde29..38bce4a2c 100644 --- a/handlers/playmobile/handler.go +++ b/handlers/playmobile/handler.go @@ -146,7 +146,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { username := msg.Channel().StringConfigForKey(configUsername, "") if username == "" { return nil, fmt.Errorf("no username set for PM channel") diff --git a/handlers/plivo/handler.go b/handlers/plivo/handler.go index d62a64e91..5f1384dc3 100644 --- a/handlers/plivo/handler.go +++ b/handlers/plivo/handler.go @@ -136,7 +136,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { authID := msg.Channel().StringConfigForKey(configPlivoAuthID, "") authToken := msg.Channel().StringConfigForKey(configPlivoAuthToken, "") plivoAppID := msg.Channel().StringConfigForKey(configPlivoAPPID, "") diff --git a/handlers/redrabbit/handler.go b/handlers/redrabbit/handler.go index 526d77817..7d34d242b 100644 --- a/handlers/redrabbit/handler.go +++ b/handlers/redrabbit/handler.go @@ -36,7 +36,7 @@ func (h *handler) Initialize(s courier.Server) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 == "" { diff --git a/handlers/rocketchat/handler.go b/handlers/rocketchat/handler.go index 617cd805d..c71edca04 100644 --- a/handlers/rocketchat/handler.go +++ b/handlers/rocketchat/handler.go @@ -105,7 +105,7 @@ type mtPayload struct { Attachments []Attachment `json:"attachments,omitempty"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { baseURL := msg.Channel().StringConfigForKey(configBaseURL, "") secret := msg.Channel().StringConfigForKey(configSecret, "") botUsername := msg.Channel().StringConfigForKey(configBotUsername, "") diff --git a/handlers/shaqodoon/handler.go b/handlers/shaqodoon/handler.go index 96d8e0d0d..4ce3bab4d 100644 --- a/handlers/shaqodoon/handler.go +++ b/handlers/shaqodoon/handler.go @@ -83,7 +83,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { sendURL := msg.Channel().StringConfigForKey(courier.ConfigSendURL, "") if sendURL == "" { return nil, fmt.Errorf("missing send_url for SQ channel") diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index fd86738ba..1d2576d4e 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -145,7 +145,7 @@ func (h *handler) resolveFile(ctx context.Context, channel courier.Channel, file return filePath, nil } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { botToken := msg.Channel().StringConfigForKey(configBotToken, "") if botToken == "" { return nil, fmt.Errorf("missing bot token for SL/slack channel") diff --git a/handlers/smscentral/handler.go b/handlers/smscentral/handler.go index 67fa5f5e8..4326c0e48 100644 --- a/handlers/smscentral/handler.go +++ b/handlers/smscentral/handler.go @@ -63,7 +63,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 SC channel") diff --git a/handlers/start/handler.go b/handlers/start/handler.go index 754954beb..dcd4a53fb 100644 --- a/handlers/start/handler.go +++ b/handlers/start/handler.go @@ -121,7 +121,7 @@ type mtResponse struct { State string `xml:"state"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 ST channel: %s", msg.Channel().UUID()) diff --git a/handlers/telegram/handler.go b/handlers/telegram/handler.go index 4373caa9a..40aadd8ba 100644 --- a/handlers/telegram/handler.go +++ b/handlers/telegram/handler.go @@ -178,7 +178,7 @@ func (h *handler) sendMsgPart(msg courier.Msg, token string, path string, form u } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { confAuth := msg.Channel().ConfigForKey(courier.ConfigAuthToken, "") authToken, isStr := confAuth.(string) if !isStr || authToken == "" { diff --git a/handlers/telesom/handler.go b/handlers/telesom/handler.go index a23a5836b..9815d9608 100644 --- a/handlers/telesom/handler.go +++ b/handlers/telesom/handler.go @@ -65,7 +65,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 TS channel") diff --git a/handlers/thinq/handler.go b/handlers/thinq/handler.go index e98fbcef5..0be6e3132 100644 --- a/handlers/thinq/handler.go +++ b/handlers/thinq/handler.go @@ -135,7 +135,7 @@ type mtMessage struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { accountID := msg.Channel().StringConfigForKey(configAccountID, "") if accountID == "" { return nil, fmt.Errorf("no account id set for TQ channel") diff --git a/handlers/twiml/handlers.go b/handlers/twiml/handlers.go index edbb2d765..30466ddff 100644 --- a/handlers/twiml/handlers.go +++ b/handlers/twiml/handlers.go @@ -214,7 +214,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // build our callback URL callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) callbackURL := fmt.Sprintf("https://%s/c/%s/%s/status?id=%d&action=callback", callbackDomain, strings.ToLower(string(h.ChannelType())), msg.Channel().UUID(), msg.ID()) diff --git a/handlers/twitter/handler.go b/handlers/twitter/handler.go index 3fbea84be..dea983063 100644 --- a/handlers/twitter/handler.go +++ b/handlers/twitter/handler.go @@ -248,7 +248,7 @@ type mtAttachment struct { } `json:"media"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { apiKey := msg.Channel().StringConfigForKey(configAPIKey, "") apiSecret := msg.Channel().StringConfigForKey(configAPISecret, "") accessToken := msg.Channel().StringConfigForKey(configAccessToken, "") diff --git a/handlers/viber/handler.go b/handlers/viber/handler.go index acc2796d7..b7990ac18 100644 --- a/handlers/viber/handler.go +++ b/handlers/viber/handler.go @@ -349,7 +349,7 @@ type mtResponse struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 auth token in config") diff --git a/handlers/vk/handler.go b/handlers/vk/handler.go index c55cf264d..e09eabf8c 100644 --- a/handlers/vk/handler.go +++ b/handlers/vk/handler.go @@ -374,7 +374,7 @@ func takeFirstAttachmentUrl(payload moNewMessagePayload) string { return "" } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) params := buildApiBaseParams(msg.Channel()) diff --git a/handlers/wavy/handler.go b/handlers/wavy/handler.go index 4ddefdc88..40848c336 100644 --- a/handlers/wavy/handler.go +++ b/handlers/wavy/handler.go @@ -120,7 +120,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 %s channel", msg.Channel().ChannelType()) diff --git a/handlers/wechat/handler.go b/handlers/wechat/handler.go index 9c64a99e0..e21556169 100644 --- a/handlers/wechat/handler.go +++ b/handlers/wechat/handler.go @@ -177,7 +177,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index 28fc20a8c..7c8c54f5c 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -493,7 +493,7 @@ type mtErrorPayload struct { const maxMsgLength = 4096 // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { conn := h.Backend().RedisPool().Get() defer conn.Close() @@ -556,7 +556,7 @@ func (h *handler) WriteRequestError(ctx context.Context, w http.ResponseWriter, return courier.WriteError(w, http.StatusOK, err) } -func buildPayloads(msg courier.Msg, h *handler, clog *courier.ChannelLog) ([]any, error) { +func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([]any, error) { var payloads []any var err error @@ -1116,7 +1116,7 @@ func checkWhatsAppContact(channel courier.Channel, baseURL string, urn urns.URN, } } -func (h *handler) getTemplating(msg courier.Msg) (*MsgTemplating, error) { +func (h *handler) getTemplating(msg courier.MsgOut) (*MsgTemplating, error) { if len(msg.Metadata()) == 0 { return nil, nil } diff --git a/handlers/yo/handler.go b/handlers/yo/handler.go index 175b01281..53bb66dab 100644 --- a/handlers/yo/handler.go +++ b/handlers/yo/handler.go @@ -97,7 +97,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(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 YO channel") diff --git a/handlers/zenvia/handlers.go b/handlers/zenvia/handlers.go index 072dd8f25..34b323935 100644 --- a/handlers/zenvia/handlers.go +++ b/handlers/zenvia/handlers.go @@ -179,7 +179,7 @@ type mtPayload struct { } // Send sends the given message, logging any HTTP calls or errors -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { channel := msg.Channel() token := channel.StringConfigForKey(courier.ConfigAPIKey, "") diff --git a/msg.go b/msg.go index e1185394c..8a4f0ec7c 100644 --- a/msg.go +++ b/msg.go @@ -62,6 +62,18 @@ type Msg interface { URN() urns.URN Channel() Channel + // incoming specific + ReceivedOn() *time.Time + WithAttachment(url string) Msg + WithContactName(name string) Msg + WithURNAuthTokens(tokens map[string]string) Msg + WithReceivedOn(date time.Time) Msg +} + +// MsgOut is our interface to represent an outgoing +type MsgOut interface { + Msg + // outgoing specific QuickReplies() []string Locale() i18n.Locale @@ -76,11 +88,4 @@ type Msg interface { Flow() *FlowReference SessionStatus() string HighPriority() bool - - // incoming specific - ReceivedOn() *time.Time - WithAttachment(url string) Msg - WithContactName(name string) Msg - WithURNAuthTokens(tokens map[string]string) Msg - WithReceivedOn(date time.Time) Msg } diff --git a/sender.go b/sender.go index 826aaa987..9eb43bc2f 100644 --- a/sender.go +++ b/sender.go @@ -105,7 +105,7 @@ func (f *Foreman) Assign() { type Sender struct { id int foreman *Foreman - job chan Msg + job chan MsgOut } // NewSender creates a new sender responsible for sending messages @@ -113,7 +113,7 @@ func NewSender(foreman *Foreman, id int) *Sender { sender := &Sender{ id: id, foreman: foreman, - job: make(chan Msg, 1), + job: make(chan MsgOut, 1), } return sender } @@ -151,7 +151,7 @@ func (w *Sender) Stop() { close(w.job) } -func (w *Sender) sendMessage(msg Msg) { +func (w *Sender) sendMessage(msg MsgOut) { log := logrus.WithField("comp", "sender").WithField("sender_id", w.id).WithField("channel_uuid", msg.Channel().UUID()) server := w.foreman.server diff --git a/test/backend.go b/test/backend.go index 5fa6526b6..d14541865 100644 --- a/test/backend.go +++ b/test/backend.go @@ -36,7 +36,7 @@ type MockBackend struct { channels map[courier.ChannelUUID]courier.Channel channelsByAddress map[courier.ChannelAddress]courier.Channel contacts map[urns.URN]courier.Contact - outgoingMsgs []courier.Msg + outgoingMsgs []courier.MsgOut media map[string]courier.Media // url -> Media errorOnQueue bool @@ -114,7 +114,7 @@ func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, tex // NewOutgoingMsg creates a new outgoing message from the given params func (mb *MockBackend) NewOutgoingMsg(channel courier.Channel, id courier.MsgID, urn urns.URN, text string, highPriority bool, quickReplies []string, - topic string, responseToExternalID string, origin courier.MsgOrigin, contactLastSeenOn *time.Time) courier.Msg { + topic string, responseToExternalID string, origin courier.MsgOrigin, contactLastSeenOn *time.Time) courier.MsgOut { return &MockMsg{ channel: channel, @@ -131,7 +131,7 @@ func (mb *MockBackend) NewOutgoingMsg(channel courier.Channel, id courier.MsgID, } // PushOutgoingMsg is a test method to add a message to our queue of messages to send -func (mb *MockBackend) PushOutgoingMsg(msg courier.Msg) { +func (mb *MockBackend) PushOutgoingMsg(msg courier.MsgOut) { mb.mutex.Lock() defer mb.mutex.Unlock() @@ -139,7 +139,7 @@ func (mb *MockBackend) PushOutgoingMsg(msg courier.Msg) { } // PopNextOutgoingMsg returns the next message that should be sent, or nil if there are none to send -func (mb *MockBackend) PopNextOutgoingMsg(ctx context.Context) (courier.Msg, error) { +func (mb *MockBackend) PopNextOutgoingMsg(ctx context.Context) (courier.MsgOut, error) { mb.mutex.Lock() defer mb.mutex.Unlock() diff --git a/test/handler.go b/test/handler.go index 8c5a6b0ea..403f74fb3 100644 --- a/test/handler.go +++ b/test/handler.go @@ -44,7 +44,7 @@ func (h *mockHandler) Initialize(s courier.Server) error { } // Send sends the given message, logging any HTTP calls or errors -func (h *mockHandler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *mockHandler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { // log a request that contains a header value that should be redacted req, _ := httpx.NewRequest("GET", "http://mock.com/send", nil, map[string]string{"Authorization": "Token sesame"}) trace, _ := httpx.DoTrace(http.DefaultClient, req, nil, nil, 1024) From 43c54358b331e4370124f59e09e177f0581dafa7 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Mon, 18 Sep 2023 14:03:49 +0200 Subject: [PATCH 109/170] Update outgoing method to use MsgOut interface --- backend.go | 2 +- backends/rapidpro/backend.go | 2 +- channel_log.go | 2 +- handlers/africastalking/handler_test.go | 2 +- handlers/arabiacell/handler_test.go | 2 +- handlers/bandwidth/handler_test.go | 2 +- handlers/bongolive/handler_test.go | 2 +- handlers/burstsms/handler_test.go | 2 +- handlers/clickatell/handler_test.go | 2 +- handlers/clickmobile/handler_test.go | 2 +- handlers/clicksend/handler_test.go | 2 +- handlers/dart/handler_test.go | 2 +- handlers/dialog360/handler_test.go | 2 +- handlers/discord/handler_test.go | 2 +- handlers/dmark/handler_test.go | 2 +- handlers/external/handler_test.go | 2 +- handlers/facebook_legacy/handler_test.go | 2 +- handlers/firebase/handler_test.go | 2 +- handlers/freshchat/handler_test.go | 2 +- handlers/globe/handler_test.go | 2 +- handlers/highconnection/handler_test.go | 2 +- handlers/hormuud/handler_test.go | 2 +- handlers/i2sms/handler_test.go | 2 +- handlers/infobip/handler_test.go | 2 +- handlers/jasmin/handler_test.go | 2 +- handlers/jiochat/handler_test.go | 2 +- handlers/justcall/handler_test.go | 2 +- handlers/kaleyra/handler_test.go | 2 +- handlers/kannel/handler_test.go | 4 ++-- handlers/line/handler_test.go | 2 +- handlers/m3tech/handler_test.go | 2 +- handlers/macrokiosk/handler_test.go | 2 +- handlers/mblox/handler_test.go | 2 +- handlers/messagebird/handler_test.go | 4 ++-- handlers/messangi/handler_test.go | 2 +- handlers/meta/facebook_test.go | 2 +- handlers/mtarget/handler_test.go | 2 +- handlers/mtn/handler_test.go | 2 +- handlers/nexmo/handler_test.go | 2 +- handlers/novo/handler_test.go | 2 +- handlers/playmobile/handler_test.go | 2 +- handlers/plivo/handler_test.go | 2 +- handlers/redrabbit/handler_test.go | 2 +- handlers/rocketchat/handler_test.go | 2 +- handlers/shaqodoon/handler_test.go | 2 +- handlers/slack/handler.go | 6 +++--- handlers/slack/handler_test.go | 12 ++++++------ handlers/smscentral/handler_test.go | 2 +- handlers/start/handler_test.go | 2 +- handlers/telegram/handler.go | 2 +- handlers/telegram/handler_test.go | 2 +- handlers/telesom/handler_test.go | 2 +- handlers/test.go | 2 +- handlers/thinq/handler_test.go | 2 +- handlers/twiml/handlers_test.go | 2 +- handlers/twitter/handler.go | 2 +- handlers/twitter/handler_test.go | 2 +- handlers/utils.go | 2 +- handlers/viber/handler_test.go | 2 +- handlers/vk/handler.go | 2 +- handlers/vk/handler_test.go | 2 +- handlers/wavy/handler_test.go | 2 +- handlers/wechat/handler_test.go | 2 +- handlers/whatsapp_legacy/handler.go | 4 ++-- handlers/whatsapp_legacy/handler_test.go | 2 +- handlers/yo/handler_test.go | 2 +- handlers/zenvia/handlers_test.go | 2 +- test/backend.go | 2 +- test/msg.go | 15 +++++++++------ 69 files changed, 87 insertions(+), 84 deletions(-) diff --git a/backend.go b/backend.go index 4d42e87eb..60f94452d 100644 --- a/backend.go +++ b/backend.go @@ -80,7 +80,7 @@ type Backend interface { // MarkOutgoingMsgComplete marks the passed in message as having been processed. Note this should be called even in the case // of errors during sending as it will manage the number of active workers per channel. The optional status parameter can be // used to determine any sort of deduping of msg sends - MarkOutgoingMsgComplete(context.Context, Msg, StatusUpdate) + MarkOutgoingMsgComplete(context.Context, MsgOut, StatusUpdate) // SaveAttachment saves an attachment to backend storage SaveAttachment(context.Context, Channel, string, []byte, string) (string, error) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index ff593f456..17c7b4712 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -481,7 +481,7 @@ func (b *backend) ClearMsgSent(ctx context.Context, id courier.MsgID) error { } // MarkOutgoingMsgComplete marks the passed in message as having completed processing, freeing up a worker for that channel -func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, status courier.StatusUpdate) { +func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.MsgOut, status courier.StatusUpdate) { rc := b.redisPool.Get() defer rc.Close() diff --git a/channel_log.go b/channel_log.go index c4585d69b..155d44abf 100644 --- a/channel_log.go +++ b/channel_log.go @@ -117,7 +117,7 @@ func NewChannelLogForIncoming(logType ChannelLogType, ch Channel, r *httpx.Recor } // NewChannelLogForSend creates a new channel log for a message send -func NewChannelLogForSend(msg Msg, redactVals []string) *ChannelLog { +func NewChannelLogForSend(msg MsgOut, redactVals []string) *ChannelLog { return newChannelLog(ChannelLogTypeMsgSend, msg.Channel(), nil, true, redactVals) } diff --git a/handlers/africastalking/handler_test.go b/handlers/africastalking/handler_test.go index cf2e663da..0bfdf2580 100644 --- a/handlers/africastalking/handler_test.go +++ b/handlers/africastalking/handler_test.go @@ -111,7 +111,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/arabiacell/handler_test.go b/handlers/arabiacell/handler_test.go index 8b523118c..96934ba98 100644 --- a/handlers/arabiacell/handler_test.go +++ b/handlers/arabiacell/handler_test.go @@ -44,7 +44,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/bandwidth/handler_test.go b/handlers/bandwidth/handler_test.go index 26b02a096..94199d6b9 100644 --- a/handlers/bandwidth/handler_test.go +++ b/handlers/bandwidth/handler_test.go @@ -247,7 +247,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "?%s" } diff --git a/handlers/bongolive/handler_test.go b/handlers/bongolive/handler_test.go index 132717c56..83e13f832 100644 --- a/handlers/bongolive/handler_test.go +++ b/handlers/bongolive/handler_test.go @@ -82,7 +82,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/burstsms/handler_test.go b/handlers/burstsms/handler_test.go index bdccff161..750873bcc 100644 --- a/handlers/burstsms/handler_test.go +++ b/handlers/burstsms/handler_test.go @@ -57,7 +57,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/clickatell/handler_test.go b/handlers/clickatell/handler_test.go index a9d09449b..eebc079c3 100644 --- a/handlers/clickatell/handler_test.go +++ b/handlers/clickatell/handler_test.go @@ -11,7 +11,7 @@ import ( ) // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/clickmobile/handler_test.go b/handlers/clickmobile/handler_test.go index b5066a206..584f313b5 100644 --- a/handlers/clickmobile/handler_test.go +++ b/handlers/clickmobile/handler_test.go @@ -143,7 +143,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, s.URL) sendURL = s.URL diff --git a/handlers/clicksend/handler_test.go b/handlers/clicksend/handler_test.go index 03289c75b..0b865afeb 100644 --- a/handlers/clicksend/handler_test.go +++ b/handlers/clicksend/handler_test.go @@ -48,7 +48,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/dart/handler_test.go b/handlers/dart/handler_test.go index d080dae0a..df0bd0188 100644 --- a/handlers/dart/handler_test.go +++ b/handlers/dart/handler_test.go @@ -94,7 +94,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { daHandler := h.(*handler) daHandler.sendURL = s.URL } diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index b71e17167..24fb14dd0 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -315,7 +315,7 @@ func TestBuildAttachmentRequest(t *testing.T) { } // setSendURL takes care of setting the base_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig("base_url", s.URL) } diff --git a/handlers/discord/handler_test.go b/handlers/discord/handler_test.go index 7fc490cad..398be68e1 100644 --- a/handlers/discord/handler_test.go +++ b/handlers/discord/handler_test.go @@ -111,7 +111,7 @@ var sendTestCases = []OutgoingTestCase{ } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { // this is actually a path, which we'll combine with the test server URL sendURL := c.StringConfigForKey("send_path", "/discord/rp/send") sendURL, _ = utils.AddURLPath(s.URL, sendURL) diff --git a/handlers/dmark/handler_test.go b/handlers/dmark/handler_test.go index 2f5d867eb..3962ce45a 100644 --- a/handlers/dmark/handler_test.go +++ b/handlers/dmark/handler_test.go @@ -91,7 +91,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/external/handler_test.go b/handlers/external/handler_test.go index 75dfe2af6..2684e30fa 100644 --- a/handlers/external/handler_test.go +++ b/handlers/external/handler_test.go @@ -280,7 +280,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { // this is actually a path, which we'll combine with the test server URL sendURL := c.StringConfigForKey("send_path", "") sendURL, _ = utils.AddURLPath(s.URL, sendURL) diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go index 8be072f83..81d2f524c 100644 --- a/handlers/facebook_legacy/handler_test.go +++ b/handlers/facebook_legacy/handler_test.go @@ -728,7 +728,7 @@ func TestVerify(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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/firebase/handler_test.go b/handlers/firebase/handler_test.go index 2f8bb07ba..f90dd9611 100644 --- a/handlers/firebase/handler_test.go +++ b/handlers/firebase/handler_test.go @@ -96,7 +96,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the base_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/freshchat/handler_test.go b/handlers/freshchat/handler_test.go index 618494981..a52241b60 100644 --- a/handlers/freshchat/handler_test.go +++ b/handlers/freshchat/handler_test.go @@ -82,7 +82,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler("FC", "FreshChat", false), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { apiURL = s.URL } diff --git a/handlers/globe/handler_test.go b/handlers/globe/handler_test.go index 1237c7962..34fc49fd8 100644 --- a/handlers/globe/handler_test.go +++ b/handlers/globe/handler_test.go @@ -166,7 +166,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "?%s" } diff --git a/handlers/highconnection/handler_test.go b/handlers/highconnection/handler_test.go index 3e4200227..34169c1c4 100644 --- a/handlers/highconnection/handler_test.go +++ b/handlers/highconnection/handler_test.go @@ -87,7 +87,7 @@ func BenchmarkHandler(b *testing.B) { } // setSend takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/hormuud/handler_test.go b/handlers/hormuud/handler_test.go index 8e81b25f9..2a8f530c1 100644 --- a/handlers/hormuud/handler_test.go +++ b/handlers/hormuud/handler_test.go @@ -67,7 +67,7 @@ func TestIncoming(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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/i2sms/handler_test.go b/handlers/i2sms/handler_test.go index e0dda402d..f8853b153 100644 --- a/handlers/i2sms/handler_test.go +++ b/handlers/i2sms/handler_test.go @@ -45,7 +45,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/infobip/handler_test.go b/handlers/infobip/handler_test.go index a2ddc6c15..d86c831fb 100644 --- a/handlers/infobip/handler_test.go +++ b/handlers/infobip/handler_test.go @@ -298,7 +298,7 @@ func BenchmarkHandler(b *testing.B) { } // setSend takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/jasmin/handler_test.go b/handlers/jasmin/handler_test.go index 89b72a88c..084f38b77 100644 --- a/handlers/jasmin/handler_test.go +++ b/handlers/jasmin/handler_test.go @@ -85,7 +85,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig("send_url", s.URL) } diff --git a/handlers/jiochat/handler_test.go b/handlers/jiochat/handler_test.go index 32f87080c..88f36f18e 100644 --- a/handlers/jiochat/handler_test.go +++ b/handlers/jiochat/handler_test.go @@ -345,7 +345,7 @@ func TestBuildAttachmentRequest(t *testing.T) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/justcall/handler_test.go b/handlers/justcall/handler_test.go index 4f6817f2b..3e59da44b 100644 --- a/handlers/justcall/handler_test.go +++ b/handlers/justcall/handler_test.go @@ -268,7 +268,7 @@ func BenchmarkHandler(b *testing.B) { } // setSend takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/kaleyra/handler_test.go b/handlers/kaleyra/handler_test.go index 18441e4ae..cb14e8836 100644 --- a/handlers/kaleyra/handler_test.go +++ b/handlers/kaleyra/handler_test.go @@ -109,7 +109,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { baseURL = s.URL } diff --git a/handlers/kannel/handler_test.go b/handlers/kannel/handler_test.go index 005fd50a9..0698849a8 100644 --- a/handlers/kannel/handler_test.go +++ b/handlers/kannel/handler_test.go @@ -129,12 +129,12 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig("send_url", s.URL) } // setSendURLWithQuery takes care of setting the send_url to our test server host -func setSendURLWithQuery(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURLWithQuery(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig("send_url", s.URL+"?auth=foo") } diff --git a/handlers/line/handler_test.go b/handlers/line/handler_test.go index ff1afce83..3b894665e 100644 --- a/handlers/line/handler_test.go +++ b/handlers/line/handler_test.go @@ -392,7 +392,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { replySendURL = s.URL + "/v2/bot/message/reply" pushSendURL = s.URL + "/v2/bot/message/push" } diff --git a/handlers/m3tech/handler_test.go b/handlers/m3tech/handler_test.go index 87dfb922d..12d84196f 100644 --- a/handlers/m3tech/handler_test.go +++ b/handlers/m3tech/handler_test.go @@ -48,7 +48,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/macrokiosk/handler_test.go b/handlers/macrokiosk/handler_test.go index c36d8f666..16fb64389 100644 --- a/handlers/macrokiosk/handler_test.go +++ b/handlers/macrokiosk/handler_test.go @@ -73,7 +73,7 @@ func TestIncoming(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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/mblox/handler_test.go b/handlers/mblox/handler_test.go index 1a7da4cce..cceb910bc 100644 --- a/handlers/mblox/handler_test.go +++ b/handlers/mblox/handler_test.go @@ -90,7 +90,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/messagebird/handler_test.go b/handlers/messagebird/handler_test.go index 2e75a0c28..51745bcb0 100644 --- a/handlers/messagebird/handler_test.go +++ b/handlers/messagebird/handler_test.go @@ -187,11 +187,11 @@ 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.Msg) { +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.Msg) { +func setMmsSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { mmsURL = s.URL } diff --git a/handlers/messangi/handler_test.go b/handlers/messangi/handler_test.go index 8750a4515..5ea8f2ccb 100644 --- a/handlers/messangi/handler_test.go +++ b/handlers/messangi/handler_test.go @@ -42,7 +42,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 483d1bef3..1d888e380 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -336,7 +336,7 @@ 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL graphURL = s.URL } diff --git a/handlers/mtarget/handler_test.go b/handlers/mtarget/handler_test.go index 8808b34fe..a803a2f00 100644 --- a/handlers/mtarget/handler_test.go +++ b/handlers/mtarget/handler_test.go @@ -83,7 +83,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/mtn/handler_test.go b/handlers/mtn/handler_test.go index e0c376681..4e9adc406 100644 --- a/handlers/mtn/handler_test.go +++ b/handlers/mtn/handler_test.go @@ -159,7 +159,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { apiHostURL = s.URL } diff --git a/handlers/nexmo/handler_test.go b/handlers/nexmo/handler_test.go index 51079338c..f3e55f292 100644 --- a/handlers/nexmo/handler_test.go +++ b/handlers/nexmo/handler_test.go @@ -119,7 +119,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/novo/handler_test.go b/handlers/novo/handler_test.go index 08bba4379..bfaa9ad0e 100644 --- a/handlers/novo/handler_test.go +++ b/handlers/novo/handler_test.go @@ -57,7 +57,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "?%s" } diff --git a/handlers/playmobile/handler_test.go b/handlers/playmobile/handler_test.go index 332c24607..b9013585c 100644 --- a/handlers/playmobile/handler_test.go +++ b/handlers/playmobile/handler_test.go @@ -128,7 +128,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "?%s" } diff --git a/handlers/plivo/handler_test.go b/handlers/plivo/handler_test.go index 15ed7c10f..ee0da1e68 100644 --- a/handlers/plivo/handler_test.go +++ b/handlers/plivo/handler_test.go @@ -69,7 +69,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "/%s/" } diff --git a/handlers/redrabbit/handler_test.go b/handlers/redrabbit/handler_test.go index d3c115ca0..93593bca0 100644 --- a/handlers/redrabbit/handler_test.go +++ b/handlers/redrabbit/handler_test.go @@ -10,7 +10,7 @@ import ( ) // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/rocketchat/handler_test.go b/handlers/rocketchat/handler_test.go index a68c97f70..8c7d25c77 100644 --- a/handlers/rocketchat/handler_test.go +++ b/handlers/rocketchat/handler_test.go @@ -104,7 +104,7 @@ func BenchmarkHandler(b *testing.B) { handlers.RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(configBaseURL, s.URL) } diff --git a/handlers/shaqodoon/handler_test.go b/handlers/shaqodoon/handler_test.go index 70637e4a1..f2ed3f0bb 100644 --- a/handlers/shaqodoon/handler_test.go +++ b/handlers/shaqodoon/handler_test.go @@ -51,7 +51,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), handleTestCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, s.URL) } diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index 1d2576d4e..2ca204692 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -181,7 +181,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, nil } -func sendTextMsgPart(msg courier.Msg, token string, clog *courier.ChannelLog) error { +func sendTextMsgPart(msg courier.MsgOut, token string, clog *courier.ChannelLog) error { sendURL := apiURL + "/chat.postMessage" msgPayload := &mtPayload{ @@ -221,7 +221,7 @@ func sendTextMsgPart(msg courier.Msg, token string, clog *courier.ChannelLog) er return nil } -func parseAttachmentToFileParams(msg courier.Msg, attachment string, clog *courier.ChannelLog) (*FileParams, error) { +func parseAttachmentToFileParams(msg courier.MsgOut, attachment string, clog *courier.ChannelLog) (*FileParams, error) { _, attURL := handlers.SplitAttachment(attachment) req, err := http.NewRequest(http.MethodGet, attURL, nil) @@ -241,7 +241,7 @@ func parseAttachmentToFileParams(msg courier.Msg, attachment string, clog *couri return &FileParams{File: respBody, FileName: filename, Channels: msg.URN().Path()}, nil } -func sendFilePart(msg courier.Msg, token string, fileParams *FileParams, clog *courier.ChannelLog) error { +func sendFilePart(msg courier.MsgOut, token string, fileParams *FileParams, clog *courier.ChannelLog) error { uploadURL := apiURL + "/files.upload" body := &bytes.Buffer{} diff --git a/handlers/slack/handler_test.go b/handlers/slack/handler_test.go index e5f8849f3..246b02aed 100644 --- a/handlers/slack/handler_test.go +++ b/handlers/slack/handler_test.go @@ -124,7 +124,7 @@ const videoFileMsg = `{ "event_time": 1653427243 }` -func setSendUrl(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { apiURL = s.URL } @@ -186,7 +186,7 @@ var defaultSendTestCases = []OutgoingTestCase{ MockResponseStatus: 200, ExpectedRequestBody: `{"channel":"U0123ABCDEF","text":"Simple Message"}`, ExpectedMsgStatus: "W", - SendPrep: setSendUrl, + SendPrep: setSendURL, }, { Label: "Unicode Send", @@ -196,7 +196,7 @@ var defaultSendTestCases = []OutgoingTestCase{ MockResponseStatus: 200, ExpectedRequestBody: `{"channel":"U0123ABCDEF","text":"☺"}`, ExpectedMsgStatus: "W", - SendPrep: setSendUrl, + SendPrep: setSendURL, }, { Label: "Send Text Auth Error", @@ -207,7 +207,7 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedRequestBody: `{"channel":"U0123ABCDEF","text":"Hello"}`, ExpectedMsgStatus: "E", ExpectedErrors: []*courier.ChannelError{courier.NewChannelError("", "", "invalid_auth")}, - SendPrep: setSendUrl, + SendPrep: setSendURL, }, } @@ -225,7 +225,7 @@ var fileSendTestCases = []OutgoingTestCase{ }: httpx.NewMockResponse(200, nil, []byte(`{"ok":true,"file":{"id":"F1L3SL4CK1D"}}`)), }, ExpectedMsgStatus: "W", - SendPrep: setSendUrl, + SendPrep: setSendURL, }, { Label: "Send Image", @@ -240,7 +240,7 @@ var fileSendTestCases = []OutgoingTestCase{ }: httpx.NewMockResponse(200, nil, []byte(`{"ok":true,"file":{"id":"F1L3SL4CK1D"}}`)), }, ExpectedMsgStatus: "W", - SendPrep: setSendUrl, + SendPrep: setSendURL, }, } diff --git a/handlers/smscentral/handler_test.go b/handlers/smscentral/handler_test.go index 77894fbf6..7a91cabb5 100644 --- a/handlers/smscentral/handler_test.go +++ b/handlers/smscentral/handler_test.go @@ -68,7 +68,7 @@ func BenchmarkHandler(b *testing.B) { } // setSend takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/start/handler_test.go b/handlers/start/handler_test.go index f8a21256b..6f415ca7c 100644 --- a/handlers/start/handler_test.go +++ b/handlers/start/handler_test.go @@ -160,7 +160,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/telegram/handler.go b/handlers/telegram/handler.go index 40aadd8ba..ebc2a65d7 100644 --- a/handlers/telegram/handler.go +++ b/handlers/telegram/handler.go @@ -142,7 +142,7 @@ type mtResponse struct { } `json:"result"` } -func (h *handler) sendMsgPart(msg courier.Msg, token string, path string, form url.Values, keyboard *ReplyKeyboardMarkup, clog *courier.ChannelLog) (string, bool, error) { +func (h *handler) sendMsgPart(msg courier.MsgOut, token string, path string, form url.Values, keyboard *ReplyKeyboardMarkup, clog *courier.ChannelLog) (string, bool, error) { // either include or remove our keyboard if keyboard == nil { form.Add("reply_markup", `{"remove_keyboard":true}`) diff --git a/handlers/telegram/handler_test.go b/handlers/telegram/handler_test.go index 8aa2b92ef..2f70e5df4 100644 --- a/handlers/telegram/handler_test.go +++ b/handlers/telegram/handler_test.go @@ -783,7 +783,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { apiURL = s.URL } diff --git a/handlers/telesom/handler_test.go b/handlers/telesom/handler_test.go index 62348deab..fc48959d0 100644 --- a/handlers/telesom/handler_test.go +++ b/handlers/telesom/handler_test.go @@ -83,7 +83,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(courier.ConfigSendURL, s.URL) sendURL = s.URL diff --git a/handlers/test.go b/handlers/test.go index 159024660..f6cd7f128 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -266,7 +266,7 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour } // SendPrepFunc allows test cases to modify the channel, msg or server before a message is sent -type SendPrepFunc func(*httptest.Server, courier.ChannelHandler, courier.Channel, courier.Msg) +type SendPrepFunc func(*httptest.Server, courier.ChannelHandler, courier.Channel, courier.MsgOut) // OutgoingTestCase defines the test values for a particular test case type OutgoingTestCase struct { diff --git a/handlers/thinq/handler_test.go b/handlers/thinq/handler_test.go index c5e82edd6..226234098 100644 --- a/handlers/thinq/handler_test.go +++ b/handlers/thinq/handler_test.go @@ -91,7 +91,7 @@ func TestIncoming(t *testing.T) { RunIncomingTestCases(t, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL + "?account_id=%s" sendMMSURL = s.URL + "?account_id=%s" } diff --git a/handlers/twiml/handlers_test.go b/handlers/twiml/handlers_test.go index f8ab6f1ea..b5b6b678f 100644 --- a/handlers/twiml/handlers_test.go +++ b/handlers/twiml/handlers_test.go @@ -544,7 +544,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { if c.ChannelType() == courier.ChannelType("TW") || c.ChannelType() == courier.ChannelType("SW") { c.(*test.MockChannel).SetConfig("send_url", s.URL) } else { diff --git a/handlers/twitter/handler.go b/handlers/twitter/handler.go index dea983063..a7f896613 100644 --- a/handlers/twitter/handler.go +++ b/handlers/twitter/handler.go @@ -363,7 +363,7 @@ func generateSignature(secret string, content string) string { return base64.StdEncoding.EncodeToString(h.Sum(nil)) } -func uploadMediaToTwitter(msg courier.Msg, mediaUrl string, attachmentMimeType string, attachmentURL string, client *http.Client, clog *courier.ChannelLog) (string, error) { +func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeType string, attachmentURL string, client *http.Client, clog *courier.ChannelLog) (string, error) { // retrieve the media to be sent from S3 req, _ := http.NewRequest(http.MethodGet, attachmentURL, nil) diff --git a/handlers/twitter/handler_test.go b/handlers/twitter/handler_test.go index ee9fae918..f98fb7510 100644 --- a/handlers/twitter/handler_test.go +++ b/handlers/twitter/handler_test.go @@ -189,7 +189,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendDomain = s.URL uploadDomain = s.URL } diff --git a/handlers/utils.go b/handlers/utils.go index d967b2cf9..8dc33a164 100644 --- a/handlers/utils.go +++ b/handlers/utils.go @@ -18,7 +18,7 @@ var ( ) // GetTextAndAttachments returns both the text of our message as well as any attachments, newline delimited -func GetTextAndAttachments(m courier.Msg) string { +func GetTextAndAttachments(m courier.MsgOut) string { buf := bytes.NewBuffer([]byte(m.Text())) for _, a := range m.Attachments() { _, url := SplitAttachment(a) diff --git a/handlers/viber/handler_test.go b/handlers/viber/handler_test.go index 6d4fbf6c7..d49163ef6 100644 --- a/handlers/viber/handler_test.go +++ b/handlers/viber/handler_test.go @@ -16,7 +16,7 @@ import ( ) // setSend takes care of setting the sendURL to call -func setSendURL(server *httptest.Server, h courier.ChannelHandler, channel courier.Channel, msg courier.Msg) { +func setSendURL(server *httptest.Server, h courier.ChannelHandler, channel courier.Channel, msg courier.MsgOut) { sendURL = server.URL } diff --git a/handlers/vk/handler.go b/handlers/vk/handler.go index e09eabf8c..19d8e0022 100644 --- a/handlers/vk/handler.go +++ b/handlers/vk/handler.go @@ -416,7 +416,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } // buildTextAndAttachmentParams builds msg text with attachment links (if needed) and attachments list param, also returns the errors that occurred -func buildTextAndAttachmentParams(msg courier.Msg, clog *courier.ChannelLog) (string, string) { +func buildTextAndAttachmentParams(msg courier.MsgOut, clog *courier.ChannelLog) (string, string) { var msgAttachments []string textBuf := bytes.Buffer{} diff --git a/handlers/vk/handler_test.go b/handlers/vk/handler_test.go index 347f1d8c3..e208a9694 100644 --- a/handlers/vk/handler_test.go +++ b/handlers/vk/handler_test.go @@ -381,7 +381,7 @@ func TestDescribeURN(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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { apiBaseURL = s.URL URLPhotoUploadServer = s.URL + "/upload/photo" } diff --git a/handlers/wavy/handler_test.go b/handlers/wavy/handler_test.go index 057051351..88cdf78be 100644 --- a/handlers/wavy/handler_test.go +++ b/handlers/wavy/handler_test.go @@ -168,7 +168,7 @@ func BenchmarkHandler(b *testing.B) { RunChannelBenchmarks(b, testChannels, newHandler(), testCases) } -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/wechat/handler_test.go b/handlers/wechat/handler_test.go index 96c66abda..e1d6d1f75 100644 --- a/handlers/wechat/handler_test.go +++ b/handlers/wechat/handler_test.go @@ -293,7 +293,7 @@ func TestBuildAttachmentRequest(t *testing.T) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURL = s.URL } diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index 7c8c54f5c..86b4ee928 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -836,7 +836,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] } // fetchMediaID tries to fetch the id for the uploaded media, setting the result in redis. -func (h *handler) fetchMediaID(msg courier.Msg, mimeType, mediaURL string, clog *courier.ChannelLog) (string, error) { +func (h *handler) fetchMediaID(msg courier.MsgOut, mimeType, mediaURL string, clog *courier.ChannelLog) (string, error) { // check in cache first rc := h.Backend().RedisPool().Get() defer rc.Close() @@ -908,7 +908,7 @@ func (h *handler) fetchMediaID(msg courier.Msg, mimeType, mediaURL string, clog return mediaID, nil } -func sendWhatsAppMsg(rc redis.Conn, msg courier.Msg, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { +func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { jsonBody, err := json.Marshal(payload) if err != nil { diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index 4af9bbe14..f2183103d 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -542,7 +542,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the base_url to our test server host -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { retryParam = "retry" c.(*test.MockChannel).SetConfig("base_url", s.URL) } diff --git a/handlers/yo/handler_test.go b/handlers/yo/handler_test.go index 223f1f57a..7e58b406b 100644 --- a/handlers/yo/handler_test.go +++ b/handlers/yo/handler_test.go @@ -49,7 +49,7 @@ func BenchmarkHandler(b *testing.B) { } // 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.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { sendURLs = []string{s.URL} } diff --git a/handlers/zenvia/handlers_test.go b/handlers/zenvia/handlers_test.go index ad25d3bea..0d1e15bce 100644 --- a/handlers/zenvia/handlers_test.go +++ b/handlers/zenvia/handlers_test.go @@ -243,7 +243,7 @@ func BenchmarkHandler(b *testing.B) { } // setSendURL takes care of setting the sendURL to call -func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { whatsappSendURL = s.URL smsSendURL = s.URL } diff --git a/test/backend.go b/test/backend.go index d14541865..797b05a9e 100644 --- a/test/backend.go +++ b/test/backend.go @@ -169,7 +169,7 @@ func (mb *MockBackend) ClearMsgSent(ctx context.Context, id courier.MsgID) error } // MarkOutgoingMsgComplete marks the passed msg as having been dealt with -func (mb *MockBackend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.Msg, s courier.StatusUpdate) { +func (mb *MockBackend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.MsgOut, s courier.StatusUpdate) { mb.mutex.Lock() defer mb.mutex.Unlock() diff --git a/test/msg.go b/test/msg.go index 800f489eb..d839ff73e 100644 --- a/test/msg.go +++ b/test/msg.go @@ -85,9 +85,12 @@ func (m *MockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { func (m *MockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } // used to create outgoing messages for testing -func (m *MockMsg) WithID(id courier.MsgID) courier.Msg { m.id = id; return m } -func (m *MockMsg) WithUUID(uuid courier.MsgUUID) courier.Msg { m.uuid = uuid; return m } -func (m *MockMsg) WithMetadata(metadata json.RawMessage) courier.Msg { m.metadata = metadata; return m } -func (m *MockMsg) WithFlow(flow *courier.FlowReference) courier.Msg { m.flow = flow; return m } -func (m *MockMsg) WithLocale(lc i18n.Locale) courier.Msg { m.locale = lc; return m } -func (m *MockMsg) WithURNAuth(token string) courier.Msg { m.urnAuth = token; return m } +func (m *MockMsg) WithID(id courier.MsgID) courier.MsgOut { m.id = id; return m } +func (m *MockMsg) WithUUID(uuid courier.MsgUUID) courier.MsgOut { m.uuid = uuid; return m } +func (m *MockMsg) WithMetadata(metadata json.RawMessage) courier.MsgOut { + m.metadata = metadata + return m +} +func (m *MockMsg) WithFlow(flow *courier.FlowReference) courier.MsgOut { m.flow = flow; return m } +func (m *MockMsg) WithLocale(lc i18n.Locale) courier.MsgOut { m.locale = lc; return m } +func (m *MockMsg) WithURNAuth(token string) courier.MsgOut { m.urnAuth = token; return m } From 556da95be736dfdd1a4b12b2e84bf2a265740220 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Mon, 18 Sep 2023 14:40:04 +0200 Subject: [PATCH 110/170] Add a separate MsgIn interface type --- backend.go | 4 ++-- backends/rapidpro/backend.go | 4 ++-- backends/rapidpro/msg.go | 10 +++++----- handler.go | 2 +- handlers/africastalking/handler.go | 2 +- handlers/bandwidth/handler.go | 2 +- handlers/base.go | 2 +- handlers/bongolive/handler.go | 4 ++-- handlers/clickatell/handler.go | 2 +- handlers/clickmobile/handler.go | 2 +- handlers/dart/handler.go | 4 ++-- handlers/discord/handler.go | 2 +- handlers/dmark/handler.go | 2 +- handlers/external/handler.go | 4 ++-- handlers/firebase/handler.go | 2 +- handlers/freshchat/handler.go | 2 +- handlers/generic.go | 2 +- handlers/globe/handler.go | 2 +- handlers/highconnection/handler.go | 2 +- handlers/hormuud/handler.go | 4 ++-- handlers/i2sms/handler.go | 4 ++-- handlers/infobip/handler.go | 2 +- handlers/jasmin/handler.go | 4 ++-- handlers/jiochat/handler.go | 2 +- handlers/justcall/handler.go | 2 +- handlers/kaleyra/handler.go | 2 +- handlers/kannel/handler.go | 2 +- handlers/line/handler.go | 2 +- handlers/m3tech/handler.go | 4 ++-- handlers/macrokiosk/handler.go | 4 ++-- handlers/mblox/handler.go | 2 +- handlers/messagebird/handler.go | 2 +- handlers/mtarget/handler.go | 2 +- handlers/mtn/handler.go | 2 +- handlers/nexmo/handler.go | 2 +- handlers/novo/handler.go | 2 +- handlers/playmobile/handler.go | 2 +- handlers/plivo/handler.go | 2 +- handlers/responses.go | 2 +- handlers/rocketchat/handler.go | 2 +- handlers/shaqodoon/handler.go | 2 +- handlers/slack/handler.go | 2 +- handlers/smscentral/handler.go | 2 +- handlers/start/handler.go | 4 ++-- handlers/telegram/handler.go | 2 +- handlers/telesom/handler.go | 2 +- handlers/thinq/handler.go | 4 ++-- handlers/twiml/handlers.go | 4 ++-- handlers/twitter/handler.go | 2 +- handlers/viber/handler.go | 2 +- handlers/wavy/handler.go | 2 +- handlers/wechat/handler.go | 4 ++-- handlers/yo/handler.go | 2 +- handlers/zenvia/handlers.go | 2 +- log.go | 2 +- msg.go | 21 +++++++++++++-------- responses.go | 4 ++-- responses_test.go | 2 +- server.go | 2 +- test/backend.go | 8 ++++---- test/handler.go | 2 +- test/msg.go | 8 ++++---- 62 files changed, 99 insertions(+), 94 deletions(-) diff --git a/backend.go b/backend.go index 60f94452d..2ab025e2a 100644 --- a/backend.go +++ b/backend.go @@ -42,10 +42,10 @@ type Backend interface { DeleteMsgByExternalID(ctx context.Context, channel Channel, externalID string) error // NewIncomingMsg creates a new message from the given params - NewIncomingMsg(Channel, urns.URN, string, string, *ChannelLog) Msg + NewIncomingMsg(Channel, urns.URN, string, string, *ChannelLog) MsgIn // WriteMsg writes the passed in message to our backend - WriteMsg(context.Context, Msg, *ChannelLog) error + WriteMsg(context.Context, MsgIn, *ChannelLog) error // NewStatusUpdate creates a new status update for the given message id NewStatusUpdate(Channel, MsgID, MsgStatus, *ChannelLog) StatusUpdate diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 17c7b4712..6ff73654c 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -380,7 +380,7 @@ func (b *backend) DeleteMsgByExternalID(ctx context.Context, channel courier.Cha } // NewIncomingMsg creates a new message from the given params -func (b *backend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) courier.Msg { +func (b *backend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) courier.MsgIn { // strip out invalid UTF8 and NULL chars urn = urns.URN(dbutil.ToValidUTF8(string(urn))) text = dbutil.ToValidUTF8(text) @@ -510,7 +510,7 @@ func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.MsgOu } // WriteMsg writes the passed in message to our store -func (b *backend) WriteMsg(ctx context.Context, m courier.Msg, clog *courier.ChannelLog) error { +func (b *backend) WriteMsg(ctx context.Context, m courier.MsgIn, clog *courier.ChannelLog) error { timeout, cancel := context.WithTimeout(ctx, backendTimeout) defer cancel() diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index de54349f7..21cc5790e 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -167,16 +167,16 @@ func (m *Msg) HighPriority() bool { return m.HighPriority_ } // incoming specific func (m *Msg) ReceivedOn() *time.Time { return m.SentOn_ } -func (m *Msg) WithAttachment(url string) courier.Msg { +func (m *Msg) WithAttachment(url string) courier.MsgIn { m.Attachments_ = append(m.Attachments_, url) return m } -func (m *Msg) WithContactName(name string) courier.Msg { m.ContactName_ = name; return m } -func (m *Msg) WithURNAuthTokens(tokens map[string]string) courier.Msg { +func (m *Msg) WithContactName(name string) courier.MsgIn { m.ContactName_ = name; return m } +func (m *Msg) WithURNAuthTokens(tokens map[string]string) courier.MsgIn { m.URNAuthTokens_ = tokens return m } -func (m *Msg) WithReceivedOn(date time.Time) courier.Msg { m.SentOn_ = &date; return m } +func (m *Msg) WithReceivedOn(date time.Time) courier.MsgIn { m.SentOn_ = &date; return m } func (m *Msg) hash() string { hash := sha1.Sum([]byte(m.Text_ + "|" + strings.Join(m.Attachments_, "|"))) @@ -184,7 +184,7 @@ func (m *Msg) hash() string { } // WriteMsg creates a message given the passed in arguments -func writeMsg(ctx context.Context, b *backend, msg courier.Msg, clog *courier.ChannelLog) error { +func writeMsg(ctx context.Context, b *backend, msg courier.MsgIn, clog *courier.ChannelLog) error { m := msg.(*Msg) // this msg has already been written (we received it twice), we are a no op diff --git a/handler.go b/handler.go index fa47709e2..a8c7cf7ac 100644 --- a/handler.go +++ b/handler.go @@ -30,7 +30,7 @@ type ChannelHandler interface { Send(context.Context, MsgOut, *ChannelLog) (StatusUpdate, error) WriteStatusSuccessResponse(context.Context, http.ResponseWriter, []StatusUpdate) error - WriteMsgSuccessResponse(context.Context, http.ResponseWriter, []Msg) error + WriteMsgSuccessResponse(context.Context, http.ResponseWriter, []MsgIn) error WriteRequestError(context.Context, http.ResponseWriter, error) error WriteRequestIgnored(context.Context, http.ResponseWriter, string) error } diff --git a/handlers/africastalking/handler.go b/handlers/africastalking/handler.go index f08ed27ca..1726947b8 100644 --- a/handlers/africastalking/handler.go +++ b/handlers/africastalking/handler.go @@ -76,7 +76,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, form.ID, clog).WithReceivedOn(date) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type statusForm struct { diff --git a/handlers/bandwidth/handler.go b/handlers/bandwidth/handler.go index 0c9a690ff..e7d231067 100644 --- a/handlers/bandwidth/handler.go +++ b/handlers/bandwidth/handler.go @@ -101,7 +101,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type moStatusData struct { diff --git a/handlers/base.go b/handlers/base.go index 998610776..d76e873f2 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -104,7 +104,7 @@ func (h *BaseHandler) WriteStatusSuccessResponse(ctx context.Context, w http.Res } // WriteMsgSuccessResponse writes a success response for the messages -func (h *BaseHandler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *BaseHandler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { return courier.WriteMsgSuccess(w, msgs) } diff --git a/handlers/bongolive/handler.go b/handlers/bongolive/handler.go index 3c96cabf6..a697dea84 100644 --- a/handlers/bongolive/handler.go +++ b/handlers/bongolive/handler.go @@ -101,11 +101,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.ID, clog).WithReceivedOn(time.Now().UTC()) // and finally queue our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { return writeBongoLiveResponse(w) } diff --git a/handlers/clickatell/handler.go b/handlers/clickatell/handler.go index b605febc7..7ffbafe95 100644 --- a/handlers/clickatell/handler.go +++ b/handlers/clickatell/handler.go @@ -131,7 +131,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, text, payload.MessageID, clog).WithReceivedOn(date.UTC()) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // utility method to decode crazy clickatell 16 bit format diff --git a/handlers/clickmobile/handler.go b/handlers/clickmobile/handler.go index b91a4f6f6..441e71b4d 100644 --- a/handlers/clickmobile/handler.go +++ b/handlers/clickmobile/handler.go @@ -80,7 +80,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, payload.Text, payload.ReferenceID, clog) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type mtPayload struct { diff --git a/handlers/dart/handler.go b/handlers/dart/handler.go index 2ba4fcf97..8bc5a8eeb 100644 --- a/handlers/dart/handler.go +++ b/handlers/dart/handler.go @@ -87,7 +87,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.MessageID, clog) // and finally queue our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type statusForm struct { @@ -139,7 +139,7 @@ func (h *handler) WriteStatusSuccessResponse(ctx context.Context, w http.Respons } // DartMedia expects "000" from a status request -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.WriteHeader(200) _, err := fmt.Fprint(w, "000") return err diff --git a/handlers/discord/handler.go b/handlers/discord/handler.go index 5ea84f0bd..ef238dd68 100644 --- a/handlers/discord/handler.go +++ b/handlers/discord/handler.go @@ -103,7 +103,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // buildStatusHandler deals with building a handler that takes what status is received in the URL diff --git a/handlers/dmark/handler.go b/handlers/dmark/handler.go index 648317d55..fa00d86d1 100644 --- a/handlers/dmark/handler.go +++ b/handlers/dmark/handler.go @@ -70,7 +70,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, "", clog).WithReceivedOn(date) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type statusForm struct { diff --git a/handlers/external/handler.go b/handlers/external/handler.go index 179163c7e..c7a494c3f 100644 --- a/handlers/external/handler.go +++ b/handlers/external/handler.go @@ -216,11 +216,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, text, "", clog).WithReceivedOn(date) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // WriteMsgSuccessResponse writes our response in TWIML format -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { moResponse := msgs[0].Channel().StringConfigForKey(configMOResponse, "") if moResponse == "" { return courier.WriteMsgSuccess(w, msgs) diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go index 07d9445f3..43df982ea 100644 --- a/handlers/firebase/handler.go +++ b/handlers/firebase/handler.go @@ -84,7 +84,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w dbMsg := h.Backend().NewIncomingMsg(channel, urn, form.Msg, "", clog).WithReceivedOn(date).WithContactName(form.Name).WithURNAuthTokens(authTokens) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{dbMsg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{dbMsg}, w, r, clog) } type registerForm struct { diff --git a/handlers/freshchat/handler.go b/handlers/freshchat/handler.go index bdb6cd0a3..50e81adaf 100644 --- a/handlers/freshchat/handler.go +++ b/handlers/freshchat/handler.go @@ -93,7 +93,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg.WithAttachment(mediaURL) } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { diff --git a/handlers/generic.go b/handlers/generic.go index 987e051d1..5926b11b5 100644 --- a/handlers/generic.go +++ b/handlers/generic.go @@ -29,7 +29,7 @@ func NewTelReceiveHandler(h courier.ChannelHandler, fromField string, bodyField } // build our msg msg := h.Server().Backend().NewIncomingMsg(c, urn, body, "", clog).WithReceivedOn(time.Now().UTC()) - return WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } } diff --git a/handlers/globe/handler.go b/handlers/globe/handler.go index 6ca0a3dc8..de7548084 100644 --- a/handlers/globe/handler.go +++ b/handlers/globe/handler.go @@ -78,7 +78,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. return nil, handlers.WriteAndLogRequestIgnored(ctx, h, c, w, r, "no messages, ignored") } - msgs := make([]courier.Msg, 0, 1) + msgs := make([]courier.MsgIn, 0, 1) // parse each inbound message for _, glMsg := range payload.InboundSMSMessageList.InboundSMSMessage { diff --git a/handlers/highconnection/handler.go b/handlers/highconnection/handler.go index b0d480756..70286e849 100644 --- a/handlers/highconnection/handler.go +++ b/handlers/highconnection/handler.go @@ -83,7 +83,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, text, msgID, clog).WithReceivedOn(date.UTC()) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type statusForm struct { diff --git a/handlers/hormuud/handler.go b/handlers/hormuud/handler.go index d6b583d7f..31dcac41c 100644 --- a/handlers/hormuud/handler.go +++ b/handlers/hormuud/handler.go @@ -63,7 +63,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } msg := h.Backend().NewIncomingMsg(c, urn, payload.MessageText, "", clog) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type mtPayload struct { @@ -129,7 +129,7 @@ type tokenResponse struct { } // FetchToken gets the current token for this channel, either from Redis if cached or by requesting it -func (h *handler) FetchToken(ctx context.Context, channel courier.Channel, msg courier.Msg, clog *courier.ChannelLog) (string, error) { +func (h *handler) FetchToken(ctx context.Context, channel courier.Channel, msg courier.MsgOut, clog *courier.ChannelLog) (string, error) { // first check whether we have it in redis conn := h.Backend().RedisPool().Get() token, err := redis.String(conn.Do("GET", fmt.Sprintf("hm_token_%s", channel.UUID()))) diff --git a/handlers/i2sms/handler.go b/handlers/i2sms/handler.go index 861619882..ce3eda937 100644 --- a/handlers/i2sms/handler.go +++ b/handlers/i2sms/handler.go @@ -63,7 +63,7 @@ func (h *handler) receive(ctx context.Context, c courier.Channel, w http.Respons // build our msg msg := h.Backend().NewIncomingMsg(c, urn, body, "", clog).WithReceivedOn(time.Now().UTC()) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // { @@ -152,7 +152,7 @@ func (h *handler) RedactValues(ch courier.Channel) []string { } // WriteMsgSuccessResponse writes a success response for the messages, i2SMS expects an empty body in our response -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.Header().Add("Content-type", "text/plain") w.WriteHeader(http.StatusOK) _, err := w.Write([]byte{}) diff --git a/handlers/infobip/handler.go b/handlers/infobip/handler.go index 23cc3f0b5..0ceed91ec 100644 --- a/handlers/infobip/handler.go +++ b/handlers/infobip/handler.go @@ -120,7 +120,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "ignoring request, no message") } - msgs := []courier.Msg{} + msgs := []courier.MsgIn{} for _, infobipMessage := range payload.Results { messageID := infobipMessage.MessageID text := infobipMessage.Text diff --git a/handlers/jasmin/handler.go b/handlers/jasmin/handler.go index c8f4334eb..4ae2e1977 100644 --- a/handlers/jasmin/handler.go +++ b/handlers/jasmin/handler.go @@ -97,10 +97,10 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. msg := h.Backend().NewIncomingMsg(c, urn, text, form.ID, clog).WithReceivedOn(time.Now().UTC()) // and finally queue our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { return writeJasminACK(w) } diff --git a/handlers/jiochat/handler.go b/handlers/jiochat/handler.go index 59e827938..eb3c1b6c1 100644 --- a/handlers/jiochat/handler.go +++ b/handlers/jiochat/handler.go @@ -145,7 +145,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } func buildMediaURL(mediaID string) string { diff --git a/handlers/justcall/handler.go b/handlers/justcall/handler.go index bb4042817..c42aa1f95 100644 --- a/handlers/justcall/handler.go +++ b/handlers/justcall/handler.go @@ -121,7 +121,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } var statusMapping = map[string]courier.MsgStatus{ diff --git a/handlers/kaleyra/handler.go b/handlers/kaleyra/handler.go index 23ef50e8a..f52fcc4c1 100644 --- a/handlers/kaleyra/handler.go +++ b/handlers/kaleyra/handler.go @@ -100,7 +100,7 @@ func (h *handler) receiveMsg(ctx context.Context, channel courier.Channel, w htt } // write msg - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } var statusMapping = map[string]courier.MsgStatus{ diff --git a/handlers/kannel/handler.go b/handlers/kannel/handler.go index c9616f215..b29f1bef4 100644 --- a/handlers/kannel/handler.go +++ b/handlers/kannel/handler.go @@ -77,7 +77,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, form.ID, clog).WithReceivedOn(date) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } var statusMapping = map[int]courier.MsgStatus{ diff --git a/handlers/line/handler.go b/handlers/line/handler.go index 0ccd4c623..e802b922e 100644 --- a/handlers/line/handler.go +++ b/handlers/line/handler.go @@ -116,7 +116,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return nil, err } - msgs := []courier.Msg{} + msgs := []courier.MsgIn{} for _, lineEvent := range payload.Events { if lineEvent.ReplyToken == "" || (lineEvent.Source.Type == "" && lineEvent.Source.UserID == "") || (lineEvent.Message.Type == "" && lineEvent.Message.ID == "") { diff --git a/handlers/m3tech/handler.go b/handlers/m3tech/handler.go index f539c8fa3..bfb51dcbf 100644 --- a/handlers/m3tech/handler.go +++ b/handlers/m3tech/handler.go @@ -58,11 +58,11 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. // create and write the message msg := h.Backend().NewIncomingMsg(c, urn, body, "", clog).WithReceivedOn(time.Now().UTC()) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // WriteMsgSuccessResponse writes a success response for the messages -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.Header().Set("Content-Type", "application/json") _, err := fmt.Fprintf(w, "SMS Accepted: %d", msgs[0].ID()) return err diff --git a/handlers/macrokiosk/handler.go b/handlers/macrokiosk/handler.go index 89873cbed..60897611f 100644 --- a/handlers/macrokiosk/handler.go +++ b/handlers/macrokiosk/handler.go @@ -129,11 +129,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // create and write the message msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, form.MsgID, clog).WithReceivedOn(date.UTC()) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // WriteMsgSuccessResponse -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.WriteHeader(200) _, err := fmt.Fprint(w, "-1") // MacroKiosk expects "-1" back for successful requests return err diff --git a/handlers/mblox/handler.go b/handlers/mblox/handler.go index df26bf03d..a49683da1 100644 --- a/handlers/mblox/handler.go +++ b/handlers/mblox/handler.go @@ -99,7 +99,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h msg := h.Backend().NewIncomingMsg(channel, urn, payload.Body, payload.ID, clog).WithReceivedOn(date.UTC()) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("not handled, unknown type: %s", payload.Type)) diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 1026e5ac7..9ba67ed4f 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -181,7 +181,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { diff --git a/handlers/mtarget/handler.go b/handlers/mtarget/handler.go index e907f5ba6..a1453ffbb 100644 --- a/handlers/mtarget/handler.go +++ b/handlers/mtarget/handler.go @@ -144,7 +144,7 @@ func (h *handler) receiveMsg(ctx context.Context, c courier.Channel, w http.Resp // otherwise, create and write the message msg := h.Backend().NewIncomingMsg(c, urn, text, msgID, clog).WithReceivedOn(time.Now().UTC()) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/mtn/handler.go b/handlers/mtn/handler.go index c5e7b72bb..8ee9a1b68 100644 --- a/handlers/mtn/handler.go +++ b/handlers/mtn/handler.go @@ -87,7 +87,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h // create and write the message msg := h.Backend().NewIncomingMsg(channel, urn, payload.Message, "", clog).WithReceivedOn(date) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } else { clog.SetType(courier.ChannelLogTypeMsgStatus) diff --git a/handlers/nexmo/handler.go b/handlers/nexmo/handler.go index c9176544c..1e7dc00dd 100644 --- a/handlers/nexmo/handler.go +++ b/handlers/nexmo/handler.go @@ -166,7 +166,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // create and write the message msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, form.MessageID, clog) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/novo/handler.go b/handlers/novo/handler.go index f5ac00d30..7f07ea51c 100644 --- a/handlers/novo/handler.go +++ b/handlers/novo/handler.go @@ -74,7 +74,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. // create and write the message msg := h.Backend().NewIncomingMsg(c, urn, body, "", clog).WithReceivedOn(time.Now().UTC()) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/playmobile/handler.go b/handlers/playmobile/handler.go index 38bce4a2c..22f0b20c7 100644 --- a/handlers/playmobile/handler.go +++ b/handlers/playmobile/handler.go @@ -105,7 +105,7 @@ func (h *handler) receiveMessage(ctx context.Context, c courier.Channel, w http. return nil, handlers.WriteAndLogRequestIgnored(ctx, h, c, w, r, "no messages, ignored") } - msgs := make([]courier.Msg, 0, 1) + msgs := make([]courier.MsgIn, 0, 1) // parse each inbound message for _, pmMsg := range payload.Message { diff --git a/handlers/plivo/handler.go b/handlers/plivo/handler.go index 5f1384dc3..9ed6e092c 100644 --- a/handlers/plivo/handler.go +++ b/handlers/plivo/handler.go @@ -124,7 +124,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // create and write the message msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, form.MessageUUID, clog) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type mtPayload struct { diff --git a/handlers/responses.go b/handlers/responses.go index b53ad8f25..35f80b503 100644 --- a/handlers/responses.go +++ b/handlers/responses.go @@ -8,7 +8,7 @@ import ( ) // WriteMsgsAndResponse writes the passed in message to our backend -func WriteMsgsAndResponse(ctx context.Context, h courier.ChannelHandler, msgs []courier.Msg, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { +func WriteMsgsAndResponse(ctx context.Context, h courier.ChannelHandler, msgs []courier.MsgIn, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { events := make([]courier.Event, len(msgs)) for i, m := range msgs { err := h.Server().Backend().WriteMsg(ctx, m, clog) diff --git a/handlers/rocketchat/handler.go b/handlers/rocketchat/handler.go index c71edca04..a37e19f03 100644 --- a/handlers/rocketchat/handler.go +++ b/handlers/rocketchat/handler.go @@ -79,7 +79,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg.WithAttachment(attachment.URL) } - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // BuildAttachmentRequest download media for message attachment with RC auth_token/user_id set diff --git a/handlers/shaqodoon/handler.go b/handlers/shaqodoon/handler.go index 4ce3bab4d..d6fe3a0df 100644 --- a/handlers/shaqodoon/handler.go +++ b/handlers/shaqodoon/handler.go @@ -79,7 +79,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // create and write the message msg := h.Backend().NewIncomingMsg(channel, urn, form.Text, "", clog).WithReceivedOn(date) - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index 2ca204692..41020f85e 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -97,7 +97,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h msg.WithAttachment(attURL) } - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "Ignoring request, no message") } diff --git a/handlers/smscentral/handler.go b/handlers/smscentral/handler.go index 4326c0e48..250265104 100644 --- a/handlers/smscentral/handler.go +++ b/handlers/smscentral/handler.go @@ -59,7 +59,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w // build our msg msg := h.Backend().NewIncomingMsg(channel, urn, form.Message, "", clog) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/start/handler.go b/handlers/start/handler.go index dcd4a53fb..b349c1996 100644 --- a/handlers/start/handler.go +++ b/handlers/start/handler.go @@ -85,11 +85,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, payload.Body.Text, payload.Service.RequestID, clog).WithReceivedOn(date) // and write it - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // Start Mobile expects a XML response from a message receive request -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.Header().Set("Content-Type", "text/xml") w.WriteHeader(200) _, err := fmt.Fprint(w, `Accepted`) diff --git a/handlers/telegram/handler.go b/handlers/telegram/handler.go index ebc2a65d7..4ab58a2c8 100644 --- a/handlers/telegram/handler.go +++ b/handlers/telegram/handler.go @@ -130,7 +130,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg.WithAttachment(mediaURL) } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } type mtResponse struct { diff --git a/handlers/telesom/handler.go b/handlers/telesom/handler.go index 9815d9608..9c306bc13 100644 --- a/handlers/telesom/handler.go +++ b/handlers/telesom/handler.go @@ -60,7 +60,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w dbMsg := h.Backend().NewIncomingMsg(channel, urn, form.Message, "", clog) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{dbMsg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{dbMsg}, w, r, clog) } diff --git a/handlers/thinq/handler.go b/handlers/thinq/handler.go index 0be6e3132..6bfca6309 100644 --- a/handlers/thinq/handler.go +++ b/handlers/thinq/handler.go @@ -70,7 +70,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) } - var msg courier.Msg + var msg courier.MsgIn if form.Type == "sms" { msg = h.Backend().NewIncomingMsg(channel, urn, form.Message, "", clog) @@ -83,7 +83,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } else { return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("unknown message type: %s", form.Type)) } - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // guid: thinQ guid returned when an outbound message is sent via our API diff --git a/handlers/twiml/handlers.go b/handlers/twiml/handlers.go index 30466ddff..4d12f6553 100644 --- a/handlers/twiml/handlers.go +++ b/handlers/twiml/handlers.go @@ -148,7 +148,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w mediaURL := r.PostForm.Get(fmt.Sprintf("MediaUrl%d", i)) msg.WithAttachment(mediaURL) } - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // receiveStatus is our HTTP handler function for status updates @@ -454,7 +454,7 @@ func twCalculateSignature(url string, form url.Values, authToken string) ([]byte } // WriteMsgSuccessResponse writes our response in TWIML format -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.Header().Set("Content-Type", "text/xml") w.WriteHeader(200) _, err := fmt.Fprint(w, ``) diff --git a/handlers/twitter/handler.go b/handlers/twitter/handler.go index a7f896613..1c6810ece 100644 --- a/handlers/twitter/handler.go +++ b/handlers/twitter/handler.go @@ -146,7 +146,7 @@ func (h *handler) receiveEvents(ctx context.Context, c courier.Channel, w http.R } // the list of messages we read - msgs := make([]courier.Msg, 0, 2) + msgs := make([]courier.MsgIn, 0, 2) // for each entry for _, entry := range payload.DirectMessageEvents { diff --git a/handlers/viber/handler.go b/handlers/viber/handler.go index b7990ac18..f3e1adc8a 100644 --- a/handlers/viber/handler.go +++ b/handlers/viber/handler.go @@ -267,7 +267,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h msg.WithAttachment(mediaURL) } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } return nil, courier.WriteError(w, http.StatusBadRequest, fmt.Errorf("not handled, unknown event: %s", event)) diff --git a/handlers/wavy/handler.go b/handlers/wavy/handler.go index 40848c336..407e90b0c 100644 --- a/handlers/wavy/handler.go +++ b/handlers/wavy/handler.go @@ -110,7 +110,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w msg := h.Backend().NewIncomingMsg(channel, urn, payload.Message, payload.ID, clog).WithReceivedOn(date.UTC()) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } diff --git a/handlers/wechat/handler.go b/handlers/wechat/handler.go index e21556169..d745b209c 100644 --- a/handlers/wechat/handler.go +++ b/handlers/wechat/handler.go @@ -152,11 +152,11 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w } // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } // WriteMsgSuccessResponse writes our response -func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *handler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { w.WriteHeader(200) _, err := fmt.Fprint(w, "") // WeChat expected empty string to not retry looking for passive reply return err diff --git a/handlers/yo/handler.go b/handlers/yo/handler.go index 53bb66dab..49160ddd1 100644 --- a/handlers/yo/handler.go +++ b/handlers/yo/handler.go @@ -93,7 +93,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w dbMsg := h.Backend().NewIncomingMsg(channel, urn, form.Message, "", clog).WithReceivedOn(date) // and finally write our message - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{dbMsg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{dbMsg}, w, r, clog) } // Send sends the given message, logging any HTTP calls or errors diff --git a/handlers/zenvia/handlers.go b/handlers/zenvia/handlers.go index 34b323935..ef2d25898 100644 --- a/handlers/zenvia/handlers.go +++ b/handlers/zenvia/handlers.go @@ -99,7 +99,7 @@ func (h *handler) receiveMessage(ctx context.Context, channel courier.Channel, w contactName := payload.Visitor.Name - msgs := []courier.Msg{} + msgs := []courier.MsgIn{} for _, content := range payload.Message.Contents { diff --git a/log.go b/log.go index ac95e56ce..9a871ea55 100644 --- a/log.go +++ b/log.go @@ -22,7 +22,7 @@ func LogMsgStatusReceived(r *http.Request, status StatusUpdate) { } // LogMsgReceived logs that we received the passed in message -func LogMsgReceived(r *http.Request, msg Msg) { +func LogMsgReceived(r *http.Request, msg MsgIn) { if logrus.IsLevelEnabled(logrus.DebugLevel) { logrus.WithFields(logrus.Fields{ "channel_uuid": msg.Channel().UUID(), diff --git a/msg.go b/msg.go index 8a4f0ec7c..7d5410d28 100644 --- a/msg.go +++ b/msg.go @@ -50,7 +50,7 @@ const ( // Msg interface //----------------------------------------------------------------------------- -// Msg is our interface to represent an incoming or outgoing message +// Msg is our interface for common methods for an incoming or outgoing message type Msg interface { Event @@ -61,13 +61,6 @@ type Msg interface { Attachments() []string URN() urns.URN Channel() Channel - - // incoming specific - ReceivedOn() *time.Time - WithAttachment(url string) Msg - WithContactName(name string) Msg - WithURNAuthTokens(tokens map[string]string) Msg - WithReceivedOn(date time.Time) Msg } // MsgOut is our interface to represent an outgoing @@ -89,3 +82,15 @@ type MsgOut interface { SessionStatus() string HighPriority() bool } + +// MsgIn is our interface to represent an incoming +type MsgIn interface { + Msg + + // incoming specific + ReceivedOn() *time.Time + WithAttachment(url string) MsgIn + WithContactName(name string) MsgIn + WithURNAuthTokens(tokens map[string]string) MsgIn + WithReceivedOn(date time.Time) MsgIn +} diff --git a/responses.go b/responses.go index c7e555a57..665d1e097 100644 --- a/responses.go +++ b/responses.go @@ -48,7 +48,7 @@ func WriteChannelEventSuccess(w http.ResponseWriter, event ChannelEvent) error { } // WriteMsgSuccess writes a JSON response for the passed in msg indicating we handled it -func WriteMsgSuccess(w http.ResponseWriter, msgs []Msg) error { +func WriteMsgSuccess(w http.ResponseWriter, msgs []MsgIn) error { data := []any{} for _, msg := range msgs { data = append(data, NewMsgReceiveData(msg)) @@ -85,7 +85,7 @@ type MsgReceiveData struct { } // NewMsgReceiveData creates a new data response for the passed in msg parameters -func NewMsgReceiveData(msg Msg) MsgReceiveData { +func NewMsgReceiveData(msg MsgIn) MsgReceiveData { return MsgReceiveData{ "msg", msg.Channel().UUID(), diff --git a/responses_test.go b/responses_test.go index 7d27d183a..6a017b0bf 100644 --- a/responses_test.go +++ b/responses_test.go @@ -46,7 +46,7 @@ func TestWriteMsgSuccess(t *testing.T) { msg := test.NewMockBackend().NewIncomingMsg(ch, "tel:+0987654321", "hi there", "", nil).(*test.MockMsg).WithUUID("588aafc4-ab5c-48ce-89e8-05c9fdeeafb7") w := httptest.NewRecorder() - err := courier.WriteMsgSuccess(w, []courier.Msg{msg}) + err := courier.WriteMsgSuccess(w, []courier.MsgIn{msg.(courier.MsgIn)}) assert.NoError(t, err) assert.Equal(t, 200, w.Code) assert.Equal(t, "{\"message\":\"Message Accepted\",\"data\":[{\"type\":\"msg\",\"channel_uuid\":\"5fccf4b6-48d7-4f5a-bce8-b0d1fd5342ec\",\"msg_uuid\":\"588aafc4-ab5c-48ce-89e8-05c9fdeeafb7\",\"text\":\"hi there\",\"urn\":\"tel:+0987654321\"}]}\n", w.Body.String()) diff --git a/server.go b/server.go index 7d19daf49..073eb567c 100644 --- a/server.go +++ b/server.go @@ -330,7 +330,7 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe for _, event := range events { switch e := event.(type) { - case Msg: + case MsgIn: clog.SetAttached(true) analytics.Gauge(fmt.Sprintf("courier.msg_receive_%s", channel.ChannelType()), secondDuration) LogMsgReceived(r, e) diff --git a/test/backend.go b/test/backend.go index 797b05a9e..94d9d6e67 100644 --- a/test/backend.go +++ b/test/backend.go @@ -43,7 +43,7 @@ type MockBackend struct { mutex sync.RWMutex redisPool *redis.Pool - writtenMsgs []courier.Msg + writtenMsgs []courier.MsgIn writtenMsgStatuses []courier.StatusUpdate writtenChannelEvents []courier.ChannelEvent writtenChannelLogs []*courier.ChannelLog @@ -98,7 +98,7 @@ func (mb *MockBackend) DeleteMsgByExternalID(ctx context.Context, channel courie } // NewIncomingMsg creates a new message from the given params -func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) courier.Msg { +func (mb *MockBackend) NewIncomingMsg(channel courier.Channel, urn urns.URN, text string, extID string, clog *courier.ChannelLog) courier.MsgIn { m := &MockMsg{ channel: channel, urn: urn, text: text, externalID: extID, } @@ -191,7 +191,7 @@ func (mb *MockBackend) SetErrorOnQueue(shouldError bool) { } // WriteMsg queues the passed in message internally -func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.Msg, clog *courier.ChannelLog) error { +func (mb *MockBackend) WriteMsg(ctx context.Context, m courier.MsgIn, clog *courier.ChannelLog) error { mm := m.(*MockMsg) // this msg has already been written (we received it twice), we are a no op @@ -376,7 +376,7 @@ func (mb *MockBackend) RedisPool() *redis.Pool { // Methods not part of the backed interface but used in tests //////////////////////////////////////////////////////////////////////////////// -func (mb *MockBackend) WrittenMsgs() []courier.Msg { return mb.writtenMsgs } +func (mb *MockBackend) WrittenMsgs() []courier.MsgIn { return mb.writtenMsgs } func (mb *MockBackend) WrittenMsgStatuses() []courier.StatusUpdate { return mb.writtenMsgStatuses } func (mb *MockBackend) WrittenChannelEvents() []courier.ChannelEvent { return mb.writtenChannelEvents } func (mb *MockBackend) WrittenChannelLogs() []*courier.ChannelLog { return mb.writtenChannelLogs } diff --git a/test/handler.go b/test/handler.go index 403f74fb3..33394171f 100644 --- a/test/handler.go +++ b/test/handler.go @@ -60,7 +60,7 @@ func (h *mockHandler) WriteStatusSuccessResponse(ctx context.Context, w http.Res return courier.WriteStatusSuccess(w, statuses) } -func (h *mockHandler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.Msg) error { +func (h *mockHandler) WriteMsgSuccessResponse(ctx context.Context, w http.ResponseWriter, msgs []courier.MsgIn) error { return courier.WriteMsgSuccess(w, msgs) } diff --git a/test/msg.go b/test/msg.go index d839ff73e..1f4b70379 100644 --- a/test/msg.go +++ b/test/msg.go @@ -73,16 +73,16 @@ func (m *MockMsg) HighPriority() bool { return m.highPriority } // incoming specific func (m *MockMsg) ReceivedOn() *time.Time { return m.receivedOn } -func (m *MockMsg) WithAttachment(url string) courier.Msg { +func (m *MockMsg) WithAttachment(url string) courier.MsgIn { m.attachments = append(m.attachments, url) return m } -func (m *MockMsg) WithContactName(name string) courier.Msg { m.contactName = name; return m } -func (m *MockMsg) WithURNAuthTokens(tokens map[string]string) courier.Msg { +func (m *MockMsg) WithContactName(name string) courier.MsgIn { m.contactName = name; return m } +func (m *MockMsg) WithURNAuthTokens(tokens map[string]string) courier.MsgIn { m.urnAuthTokens = tokens return m } -func (m *MockMsg) WithReceivedOn(date time.Time) courier.Msg { m.receivedOn = &date; return m } +func (m *MockMsg) WithReceivedOn(date time.Time) courier.MsgIn { m.receivedOn = &date; return m } // used to create outgoing messages for testing func (m *MockMsg) WithID(id courier.MsgID) courier.MsgOut { m.id = id; return m } From 503158e2e70181e4764162205c64f1ae2a9281a8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 18 Sep 2023 09:23:30 -0500 Subject: [PATCH 111/170] Update CHANGELOG.md for v8.3.18 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc2c3df54..c581e6451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +v8.3.18 (2023-09-18) +------------------------- + * Add separate MsgIn and MsgOut interface types + * Use functional options pattern to create base handlers + * Improve testing of status updates from handlers and allow testing of multiple status updates per request + * Split up Meta notification payload into whatsapp and messenger specific parts + v8.3.17 (2023-09-14) ------------------------- * Fix stop contact event task names From 6c4ab5c884a3d1c181c6722ae23143583459a68c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Sep 2023 14:16:28 -0500 Subject: [PATCH 112/170] Simplfy handlers splitting up messages --- go.mod | 2 +- handlers/base_test.go | 12 +++---- handlers/meta/handlers.go | 23 +++++-------- handlers/utils.go | 70 ++++++++++++++++++++++++++++++++++++--- handlers/viber/handler.go | 23 +++---------- 5 files changed, 87 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index f01da50c0..4fe048ab5 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/mod v0.12.0 gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/h2non/filetype.v1 v1.0.5 @@ -51,7 +52,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect diff --git a/handlers/base_test.go b/handlers/base_test.go index b8cd79d4e..33beeecf9 100644 --- a/handlers/base_test.go +++ b/handlers/base_test.go @@ -37,13 +37,13 @@ func TestDecodePossibleBase64(t *testing.T) { assert.Contains(DecodePossibleBase64(test6), "I received your letter today") } -func TestSplitMsg(t *testing.T) { +func TestSplitText(t *testing.T) { assert := assert.New(t) - assert.Equal([]string{""}, SplitMsg("", 160)) - assert.Equal([]string{"Simple message"}, SplitMsg("Simple message", 160)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitMsg("This is a message longer than 10", 20)) - assert.Equal([]string{" "}, SplitMsg(" ", 20)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitMsg("This is a message longer than 10", 20)) + assert.Equal([]string{""}, SplitText("", 160)) + assert.Equal([]string{"Simple message"}, SplitText("Simple message", 160)) + assert.Equal([]string{"This is a message", "longer than 10"}, SplitText("This is a message longer than 10", 20)) + assert.Equal([]string{" "}, SplitText(" ", 20)) + assert.Equal([]string{"This is a message", "longer than 10"}, SplitText("This is a message longer than 10", 20)) } func TestSplitMsgByChannel(t *testing.T) { diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 8f9890170..9c463b992 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -683,18 +683,13 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - msgParts := make([]string, 0) - if msg.Text() != "" { - msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) - } - - // send each part and each attachment separately. we send attachments first as otherwise quick replies - // attached to text messages get hidden when images get delivered - for i := 0; i < len(msgParts)+len(msg.Attachments()); i++ { - if i < len(msg.Attachments()) { + // 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}) { + if part.Type == handlers.MsgPartTypeAttachment { // this is an attachment payload.Message.Attachment = &messenger.Attachment{} - attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) + attType, attURL := handlers.SplitAttachment(part.Attachment) attType = strings.Split(attType, "/")[0] if attType == "application" { attType = "file" @@ -704,13 +699,13 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO payload.Message.Attachment.Payload.IsReusable = true payload.Message.Text = "" } else { - // this is still a msg part - payload.Message.Text = msgParts[i-len(msg.Attachments())] + // this is still a text part + payload.Message.Text = part.Text payload.Message.Attachment = nil } // include any quick replies on the last piece we send - if i == (len(msgParts)+len(msg.Attachments()))-1 { + if part.IsLast { for _, qr := range msg.QuickReplies() { payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{qr, qr, "text"}) } @@ -749,7 +744,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO } // if this is our first message, record the external id - if i == 0 { + if part.IsFirst { status.SetExternalID(respPayload.ExternalID) if msg.URN().IsFacebookRef() { recipientID := respPayload.RecipientID diff --git a/handlers/utils.go b/handlers/utils.go index 8dc33a164..23c647fb8 100644 --- a/handlers/utils.go +++ b/handlers/utils.go @@ -11,6 +11,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/urns" + "golang.org/x/exp/slices" ) var ( @@ -92,15 +93,76 @@ func DecodePossibleBase64(original string) string { return decoded } -// SplitMsgByChannel splits the passed in string into segments that are at most channel config max length or type max length +type MsgPartType int + +const ( + MsgPartTypeText MsgPartType = iota + MsgPartTypeAttachment + MsgPartTypeCaptionedAttachment +) + +// MsgPart represents a message part - either Text or Attachment will be set +type MsgPart struct { + Type MsgPartType + Text string + Attachment string + IsFirst bool + IsLast bool +} + +type SplitOptions struct { + MaxTextLen int + MaxCaptionLen int + Captionable []MediaType +} + +// SplitMsg splits an outgoing message into separate text and attachment parts, with attachment parts first. +// TODO combine with ResolveMedia +func SplitMsg(m courier.MsgOut, opts SplitOptions) []MsgPart { + text := m.Text() + attachments := m.Attachments() + parts := make([]MsgPart, 0, 5) + asCaption := false + + // if we have a single attachment and text we may be able to combine them into a captioned attachment + if len(attachments) == 1 && len(text) > 0 && (len(text) <= opts.MaxCaptionLen || opts.MaxCaptionLen == 0) { + att := attachments[0] + mediaType, _ := SplitAttachment(att) + mediaType = strings.Split(mediaType, "/")[0] + if slices.Contains(opts.Captionable, MediaType(mediaType)) { + parts = append(parts, MsgPart{Type: MsgPartTypeCaptionedAttachment, Text: text, Attachment: attachments[0]}) + asCaption = true + } + } + + if !asCaption { + for _, a := range attachments { + parts = append(parts, MsgPart{Type: MsgPartTypeAttachment, Attachment: a}) + } + for _, t := range SplitMsgByChannel(m.Channel(), text, opts.MaxTextLen) { + if len(t) > 0 { + parts = append(parts, MsgPart{Type: MsgPartTypeText, Text: t}) + } + } + } + + if len(parts) > 0 { + parts[0].IsFirst = true + parts[len(parts)-1].IsLast = true + } + + return parts +} + +// deprecated use SplitMsg instead func SplitMsgByChannel(channel courier.Channel, text string, maxLength int) []string { max := channel.IntConfigForKey(courier.ConfigMaxLength, maxLength) - return SplitMsg(text, max) + return SplitText(text, max) } -// SplitMsg splits the passed in string into segments that are at most max length -func SplitMsg(text string, max int) []string { +// SplitText splits the passed in string into segments that are at most max length +func SplitText(text string, max int) []string { // smaller than our max, just return it if len(text) <= max { return []string{text} diff --git a/handlers/viber/handler.go b/handlers/viber/handler.go index f3e1adc8a..86d04414b 100644 --- a/handlers/viber/handler.go +++ b/handlers/viber/handler.go @@ -365,21 +365,8 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch buttonLayout := msg.Channel().ConfigForKey("button_layout", map[string]any{}).(map[string]any) keyboard = NewKeyboardFromReplies(qrs, buttonLayout) } - parts := handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) - descriptionPart := "" - if len(msg.Attachments()) == 1 && len(msg.Text()) < descriptionMaxLength { - mediaType, _ := handlers.SplitAttachment(msg.Attachments()[0]) - isImage := strings.Split(mediaType, "/")[0] == "image" - - if isImage { - descriptionPart = msg.Text() - parts = []string{} - } - - } - - for i := 0; i < len(parts)+len(msg.Attachments()); i++ { + for _, part := range handlers.SplitMsg(msg, handlers.SplitOptions{MaxTextLen: maxMsgLength, MaxCaptionLen: descriptionMaxLength, Captionable: []handlers.MediaType{handlers.MediaTypeImage}}) { msgType := "text" attSize := -1 attURL := "" @@ -387,13 +374,13 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch msgText := "" var err error - if i < len(msg.Attachments()) { - mediaType, mediaURL := handlers.SplitAttachment(msg.Attachments()[0]) + if part.Type == handlers.MsgPartTypeAttachment || part.Type == handlers.MsgPartTypeCaptionedAttachment { + mediaType, mediaURL := handlers.SplitAttachment(part.Attachment) switch strings.Split(mediaType, "/")[0] { case "image": msgType = "picture" attURL = mediaURL - msgText = descriptionPart + msgText = part.Text case "video": msgType = "video" @@ -419,7 +406,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } } else { - msgText = parts[i-len(msg.Attachments())] + msgText = part.Text } payload := mtPayload{ From d9c68e7be36a2c35e8b47c53c773b86d7e306f29 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Sep 2023 15:00:25 -0500 Subject: [PATCH 113/170] Better testing of message splitting --- handler_test.go | 4 +- handlers/base_test.go | 75 ------------------ handlers/meta/whatsapp/templates_test.go | 2 +- handlers/split.go | 99 ++++++++++++++++++++++++ handlers/split_test.go | 85 ++++++++++++++++++++ handlers/utils.go | 92 ---------------------- handlers/utils_test.go | 29 +++++++ test/msg.go | 13 ++-- 8 files changed, 223 insertions(+), 176 deletions(-) delete mode 100644 handlers/base_test.go create mode 100644 handlers/split.go create mode 100644 handlers/split_test.go diff --git a/handler_test.go b/handler_test.go index bfc5007e8..ccd335c8f 100644 --- a/handler_test.go +++ b/handler_test.go @@ -44,7 +44,7 @@ func TestHandling(t *testing.T) { mockChannel := test.NewMockChannel("e4bb1578-29da-4fa5-a214-9da19dd24230", "MCK", "2020", "US", map[string]any{}) mb.AddChannel(mockChannel) - msg := test.NewMockMsg(courier.MsgID(101), courier.NilMsgUUID, brokenChannel, "tel:+250788383383", "test message") + msg := test.NewMockMsg(courier.MsgID(101), courier.NilMsgUUID, brokenChannel, "tel:+250788383383", "test message", nil) mb.PushOutgoingMsg(msg) // sleep a second, sender should take care of it in that time @@ -59,7 +59,7 @@ func TestHandling(t *testing.T) { mb.Reset() // change our channel to our dummy channel - msg = test.NewMockMsg(courier.MsgID(102), courier.NilMsgUUID, mockChannel, "tel:+250788383383", "test message 2") + msg = test.NewMockMsg(courier.MsgID(102), courier.NilMsgUUID, mockChannel, "tel:+250788383383", "test message 2", nil) // send it mb.PushOutgoingMsg(msg) diff --git a/handlers/base_test.go b/handlers/base_test.go deleted file mode 100644 index 33beeecf9..000000000 --- a/handlers/base_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package handlers - -import ( - "testing" - - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/test" - "github.com/stretchr/testify/assert" -) - -var test6 = ` -SSByZWNlaXZlZCB5b3VyIGxldHRlciB0b2RheSwgaW4gd2hpY2ggeW91IHNheSB5b3Ugd2FudCB0 -byByZXNjdWUgTm9ydGggQ2Fyb2xpbmlhbnMgZnJvbSB0aGUgQUNBLCBvciBPYmFtYWNhcmUgYXMg -eW91IG9kZGx5IGluc2lzdCBvbiBjYWxsaW5nIGl0LiAKCkkgaGF2ZSB0byBjYWxsIHlvdXIgYXR0 -ZW50aW9uIHRvIHlvdXIgc2luIG9mIG9taXNzaW9uLiBZb3Ugc2F5IHRoYXQgd2UgYXJlIGRvd24g -dG8gb25lIGluc3VyZXIgYmVjYXVzZSBvZiBPYmFtYWNhcmUuIERpZCB5b3UgZm9yZ2V0IHRoYXQg -VGhlIEJhdGhyb29tIFN0YXRlIGhhcyBkb25lIGV2ZXJ5dGhpbmcgcG9zc2libGUgdG8gbWFrZSBU -aGUgQUNBIGZhaWw/ICBJbmNsdWRpbmcgbWlsbGlvbnMgb2YgZG9sbGFycyBmcm9tIHRoZSBmZWQ/ -CgpXZSBkb24ndCBuZWVkIHRvIGJlIHNhdmVkIGZyb20gYSBwcm9ncmFtIHRoYXQgaGFzIGhlbHBl -ZCB0aG91c2FuZHMuIFdlIG5lZWQgeW91IHRvIGJ1Y2tsZSBkb3duIGFuZCBpbXByb3ZlIHRoZSBB -Q0EuIFlvdSBoYWQgeWVhcnMgdG8gY29tZSB1cCB3aXRoIGEgcGxhbi4gWW91IGZhaWxlZC4gCgpU -aGUgbGF0ZXN0IHZlcnNpb24geW91ciBwYXJ0eSBoYXMgY29tZSB1cCB3aXRoIHVzIHdvcnNlIHRo -YW4gdGhlIGxhc3QuIFBsZWFzZSB2b3RlIGFnYWluc3QgaXQuIERvbid0IGNvbmRlbW4gdGhlIGdv -b2Qgb2YgcGVvcGxlIG9mIE5DIHRvIGxpdmVzIHRoYXQgYXJlIG5hc3R5LCBicnV0aXNoIGFuZCBz -aG9ydC4gSSdtIG9uZSBvZiB0aGUgZm9sa3Mgd2hvIHdpbGwgZGllIGlmIHlvdSByaXAgdGhlIHBy -b3RlY3Rpb25zIGF3YXkuIAoKVm90ZSBOTyBvbiBhbnkgYmlsbCB0aGF0IGRvZXNuJ3QgY29udGFp -biBwcm90ZWN0aW9ucyBpbnN0ZWFkIG9mIHB1bmlzaG1lbnRzLiBXZSBhcmUgd2F0Y2hpbmcgY2xv -c2VseS4g` - -func TestDecodePossibleBase64(t *testing.T) { - assert := assert.New(t) - assert.Equal("This test\nhas a newline", DecodePossibleBase64("This test\nhas a newline")) - assert.Equal("Please vote NO on the confirmation of Gorsuch.", DecodePossibleBase64("Please vote NO on the confirmation of Gorsuch.")) - assert.Equal("Bannon Explains The World ...\n“The Camp of the Saints", DecodePossibleBase64("QmFubm9uIEV4cGxhaW5zIFRoZSBXb3JsZCAuLi4K4oCcVGhlIENhbXAgb2YgdGhlIFNhaW50c+KA\r")) - assert.Equal("the sweat, the tears and the sacrifice of working America", DecodePossibleBase64("dGhlIHN3ZWF0LCB0aGUgdGVhcnMgYW5kIHRoZSBzYWNyaWZpY2Ugb2Ygd29ya2luZyBBbWVyaWNh\r")) - assert.Contains(DecodePossibleBase64("Tm93IGlzDQp0aGUgdGltZQ0KZm9yIGFsbCBnb29kDQpwZW9wbGUgdG8NCnJlc2lzdC4NCg0KSG93IGFib3V0IGhhaWt1cz8NCkkgZmluZCB0aGVtIHRvIGJlIGZyaWVuZGx5Lg0KcmVmcmlnZXJhdG9yDQoNCjAxMjM0NTY3ODkNCiFAIyQlXiYqKCkgW117fS09Xys7JzoiLC4vPD4/fFx+YA0KQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg=="), "I find them to be friendly") - assert.Contains(DecodePossibleBase64(test6), "I received your letter today") -} - -func TestSplitText(t *testing.T) { - assert := assert.New(t) - assert.Equal([]string{""}, SplitText("", 160)) - assert.Equal([]string{"Simple message"}, SplitText("Simple message", 160)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitText("This is a message longer than 10", 20)) - assert.Equal([]string{" "}, SplitText(" ", 20)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitText("This is a message longer than 10", 20)) -} - -func TestSplitMsgByChannel(t *testing.T) { - assert := assert.New(t) - var channelWithMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", - map[string]any{ - courier.ConfigUsername: "user1", - courier.ConfigPassword: "pass1", - courier.ConfigMaxLength: 25, - }) - var channelWithoutMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", - map[string]any{ - courier.ConfigUsername: "user1", - courier.ConfigPassword: "pass1", - }) - - assert.Equal([]string{""}, SplitMsgByChannel(channelWithoutMaxLength, "", 160)) - assert.Equal([]string{"Simple message"}, SplitMsgByChannel(channelWithoutMaxLength, "Simple message", 160)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitMsgByChannel(channelWithoutMaxLength, "This is a message longer than 10", 20)) - assert.Equal([]string{" "}, SplitMsgByChannel(channelWithoutMaxLength, " ", 20)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitMsgByChannel(channelWithoutMaxLength, "This is a message longer than 10", 20)) - - // Max length should be the one configured on the channel - assert.Equal([]string{""}, SplitMsgByChannel(channelWithMaxLength, "", 160)) - assert.Equal([]string{"Simple message"}, SplitMsgByChannel(channelWithMaxLength, "Simple message", 160)) - assert.Equal([]string{"This is a message longer", "than 10"}, SplitMsgByChannel(channelWithMaxLength, "This is a message longer than 10", 20)) - assert.Equal([]string{" "}, SplitMsgByChannel(channelWithMaxLength, " ", 20)) - assert.Equal([]string{"This is a message", "longer than 10"}, SplitMsgByChannel(channelWithMaxLength, "This is a message longer than 10", 20)) -} diff --git a/handlers/meta/whatsapp/templates_test.go b/handlers/meta/whatsapp/templates_test.go index 7272341c9..19c3d81ac 100644 --- a/handlers/meta/whatsapp/templates_test.go +++ b/handlers/meta/whatsapp/templates_test.go @@ -10,7 +10,7 @@ import ( ) func TestGetTemplating(t *testing.T) { - msg := test.NewMockMsg(1, "87995844-2017-4ba0-bc73-f3da75b32f9b", nil, "tel:+1234567890", "hi") + msg := test.NewMockMsg(1, "87995844-2017-4ba0-bc73-f3da75b32f9b", nil, "tel:+1234567890", "hi", nil) // no metadata, no templating tpl, err := whatsapp.GetTemplating(msg) diff --git a/handlers/split.go b/handlers/split.go new file mode 100644 index 000000000..dbfd91605 --- /dev/null +++ b/handlers/split.go @@ -0,0 +1,99 @@ +package handlers + +import ( + "bytes" + "strings" + + "github.com/nyaruka/courier" + "golang.org/x/exp/slices" +) + +type MsgPartType int + +const ( + MsgPartTypeText MsgPartType = iota + MsgPartTypeAttachment + MsgPartTypeCaptionedAttachment +) + +// MsgPart represents a message part - either Text or Attachment will be set +type MsgPart struct { + Type MsgPartType + Text string + Attachment string + IsFirst bool + IsLast bool +} + +type SplitOptions struct { + MaxTextLen int + MaxCaptionLen int + Captionable []MediaType +} + +// SplitMsg splits an outgoing message into separate text and attachment parts, with attachment parts first. +func SplitMsg(m courier.MsgOut, opts SplitOptions) []MsgPart { + text := m.Text() + attachments := m.Attachments() + parts := make([]MsgPart, 0, 5) + asCaption := false + + // if we have a single attachment and text we may be able to combine them into a captioned attachment + if len(attachments) == 1 && len(text) > 0 && (len(text) <= opts.MaxCaptionLen || opts.MaxCaptionLen == 0) { + att := attachments[0] + mediaType, _ := SplitAttachment(att) + mediaType = strings.Split(mediaType, "/")[0] + if slices.Contains(opts.Captionable, MediaType(mediaType)) { + parts = append(parts, MsgPart{Type: MsgPartTypeCaptionedAttachment, Text: text, Attachment: attachments[0]}) + asCaption = true + } + } + + if !asCaption { + for _, a := range attachments { + parts = append(parts, MsgPart{Type: MsgPartTypeAttachment, Attachment: a}) + } + for _, t := range SplitMsgByChannel(m.Channel(), text, opts.MaxTextLen) { + if len(t) > 0 { + parts = append(parts, MsgPart{Type: MsgPartTypeText, Text: t}) + } + } + } + + if len(parts) > 0 { + parts[0].IsFirst = true + parts[len(parts)-1].IsLast = true + } + + return parts +} + +// deprecated use SplitMsg instead +func SplitMsgByChannel(channel courier.Channel, text string, maxLength int) []string { + max := channel.IntConfigForKey(courier.ConfigMaxLength, maxLength) + + return SplitText(text, max) +} + +// SplitText splits the passed in string into segments that are at most max length +func SplitText(text string, max int) []string { + // smaller than our max, just return it + if len(text) <= max { + return []string{text} + } + + parts := make([]string, 0, 2) + part := bytes.Buffer{} + for _, r := range text { + part.WriteRune(r) + if part.Len() == max || (part.Len() > max-6 && r == ' ') { + parts = append(parts, strings.TrimSpace(part.String())) + part.Reset() + } + } + if part.Len() > 0 { + parts = append(parts, strings.TrimSpace(part.String())) + } + + return parts +} diff --git a/handlers/split_test.go b/handlers/split_test.go new file mode 100644 index 000000000..b62d3b349 --- /dev/null +++ b/handlers/split_test.go @@ -0,0 +1,85 @@ +package handlers_test + +import ( + "testing" + + "github.com/nyaruka/courier" + "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/courier/test" + "github.com/stretchr/testify/assert" +) + +func TestSplitMsg(t *testing.T) { + var channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", nil) + + tcs := []struct { + msg courier.MsgOut + opts handlers.SplitOptions + expectedParts []handlers.MsgPart + }{ + { + msg: test.NewMockMsg(1001, "b6454f25-e5b9-4795-a180-b9e35ca3a523", channel, "tel+1234567890", "This is a message longer than 10", nil), + opts: handlers.SplitOptions{MaxTextLen: 20}, + expectedParts: []handlers.MsgPart{ + {Type: handlers.MsgPartTypeText, Text: "This is a message", IsFirst: true}, + {Type: handlers.MsgPartTypeText, Text: "longer than 10", IsLast: true}, + }, + }, + { + msg: test.NewMockMsg(1001, "b6454f25-e5b9-4795-a180-b9e35ca3a523", channel, "tel+1234567890", "Lovely image", []string{"image/jpeg:http://test.jpg"}), + opts: handlers.SplitOptions{MaxTextLen: 20}, + expectedParts: []handlers.MsgPart{ + {Type: handlers.MsgPartTypeAttachment, Attachment: "image/jpeg:http://test.jpg", IsFirst: true}, + {Type: handlers.MsgPartTypeText, Text: "Lovely image", IsLast: true}, + }, + }, + { + msg: test.NewMockMsg(1001, "b6454f25-e5b9-4795-a180-b9e35ca3a523", channel, "tel+1234567890", "Lovely image", []string{"image/jpeg:http://test.jpg"}), + opts: handlers.SplitOptions{MaxTextLen: 20, Captionable: []handlers.MediaType{handlers.MediaTypeImage}}, + expectedParts: []handlers.MsgPart{ + {Type: handlers.MsgPartTypeCaptionedAttachment, Text: "Lovely image", Attachment: "image/jpeg:http://test.jpg", IsFirst: true, IsLast: true}, + }, + }, + } + + for _, tc := range tcs { + actualParts := handlers.SplitMsg(tc.msg, tc.opts) + assert.Equal(t, tc.expectedParts, actualParts) + } + +} + +func TestSplitMsgByChannel(t *testing.T) { + var channelWithMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", + map[string]any{ + courier.ConfigUsername: "user1", + courier.ConfigPassword: "pass1", + courier.ConfigMaxLength: 25, + }) + var channelWithoutMaxLength = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AC", "2020", "US", + map[string]any{ + courier.ConfigUsername: "user1", + courier.ConfigPassword: "pass1", + }) + + assert.Equal(t, []string{""}, handlers.SplitMsgByChannel(channelWithoutMaxLength, "", 160)) + assert.Equal(t, []string{"Simple message"}, handlers.SplitMsgByChannel(channelWithoutMaxLength, "Simple message", 160)) + assert.Equal(t, []string{"This is a message", "longer than 10"}, handlers.SplitMsgByChannel(channelWithoutMaxLength, "This is a message longer than 10", 20)) + assert.Equal(t, []string{" "}, handlers.SplitMsgByChannel(channelWithoutMaxLength, " ", 20)) + assert.Equal(t, []string{"This is a message", "longer than 10"}, handlers.SplitMsgByChannel(channelWithoutMaxLength, "This is a message longer than 10", 20)) + + // Max length should be the one configured on the channel + assert.Equal(t, []string{""}, handlers.SplitMsgByChannel(channelWithMaxLength, "", 160)) + assert.Equal(t, []string{"Simple message"}, handlers.SplitMsgByChannel(channelWithMaxLength, "Simple message", 160)) + assert.Equal(t, []string{"This is a message longer", "than 10"}, handlers.SplitMsgByChannel(channelWithMaxLength, "This is a message longer than 10", 20)) + assert.Equal(t, []string{" "}, handlers.SplitMsgByChannel(channelWithMaxLength, " ", 20)) + assert.Equal(t, []string{"This is a message", "longer than 10"}, handlers.SplitMsgByChannel(channelWithMaxLength, "This is a message longer than 10", 20)) +} + +func TestSplitText(t *testing.T) { + assert.Equal(t, []string{""}, handlers.SplitText("", 160)) + assert.Equal(t, []string{"Simple message"}, handlers.SplitText("Simple message", 160)) + assert.Equal(t, []string{"This is a message", "longer than 10"}, handlers.SplitText("This is a message longer than 10", 20)) + assert.Equal(t, []string{" "}, handlers.SplitText(" ", 20)) + assert.Equal(t, []string{"This is a message", "longer than 10"}, handlers.SplitText("This is a message longer than 10", 20)) +} diff --git a/handlers/utils.go b/handlers/utils.go index 23c647fb8..c00367da0 100644 --- a/handlers/utils.go +++ b/handlers/utils.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/urns" - "golang.org/x/exp/slices" ) var ( @@ -93,97 +92,6 @@ func DecodePossibleBase64(original string) string { return decoded } -type MsgPartType int - -const ( - MsgPartTypeText MsgPartType = iota - MsgPartTypeAttachment - MsgPartTypeCaptionedAttachment -) - -// MsgPart represents a message part - either Text or Attachment will be set -type MsgPart struct { - Type MsgPartType - Text string - Attachment string - IsFirst bool - IsLast bool -} - -type SplitOptions struct { - MaxTextLen int - MaxCaptionLen int - Captionable []MediaType -} - -// SplitMsg splits an outgoing message into separate text and attachment parts, with attachment parts first. -// TODO combine with ResolveMedia -func SplitMsg(m courier.MsgOut, opts SplitOptions) []MsgPart { - text := m.Text() - attachments := m.Attachments() - parts := make([]MsgPart, 0, 5) - asCaption := false - - // if we have a single attachment and text we may be able to combine them into a captioned attachment - if len(attachments) == 1 && len(text) > 0 && (len(text) <= opts.MaxCaptionLen || opts.MaxCaptionLen == 0) { - att := attachments[0] - mediaType, _ := SplitAttachment(att) - mediaType = strings.Split(mediaType, "/")[0] - if slices.Contains(opts.Captionable, MediaType(mediaType)) { - parts = append(parts, MsgPart{Type: MsgPartTypeCaptionedAttachment, Text: text, Attachment: attachments[0]}) - asCaption = true - } - } - - if !asCaption { - for _, a := range attachments { - parts = append(parts, MsgPart{Type: MsgPartTypeAttachment, Attachment: a}) - } - for _, t := range SplitMsgByChannel(m.Channel(), text, opts.MaxTextLen) { - if len(t) > 0 { - parts = append(parts, MsgPart{Type: MsgPartTypeText, Text: t}) - } - } - } - - if len(parts) > 0 { - parts[0].IsFirst = true - parts[len(parts)-1].IsLast = true - } - - return parts -} - -// deprecated use SplitMsg instead -func SplitMsgByChannel(channel courier.Channel, text string, maxLength int) []string { - max := channel.IntConfigForKey(courier.ConfigMaxLength, maxLength) - - return SplitText(text, max) -} - -// SplitText splits the passed in string into segments that are at most max length -func SplitText(text string, max int) []string { - // smaller than our max, just return it - if len(text) <= max { - return []string{text} - } - - parts := make([]string, 0, 2) - part := bytes.Buffer{} - for _, r := range text { - part.WriteRune(r) - if part.Len() == max || (part.Len() > max-6 && r == ' ') { - parts = append(parts, strings.TrimSpace(part.String())) - part.Reset() - } - } - if part.Len() > 0 { - parts = append(parts, strings.TrimSpace(part.String())) - } - - return parts -} - // StrictTelForCountry wraps urns.NewURNTelForCountry but is stricter in // what it accepts. Incoming tels must be numeric or we will return an // error. (IE, alphanumeric shortcodes are not ok) diff --git a/handlers/utils_test.go b/handlers/utils_test.go index 9ac5dd966..51ef6643e 100644 --- a/handlers/utils_test.go +++ b/handlers/utils_test.go @@ -95,3 +95,32 @@ func TestIsURL(t *testing.T) { assert.Equal(t, tc.valid, handlers.IsURL(tc.text), "isURL mimatch for input %s", tc.text) } } + +var test6 = ` +SSByZWNlaXZlZCB5b3VyIGxldHRlciB0b2RheSwgaW4gd2hpY2ggeW91IHNheSB5b3Ugd2FudCB0 +byByZXNjdWUgTm9ydGggQ2Fyb2xpbmlhbnMgZnJvbSB0aGUgQUNBLCBvciBPYmFtYWNhcmUgYXMg +eW91IG9kZGx5IGluc2lzdCBvbiBjYWxsaW5nIGl0LiAKCkkgaGF2ZSB0byBjYWxsIHlvdXIgYXR0 +ZW50aW9uIHRvIHlvdXIgc2luIG9mIG9taXNzaW9uLiBZb3Ugc2F5IHRoYXQgd2UgYXJlIGRvd24g +dG8gb25lIGluc3VyZXIgYmVjYXVzZSBvZiBPYmFtYWNhcmUuIERpZCB5b3UgZm9yZ2V0IHRoYXQg +VGhlIEJhdGhyb29tIFN0YXRlIGhhcyBkb25lIGV2ZXJ5dGhpbmcgcG9zc2libGUgdG8gbWFrZSBU +aGUgQUNBIGZhaWw/ICBJbmNsdWRpbmcgbWlsbGlvbnMgb2YgZG9sbGFycyBmcm9tIHRoZSBmZWQ/ +CgpXZSBkb24ndCBuZWVkIHRvIGJlIHNhdmVkIGZyb20gYSBwcm9ncmFtIHRoYXQgaGFzIGhlbHBl +ZCB0aG91c2FuZHMuIFdlIG5lZWQgeW91IHRvIGJ1Y2tsZSBkb3duIGFuZCBpbXByb3ZlIHRoZSBB +Q0EuIFlvdSBoYWQgeWVhcnMgdG8gY29tZSB1cCB3aXRoIGEgcGxhbi4gWW91IGZhaWxlZC4gCgpU +aGUgbGF0ZXN0IHZlcnNpb24geW91ciBwYXJ0eSBoYXMgY29tZSB1cCB3aXRoIHVzIHdvcnNlIHRo +YW4gdGhlIGxhc3QuIFBsZWFzZSB2b3RlIGFnYWluc3QgaXQuIERvbid0IGNvbmRlbW4gdGhlIGdv +b2Qgb2YgcGVvcGxlIG9mIE5DIHRvIGxpdmVzIHRoYXQgYXJlIG5hc3R5LCBicnV0aXNoIGFuZCBz +aG9ydC4gSSdtIG9uZSBvZiB0aGUgZm9sa3Mgd2hvIHdpbGwgZGllIGlmIHlvdSByaXAgdGhlIHBy +b3RlY3Rpb25zIGF3YXkuIAoKVm90ZSBOTyBvbiBhbnkgYmlsbCB0aGF0IGRvZXNuJ3QgY29udGFp +biBwcm90ZWN0aW9ucyBpbnN0ZWFkIG9mIHB1bmlzaG1lbnRzLiBXZSBhcmUgd2F0Y2hpbmcgY2xv +c2VseS4g` + +func TestDecodePossibleBase64(t *testing.T) { + assert := assert.New(t) + assert.Equal("This test\nhas a newline", handlers.DecodePossibleBase64("This test\nhas a newline")) + assert.Equal("Please vote NO on the confirmation of Gorsuch.", handlers.DecodePossibleBase64("Please vote NO on the confirmation of Gorsuch.")) + assert.Equal("Bannon Explains The World ...\n“The Camp of the Saints", handlers.DecodePossibleBase64("QmFubm9uIEV4cGxhaW5zIFRoZSBXb3JsZCAuLi4K4oCcVGhlIENhbXAgb2YgdGhlIFNhaW50c+KA\r")) + assert.Equal("the sweat, the tears and the sacrifice of working America", handlers.DecodePossibleBase64("dGhlIHN3ZWF0LCB0aGUgdGVhcnMgYW5kIHRoZSBzYWNyaWZpY2Ugb2Ygd29ya2luZyBBbWVyaWNh\r")) + assert.Contains(handlers.DecodePossibleBase64("Tm93IGlzDQp0aGUgdGltZQ0KZm9yIGFsbCBnb29kDQpwZW9wbGUgdG8NCnJlc2lzdC4NCg0KSG93IGFib3V0IGhhaWt1cz8NCkkgZmluZCB0aGVtIHRvIGJlIGZyaWVuZGx5Lg0KcmVmcmlnZXJhdG9yDQoNCjAxMjM0NTY3ODkNCiFAIyQlXiYqKCkgW117fS09Xys7JzoiLC4vPD4/fFx+YA0KQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg=="), "I find them to be friendly") + assert.Contains(handlers.DecodePossibleBase64(test6), "I received your letter today") +} diff --git a/test/msg.go b/test/msg.go index 1f4b70379..84064c8a3 100644 --- a/test/msg.go +++ b/test/msg.go @@ -37,13 +37,14 @@ type MockMsg struct { sentOn *time.Time } -func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, urn urns.URN, text string) *MockMsg { +func NewMockMsg(id courier.MsgID, uuid courier.MsgUUID, channel courier.Channel, urn urns.URN, text string, attachments []string) *MockMsg { return &MockMsg{ - id: id, - uuid: uuid, - channel: channel, - urn: urn, - text: text, + id: id, + uuid: uuid, + channel: channel, + urn: urn, + text: text, + attachments: attachments, } } From 79ac0324c496c918304a3d944138fbce1317a5fd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Sep 2023 15:34:27 -0500 Subject: [PATCH 114/170] Implement sending opt-in requests for FBA channels --- backends/rapidpro/msg.go | 35 ++++++++++++++++---------------- handlers/meta/facebook_test.go | 11 ++++++++++ handlers/meta/handlers.go | 27 ++++++++++++++++--------- handlers/meta/messenger/api.go | 8 ++++++-- handlers/split.go | 27 ++++++++++++++----------- handlers/test.go | 4 ++++ msg.go | 6 ++++++ test/msg.go | 37 ++++++++++++++++++---------------- 8 files changed, 97 insertions(+), 58 deletions(-) diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 21cc5790e..11cb6aa87 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -31,9 +31,8 @@ type MsgDirection string // Possible values for MsgDirection const ( - MsgIncoming MsgDirection = "I" - MsgOutgoing MsgDirection = "O" - NilMsgDirection MsgDirection = "" + MsgIncoming MsgDirection = "I" + MsgOutgoing MsgDirection = "O" ) // MsgVisibility is the visibility of a message @@ -78,14 +77,15 @@ type Msg struct { LogUUIDs pq.StringArray ` db:"log_uuids"` // extra non-model fields that mailroom will include in queued payload - ChannelUUID_ courier.ChannelUUID `json:"channel_uuid"` - URN_ urns.URN `json:"urn"` - URNAuth_ string `json:"urn_auth"` - ResponseToExternalID_ string `json:"response_to_external_id"` - IsResend_ bool `json:"is_resend"` - Flow_ *courier.FlowReference `json:"flow"` - Origin_ courier.MsgOrigin `json:"origin"` - ContactLastSeenOn_ *time.Time `json:"contact_last_seen_on"` + ChannelUUID_ courier.ChannelUUID `json:"channel_uuid"` + URN_ urns.URN `json:"urn"` + URNAuth_ string `json:"urn_auth"` + ResponseToExternalID_ string `json:"response_to_external_id"` + IsResend_ bool `json:"is_resend"` + Flow_ *courier.FlowReference `json:"flow"` + OptIn_ *courier.OptInReference `json:"optin"` + Origin_ courier.MsgOrigin `json:"origin"` + ContactLastSeenOn_ *time.Time `json:"contact_last_seen_on"` // extra fields used to allow courier to update a session's timeout to *after* the message has been sent SessionID_ SessionID `json:"session_id"` @@ -158,12 +158,13 @@ func (m *Msg) Topic() string { func (m *Msg) Metadata() json.RawMessage { return m.Metadata_ } -func (m *Msg) ResponseToExternalID() string { return m.ResponseToExternalID_ } -func (m *Msg) SentOn() *time.Time { return m.SentOn_ } -func (m *Msg) IsResend() bool { return m.IsResend_ } -func (m *Msg) Flow() *courier.FlowReference { return m.Flow_ } -func (m *Msg) SessionStatus() string { return m.SessionStatus_ } -func (m *Msg) HighPriority() bool { return m.HighPriority_ } +func (m *Msg) ResponseToExternalID() string { return m.ResponseToExternalID_ } +func (m *Msg) SentOn() *time.Time { return m.SentOn_ } +func (m *Msg) IsResend() bool { return m.IsResend_ } +func (m *Msg) Flow() *courier.FlowReference { return m.Flow_ } +func (m *Msg) OptIn() *courier.OptInReference { return m.OptIn_ } +func (m *Msg) SessionStatus() string { return m.SessionStatus_ } +func (m *Msg) HighPriority() bool { return m.HighPriority_ } // incoming specific func (m *Msg) ReceivedOn() *time.Time { return m.SentOn_ } diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 1d888e380..622d9a377 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -455,6 +455,17 @@ var facebookOutgoingTests = []OutgoingTestCase{ ExpectedExternalID: "mid.133", SendPrep: setSendURL, }, + { + Label: "Opt-in request", + MsgURN: "facebook:12345", + MsgOptIn: &courier.OptInReference{UUID: "9f6651f2-e962-4367-b175-c07a6ff0f6dd", 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":"9f6651f2-e962-4367-b175-c07a6ff0f6dd"}}}}`, + ExpectedMsgStatus: "W", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, { Label: "Response doesn't contain message id", MsgText: "ID Error", diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 9c463b992..8eeac53ef 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -652,6 +652,13 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO isHuman := msg.Origin() == courier.MsgOriginChat || msg.Origin() == courier.MsgOriginTicket payload := &messenger.SendRequest{} + // build our recipient + if msg.URN().IsFacebookRef() { + payload.Recipient.UserRef = msg.URN().FacebookRef() + } else { + payload.Recipient.ID = msg.URN().Path() + } + if msg.Topic() != "" || isHuman { payload.MessagingType = "MESSAGE_TAG" @@ -669,13 +676,6 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO } } - // build our recipient - if msg.URN().IsFacebookRef() { - payload.Recipient.UserRef = msg.URN().FacebookRef() - } else { - payload.Recipient.ID = msg.URN().Path() - } - msgURL, _ := url.Parse(sendURL) query := url.Values{} query.Set("access_token", accessToken) @@ -686,8 +686,15 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO // 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}) { - if part.Type == handlers.MsgPartTypeAttachment { - // this is an attachment + if part.Type == handlers.MsgPartTypeOptIn { + payload.Message.Attachment = &messenger.Attachment{} + payload.Message.Attachment.Type = "template" + payload.Message.Attachment.Payload.TemplateType = "notification_messages" + payload.Message.Attachment.Payload.Title = part.OptIn.Name + payload.Message.Attachment.Payload.Payload = part.OptIn.UUID + payload.Message.Text = "" + + } else if part.Type == handlers.MsgPartTypeAttachment { payload.Message.Attachment = &messenger.Attachment{} attType, attURL := handlers.SplitAttachment(part.Attachment) attType = strings.Split(attType, "/")[0] @@ -698,8 +705,8 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO payload.Message.Attachment.Payload.URL = attURL payload.Message.Attachment.Payload.IsReusable = true payload.Message.Text = "" + } else { - // this is still a text part payload.Message.Text = part.Text payload.Message.Attachment = nil } diff --git a/handlers/meta/messenger/api.go b/handlers/meta/messenger/api.go index b6510c643..83d12ebdc 100644 --- a/handlers/meta/messenger/api.go +++ b/handlers/meta/messenger/api.go @@ -33,8 +33,12 @@ type SendRequest struct { type Attachment struct { Type string `json:"type"` Payload struct { - URL string `json:"url"` - IsReusable bool `json:"is_reusable"` + URL string `json:"url,omitempty"` + IsReusable bool `json:"is_reusable,omitempty"` + + TemplateType string `json:"template_type,omitempty"` + Title string `json:"title,omitempty"` + Payload string `json:"payload,omitempty"` } `json:"payload"` } diff --git a/handlers/split.go b/handlers/split.go index dbfd91605..2195ba908 100644 --- a/handlers/split.go +++ b/handlers/split.go @@ -14,6 +14,7 @@ const ( MsgPartTypeText MsgPartType = iota MsgPartTypeAttachment MsgPartTypeCaptionedAttachment + MsgPartTypeOptIn ) // MsgPart represents a message part - either Text or Attachment will be set @@ -21,6 +22,7 @@ type MsgPart struct { Type MsgPartType Text string Attachment string + OptIn *courier.OptInReference IsFirst bool IsLast bool } @@ -35,8 +37,10 @@ type SplitOptions struct { func SplitMsg(m courier.MsgOut, opts SplitOptions) []MsgPart { text := m.Text() attachments := m.Attachments() - parts := make([]MsgPart, 0, 5) - asCaption := false + + if m.OptIn() != nil { + return []MsgPart{{Type: MsgPartTypeOptIn, Text: text, OptIn: m.OptIn(), IsFirst: true, IsLast: true}} + } // if we have a single attachment and text we may be able to combine them into a captioned attachment if len(attachments) == 1 && len(text) > 0 && (len(text) <= opts.MaxCaptionLen || opts.MaxCaptionLen == 0) { @@ -44,19 +48,18 @@ func SplitMsg(m courier.MsgOut, opts SplitOptions) []MsgPart { mediaType, _ := SplitAttachment(att) mediaType = strings.Split(mediaType, "/")[0] if slices.Contains(opts.Captionable, MediaType(mediaType)) { - parts = append(parts, MsgPart{Type: MsgPartTypeCaptionedAttachment, Text: text, Attachment: attachments[0]}) - asCaption = true + return []MsgPart{{Type: MsgPartTypeCaptionedAttachment, Text: text, Attachment: attachments[0], IsFirst: true, IsLast: true}} } } - if !asCaption { - for _, a := range attachments { - parts = append(parts, MsgPart{Type: MsgPartTypeAttachment, Attachment: a}) - } - for _, t := range SplitMsgByChannel(m.Channel(), text, opts.MaxTextLen) { - if len(t) > 0 { - parts = append(parts, MsgPart{Type: MsgPartTypeText, Text: t}) - } + parts := make([]MsgPart, 0, 5) + + for _, a := range attachments { + parts = append(parts, MsgPart{Type: MsgPartTypeAttachment, Attachment: a}) + } + for _, t := range SplitMsgByChannel(m.Channel(), text, opts.MaxTextLen) { + if len(t) > 0 { + parts = append(parts, MsgPart{Type: MsgPartTypeText, Text: t}) } } diff --git a/handlers/test.go b/handlers/test.go index f6cd7f128..ddabc37f6 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -284,6 +284,7 @@ type OutgoingTestCase struct { MsgResponseToExternalID string MsgMetadata json.RawMessage MsgFlow *courier.FlowReference + MsgOptIn *courier.OptInReference MsgOrigin courier.MsgOrigin MsgContactLastSeenOn *time.Time @@ -342,6 +343,9 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier if tc.MsgFlow != nil { msg.WithFlow(tc.MsgFlow) } + if tc.MsgOptIn != nil { + msg.WithOptIn(tc.MsgOptIn) + } var testRequest *http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/msg.go b/msg.go index 7d5410d28..6b07d566d 100644 --- a/msg.go +++ b/msg.go @@ -37,6 +37,11 @@ type FlowReference struct { Name string `json:"name"` } +type OptInReference struct { + UUID string `json:"uuid" validate:"uuid4"` + Name string `json:"name"` +} + type MsgOrigin string const ( @@ -79,6 +84,7 @@ type MsgOut interface { SentOn() *time.Time IsResend() bool Flow() *FlowReference + OptIn() *OptInReference SessionStatus() string HighPriority() bool } diff --git a/test/msg.go b/test/msg.go index 84064c8a3..c4fdad252 100644 --- a/test/msg.go +++ b/test/msg.go @@ -31,7 +31,8 @@ type MockMsg struct { alreadyWritten bool isResend bool - flow *courier.FlowReference + flow *courier.FlowReference + optIn *courier.OptInReference receivedOn *time.Time sentOn *time.Time @@ -58,19 +59,20 @@ func (m *MockMsg) URN() urns.URN { return m.urn } func (m *MockMsg) Channel() courier.Channel { return m.channel } // outgoing specific -func (m *MockMsg) QuickReplies() []string { return m.quickReplies } -func (m *MockMsg) Locale() i18n.Locale { return m.locale } -func (m *MockMsg) URNAuth() string { return m.urnAuth } -func (m *MockMsg) Origin() courier.MsgOrigin { return m.origin } -func (m *MockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } -func (m *MockMsg) Topic() string { return m.topic } -func (m *MockMsg) Metadata() json.RawMessage { return m.metadata } -func (m *MockMsg) ResponseToExternalID() string { return m.responseToExternalID } -func (m *MockMsg) SentOn() *time.Time { return m.sentOn } -func (m *MockMsg) IsResend() bool { return m.isResend } -func (m *MockMsg) Flow() *courier.FlowReference { return m.flow } -func (m *MockMsg) SessionStatus() string { return "" } -func (m *MockMsg) HighPriority() bool { return m.highPriority } +func (m *MockMsg) QuickReplies() []string { return m.quickReplies } +func (m *MockMsg) Locale() i18n.Locale { return m.locale } +func (m *MockMsg) URNAuth() string { return m.urnAuth } +func (m *MockMsg) Origin() courier.MsgOrigin { return m.origin } +func (m *MockMsg) ContactLastSeenOn() *time.Time { return m.contactLastSeenOn } +func (m *MockMsg) Topic() string { return m.topic } +func (m *MockMsg) Metadata() json.RawMessage { return m.metadata } +func (m *MockMsg) ResponseToExternalID() string { return m.responseToExternalID } +func (m *MockMsg) SentOn() *time.Time { return m.sentOn } +func (m *MockMsg) IsResend() bool { return m.isResend } +func (m *MockMsg) Flow() *courier.FlowReference { return m.flow } +func (m *MockMsg) OptIn() *courier.OptInReference { return m.optIn } +func (m *MockMsg) SessionStatus() string { return "" } +func (m *MockMsg) HighPriority() bool { return m.highPriority } // incoming specific func (m *MockMsg) ReceivedOn() *time.Time { return m.receivedOn } @@ -92,6 +94,7 @@ func (m *MockMsg) WithMetadata(metadata json.RawMessage) courier.MsgOut { m.metadata = metadata return m } -func (m *MockMsg) WithFlow(flow *courier.FlowReference) courier.MsgOut { m.flow = flow; return m } -func (m *MockMsg) WithLocale(lc i18n.Locale) courier.MsgOut { m.locale = lc; return m } -func (m *MockMsg) WithURNAuth(token string) courier.MsgOut { m.urnAuth = token; return m } +func (m *MockMsg) WithFlow(flow *courier.FlowReference) courier.MsgOut { m.flow = flow; return m } +func (m *MockMsg) WithOptIn(optIn *courier.OptInReference) courier.MsgOut { m.optIn = optIn; return m } +func (m *MockMsg) WithLocale(lc i18n.Locale) courier.MsgOut { m.locale = lc; return m } +func (m *MockMsg) WithURNAuth(token string) courier.MsgOut { m.urnAuth = token; return m } From bbed4a373005fc6e87ebfaa4943719b2285ccf58 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Sep 2023 15:41:28 -0500 Subject: [PATCH 115/170] Fix queueing of optin/optout events to mailroom --- backends/rapidpro/task.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index 2b84453cd..76dc5ad11 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -76,6 +76,28 @@ func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { } return queueMailroomTask(rc, "new_conversation", e.OrgID_, e.ContactID_, body) + case courier.EventTypeOptIn: + body := map[string]any{ + "org_id": e.OrgID_, + "contact_id": e.ContactID_, + "urn_id": e.ContactURNID_, + "channel_id": e.ChannelID_, + "extra": e.Extra(), + "occurred_on": e.OccurredOn_, + } + return queueMailroomTask(rc, "optin", e.OrgID_, e.ContactID_, body) + + case courier.EventTypeOptOut: + body := map[string]any{ + "org_id": e.OrgID_, + "contact_id": e.ContactID_, + "urn_id": e.ContactURNID_, + "channel_id": e.ChannelID_, + "extra": e.Extra(), + "occurred_on": e.OccurredOn_, + } + return queueMailroomTask(rc, "optout", e.OrgID_, e.ContactID_, body) + default: return fmt.Errorf("unknown event type: %s", e.EventType()) } From 6765f07bd68a8cdc73319983336ceddf8848e1f9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Sep 2023 15:48:23 -0500 Subject: [PATCH 116/170] Update CHANGELOG.md for v8.3.19 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c581e6451..ae969cdf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.19 (2023-09-20) +------------------------- + * Fix queueing of optin/optout events to mailroom + * Implement sending opt-in requests for FBA channels + * Simplfy handlers splitting up messages + v8.3.18 (2023-09-18) ------------------------- * Add separate MsgIn and MsgOut interface types From 21cc64ba391c4e47152822e36f5ab0fddc13077b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 21 Sep 2023 13:44:47 -0500 Subject: [PATCH 117/170] Switch to using optin ids instead of uuids --- backends/rapidpro/backend_test.go | 8 +++---- backends/rapidpro/channel_event.go | 8 +++---- channel_event.go | 4 ++-- handlers/facebook_legacy/handler.go | 6 ++--- handlers/facebook_legacy/handler_test.go | 12 +++++----- handlers/meta/facebook_test.go | 22 +++++++++---------- handlers/meta/handlers.go | 19 +++++++++------- handlers/meta/instagram_test.go | 2 +- .../fba/notification_messages_optin.json | 2 +- .../fba/notification_messages_optout.json | 2 +- handlers/test.go | 2 +- msg.go | 4 ++-- responses.go | 12 +++++----- test/channel_event.go | 6 ++--- 14 files changed, 56 insertions(+), 53 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 72b26bb1d..12727d383 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1265,7 +1265,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}).WithContactName("kermit frog") + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}).WithContactName("kermit frog") err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) @@ -1277,7 +1277,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.EventTypeReferral) - ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) } @@ -1305,7 +1305,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1319,7 +1319,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) ts.NoError(err) ts.Equal(dbE.EventType_, courier.EventTypeReferral) - ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index 48f884081..78fcdef7f 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -43,7 +43,7 @@ type ChannelEvent struct { ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` URN_ urns.URN `json:"urn" db:"urn"` EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` - Extra_ null.Map[string] `json:"extra" db:"extra"` + Extra_ null.Map[any] `json:"extra" db:"extra"` OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` CreatedOn_ time.Time `json:"created_on" db:"created_on"` LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` @@ -82,7 +82,7 @@ func (e *ChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID func (e *ChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } func (e *ChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } func (e *ChannelEvent) URN() urns.URN { return e.URN_ } -func (e *ChannelEvent) Extra() map[string]string { return e.Extra_ } +func (e *ChannelEvent) Extra() map[string]any { return e.Extra_ } func (e *ChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } func (e *ChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } func (e *ChannelEvent) Channel() *Channel { return e.channel } @@ -97,8 +97,8 @@ func (e *ChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.Chann return e } -func (e *ChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { - e.Extra_ = null.Map[string](extra) +func (e *ChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { + e.Extra_ = null.Map[any](extra) return e } diff --git a/channel_event.go b/channel_event.go index ef26fa617..f93ff9627 100644 --- a/channel_event.go +++ b/channel_event.go @@ -30,12 +30,12 @@ type ChannelEvent interface { ChannelUUID() ChannelUUID URN() urns.URN EventType() ChannelEventType - Extra() map[string]string + Extra() map[string]any CreatedOn() time.Time OccurredOn() time.Time WithContactName(name string) ChannelEvent WithURNAuthTokens(tokens map[string]string) ChannelEvent - WithExtra(extra map[string]string) ChannelEvent + WithExtra(extra map[string]any) ChannelEvent WithOccurredOn(time.Time) ChannelEvent } diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index 6ca628e7d..2a1267ca5 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -273,7 +273,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]string{ + extra := map[string]any{ referrerIDKey: msg.OptIn.Ref, } event = event.WithExtra(extra) @@ -295,7 +295,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]string{ + extra := map[string]any{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -326,7 +326,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]string{ + extra := map[string]any{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go index 81d2f524c..ed62a4c6b 100644 --- a/handlers/facebook_legacy/handler_test.go +++ b/handlers/facebook_legacy/handler_test.go @@ -482,7 +482,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, }, }, { @@ -492,7 +492,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, }, }, { @@ -502,7 +502,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started"}}, }, }, { @@ -512,7 +512,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, }, { @@ -522,7 +522,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, }, { @@ -532,7 +532,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, }, { diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 622d9a377..ae29a519b 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -100,7 +100,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -111,7 +111,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -122,9 +122,9 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": 3456, "optin_name": "Bird Facts"}}, }, - ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:2fad015d-2126-4ac2-a008-b5ac95c3906b": "12345678901234567890"}}, + ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:3456": "12345678901234567890"}}, PrepRequest: addValidSignature, }, { @@ -134,7 +134,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"optin_uuid": "2fad015d-2126-4ac2-a008-b5ac95c3906b", "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": 3456, "optin_name": "Bird Facts"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, PrepRequest: addValidSignature, @@ -146,7 +146,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, @@ -157,7 +157,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, PrepRequest: addValidSignature, }, @@ -168,7 +168,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, @@ -179,7 +179,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, @@ -458,10 +458,10 @@ var facebookOutgoingTests = []OutgoingTestCase{ { Label: "Opt-in request", MsgURN: "facebook:12345", - MsgOptIn: &courier.OptInReference{UUID: "9f6651f2-e962-4367-b175-c07a6ff0f6dd", Name: "Joke Of The Day"}, + 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":"9f6651f2-e962-4367-b175-c07a6ff0f6dd"}}}}`, + 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", ExpectedExternalID: "mid.133", SendPrep: setSendURL, diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 8eeac53ef..e5c92fb09 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -436,9 +436,12 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if msg.OptIn.Type == "notification_messages" { eventType := courier.EventTypeOptIn - optInName := msg.OptIn.Title - optInUUID := msg.OptIn.Payload authToken := msg.OptIn.NotificationMessagesToken + optInName := msg.OptIn.Title + optInID, err := strconv.Atoi(msg.OptIn.Payload) + if err != nil { + return nil, nil, err + } if msg.OptIn.NotificationMessagesStatus == "STOP_NOTIFICATIONS" { eventType = courier.EventTypeOptOut @@ -447,8 +450,8 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, eventType, urn, clog). WithOccurredOn(date). - WithExtra(map[string]string{"optin_uuid": optInUUID, "optin_name": optInName}). - WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInUUID): authToken}) + WithExtra(map[string]any{"optin_id": optInID, "optin_name": optInName}). + WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%d", optInID): authToken}) } else { // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) @@ -466,7 +469,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog). WithOccurredOn(date). - WithExtra(map[string]string{ + WithExtra(map[string]any{ referrerIDKey: msg.OptIn.Ref, }) } @@ -488,7 +491,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]string{ + extra := map[string]any{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -519,7 +522,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]string{ + extra := map[string]any{ sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type, } @@ -691,7 +694,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO payload.Message.Attachment.Type = "template" payload.Message.Attachment.Payload.TemplateType = "notification_messages" payload.Message.Attachment.Payload.Title = part.OptIn.Name - payload.Message.Attachment.Payload.Payload = part.OptIn.UUID + payload.Message.Attachment.Payload.Payload = fmt.Sprint(part.OptIn.ID) payload.Message.Text = "" } else if part.Type == handlers.MsgPartTypeAttachment { diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 24b829bcd..25371e148 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -86,7 +86,7 @@ var instagramIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "icebreaker question", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, diff --git a/handlers/meta/testdata/fba/notification_messages_optin.json b/handlers/meta/testdata/fba/notification_messages_optin.json index 112bb0fb1..2f1fcd920 100644 --- a/handlers/meta/testdata/fba/notification_messages_optin.json +++ b/handlers/meta/testdata/fba/notification_messages_optin.json @@ -15,7 +15,7 @@ "timestamp": 1459991487970, "optin": { "type": "notification_messages", - "payload": "2fad015d-2126-4ac2-a008-b5ac95c3906b", + "payload": "3456", "notification_messages_token": "12345678901234567890", "notification_messages_frequency": "DAILY", "token_expiry_timestamp": 2145916800000, diff --git a/handlers/meta/testdata/fba/notification_messages_optout.json b/handlers/meta/testdata/fba/notification_messages_optout.json index 14746b875..45a66a51e 100644 --- a/handlers/meta/testdata/fba/notification_messages_optout.json +++ b/handlers/meta/testdata/fba/notification_messages_optout.json @@ -15,7 +15,7 @@ }, "optin": { "type": "notification_messages", - "payload": "2fad015d-2126-4ac2-a008-b5ac95c3906b", + "payload": "3456", "notification_messages_token": "12345678901234567890", "notification_messages_frequency": "DAILY", "token_expiry_timestamp": 2145916800000, diff --git a/handlers/test.go b/handlers/test.go index ddabc37f6..bdc4482f4 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -40,7 +40,7 @@ type ExpectedEvent struct { Type courier.ChannelEventType URN urns.URN Time time.Time - Extra map[string]string + Extra map[string]any } // IncomingTestCase defines the test values for a particular test case diff --git a/msg.go b/msg.go index 6b07d566d..c7f36ae7a 100644 --- a/msg.go +++ b/msg.go @@ -38,8 +38,8 @@ type FlowReference struct { } type OptInReference struct { - UUID string `json:"uuid" validate:"uuid4"` - Name string `json:"name"` + ID int64 `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` } type MsgOrigin string diff --git a/responses.go b/responses.go index 665d1e097..77e07cb3c 100644 --- a/responses.go +++ b/responses.go @@ -100,12 +100,12 @@ func NewMsgReceiveData(msg MsgIn) MsgReceiveData { // EventReceiveData is our response payload for a channel event type EventReceiveData struct { - Type string `json:"type"` - ChannelUUID ChannelUUID `json:"channel_uuid"` - EventType ChannelEventType `json:"event_type"` - URN urns.URN `json:"urn"` - ReceivedOn time.Time `json:"received_on"` - Extra map[string]string `json:"extra,omitempty"` + Type string `json:"type"` + ChannelUUID ChannelUUID `json:"channel_uuid"` + EventType ChannelEventType `json:"event_type"` + URN urns.URN `json:"urn"` + ReceivedOn time.Time `json:"received_on"` + Extra map[string]any `json:"extra,omitempty"` } // NewEventReceiveData creates a new receive data for the passed in event diff --git a/test/channel_event.go b/test/channel_event.go index 177f5300c..a65f8a2d6 100644 --- a/test/channel_event.go +++ b/test/channel_event.go @@ -16,7 +16,7 @@ type mockChannelEvent struct { contactName string urnAuthTokens map[string]string - extra map[string]string + extra map[string]any } func (e *mockChannelEvent) EventID() int64 { return 0 } @@ -24,11 +24,11 @@ func (e *mockChannelEvent) ChannelUUID() courier.ChannelUUID { return e.chann func (e *mockChannelEvent) EventType() courier.ChannelEventType { return e.eventType } func (e *mockChannelEvent) CreatedOn() time.Time { return e.createdOn } func (e *mockChannelEvent) OccurredOn() time.Time { return e.occurredOn } -func (e *mockChannelEvent) Extra() map[string]string { return e.extra } +func (e *mockChannelEvent) Extra() map[string]any { return e.extra } func (e *mockChannelEvent) ContactName() string { return e.contactName } func (e *mockChannelEvent) URN() urns.URN { return e.urn } -func (e *mockChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { +func (e *mockChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { e.extra = extra return e } From 32135e9d97e257bdcd348ea59c6d735831731232 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 21 Sep 2023 14:05:36 -0500 Subject: [PATCH 118/170] Update CHANGELOG.md for v8.3.20 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae969cdf5..fa850632b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.20 (2023-09-21) +------------------------- + * Switch to using optin ids instead of uuids + v8.3.19 (2023-09-20) ------------------------- * Fix queueing of optin/optout events to mailroom From 1b4810e3d96b9489b3ddf7dc0ab4cf4982244eb9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 22 Sep 2023 14:18:47 -0500 Subject: [PATCH 119/170] Support sending facebook message with opt-in auth token --- handlers/meta/facebook_test.go | 13 +++++++++++++ handlers/meta/handlers.go | 2 ++ handlers/meta/messenger/api.go | 5 +++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index ae29a519b..a0ede7c00 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -366,6 +366,19 @@ var facebookOutgoingTests = []OutgoingTestCase{ ExpectedExternalID: "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", + ExpectedExternalID: "mid.133", + SendPrep: setSendURL, + }, { Label: "Text only flow response", MsgText: "Simple Message", diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index e5c92fb09..28226ca76 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -658,6 +658,8 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO // build our recipient if msg.URN().IsFacebookRef() { payload.Recipient.UserRef = msg.URN().FacebookRef() + } else if msg.URNAuth() != "" { + payload.Recipient.NotificationMessagesToken = msg.URNAuth() } else { payload.Recipient.ID = msg.URN().Path() } diff --git a/handlers/meta/messenger/api.go b/handlers/meta/messenger/api.go index 83d12ebdc..8592fce59 100644 --- a/handlers/meta/messenger/api.go +++ b/handlers/meta/messenger/api.go @@ -20,8 +20,9 @@ type SendRequest struct { MessagingType string `json:"messaging_type"` Tag string `json:"tag,omitempty"` Recipient struct { - UserRef string `json:"user_ref,omitempty"` - ID string `json:"id,omitempty"` + UserRef string `json:"user_ref,omitempty"` + ID string `json:"id,omitempty"` + NotificationMessagesToken string `json:"notification_messages_token,omitempty"` } `json:"recipient"` Message struct { Text string `json:"text,omitempty"` From 4fd6d77a900061af1f83decc124ccdbe595d7769 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 25 Sep 2023 08:48:46 -0500 Subject: [PATCH 120/170] Update CHANGELOG.md for v8.3.21 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa850632b..92533a5d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.21 (2023-09-25) +------------------------- + * Support sending facebook message with opt-in auth token + v8.3.20 (2023-09-21) ------------------------- * Switch to using optin ids instead of uuids From 532f12ef048b89af6f808f9655ef87218bf87579 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 21 Sep 2023 08:54:57 +0200 Subject: [PATCH 121/170] Use Facebook API v17.0 --- handlers/meta/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 28226ca76..59c0af62e 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -26,8 +26,8 @@ import ( // Endpoints we hit var ( - sendURL = "https://graph.facebook.com/v12.0/me/messages" - graphURL = "https://graph.facebook.com/v12.0/" + sendURL = "https://graph.facebook.com/v17.0/me/messages" + graphURL = "https://graph.facebook.com/v17.0/" signatureHeader = "X-Hub-Signature-256" From e4f20bf57ecdabede0fd82d28a7f122214c41770 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Sep 2023 08:47:37 -0500 Subject: [PATCH 122/170] Update CHANGELOG.md for v8.3.22 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92533a5d0..3e0e417ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.22 (2023-09-27) +------------------------- + * Use Facebook API v17.0 + v8.3.21 (2023-09-25) ------------------------- * Support sending facebook message with opt-in auth token From 5f0f100ea2bbc6a5de35f7f1622b912120ab21fc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 29 Sep 2023 15:34:04 -0500 Subject: [PATCH 123/170] Allow outgoing tests to check multiple requests --- handlers/africastalking/handler_test.go | 29 ++++-- handlers/arabiacell/handler_test.go | 21 ++-- handlers/bandwidth/handler_test.go | 80 +++++++++------ handlers/bongolive/handler_test.go | 39 +++++--- handlers/burstsms/handler_test.go | 13 ++- handlers/i2sms/handler_test.go | 15 ++- handlers/test.go | 123 ++++++++++++++++-------- handlers/twiml/handlers_test.go | 32 +++--- 8 files changed, 226 insertions(+), 126 deletions(-) diff --git a/handlers/africastalking/handler_test.go b/handlers/africastalking/handler_test.go index 0bfdf2580..42c257766 100644 --- a/handlers/africastalking/handler_test.go +++ b/handlers/africastalking/handler_test.go @@ -2,6 +2,7 @@ package africastalking import ( "net/http/httptest" + "net/url" "testing" "time" @@ -123,7 +124,9 @@ var outgoingTestCases = []OutgoingTestCase{ MockResponseBody: `{ "SMSMessageData": {"Recipients": [{"status": "Success", "messageId": "1002"}] } }`, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"apikey": "KEY"}, - ExpectedPostParams: map[string]string{"message": "Simple Message ☺", "username": "Username", "to": "+250788383383", "from": "2020"}, + ExpectedRequests: []ExpectedRequest{ + {Form: url.Values{"message": {"Simple Message ☺"}, "username": {"Username"}, "to": {"+250788383383"}, "from": {"2020"}}}, + }, ExpectedMsgStatus: "W", ExpectedExternalID: "1002", SendPrep: setSendURL, @@ -135,7 +138,9 @@ var outgoingTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{ "SMSMessageData": {"Recipients": [{"status": "Success", "messageId": "1002"}] } }`, MockResponseStatus: 200, - ExpectedPostParams: map[string]string{"message": "My pic!\nhttps://foo.bar/image.jpg"}, + ExpectedRequests: []ExpectedRequest{ + {Form: url.Values{"message": {"My pic!\nhttps://foo.bar/image.jpg"}, "username": {"Username"}, "to": {"+250788383383"}, "from": {"2020"}}}, + }, ExpectedMsgStatus: "W", ExpectedExternalID: "1002", SendPrep: setSendURL, @@ -146,9 +151,11 @@ var outgoingTestCases = []OutgoingTestCase{ MsgURN: "tel:+250788383383", MockResponseBody: `{ "SMSMessageData": {"Recipients": [{"status": "Failed" }] } }`, MockResponseStatus: 200, - ExpectedPostParams: map[string]string{"message": `No External ID`}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{ + {Form: url.Values{"message": {`No External ID`}, "username": {"Username"}, "to": {"+250788383383"}, "from": {"2020"}}}, + }, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, }, { Label: "Error Sending", @@ -156,9 +163,11 @@ var outgoingTestCases = []OutgoingTestCase{ MsgURN: "tel:+250788383383", MockResponseBody: `{ "error": "failed" }`, MockResponseStatus: 401, - ExpectedPostParams: map[string]string{"message": `Error Message`}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, + ExpectedRequests: []ExpectedRequest{ + {Form: url.Values{"message": {`Error Message`}, "username": {"Username"}, "to": {"+250788383383"}, "from": {"2020"}}}, + }, + ExpectedMsgStatus: "E", + SendPrep: setSendURL, }, } @@ -170,7 +179,9 @@ var sharedSendTestCases = []OutgoingTestCase{ MockResponseBody: `{ "SMSMessageData": {"Recipients": [{"status": "Success", "messageId": "1002"}] } }`, MockResponseStatus: 200, ExpectedHeaders: map[string]string{"apikey": "KEY"}, - ExpectedPostParams: map[string]string{"message": "Simple Message ☺", "username": "Username", "to": "+250788383383", "from": ""}, + ExpectedRequests: []ExpectedRequest{ + {Form: url.Values{"message": {"Simple Message ☺"}, "username": {"Username"}, "to": {"+250788383383"}}}, + }, ExpectedMsgStatus: "W", ExpectedExternalID: "1002", SendPrep: setSendURL, diff --git a/handlers/arabiacell/handler_test.go b/handlers/arabiacell/handler_test.go index 96934ba98..22e8b3e64 100644 --- a/handlers/arabiacell/handler_test.go +++ b/handlers/arabiacell/handler_test.go @@ -2,6 +2,7 @@ package arabiacell import ( "net/http/httptest" + "net/url" "testing" "github.com/nyaruka/courier" @@ -60,14 +61,18 @@ var defaultSendTestCases = []OutgoingTestCase{ external1 `, MockResponseStatus: 200, - ExpectedPostParams: map[string]string{ - "userName": "user1", - "password": "pass1", - "handlerType": "send_msg", - "serviceId": "service1", - "msisdn": "+250788383383", - "messageBody": "Simple Message ☺\nhttps://foo.bar/image.jpg", - "chargingLevel": "0", + ExpectedRequests: []ExpectedRequest{ + { + Form: url.Values{ + "userName": {"user1"}, + "password": {"pass1"}, + "handlerType": {"send_msg"}, + "serviceId": {"service1"}, + "msisdn": {"+250788383383"}, + "messageBody": {"Simple Message ☺\nhttps://foo.bar/image.jpg"}, + "chargingLevel": {"0"}, + }, + }, }, ExpectedMsgStatus: "W", ExpectedExternalID: "external1", diff --git a/handlers/bandwidth/handler_test.go b/handlers/bandwidth/handler_test.go index 94199d6b9..5707ae0d2 100644 --- a/handlers/bandwidth/handler_test.go +++ b/handlers/bandwidth/handler_test.go @@ -258,15 +258,19 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgURN: "tel:+12067791234", MockResponseBody: `{"id": "55555"}`, MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic dXNlcjE6cGFzczE=", + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic dXNlcjE6cGFzczE=", + }, + Body: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"Simple Message ☺"}`, + }, }, - ExpectedRequestBody: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"Simple Message ☺"}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "55555", - SendPrep: setSendURL, + ExpectedMsgStatus: "W", + ExpectedExternalID: "55555", + SendPrep: setSendURL, }, { Label: "Send Attachment", @@ -275,15 +279,19 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{"id": "55555"}`, MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic dXNlcjE6cGFzczE=", + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic dXNlcjE6cGFzczE=", + }, + Body: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"My pic!","media":["https://foo.bar/image.jpg"]}`, + }, }, - ExpectedRequestBody: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"My pic!","media":["https://foo.bar/image.jpg"]}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "55555", - SendPrep: setSendURL, + ExpectedMsgStatus: "W", + ExpectedExternalID: "55555", + SendPrep: setSendURL, }, { Label: "No External ID", @@ -291,15 +299,19 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgURN: "tel:+12067791234", MockResponseBody: `{}`, MockResponseStatus: 200, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic dXNlcjE6cGFzczE=", + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic dXNlcjE6cGFzczE=", + }, + Body: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"No External ID"}`, + }, }, - ExpectedRequestBody: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"No External ID"}`, - ExpectedMsgStatus: "W", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("id")}, - SendPrep: setSendURL, + ExpectedMsgStatus: "W", + ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("id")}, + SendPrep: setSendURL, }, { Label: "Error Sending", @@ -307,15 +319,19 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgURN: "tel:+12067791234", MockResponseBody: `{ "type": "request-validation", "description": "Your request could not be accepted" }`, MockResponseStatus: 401, - ExpectedHeaders: map[string]string{ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": "Basic dXNlcjE6cGFzczE=", + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic dXNlcjE6cGFzczE=", + }, + Body: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"Error Message"}`, + }, }, - ExpectedRequestBody: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"Error Message"}`, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("request-validation", "Your request could not be accepted")}, - SendPrep: setSendURL, + ExpectedMsgStatus: "E", + ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("request-validation", "Your request could not be accepted")}, + SendPrep: setSendURL, }, } diff --git a/handlers/bongolive/handler_test.go b/handlers/bongolive/handler_test.go index 83e13f832..4689add5f 100644 --- a/handlers/bongolive/handler_test.go +++ b/handlers/bongolive/handler_test.go @@ -2,6 +2,7 @@ package bongolive import ( "net/http/httptest" + "net/url" "testing" "github.com/nyaruka/courier" @@ -94,13 +95,18 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{"results": [{"status": "0", "msgid": "123"}]}`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{ - "USERNAME": "user1", - "PASSWORD": "pass1", - "SOURCEADDR": "2020", - "DESTADDR": "250788383383", - "DLR": "1", - "MESSAGE": "Simple Message ☺\nhttps://foo.bar/image.jpg", + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{ + "USERNAME": {"user1"}, + "PASSWORD": {"pass1"}, + "SOURCEADDR": {"2020"}, + "DESTADDR": {"250788383383"}, + "DLR": {"1"}, + "MESSAGE": {"Simple Message ☺\nhttps://foo.bar/image.jpg"}, + "CHARCODE": {"2"}, + }, + }, }, ExpectedMsgStatus: "W", ExpectedExternalID: "123", @@ -113,13 +119,18 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{"results": [{"status": "3"}]}`, MockResponseStatus: 200, - ExpectedURLParams: map[string]string{ - "USERNAME": "user1", - "PASSWORD": "pass1", - "SOURCEADDR": "2020", - "DESTADDR": "250788383383", - "DLR": "1", - "MESSAGE": "Simple Message ☺\nhttps://foo.bar/image.jpg", + ExpectedRequests: []ExpectedRequest{ + { + Params: url.Values{ + "USERNAME": {"user1"}, + "PASSWORD": {"pass1"}, + "SOURCEADDR": {"2020"}, + "DESTADDR": {"250788383383"}, + "DLR": {"1"}, + "MESSAGE": {"Simple Message ☺\nhttps://foo.bar/image.jpg"}, + "CHARCODE": {"2"}, + }, + }, }, ExpectedMsgStatus: "E", SendPrep: setSendURL, diff --git a/handlers/burstsms/handler_test.go b/handlers/burstsms/handler_test.go index 750873bcc..eae25d0d9 100644 --- a/handlers/burstsms/handler_test.go +++ b/handlers/burstsms/handler_test.go @@ -2,6 +2,7 @@ package burstsms import ( "net/http/httptest" + "net/url" "testing" "github.com/nyaruka/courier" @@ -69,10 +70,14 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{ "message_id": 19835, "recipients": 3, "cost": 1.000 }`, MockResponseStatus: 200, - ExpectedPostParams: map[string]string{ - "to": "250788383383", - "message": "Simple Message ☺\nhttps://foo.bar/image.jpg", - "from": "2020", + ExpectedRequests: []ExpectedRequest{ + { + Form: url.Values{ + "to": {"250788383383"}, + "message": {"Simple Message ☺\nhttps://foo.bar/image.jpg"}, + "from": {"2020"}, + }, + }, }, ExpectedMsgStatus: "W", ExpectedExternalID: "19835", diff --git a/handlers/i2sms/handler_test.go b/handlers/i2sms/handler_test.go index f8853b153..799e0fd6d 100644 --- a/handlers/i2sms/handler_test.go +++ b/handlers/i2sms/handler_test.go @@ -2,6 +2,7 @@ package i2sms import ( "net/http/httptest" + "net/url" "testing" "github.com/nyaruka/courier" @@ -57,11 +58,15 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{"result":{"session_id":"5b8fc97d58795484819426"}, "error_code": "00", "error_desc": "Success"}`, MockResponseStatus: 200, - ExpectedPostParams: map[string]string{ - "action": "send_single", - "mobile": "250788383383", - "message": "Simple Message ☺\nhttps://foo.bar/image.jpg", - "channel": "hash123", + ExpectedRequests: []ExpectedRequest{ + { + Form: url.Values{ + "action": {"send_single"}, + "mobile": {"250788383383"}, + "message": {"Simple Message ☺\nhttps://foo.bar/image.jpg"}, + "channel": {"hash123"}, + }, + }, }, ExpectedMsgStatus: "W", ExpectedExternalID: "5b8fc97d58795484819426", diff --git a/handlers/test.go b/handlers/test.go index bdc4482f4..9cee2259f 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -268,6 +268,36 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour // SendPrepFunc allows test cases to modify the channel, msg or server before a message is sent type SendPrepFunc func(*httptest.Server, courier.ChannelHandler, courier.Channel, courier.MsgOut) +type ExpectedRequest struct { + Headers map[string]string + Path string + Params url.Values + Form url.Values + Body string +} + +func (e *ExpectedRequest) AssertMatches(t *testing.T, actual *http.Request, requestNum int) { + if e.Headers != nil { + for k, v := range e.Headers { + assert.Equal(t, v, actual.Header.Get(k), "header %s mismatch for request %d", k, requestNum) + } + } + if e.Path != "" { + assert.Equal(t, e.Path, actual.URL.Path, "patch mismatch for request %d", requestNum) + } + if e.Params != nil { + assert.Equal(t, e.Params, actual.URL.Query(), "URL params mismatch for request %d", requestNum) + } + if e.Form != nil { + actual.ParseMultipartForm(32 << 20) + assert.Equal(t, e.Form, actual.PostForm, "form mismatch for request %d", requestNum) + } + if e.Body != "" { + value, _ := io.ReadAll(actual.Body) + assert.Equal(t, e.Body, strings.Trim(string(value), "\n"), "body mismatch for request %d", requestNum) + } +} + // OutgoingTestCase defines the test values for a particular test case type OutgoingTestCase struct { Label string @@ -292,18 +322,20 @@ type OutgoingTestCase struct { MockResponseBody string MockResponses map[MockedRequest]*httpx.MockResponse - ExpectedRequestPath string - ExpectedURLParams map[string]string - ExpectedPostParams map[string]string // deprecated, use ExpectedPostForm - ExpectedPostForm url.Values - ExpectedRequestBody string - ExpectedHeaders map[string]string + ExpectedRequests []ExpectedRequest ExpectedMsgStatus courier.MsgStatus ExpectedExternalID string ExpectedErrors []*courier.ChannelError ExpectedStopEvent bool ExpectedContactURNs map[string]bool ExpectedNewURN string + + // deprecated, use ExpectedRequests + ExpectedRequestPath string + ExpectedURLParams map[string]string + ExpectedPostParams map[string]string + ExpectedRequestBody string + ExpectedHeaders map[string]string } // RunOutgoingTestCases runs all the passed in test cases against the channel @@ -347,11 +379,13 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier msg.WithOptIn(tc.MsgOptIn) } - var testRequest *http.Request + actualRequests := make([]*http.Request, 0, 1) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // copy request and add to list body, _ := io.ReadAll(r.Body) - testRequest = httptest.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(body)) - testRequest.Header = r.Header + copy := httptest.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(body)) + copy.Header = r.Header + actualRequests = append(actualRequests, copy) if (len(tc.MockResponses)) == 0 { w.WriteHeader(tc.MockResponseStatus) @@ -387,49 +421,54 @@ func RunOutgoingTestCases(t *testing.T, channel courier.Channel, handler courier assert.Equal(t, tc.ExpectedErrors, clog.Errors(), "unexpected errors logged") - if tc.ExpectedRequestPath != "" { - require.NotNil(testRequest, "path should not be nil") - require.Equal(tc.ExpectedRequestPath, testRequest.URL.Path) - } + if tc.ExpectedRequestPath != "" || tc.ExpectedURLParams != nil || tc.ExpectedPostParams != nil || tc.ExpectedRequestBody != "" || tc.ExpectedHeaders != nil { + testRequest := actualRequests[len(actualRequests)-1] - if tc.ExpectedURLParams != nil { - require.NotNil(testRequest) - for k, v := range tc.ExpectedURLParams { - value := testRequest.URL.Query().Get(k) - require.Equal(v, value, fmt.Sprintf("%s not equal", k)) + if tc.ExpectedRequestPath != "" { + require.NotNil(testRequest, "path should not be nil") + require.Equal(tc.ExpectedRequestPath, testRequest.URL.Path) } - } - - if tc.ExpectedPostParams != nil { - require.NotNil(testRequest, "post body should not be nil") - for k, v := range tc.ExpectedPostParams { - value := testRequest.PostFormValue(k) - require.Equal(v, value) + if tc.ExpectedURLParams != nil { + require.NotNil(testRequest) + for k, v := range tc.ExpectedURLParams { + value := testRequest.URL.Query().Get(k) + require.Equal(v, value, fmt.Sprintf("%s not equal", k)) + } } - } else if tc.ExpectedPostForm != nil { - require.NotNil(testRequest, "post body should not be nil") - testRequest.ParseMultipartForm(32 << 20) - assert.Equal(t, tc.ExpectedPostForm, testRequest.PostForm) - } + if tc.ExpectedPostParams != nil { + require.NotNil(testRequest, "post body should not be nil") + for k, v := range tc.ExpectedPostParams { + value := testRequest.PostFormValue(k) + require.Equal(v, value) + } + } + if tc.ExpectedRequestBody != "" { + require.NotNil(testRequest, "request body should not be nil") + value, _ := io.ReadAll(testRequest.Body) + require.Equal(tc.ExpectedRequestBody, strings.Trim(string(value), "\n")) + } + if tc.ExpectedHeaders != nil { + require.NotNil(testRequest, "headers should not be nil") + for k, v := range tc.ExpectedHeaders { + value := testRequest.Header.Get(k) + require.Equal(v, value) + } + } + } else if len(tc.ExpectedRequests) > 0 { + assert.Len(t, actualRequests, len(tc.ExpectedRequests), "unexpected number of requests made") - if tc.ExpectedRequestBody != "" { - require.NotNil(testRequest, "request body should not be nil") - value, _ := io.ReadAll(testRequest.Body) - require.Equal(tc.ExpectedRequestBody, strings.Trim(string(value), "\n")) + for i, expectedRequest := range tc.ExpectedRequests { + if (len(actualRequests) - 1) < i { + break + } + expectedRequest.AssertMatches(t, actualRequests[i], i) + } } if (len(tc.MockResponses)) != 0 { assert.Equal(t, len(tc.MockResponses), mockRRCount, "mocked request count mismatch") } - if tc.ExpectedHeaders != nil { - require.NotNil(testRequest, "headers should not be nil") - for k, v := range tc.ExpectedHeaders { - value := testRequest.Header.Get(k) - require.Equal(v, value) - } - } - if tc.ExpectedExternalID != "" { require.Equal(tc.ExpectedExternalID, status.ExternalID()) } diff --git a/handlers/twiml/handlers_test.go b/handlers/twiml/handlers_test.go index b5b6b678f..033b5e9c5 100644 --- a/handlers/twiml/handlers_test.go +++ b/handlers/twiml/handlers_test.go @@ -631,12 +631,16 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, MockResponseBody: `{ "sid": "1002" }`, MockResponseStatus: 200, - ExpectedPostForm: url.Values{ - "Body": []string{"My pic!"}, - "To": []string{"+250788383383"}, - "MediaUrl": []string{"https://foo.bar/image.jpg"}, - "From": []string{"2020"}, - "StatusCallback": []string{"https://localhost/c/t/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, + ExpectedRequests: []ExpectedRequest{ + { + Form: url.Values{ + "Body": []string{"My pic!"}, + "To": []string{"+250788383383"}, + "MediaUrl": []string{"https://foo.bar/image.jpg"}, + "From": []string{"2020"}, + "StatusCallback": []string{"https://localhost/c/t/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, + }, + }, }, ExpectedMsgStatus: "W", SendPrep: setSendURL, @@ -647,12 +651,16 @@ var defaultSendTestCases = []OutgoingTestCase{ MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg", "audio/mp4:https://foo.bar/audio.m4a"}, MockResponseBody: `{ "sid": "1002" }`, MockResponseStatus: 200, - ExpectedPostForm: url.Values{ - "Body": []string{""}, - "To": []string{"+250788383383"}, - "MediaUrl": []string{"https://foo.bar/image.jpg", "https://foo.bar/audio.m4a"}, - "From": []string{"2020"}, - "StatusCallback": []string{"https://localhost/c/t/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, + ExpectedRequests: []ExpectedRequest{ + { + Form: url.Values{ + "Body": []string{""}, + "To": []string{"+250788383383"}, + "MediaUrl": []string{"https://foo.bar/image.jpg", "https://foo.bar/audio.m4a"}, + "From": []string{"2020"}, + "StatusCallback": []string{"https://localhost/c/t/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status?id=10&action=callback"}, + }, + }, }, ExpectedMsgStatus: "W", SendPrep: setSendURL, From 89c7fa5015e86d827b020f5f9449a8b986fe9513 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Oct 2023 17:02:36 -0500 Subject: [PATCH 124/170] Cleanup some handler code --- handlers/bandwidth/handler.go | 6 ++---- handlers/dialog360/handler.go | 6 ++---- handlers/facebook_legacy/handler.go | 7 ++----- handlers/firebase/handler.go | 6 ++---- handlers/freshchat/handler.go | 7 ++----- handlers/messagebird/handler.go | 7 ++----- handlers/meta/handlers.go | 27 +++++++++++---------------- handlers/playmobile/handler.go | 8 ++------ handlers/rocketchat/handler.go | 7 ++----- handlers/twitter/handler.go | 7 ++----- handlers/wavy/handler.go | 7 ++----- handlers/whatsapp_legacy/handler.go | 18 ++++-------------- handlers/zenvia/handlers.go | 8 ++------ 13 files changed, 37 insertions(+), 84 deletions(-) diff --git a/handlers/bandwidth/handler.go b/handlers/bandwidth/handler.go index e7d231067..51aa15f31 100644 --- a/handlers/bandwidth/handler.go +++ b/handlers/bandwidth/handler.go @@ -13,6 +13,7 @@ import ( "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/jsonx" ) var ( @@ -215,10 +216,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Media = attachments } - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } + jsonBody := jsonx.MustMarshal(payload) // build our request req, err := http.NewRequest(http.MethodPost, fmt.Sprintf(sendURL, accountID), bytes.NewReader(jsonBody)) diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index 9cad1494a..742c9606f 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -16,6 +16,7 @@ import ( "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -590,10 +591,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } func requestD3C(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) if err != nil { diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index 2a1267ca5..207133dff 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -3,7 +3,6 @@ package facebook_legacy import ( "bytes" "context" - "encoding/json" "fmt" "net/http" "net/url" @@ -14,6 +13,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -540,10 +540,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Message.QuickReplies = nil } - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, msgURL.String(), bytes.NewReader(jsonBody)) if err != nil { diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go index 43df982ea..672cbc6cb 100644 --- a/handlers/firebase/handler.go +++ b/handlers/firebase/handler.go @@ -11,6 +11,7 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" ) @@ -185,10 +186,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.ContentAvailable = true } - jsonPayload, err := json.Marshal(payload) - if err != nil { - return nil, err - } + jsonPayload := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(jsonPayload)) if err != nil { diff --git a/handlers/freshchat/handler.go b/handlers/freshchat/handler.go index 50e81adaf..8cfd0cb4a 100644 --- a/handlers/freshchat/handler.go +++ b/handlers/freshchat/handler.go @@ -11,7 +11,6 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" "encoding/pem" "fmt" "io" @@ -21,6 +20,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" ) @@ -147,10 +147,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } } - jsonBody, err := json.Marshal(payload) - if err != nil { - return nil, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(jsonBody)) diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 9ba67ed4f..4b01076aa 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -9,7 +9,6 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "encoding/json" "strconv" "fmt" @@ -21,6 +20,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/sirupsen/logrus" ) @@ -216,10 +216,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.MediaURLs = append(payload.MediaURLs, mediaURL) } - jsonBody, err := json.Marshal(payload) - if err != nil { - return nil, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, sendUrl, bytes.NewReader(jsonBody)) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 59c0af62e..b9fc25119 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -20,6 +20,7 @@ import ( "github.com/nyaruka/courier/handlers/meta/messenger" "github.com/nyaruka/courier/handlers/meta/whatsapp" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -719,16 +720,13 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO // include any quick replies on the last piece we send if part.IsLast { for _, qr := range msg.QuickReplies() { - payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{qr, qr, "text"}) + payload.Message.QuickReplies = append(payload.Message.QuickReplies, messenger.QuickReply{Title: qr, Payload: qr, ContentType: "text"}) } } else { payload.Message.QuickReplies = nil } - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, msgURL.String(), bytes.NewReader(jsonBody)) if err != nil { @@ -1013,7 +1011,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog zeroIndex = true } payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} - status, err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil } @@ -1082,7 +1080,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog zeroIndex = true } - status, err := requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, err } @@ -1094,15 +1092,12 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog return status, nil } -func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } +func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) error { + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) if err != nil { - return nil, err + return err } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) @@ -1114,12 +1109,12 @@ func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) - return status, nil + return nil } if respPayload.Error.Code != 0 { clog.Error(courier.ErrorExternal(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message)) - return status, nil + return nil } externalID := respPayload.Messages[0].ID @@ -1128,7 +1123,7 @@ func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier } // this was wired successfully status.SetStatus(courier.MsgStatusWired) - return status, nil + return nil } // DescribeURN looks up URN metadata for new contacts diff --git a/handlers/playmobile/handler.go b/handlers/playmobile/handler.go index 22f0b20c7..ecce2fff0 100644 --- a/handlers/playmobile/handler.go +++ b/handlers/playmobile/handler.go @@ -3,7 +3,6 @@ package playmobile import ( "bytes" "context" - "encoding/json" "encoding/xml" "errors" "fmt" @@ -13,6 +12,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/jsonx" ) const ( @@ -183,11 +183,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch message.SMS.Content.Text = part payload.Messages = append(payload.Messages, message) - jsonBody, err := json.Marshal(payload) - - if err != nil { - return nil, err - } + jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, fmt.Sprintf(sendURL, baseURL), bytes.NewReader(jsonBody)) if err != nil { diff --git a/handlers/rocketchat/handler.go b/handlers/rocketchat/handler.go index a37e19f03..2937bdd41 100644 --- a/handlers/rocketchat/handler.go +++ b/handlers/rocketchat/handler.go @@ -3,7 +3,6 @@ package rocketchat import ( "bytes" "context" - "encoding/json" "errors" "fmt" "net/http" @@ -11,6 +10,7 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" ) @@ -123,10 +123,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Attachments = append(payload.Attachments, Attachment{mimeType, url}) } - body, err := json.Marshal(payload) - if err != nil { - return status, err - } + body := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, baseURL+"/message", bytes.NewReader(body)) if err != nil { diff --git a/handlers/twitter/handler.go b/handlers/twitter/handler.go index 1c6810ece..a5ee3d19f 100644 --- a/handlers/twitter/handler.go +++ b/handlers/twitter/handler.go @@ -6,7 +6,6 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" - "encoding/json" "fmt" "io" "mime/multipart" @@ -21,6 +20,7 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" ) @@ -322,10 +322,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Event.MessageCreate.MessageData.QuickReply = qrs } - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } + jsonBody := jsonx.MustMarshal(payload) req, _ := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") diff --git a/handlers/wavy/handler.go b/handlers/wavy/handler.go index 407e90b0c..073315425 100644 --- a/handlers/wavy/handler.go +++ b/handlers/wavy/handler.go @@ -3,7 +3,6 @@ package wavy import ( "bytes" "context" - "encoding/json" "fmt" "net/http" "strings" @@ -12,6 +11,7 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" ) var ( @@ -137,10 +137,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Destination = strings.TrimPrefix(msg.URN().Path(), "+") payload.Message = handlers.GetTextAndAttachments(msg) - jsonPayload, err := json.Marshal(payload) - if err != nil { - return nil, err - } + jsonPayload := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(jsonPayload)) if err != nil { diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index 86b4ee928..b1de6774b 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -18,6 +18,7 @@ import ( "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/i18n" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" @@ -909,11 +910,8 @@ func (h *handler) fetchMediaID(msg courier.MsgOut, mimeType, mediaURL string, cl } func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { - jsonBody, err := json.Marshal(payload) + jsonBody := jsonx.MustMarshal(payload) - if err != nil { - return "", "", err - } req, _ := http.NewRequest(http.MethodPost, sendPath.String(), bytes.NewReader(jsonBody)) req.Header = buildWhatsAppHeaders(msg.Channel()) @@ -997,11 +995,7 @@ func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, paylo // marshal updated payload if updatedPayload != nil { payload = updatedPayload - jsonBody, err = json.Marshal(payload) - - if err != nil { - return "", "", err - } + jsonBody = jsonx.MustMarshal(payload) } } // try send msg again @@ -1091,11 +1085,7 @@ func checkWhatsAppContact(channel courier.Channel, baseURL string, urn urns.URN, Contacts: []string{fmt.Sprintf("+%s", urn.Path())}, ForceCheck: true, } - reqBody, err := json.Marshal(payload) - - if err != nil { - return nil, err - } + reqBody := jsonx.MustMarshal(payload) sendURL := fmt.Sprintf("%s/v1/contacts", baseURL) req, _ := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(reqBody)) req.Header = buildWhatsAppHeaders(channel) diff --git a/handlers/zenvia/handlers.go b/handlers/zenvia/handlers.go index ef2d25898..392e8d300 100644 --- a/handlers/zenvia/handlers.go +++ b/handlers/zenvia/handlers.go @@ -3,7 +3,6 @@ package zenvia import ( "bytes" "context" - "encoding/json" "fmt" "net/http" "strings" @@ -12,6 +11,7 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" ) @@ -223,11 +223,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch }) } - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } - + jsonBody := jsonx.MustMarshal(payload) sendURL := whatsappSendURL if channel.ChannelType() == "ZVS" { sendURL = smsSendURL From 048fc88358bbd82421bfa0825a2f48a9640c0617 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 4 Oct 2023 10:31:49 -0500 Subject: [PATCH 125/170] Add optin_id to channels_channelevent --- backends/rapidpro/backend_test.go | 29 +++++++++++++++++++---------- backends/rapidpro/channel_event.go | 12 ++++++++++-- backends/rapidpro/schema.sql | 11 ++++++++++- backends/rapidpro/testdata.sql | 10 ++++++++-- handlers/meta/facebook_test.go | 4 ++-- handlers/meta/handlers.go | 7 ++----- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 12727d383..34701bd27 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1274,12 +1274,21 @@ func (ts *BackendTestSuite) TestChannelEvent() { ts.Equal(null.String("kermit frog"), contact.Name_) dbE := event.(*ChannelEvent) - dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) - ts.NoError(err) + dbE = readChannelEventFromDB(ts.b, dbE.ID_) ts.Equal(dbE.EventType_, courier.EventTypeReferral) ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) + + event = ts.b.NewChannelEvent(channel, courier.EventTypeOptIn, urn, clog).WithExtra(map[string]any{"optin_id": "1", "optin_name": "Polls"}) + err = ts.b.WriteChannelEvent(ctx, event, clog) + ts.NoError(err) + + dbE = event.(*ChannelEvent) + dbE = readChannelEventFromDB(ts.b, dbE.ID_) + ts.Equal(dbE.EventType_, courier.EventTypeOptIn) + ts.Equal(map[string]any{"optin_id": "1", "optin_name": "Polls"}, dbE.Extra()) + ts.Equal(null.Int(1), dbE.OptInID_) } func (ts *BackendTestSuite) TestSessionTimeout() { @@ -1316,8 +1325,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { ts.Equal(null.String("kermit frog"), contact.Name_) dbE := event.(*ChannelEvent) - dbE, err = readChannelEventFromDB(ts.b, dbE.ID_) - ts.NoError(err) + dbE = readChannelEventFromDB(ts.b, dbE.ID_) ts.Equal(dbE.EventType_, courier.EventTypeReferral) ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) @@ -1552,14 +1560,15 @@ WHERE ` const sqlSelectEvent = ` -SELECT org_id, channel_id, contact_id, contact_urn_id, event_type, extra, occurred_on, created_on, log_uuids +SELECT id, org_id, channel_id, contact_id, contact_urn_id, event_type, optin_id, extra, occurred_on, created_on, log_uuids FROM channels_channelevent WHERE id = $1` -func readChannelEventFromDB(b *backend, id ChannelEventID) (*ChannelEvent, error) { - e := &ChannelEvent{ - ID_: id, - } +func readChannelEventFromDB(b *backend, id ChannelEventID) *ChannelEvent { + e := &ChannelEvent{} err := b.db.Get(e, sqlSelectEvent, id) - return e, err + if err != nil { + panic(err) + } + return e } diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index 78fcdef7f..da0d2bead 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -43,6 +43,7 @@ type ChannelEvent struct { ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"` URN_ urns.URN `json:"urn" db:"urn"` EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` + OptInID_ null.Int `json:"optin_id" db:"optin_id"` Extra_ null.Map[any] `json:"extra" db:"extra"` OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` CreatedOn_ time.Time `json:"created_on" db:"created_on"` @@ -98,6 +99,13 @@ func (e *ChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.Chann } func (e *ChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { + optInID, ok := extra["optin_id"] + if ok { + asStr, _ := optInID.(string) + asInt, _ := strconv.Atoi(asStr) + e.OptInID_ = null.Int(asInt) + } + e.Extra_ = null.Map[any](extra) return e } @@ -127,8 +135,8 @@ func writeChannelEvent(ctx context.Context, b *backend, event courier.ChannelEve const sqlInsertChannelEvent = ` INSERT INTO - channels_channelevent( org_id, channel_id, contact_id, contact_urn_id, event_type, extra, occurred_on, created_on, log_uuids) - VALUES(:org_id, :channel_id, :contact_id, :contact_urn_id, :event_type, :extra, :occurred_on, :created_on, :log_uuids) + channels_channelevent( org_id, channel_id, contact_id, contact_urn_id, event_type, optin_id, extra, occurred_on, created_on, log_uuids) + VALUES(:org_id, :channel_id, :contact_id, :contact_urn_id, :event_type, :optin_id, :extra, :occurred_on, :created_on, :log_uuids) RETURNING id` // writeChannelEventToDB writes the passed in msg status to our db diff --git a/backends/rapidpro/schema.sql b/backends/rapidpro/schema.sql index 94bc18257..857260fe3 100644 --- a/backends/rapidpro/schema.sql +++ b/backends/rapidpro/schema.sql @@ -56,6 +56,14 @@ CREATE TABLE contacts_contacturn ( UNIQUE (org_id, identity) ); +DROP TABLE IF EXISTS msgs_optin CASCADE; +CREATE TABLE msgs_optin ( + id serial primary key, + uuid uuid NOT NULL, + org_id integer NOT NULL references orgs_org(id) on delete cascade, + name character varying(64) +); + DROP TABLE IF EXISTS msgs_msg CASCADE; CREATE TABLE msgs_msg ( id bigserial primary key, @@ -83,7 +91,7 @@ CREATE TABLE msgs_msg ( contact_urn_id integer NOT NULL references contacts_contacturn(id) on delete cascade, org_id integer NOT NULL references orgs_org(id) on delete cascade, metadata text, - topup_id integer, + optin_id integer references msgs_optin(id) on delete cascade, delete_from_counts boolean, log_uuids uuid[] ); @@ -111,6 +119,7 @@ CREATE TABLE channels_channelevent ( channel_id integer NOT NULL references channels_channel(id) on delete cascade, contact_id integer NOT NULL references contacts_contact(id) on delete cascade, contact_urn_id integer NOT NULL references contacts_contacturn(id) on delete cascade, + optin_id integer references msgs_optin(id) on delete cascade, org_id integer NOT NULL references orgs_org(id) on delete cascade, log_uuids uuid[] ); diff --git a/backends/rapidpro/testdata.sql b/backends/rapidpro/testdata.sql index ebee9b009..5487a2fc4 100644 --- a/backends/rapidpro/testdata.sql +++ b/backends/rapidpro/testdata.sql @@ -36,8 +36,14 @@ DELETE FROM contacts_contacturn; INSERT INTO contacts_contacturn("id", "identity", "path", "scheme", "priority", "channel_id", "contact_id", "org_id") VALUES(1000, 'tel:+12067799192', '+12067799192', 'tel', 50, 10, 100, 1); -/** Msg with id 10,000 */ -DELETE from msgs_msg; +/* Msg optins with ids 1, 2 */ +DELETE FROM msgs_optin; +INSERT INTO msgs_optin(id, uuid, org_id, name) VALUES + (1, 'fc1cef6e-b5b1-452d-9528-a4b24db28eb0', 1, 'Polls'), + (2, '2b1eba23-4a97-46ac-9022-11304412b32f', 1, 'Jokes'); + +/** Msg with id 10000 */ +DELETE FROM msgs_msg; INSERT INTO msgs_msg("id", "text", "high_priority", "created_on", "modified_on", "sent_on", "queued_on", "direction", "status", "visibility", "msg_type", "msg_count", "error_count", "next_attempt", "external_id", "channel_id", "contact_id", "contact_urn_id", "org_id") VALUES(10000, 'test message', True, now(), now(), now(), now(), 'O', 'W', 'V', 'T', diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index a0ede7c00..2ae6e239c 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -122,7 +122,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": 3456, "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": "3456", "optin_name": "Bird Facts"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:3456": "12345678901234567890"}}, PrepRequest: addValidSignature, @@ -134,7 +134,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": 3456, "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": "3456", "optin_name": "Bird Facts"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, PrepRequest: addValidSignature, diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index b9fc25119..bd5358627 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -438,11 +438,8 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if msg.OptIn.Type == "notification_messages" { eventType := courier.EventTypeOptIn authToken := msg.OptIn.NotificationMessagesToken + optInID := msg.OptIn.Payload optInName := msg.OptIn.Title - optInID, err := strconv.Atoi(msg.OptIn.Payload) - if err != nil { - return nil, nil, err - } if msg.OptIn.NotificationMessagesStatus == "STOP_NOTIFICATIONS" { eventType = courier.EventTypeOptOut @@ -452,7 +449,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, eventType, urn, clog). WithOccurredOn(date). WithExtra(map[string]any{"optin_id": optInID, "optin_name": optInName}). - WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%d", optInID): authToken}) + WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInID): authToken}) } else { // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) From ceab9b0d90f856ed386624707d348496c98cd7a9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 4 Oct 2023 10:50:01 -0500 Subject: [PATCH 126/170] Switch channelevent.extra to always be strings --- backends/rapidpro/backend_test.go | 12 ++++++------ backends/rapidpro/channel_event.go | 19 ++++++++++--------- channel_event.go | 4 ++-- handlers/facebook_legacy/handler.go | 11 +++-------- handlers/facebook_legacy/handler_test.go | 12 ++++++------ handlers/meta/facebook_test.go | 16 ++++++++-------- handlers/meta/handlers.go | 20 +++++--------------- handlers/meta/instagram_test.go | 2 +- handlers/test.go | 2 +- responses.go | 12 ++++++------ test/channel_event.go | 6 +++--- 11 files changed, 51 insertions(+), 65 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 34701bd27..2b7231497 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -1265,7 +1265,7 @@ func (ts *BackendTestSuite) TestChannelEvent() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}).WithContactName("kermit frog") + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}).WithContactName("kermit frog") err := ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) @@ -1276,18 +1276,18 @@ func (ts *BackendTestSuite) TestChannelEvent() { dbE := event.(*ChannelEvent) dbE = readChannelEventFromDB(ts.b, dbE.ID_) ts.Equal(dbE.EventType_, courier.EventTypeReferral) - ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) - event = ts.b.NewChannelEvent(channel, courier.EventTypeOptIn, urn, clog).WithExtra(map[string]any{"optin_id": "1", "optin_name": "Polls"}) + event = ts.b.NewChannelEvent(channel, courier.EventTypeOptIn, urn, clog).WithExtra(map[string]string{"title": "Polls", "payload": "1"}) err = ts.b.WriteChannelEvent(ctx, event, clog) ts.NoError(err) dbE = event.(*ChannelEvent) dbE = readChannelEventFromDB(ts.b, dbE.ID_) ts.Equal(dbE.EventType_, courier.EventTypeOptIn) - ts.Equal(map[string]any{"optin_id": "1", "optin_name": "Polls"}, dbE.Extra()) + ts.Equal(map[string]string{"title": "Polls", "payload": "1"}, dbE.Extra()) ts.Equal(null.Int(1), dbE.OptInID_) } @@ -1314,7 +1314,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]any{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1327,7 +1327,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { dbE := event.(*ChannelEvent) dbE = readChannelEventFromDB(ts.b, dbE.ID_) ts.Equal(dbE.EventType_, courier.EventTypeReferral) - ts.Equal(map[string]any{"ref_id": "12345"}, dbE.Extra()) + ts.Equal(map[string]string{"ref_id": "12345"}, dbE.Extra()) ts.Equal(contact.ID_, dbE.ContactID_) ts.Equal(contact.URNID_, dbE.ContactURNID_) diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index da0d2bead..8cb530dc9 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -44,7 +44,7 @@ type ChannelEvent struct { URN_ urns.URN `json:"urn" db:"urn"` EventType_ courier.ChannelEventType `json:"event_type" db:"event_type"` OptInID_ null.Int `json:"optin_id" db:"optin_id"` - Extra_ null.Map[any] `json:"extra" db:"extra"` + Extra_ null.Map[string] `json:"extra" db:"extra"` OccurredOn_ time.Time `json:"occurred_on" db:"occurred_on"` CreatedOn_ time.Time `json:"created_on" db:"created_on"` LogUUIDs pq.StringArray `json:"log_uuids" db:"log_uuids"` @@ -83,7 +83,7 @@ func (e *ChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID func (e *ChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ } func (e *ChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ } func (e *ChannelEvent) URN() urns.URN { return e.URN_ } -func (e *ChannelEvent) Extra() map[string]any { return e.Extra_ } +func (e *ChannelEvent) Extra() map[string]string { return e.Extra_ } func (e *ChannelEvent) OccurredOn() time.Time { return e.OccurredOn_ } func (e *ChannelEvent) CreatedOn() time.Time { return e.CreatedOn_ } func (e *ChannelEvent) Channel() *Channel { return e.channel } @@ -98,15 +98,16 @@ func (e *ChannelEvent) WithURNAuthTokens(tokens map[string]string) courier.Chann return e } -func (e *ChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { - optInID, ok := extra["optin_id"] - if ok { - asStr, _ := optInID.(string) - asInt, _ := strconv.Atoi(asStr) - e.OptInID_ = null.Int(asInt) +func (e *ChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { + if e.EventType_ == courier.EventTypeOptIn || e.EventType_ == courier.EventTypeOptOut { + optInID := extra["payload"] + if optInID != "" { + asInt, _ := strconv.Atoi(optInID) + e.OptInID_ = null.Int(asInt) + } } - e.Extra_ = null.Map[any](extra) + e.Extra_ = null.Map[string](extra) return e } diff --git a/channel_event.go b/channel_event.go index f93ff9627..ef26fa617 100644 --- a/channel_event.go +++ b/channel_event.go @@ -30,12 +30,12 @@ type ChannelEvent interface { ChannelUUID() ChannelUUID URN() urns.URN EventType() ChannelEventType - Extra() map[string]any + Extra() map[string]string CreatedOn() time.Time OccurredOn() time.Time WithContactName(name string) ChannelEvent WithURNAuthTokens(tokens map[string]string) ChannelEvent - WithExtra(extra map[string]any) ChannelEvent + WithExtra(extra map[string]string) ChannelEvent WithOccurredOn(time.Time) ChannelEvent } diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index 207133dff..c5ca9e671 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -273,9 +273,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ - referrerIDKey: msg.OptIn.Ref, - } + extra := map[string]string{referrerIDKey: msg.OptIn.Ref} event = event.WithExtra(extra) err := h.Backend().WriteChannelEvent(ctx, event, clog) @@ -295,7 +293,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ + extra := map[string]string{ titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload, } @@ -326,10 +324,7 @@ func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ - sourceKey: msg.Referral.Source, - typeKey: msg.Referral.Type, - } + extra := map[string]string{sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type} // add referrer id if present if msg.Referral.Ref != "" { diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go index ed62a4c6b..81d2f524c 100644 --- a/handlers/facebook_legacy/handler_test.go +++ b/handlers/facebook_legacy/handler_test.go @@ -482,7 +482,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, }, { @@ -492,7 +492,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, }, { @@ -502,7 +502,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, }, }, { @@ -512,7 +512,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, }, { @@ -522,7 +522,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, }, { @@ -532,7 +532,7 @@ var testCases = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, }, { diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 2ae6e239c..9c73df9df 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -100,7 +100,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:ref:optin_user_ref", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -111,7 +111,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "optin_ref"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "optin_ref"}}, }, PrepRequest: addValidSignature, }, @@ -122,7 +122,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": "3456", "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptIn, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "Bird Facts", "payload": "3456"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {"optin:3456": "12345678901234567890"}}, PrepRequest: addValidSignature, @@ -134,7 +134,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"optin_id": "3456", "optin_name": "Bird Facts"}}, + {Type: courier.EventTypeOptOut, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "Bird Facts", "payload": "3456"}}, }, ExpectedURNAuthTokens: map[urns.URN]map[string]string{"facebook:5678": {}}, PrepRequest: addValidSignature, @@ -146,7 +146,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, @@ -157,7 +157,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}}, }, PrepRequest: addValidSignature, }, @@ -168,7 +168,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, @@ -179,7 +179,7 @@ var facebookIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: `"referrer_id":"referral id"`, ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, }, PrepRequest: addValidSignature, }, diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index bd5358627..44a498db2 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -438,8 +438,6 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if msg.OptIn.Type == "notification_messages" { eventType := courier.EventTypeOptIn authToken := msg.OptIn.NotificationMessagesToken - optInID := msg.OptIn.Payload - optInName := msg.OptIn.Title if msg.OptIn.NotificationMessagesStatus == "STOP_NOTIFICATIONS" { eventType = courier.EventTypeOptOut @@ -448,8 +446,8 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, eventType, urn, clog). WithOccurredOn(date). - WithExtra(map[string]any{"optin_id": optInID, "optin_name": optInName}). - WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", optInID): authToken}) + WithExtra(map[string]string{titleKey: msg.OptIn.Title, payloadKey: msg.OptIn.Payload}). + WithURNAuthTokens(map[string]string{fmt.Sprintf("optin:%s", msg.OptIn.Payload): authToken}) } else { // this is an opt in, if we have a user_ref, use that as our URN (this is a checkbox plugin) @@ -467,9 +465,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event = h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog). WithOccurredOn(date). - WithExtra(map[string]any{ - referrerIDKey: msg.OptIn.Ref, - }) + WithExtra(map[string]string{referrerIDKey: msg.OptIn.Ref}) } err := h.Backend().WriteChannelEvent(ctx, event, clog) @@ -489,10 +485,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, eventType, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ - titleKey: msg.Postback.Title, - payloadKey: msg.Postback.Payload, - } + extra := map[string]string{titleKey: msg.Postback.Title, payloadKey: msg.Postback.Payload} // add in referral information if we have it if eventType == courier.EventTypeReferral { @@ -520,10 +513,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c event := h.Backend().NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithOccurredOn(date) // build our extra - extra := map[string]any{ - sourceKey: msg.Referral.Source, - typeKey: msg.Referral.Type, - } + extra := map[string]string{sourceKey: msg.Referral.Source, typeKey: msg.Referral.Type} // add referrer id if present if msg.Referral.Ref != "" { diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 25371e148..24b829bcd 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -86,7 +86,7 @@ var instagramIncomingTests = []IncomingTestCase{ ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", ExpectedEvents: []ExpectedEvent{ - {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]any{"title": "icebreaker question", "payload": "get_started"}}, + {Type: courier.EventTypeNewConversation, URN: "instagram:5678", Time: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), Extra: map[string]string{"title": "icebreaker question", "payload": "get_started"}}, }, PrepRequest: addValidSignature, }, diff --git a/handlers/test.go b/handlers/test.go index 9cee2259f..e8b212403 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -40,7 +40,7 @@ type ExpectedEvent struct { Type courier.ChannelEventType URN urns.URN Time time.Time - Extra map[string]any + Extra map[string]string } // IncomingTestCase defines the test values for a particular test case diff --git a/responses.go b/responses.go index 77e07cb3c..665d1e097 100644 --- a/responses.go +++ b/responses.go @@ -100,12 +100,12 @@ func NewMsgReceiveData(msg MsgIn) MsgReceiveData { // EventReceiveData is our response payload for a channel event type EventReceiveData struct { - Type string `json:"type"` - ChannelUUID ChannelUUID `json:"channel_uuid"` - EventType ChannelEventType `json:"event_type"` - URN urns.URN `json:"urn"` - ReceivedOn time.Time `json:"received_on"` - Extra map[string]any `json:"extra,omitempty"` + Type string `json:"type"` + ChannelUUID ChannelUUID `json:"channel_uuid"` + EventType ChannelEventType `json:"event_type"` + URN urns.URN `json:"urn"` + ReceivedOn time.Time `json:"received_on"` + Extra map[string]string `json:"extra,omitempty"` } // NewEventReceiveData creates a new receive data for the passed in event diff --git a/test/channel_event.go b/test/channel_event.go index a65f8a2d6..177f5300c 100644 --- a/test/channel_event.go +++ b/test/channel_event.go @@ -16,7 +16,7 @@ type mockChannelEvent struct { contactName string urnAuthTokens map[string]string - extra map[string]any + extra map[string]string } func (e *mockChannelEvent) EventID() int64 { return 0 } @@ -24,11 +24,11 @@ func (e *mockChannelEvent) ChannelUUID() courier.ChannelUUID { return e.chann func (e *mockChannelEvent) EventType() courier.ChannelEventType { return e.eventType } func (e *mockChannelEvent) CreatedOn() time.Time { return e.createdOn } func (e *mockChannelEvent) OccurredOn() time.Time { return e.occurredOn } -func (e *mockChannelEvent) Extra() map[string]any { return e.extra } +func (e *mockChannelEvent) Extra() map[string]string { return e.extra } func (e *mockChannelEvent) ContactName() string { return e.contactName } func (e *mockChannelEvent) URN() urns.URN { return e.urn } -func (e *mockChannelEvent) WithExtra(extra map[string]any) courier.ChannelEvent { +func (e *mockChannelEvent) WithExtra(extra map[string]string) courier.ChannelEvent { e.extra = extra return e } From ccab456f04498a1ba6e801cbf3fb337966a11957 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 4 Oct 2023 13:25:02 -0500 Subject: [PATCH 127/170] Update CHANGELOG.md for v8.3.23 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0e417ba..517e20f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.23 (2023-10-04) +------------------------- + * Switch channelevent.extra to always be strings + * Add optin_id to channels_channelevent + * Allow outgoing tests to check multiple requests + v8.3.22 (2023-09-27) ------------------------- * Use Facebook API v17.0 From e53e137e9ff2ddc42502341ae2757ae8cdad6b53 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Fri, 6 Oct 2023 12:26:43 +0200 Subject: [PATCH 128/170] Start switching to use slog --- .github/workflows/ci.yml | 2 +- cmd/courier/main.go | 26 ++++++--- go.mod | 2 +- go.sum | 2 + handlers/jiochat/handler_test.go | 4 +- handlers/test.go | 4 +- handlers/wechat/handler_test.go | 4 +- queue/queue.go | 6 +-- server.go | 5 +- server_test.go | 6 +-- utils/logrus.go | 92 ++++++++++++++++++++++++++++++++ 11 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 utils/logrus.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 283974b5b..d359379ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: [push, pull_request] env: - go-version: "1.20.x" + go-version: "1.21.x" jobs: test: name: Test diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 467fb7572..577a2e89c 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -1,6 +1,7 @@ package main import ( + "log/slog" "os" "os/signal" "syscall" @@ -8,6 +9,7 @@ import ( "github.com/evalphobia/logrus_sentry" _ "github.com/lib/pq" "github.com/nyaruka/courier" + "github.com/nyaruka/courier/utils" "github.com/sirupsen/logrus" // load channel handler packages @@ -88,10 +90,17 @@ func main() { logrus.SetOutput(os.Stdout) level, err := logrus.ParseLevel(config.LogLevel) if err != nil { - logrus.Fatalf("Invalid log level '%s'", level) + slog.Error("invalid log level", "level", level) + os.Exit(1) } logrus.SetLevel(level) + // configure golang std structured logging to route to logrus + slog.SetDefault(slog.New(utils.NewLogrusHandler(logrus.StandardLogger()))) + + log := slog.With("comp", "main") + log.Info("starting courier", "version", version) + // if we have a DSN entry, try to initialize it if config.SentryDSN != "" { hook, err := logrus_sentry.NewSentryHook(config.SentryDSN, []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}) @@ -100,7 +109,8 @@ func main() { hook.StacktraceConfiguration.Skip = 4 hook.StacktraceConfiguration.Context = 5 if err != nil { - logrus.Fatalf("Invalid sentry DSN: '%s': %s", config.SentryDSN, err) + log.Error("unable to configure sentry hook", "dsn", config.SentryDSN, "error", err) + os.Exit(1) } logrus.StandardLogger().Hooks.Add(hook) } @@ -108,18 +118,22 @@ func main() { // load our backend backend, err := courier.NewBackend(config) if err != nil { - logrus.Fatalf("Error creating backend: %s", err) + log.Error("error creating backend", "error", err) + os.Exit(1) } server := courier.NewServer(config, backend) err = server.Start() if err != nil { - logrus.Fatalf("Error starting server: %s", err) + log.Error("unable to start server", "error", err) + os.Exit(1) } - ch := make(chan os.Signal) + ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - logrus.WithField("comp", "main").WithField("signal", <-ch).Info("stopping") + slog.Info("stopping", "comp", "main", "signal", <-ch) + logrus.WithField("comp", "main").WithField("signal", <-ch) server.Stop() + } diff --git a/go.mod b/go.mod index 4fe048ab5..70ee91d1d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nyaruka/courier -go 1.20 +go 1.21 require ( github.com/antchfx/xmlquery v1.3.17 diff --git a/go.sum b/go.sum index 6a187c876..602b309d8 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,7 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -46,6 +47,7 @@ github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= diff --git a/handlers/jiochat/handler_test.go b/handlers/jiochat/handler_test.go index 88f36f18e..865d902e1 100644 --- a/handlers/jiochat/handler_test.go +++ b/handlers/jiochat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -260,8 +261,7 @@ func buildMockJCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null - logger := logrus.New() - logger.Out = io.Discard + logger := slog.Default() logrus.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" diff --git a/handlers/test.go b/handlers/test.go index e8b212403..ec8693b88 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "mime/multipart" "net/http" "net/http/httptest" @@ -147,8 +148,7 @@ func testHandlerRequest(tb testing.TB, s courier.Server, path string, headers ma func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null - logger := logrus.New() - logger.Out = io.Discard + logger := slog.Default() logrus.SetOutput(io.Discard) config := courier.NewConfig() diff --git a/handlers/wechat/handler_test.go b/handlers/wechat/handler_test.go index e1d6d1f75..b784ec8fc 100644 --- a/handlers/wechat/handler_test.go +++ b/handlers/wechat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -212,8 +213,7 @@ func buildMockWCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null - logger := logrus.New() - logger.Out = io.Discard + logger := slog.Default() logrus.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" diff --git a/queue/queue.go b/queue/queue.go index 2dd5b3123..f261463ad 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -1,12 +1,12 @@ package queue import ( + "log/slog" "strconv" "sync" "time" "github.com/gomodule/redigo/redis" - "github.com/sirupsen/logrus" ) // Priority represents the priority of an item in a queue @@ -201,7 +201,7 @@ func PopFromQueue(conn redis.Conn, qType string) (WorkerToken, string, error) { epochMS := strconv.FormatFloat(float64(time.Now().UnixNano()/int64(time.Microsecond))/float64(1000000), 'f', 6, 64) values, err := redis.Strings(luaPop.Do(conn, epochMS, qType)) if err != nil { - logrus.Error(err) + slog.Error("error popping from queue", "error", err) return "", "", err } return WorkerToken(values[0]), values[1], nil @@ -275,7 +275,7 @@ func StartDethrottler(redis *redis.Pool, quitter chan bool, wg *sync.WaitGroup, conn := redis.Get() _, err := luaDethrottle.Do(conn, qType) if err != nil { - logrus.WithError(err).Error("error dethrottling") + slog.Error("error dethrottling", "error", err) } conn.Close() diff --git a/server.go b/server.go index 073eb567c..f132a470c 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "log" + "log/slog" "net/http" "os" "runtime/debug" @@ -56,13 +57,13 @@ type Server interface { // afterwards, which is when configuration options are checked. func NewServer(config *Config, backend Backend) Server { // create our top level router - logger := logrus.New() + logger := slog.Default() return NewServerWithLogger(config, backend, logger) } // NewServerWithLogger creates a new Server for the passed in configuration. The server will have to be started // afterwards, which is when configuration options are checked. -func NewServerWithLogger(config *Config, backend Backend, logger *logrus.Logger) Server { +func NewServerWithLogger(config *Config, backend Backend, logger *slog.Logger) Server { router := chi.NewRouter() router.Use(middleware.Compress(flate.DefaultCompression)) router.Use(middleware.StripSlashes) diff --git a/server_test.go b/server_test.go index da5a12fa9..a34a6caf8 100644 --- a/server_test.go +++ b/server_test.go @@ -1,6 +1,7 @@ package courier_test import ( + "log/slog" "net/http" "strings" "testing" @@ -10,13 +11,12 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/uuids" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestServer(t *testing.T) { - logger := logrus.New() + logger := slog.Default() config := courier.NewConfig() config.StatusUsername = "admin" config.StatusPassword = "password123" @@ -89,7 +89,7 @@ func TestFetchAttachment(t *testing.T) { defer uuids.SetGenerator(uuids.DefaultGenerator) uuids.SetGenerator(uuids.NewSeededGenerator(1234)) - logger := logrus.New() + logger := slog.Default() config := courier.NewConfig() config.AuthToken = "sesame" diff --git a/utils/logrus.go b/utils/logrus.go new file mode 100644 index 000000000..2650bf9bb --- /dev/null +++ b/utils/logrus.go @@ -0,0 +1,92 @@ +// Structured logging handler for logrus so we can rewrite code to use slog package incrementally. Once all logging is +// happening via slog, we just need to hook up Sentry directly to that, and then we can get rid of this file. +package utils + +import ( + "context" + "log/slog" + "slices" + "strings" + + "github.com/sirupsen/logrus" +) + +var levels = map[slog.Level]logrus.Level{ + slog.LevelError: logrus.ErrorLevel, + slog.LevelWarn: logrus.WarnLevel, + slog.LevelInfo: logrus.InfoLevel, + slog.LevelDebug: logrus.DebugLevel, +} + +type LogrusHandler struct { + logger *logrus.Logger + groups []string + attrs []slog.Attr +} + +func NewLogrusHandler(logger *logrus.Logger) *LogrusHandler { + return &LogrusHandler{logger: logger} +} + +func (l *LogrusHandler) clone() *LogrusHandler { + return &LogrusHandler{ + logger: l.logger, + groups: slices.Clip(l.groups), + attrs: slices.Clip(l.attrs), + } +} + +func (l *LogrusHandler) Enabled(ctx context.Context, level slog.Level) bool { + return levels[level] <= l.logger.GetLevel() +} + +func (l *LogrusHandler) Handle(ctx context.Context, r slog.Record) error { + log := logrus.NewEntry(l.logger) + if r.Time.IsZero() { + log = log.WithTime(r.Time) + } + + f := logrus.Fields{} + for _, a := range l.attrs { + if a.Key != "" { + f[a.Key] = a.Value + } + } + log = log.WithFields(f) + + r.Attrs(func(attr slog.Attr) bool { + if attr.Key == "" { + return true + } + log = log.WithField(attr.Key, attr.Value) + return true + }) + log.Logf(levels[r.Level], r.Message) + return nil +} + +func (l *LogrusHandler) groupPrefix() string { + if len(l.groups) > 0 { + return strings.Join(l.groups, ":") + ":" + } + return "" +} + +func (l *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + newHandler := l.clone() + for _, a := range attrs { + newHandler.attrs = append(newHandler.attrs, slog.Attr{ + Key: l.groupPrefix() + a.Key, + Value: a.Value, + }) + } + return newHandler +} + +func (l *LogrusHandler) WithGroup(name string) slog.Handler { + newHandler := l.clone() + newHandler.groups = append(newHandler.groups, name) + return newHandler +} + +var _ slog.Handler = &LogrusHandler{} From 05166db2d14aaf972e7011aae2d573ea8f2d66a7 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Tue, 10 Oct 2023 16:46:07 +0200 Subject: [PATCH 129/170] More logrus replacement to use slog --- attachments.go | 4 +- cmd/courier/main.go | 1 - log.go | 102 ++++++++++++++++++++++---------------------- sender.go | 51 +++++++++++----------- server.go | 52 +++++++++++----------- spool.go | 15 +++---- 6 files changed, 110 insertions(+), 115 deletions(-) diff --git a/attachments.go b/attachments.go index 022645ff8..e6b587fcd 100644 --- a/attachments.go +++ b/attachments.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "log/slog" "mime" "net/http" "net/url" @@ -12,7 +13,6 @@ import ( "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/httpx" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "gopkg.in/h2non/filetype.v1" ) @@ -64,7 +64,7 @@ func fetchAttachment(ctx context.Context, b Backend, r *http.Request) (*fetchAtt // try to write channel log even if we have an error clog.End() if err := b.WriteChannelLog(ctx, clog); err != nil { - logrus.WithError(err).Error() + slog.Error("error writing log", "error", err) } if err != nil { diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 577a2e89c..be2563ac0 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -132,7 +132,6 @@ func main() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) slog.Info("stopping", "comp", "main", "signal", <-ch) - logrus.WithField("comp", "main").WithField("signal", <-ch) server.Stop() diff --git a/log.go b/log.go index 9a871ea55..fc9dfdac6 100644 --- a/log.go +++ b/log.go @@ -1,88 +1,90 @@ package courier import ( + "log/slog" "net/http" "time" - - "github.com/sirupsen/logrus" ) // LogMsgStatusReceived logs our that we received a new MsgStatus func LogMsgStatusReceived(r *http.Request, status StatusUpdate) { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": status.ChannelUUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "status": status.Status(), - "msg_id": status.MsgID(), - "msg_external_id": status.ExternalID(), - }).Debug("status updated") + if slog.Default().Enabled(r.Context(), slog.LevelDebug) { + slog.Debug("status updated", + "channel_uuid", status.ChannelUUID(), + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "status", status.Status(), + "msg_id", status.MsgID(), + "msg_external_id", status.ExternalID(), + ) } + } // LogMsgReceived logs that we received the passed in message func LogMsgReceived(r *http.Request, msg MsgIn) { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": msg.Channel().UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "msg_uuid": msg.UUID(), - "msg_id": msg.ID(), - "msg_urn": msg.URN().Identity(), - "msg_text": msg.Text(), - "msg_attachments": msg.Attachments(), - }).Debug("msg received") + if slog.Default().Enabled(r.Context(), slog.LevelDebug) { + slog.Debug("msg received", + "channel_uuid", msg.Channel().UUID(), + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "msg_uuid", msg.UUID(), + "msg_id", msg.ID(), + "msg_urn", msg.URN().Identity(), + "msg_text", msg.Text(), + "msg_attachments", msg.Attachments(), + ) } + } // LogChannelEventReceived logs that we received the passed in channel event func LogChannelEventReceived(r *http.Request, event ChannelEvent) { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": event.ChannelUUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "event_type": event.EventType(), - "event_urn": event.URN().Identity(), - }).Debug("event received") + if slog.Default().Enabled(r.Context(), slog.LevelDebug) { + slog.Debug("event received", + "channel_uuid", event.ChannelUUID(), + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "event_type", event.EventType(), + "event_urn", event.URN().Identity(), + ) } } // LogRequestIgnored logs that we ignored the passed in request func LogRequestIgnored(r *http.Request, channel Channel, details string) { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": channel.UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "details": details, - }).Debug("request ignored") + if slog.Default().Enabled(r.Context(), slog.LevelDebug) { + slog.Debug("request ignored", + "channel_uuid", channel.UUID(), + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "details", details, + ) } } // LogRequestHandled logs that we handled the passed in request but didn't create any events func LogRequestHandled(r *http.Request, channel Channel, details string) { - if logrus.IsLevelEnabled(logrus.DebugLevel) { - logrus.WithFields(logrus.Fields{ - "channel_uuid": channel.UUID(), - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "details": details, - }).Debug("request handled") + if slog.Default().Enabled(r.Context(), slog.LevelDebug) { + slog.Debug("request handled", + "channel_uuid", channel.UUID(), + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "details", details, + ) } } // LogRequestError logs that errored during parsing (this is logged as an info as it isn't an error on our side) func LogRequestError(r *http.Request, channel Channel, err error) { - log := logrus.WithFields(logrus.Fields{ - "url": r.Context().Value(contextRequestURL), - "elapsed_ms": getElapsedMS(r), - "error": err.Error(), - }) + log := slog.With( + "url", r.Context().Value(contextRequestURL), + "elapsed_ms", getElapsedMS(r), + "error", err, + ) + if channel != nil { - log = log.WithField("channel_uuid", channel.UUID()) + log = log.With("channel_uuid", channel.UUID()) } log.Info("request errored") } diff --git a/sender.go b/sender.go index 9eb43bc2f..771cef7b0 100644 --- a/sender.go +++ b/sender.go @@ -3,10 +3,10 @@ package courier import ( "context" "fmt" + "log/slog" "time" "github.com/nyaruka/gocommon/analytics" - "github.com/sirupsen/logrus" ) // Foreman takes care of managing our set of sending workers and assigns msgs for each to send @@ -47,7 +47,7 @@ func (f *Foreman) Stop() { sender.Stop() } close(f.quit) - logrus.WithField("comp", "foreman").WithField("state", "stopping").Info("foreman stopping") + slog.Info("foreman stopping", "comp", "foreman", "state", "stopping") } // Assign is our main loop for the Foreman, it takes care of popping the next outgoing messages from our @@ -55,12 +55,11 @@ func (f *Foreman) Stop() { func (f *Foreman) Assign() { f.server.WaitGroup().Add(1) defer f.server.WaitGroup().Done() - log := logrus.WithField("comp", "foreman") + log := slog.With("comp", "foreman") - log.WithFields(logrus.Fields{ - "state": "started", - "senders": len(f.senders), - }).Info("senders started and waiting") + log.Info("senders started and waiting", + "state", "started", + "senders", len(f.senders)) backend := f.server.Backend() lastSleep := false @@ -69,7 +68,7 @@ func (f *Foreman) Assign() { select { // return if we have been told to stop case <-f.quit: - log.WithField("state", "stopped").Info("foreman stopped") + log.Info("foreman stopped", "state", "stopped") return // otherwise, grab the next msg and assign it to a sender @@ -86,7 +85,7 @@ func (f *Foreman) Assign() { } else { // we received an error getting the next message, log it if err != nil { - log.WithError(err).Error("error popping outgoing msg") + log.Error("error popping outgoing msg", "error", err) } // add our sender back to our queue and sleep a bit @@ -124,10 +123,7 @@ func (w *Sender) Start() { go func() { defer w.foreman.server.WaitGroup().Done() - - log := logrus.WithField("comp", "sender").WithField("sender_id", w.id) - log.Debug("started") - + slog.Debug("started", "comp", "sender", "sender_id", w.id) for { // list ourselves as available for work w.foreman.availableSenders <- w @@ -137,7 +133,7 @@ func (w *Sender) Start() { // exit if we were stopped if msg == nil { - log.Debug("stopped") + slog.Debug("stopped") return } @@ -152,7 +148,8 @@ func (w *Sender) Stop() { } func (w *Sender) sendMessage(msg MsgOut) { - log := logrus.WithField("comp", "sender").WithField("sender_id", w.id).WithField("channel_uuid", msg.Channel().UUID()) + + log := slog.With("comp", "sender", "sender_id", w.id, "channel_uuid", msg.Channel().UUID()) server := w.foreman.server backend := server.Backend() @@ -161,12 +158,12 @@ func (w *Sender) sendMessage(msg MsgOut) { sendCTX, cancel := context.WithTimeout(context.Background(), time.Second*35) defer cancel() - log = log.WithField("msg_id", msg.ID()).WithField("msg_text", msg.Text()).WithField("msg_urn", msg.URN().Identity()) + log = log.With("msg_id", msg.ID(), "msg_text", msg.Text(), "msg_urn", msg.URN().Identity()) if len(msg.Attachments()) > 0 { - log = log.WithField("attachments", msg.Attachments()) + log = log.With("attachments", msg.Attachments()) } if len(msg.QuickReplies()) > 0 { - log = log.WithField("quick_replies", msg.QuickReplies()) + log = log.With("quick_replies", msg.QuickReplies()) } start := time.Now() @@ -175,7 +172,7 @@ func (w *Sender) sendMessage(msg MsgOut) { if msg.IsResend() { err := backend.ClearMsgSent(sendCTX, msg.ID()) if err != nil { - log.WithError(err).Error("error clearing sent status for msg") + log.Error("error clearing sent status for msg", "error", err) } } @@ -184,7 +181,7 @@ func (w *Sender) sendMessage(msg MsgOut) { // failing on a lookup isn't a halting problem but we should log it if err != nil { - log.WithError(err).Error("error looking up msg was sent") + log.Error("error looking up msg was sent", "error", err) } var status StatusUpdate @@ -199,12 +196,12 @@ func (w *Sender) sendMessage(msg MsgOut) { if handler == nil { // if there's no handler, create a FAILED status for it status = backend.NewStatusUpdate(msg.Channel(), msg.ID(), MsgStatusFailed, clog) - log.Errorf("unable to find handler for channel type: %s", msg.Channel().ChannelType()) + log.Error(fmt.Sprintf("unable to find handler for channel type: %s", msg.Channel().ChannelType())) } else if sent { // if this message was already sent, create a WIRED status for it status = backend.NewStatusUpdate(msg.Channel(), msg.ID(), MsgStatusWired, clog) - log.Warning("duplicate send, marking as wired") + log.Warn("duplicate send, marking as wired") } else { // send our message @@ -213,7 +210,7 @@ func (w *Sender) sendMessage(msg MsgOut) { secondDuration := float64(duration) / float64(time.Second) if err != nil { - log.WithError(err).WithField("elapsed", duration).Error("error sending message") + log.Error("error sending message", "error", err, "elapsed", duration) // handlers should log errors implicitly with user friendly messages.. but if not.. add what we have if len(clog.Errors()) == 0 { @@ -228,10 +225,10 @@ func (w *Sender) sendMessage(msg MsgOut) { // report to librato and log locally if status.Status() == MsgStatusErrored || status.Status() == MsgStatusFailed { - log.WithField("elapsed", duration).Warning("msg errored") + log.Warn("msg errored", "elapsed", duration) analytics.Gauge(fmt.Sprintf("courier.msg_send_error_%s", msg.Channel().ChannelType()), secondDuration) } else { - log.WithField("elapsed", duration).Debug("msg sent") + log.Debug("msg sent", "elapsed", duration) analytics.Gauge(fmt.Sprintf("courier.msg_send_%s", msg.Channel().ChannelType()), secondDuration) } } @@ -242,7 +239,7 @@ func (w *Sender) sendMessage(msg MsgOut) { err = backend.WriteStatusUpdate(writeCTX, status) if err != nil { - log.WithError(err).Info("error writing msg status") + log.Info("error writing msg status", "error", err) } clog.End() @@ -250,7 +247,7 @@ func (w *Sender) sendMessage(msg MsgOut) { // write our logs as well err = backend.WriteChannelLog(writeCTX, clog) if err != nil { - log.WithError(err).Info("error writing msg logs") + log.Info("error writing msg logs", "error", err) } // mark our send task as complete diff --git a/server.go b/server.go index f132a470c..6210e3c3e 100644 --- a/server.go +++ b/server.go @@ -22,7 +22,6 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // for use in request.Context @@ -138,7 +137,7 @@ func (s *server) Start() error { defer s.waitGroup.Done() err := s.httpServer.ListenAndServe() if err != nil && err != http.ErrServerClosed { - logrus.WithFields(logrus.Fields{"comp": "server", "state": "stopping"}).Error(err) + slog.Error("failed to start server", "error", err, "comp", "server", "state", "stopping") } }() @@ -155,18 +154,18 @@ func (s *server) Start() error { case <-time.After(time.Minute): err := s.backend.Heartbeat() if err != nil { - logrus.WithError(err).Error("error running backend heartbeat") + slog.Error("error running backend heartbeat", "error", err) } } } }() - logrus.WithFields(logrus.Fields{ - "comp": "server", - "port": s.config.Port, - "state": "started", - "version": s.config.Version, - }).Info("server listening on ", s.config.Port) + slog.Info(fmt.Sprintf("server listening on %d", s.config.Port), + "comp", "server", + "port", s.config.Port, + "state", "started", + "version", s.config.Version, + ) // start our foreman for outgoing messages s.foreman = NewForeman(s, s.config.MaxWorkers) @@ -177,15 +176,15 @@ func (s *server) Start() error { // Stop stops the server, returning only after all threads have stopped func (s *server) Stop() error { - log := logrus.WithField("comp", "server") - log.WithField("state", "stopping").Info("stopping server") + log := slog.With("comp", "server") + log.Info("stopping server", "state", "stopping") // stop our foreman s.foreman.Stop() // shut down our HTTP server if err := s.httpServer.Shutdown(context.Background()); err != nil { - log.WithField("state", "stopping").WithError(err).Error("error shutting down server") + log.Error("error shutting down server", "error", err, "state", "stopping") } // stop everything @@ -205,8 +204,7 @@ func (s *server) Stop() error { // clean things up, tearing down any connections s.backend.Cleanup() - - log.WithField("state", "stopped").Info("server stopped") + log.Info("server stopped", "state", "stopped") return nil } @@ -252,7 +250,7 @@ func (s *server) initializeChannelHandlers() { } activeHandlers[handler.ChannelType()] = handler - logrus.WithField("comp", "server").WithField("handler", handler.ChannelName()).WithField("handler_type", channelType).Info("handler initialized") + slog.Info("handler initialized", "comp", "server", "handler", handler.ChannelName(), "handler_type", channelType) } } @@ -296,7 +294,7 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe panicLog := recover() if panicLog != nil { debug.PrintStack() - logrus.WithError(err).WithField("channel_uuid", channelUUID).WithField("request", string(recorder.Trace.RequestTrace)).WithField("trace", panicLog).Error("panic handling request") + slog.Error("panic handling request", "error", err, "channel_uuid", channelUUID, "request", recorder.Trace.RequestTrace, "trace", panicLog) writeAndLogRequestError(ctx, handler, recorder.ResponseWriter, r, channel, errors.New("panic handling msg")) } }() @@ -309,13 +307,13 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe // if we received an error, write it out and report it if hErr != nil { - logrus.WithError(hErr).WithField("channel_uuid", channelUUID).WithField("request", string(recorder.Trace.RequestTrace)).Error("error handling request") + slog.Error("error handling request", "error", err, "channel_uuid", channelUUID, "request", recorder.Trace.RequestTrace) writeAndLogRequestError(ctx, handler, recorder.ResponseWriter, r, channel, hErr) } // end recording of the request so that we have a response trace if err := recorder.End(); err != nil { - logrus.WithError(err).WithField("channel_uuid", channelUUID).WithField("request", string(recorder.Trace.RequestTrace)).Error("error recording request") + slog.Error("error recording request", "error", err, "channel_uuid", channelUUID, "request", recorder.Trace.RequestTrace) writeAndLogRequestError(ctx, handler, w, r, channel, err) } @@ -348,12 +346,11 @@ func (s *server) channelHandleWrapper(handler ChannelHandler, handlerFunc Channe clog.End() if err := s.backend.WriteChannelLog(ctx, clog); err != nil { - logrus.WithError(err).Error("error writing channel log") + slog.Error("error writing channel log", "error", err) } } else { - logrus.WithError(err).WithFields( - logrus.Fields{"channel_type": handler.ChannelType(), "request": string(recorder.Trace.RequestTrace), "status": recorder.Trace.Response.StatusCode}, - ).Info("non-channel specific request") + slog.Info("non-channel specific request", "error", err, "channel_type", handler.ChannelType(), "request", recorder.Trace.RequestTrace, "status", recorder.Trace.Response.StatusCode) + } } } @@ -404,7 +401,7 @@ func (s *server) handleFetchAttachment(w http.ResponseWriter, r *http.Request) { resp, err := fetchAttachment(ctx, s.backend, r) if err != nil { - logrus.WithError(err).Error() + slog.Error("error fetching attachment", "error", err) WriteError(w, http.StatusBadRequest, err) return } @@ -415,20 +412,21 @@ func (s *server) handleFetchAttachment(w http.ResponseWriter, r *http.Request) { } func (s *server) handle404(w http.ResponseWriter, r *http.Request) { - logrus.WithField("url", r.URL.String()).WithField("method", r.Method).WithField("resp_status", "404").Info("not found") + slog.Info("not found", "url", r.URL.String(), "method", r.Method, "resp_status", "404") errors := []any{NewErrorData(fmt.Sprintf("not found: %s", r.URL.String()))} err := WriteDataResponse(w, http.StatusNotFound, "Not Found", errors) if err != nil { - logrus.WithError(err).Error() + slog.Error("error writing response", "error", err) } } func (s *server) handle405(w http.ResponseWriter, r *http.Request) { - logrus.WithField("url", r.URL.String()).WithField("method", r.Method).WithField("resp_status", "405").Info("invalid method") + slog.Info("invalid method", "url", r.URL.String(), "method", r.Method, "resp_status", "405") errors := []any{NewErrorData(fmt.Sprintf("method not allowed: %s", r.Method))} err := WriteDataResponse(w, http.StatusMethodNotAllowed, "Method Not Allowed", errors) if err != nil { - logrus.WithError(err).Error() + slog.Error("error writing response", "error", err) + } } diff --git a/spool.go b/spool.go index 6e4cee5a9..fc451bed1 100644 --- a/spool.go +++ b/spool.go @@ -4,13 +4,12 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "os" "path" "path/filepath" "strings" "time" - - "github.com/sirupsen/logrus" ) // FlusherFunc defines our interface for flushers, they are handed a filename and byte blob and are expected @@ -46,8 +45,8 @@ func startSpoolFlushers(s Server) { go func() { defer s.WaitGroup().Done() - log := logrus.WithField("comp", "spool") - log.WithField("state", "started").Info("spool started") + log := slog.With("comp", "spool") + log.Info("spool started", "state", "started") // runs until stopped, checking every 30 seconds if there is anything to flush from our spool for { @@ -55,7 +54,7 @@ func startSpoolFlushers(s Server) { // our server is shutting down, exit case <-s.StopChan(): - log.WithField("state", "stopped").Info("spool stopped") + log.Info("spool stopped", "state", "stopped") return // every 30 seconds we check to see if there are any files to spool @@ -99,18 +98,18 @@ func newSpoolFlusher(s Server, dir string, flusherFunc FlusherFunc) *flusher { return nil } - log := logrus.WithField("comp", "spool").WithField("filename", filename) + log := slog.With("comp", "spool", "filename", filename) // otherwise, read our msg json contents, err := os.ReadFile(filename) if err != nil { - log.WithError(err).Error("reading spool file") + log.Error("reading spool file", "error", err) return nil } err = flusherFunc(filename, contents) if err != nil { - log.WithError(err).Error("flushing spool file") + log.Error("flushing spool file", "error", err) return err } log.Info("flushed") From 081aff8de07a6b70fc2690829fce0f876e15741b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Oct 2023 15:12:34 -0500 Subject: [PATCH 130/170] Ignore attachments of type fallback on FBA channels --- handlers/meta/facebook_test.go | 8 ++++++ handlers/meta/handlers.go | 12 +++------ handlers/meta/testdata/fba/fallback.json | 32 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 handlers/meta/testdata/fba/fallback.json diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 9c73df9df..36ef60036 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -183,6 +183,14 @@ var facebookIncomingTests = []IncomingTestCase{ }, PrepRequest: addValidSignature, }, + { + Label: "Receive Fallback Attachment", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/fallback.json")), + ExpectedRespStatus: 200, + ExpectedEvents: []ExpectedEvent{}, + PrepRequest: addValidSignature, + }, { Label: "Receive DLR", URL: "/c/fba/receive", diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 44a498db2..e1876e9a4 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -552,14 +552,11 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c continue } - has_story_mentions := false - text := msg.Message.Text - attachmentURLs := make([]string, 0, 2) - // if we have a sticker ID, use that as our text for _, att := range msg.Message.Attachments { + // if we have a sticker ID, use that as our text if att.Type == "image" && att.Payload != nil && att.Payload.StickerID != 0 { text = stickerIDToEmoji[att.Payload.StickerID] } @@ -570,18 +567,17 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if att.Type == "story_mention" { data = append(data, courier.NewInfoData("ignoring story_mention")) - has_story_mentions = true continue } - if att.Payload != nil && att.Payload.URL != "" { + if att.Payload != nil && att.Payload.URL != "" && att.Type != "fallback" { attachmentURLs = append(attachmentURLs, att.Payload.URL) } } - // if we have a story mention, skip and do not save any message - if has_story_mentions { + // if we have no text or accepted attachments, don't create a message + if text == "" && len(attachmentURLs) == 0 { continue } diff --git a/handlers/meta/testdata/fba/fallback.json b/handlers/meta/testdata/fba/fallback.json new file mode 100644 index 000000000..61001574c --- /dev/null +++ b/handlers/meta/testdata/fba/fallback.json @@ -0,0 +1,32 @@ +{ + "object": "page", + "entry": [ + { + "id": "12345", + "messaging": [ + { + "message": { + "mid": "external_id", + "attachments": [ + { + "type": "fallback", + "payload": { + "url": "My Documents/foo.doc", + "title": "Foo" + } + } + ] + }, + "recipient": { + "id": "12345" + }, + "sender": { + "id": "5678" + }, + "timestamp": 1459991487970 + } + ], + "time": 1459991487970 + } + ] +} \ No newline at end of file From acb5441771490f7f525772693cca0d6b1e08f455 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Oct 2023 15:24:30 -0500 Subject: [PATCH 131/170] Fix handling IG like hearts --- handlers/meta/handlers.go | 3 +++ handlers/meta/instagram_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index e1876e9a4..f70e32ccf 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -560,6 +560,9 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c if att.Type == "image" && att.Payload != nil && att.Payload.StickerID != 0 { text = stickerIDToEmoji[att.Payload.StickerID] } + if att.Type == "like_heart" { + text = "❤️" + } if att.Type == "location" { attachmentURLs = append(attachmentURLs, fmt.Sprintf("geo:%f,%f", att.Payload.Coordinates.Lat, att.Payload.Coordinates.Long)) diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 24b829bcd..078e32483 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -73,7 +73,7 @@ var instagramIncomingTests = []IncomingTestCase{ Data: string(test.ReadFile("./testdata/ig/like_heart.json")), ExpectedRespStatus: 200, ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), + ExpectedMsgText: Sp("❤️"), ExpectedURN: "instagram:5678", ExpectedExternalID: "external_id", ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), From 06241fea9700ecc0a75d4afc51bd4490f3b94403 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Oct 2023 15:33:59 -0500 Subject: [PATCH 132/170] Update CHANGELOG.md for v8.3.24 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 517e20f0f..c49246001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.24 (2023-10-10) +------------------------- + * Fix handling IG like hearts + * Ignore attachments of type fallback on FBA channels + * More logrus replacement to use slog + v8.3.23 (2023-10-04) ------------------------- * Switch channelevent.extra to always be strings From 1bbe4367d1c369be514b94012b117f9e6a55efee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:05:25 +0000 Subject: [PATCH 133/170] Bump golang.org/x/net from 0.14.0 to 0.17.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.14.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 70ee91d1d..c258c73ec 100644 --- a/go.mod +++ b/go.mod @@ -51,10 +51,10 @@ require ( github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 602b309d8..b4a707b82 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -122,8 +122,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -135,8 +135,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -146,8 +146,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From a5aaa1e419d7d93a717a8f50e818813a318ea43e Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 11:07:54 +0200 Subject: [PATCH 134/170] Replace logrus with slog in rapidpro backend --- backends/rapidpro/backend.go | 50 ++++++++++++++---------------- backends/rapidpro/channel_event.go | 6 ++-- backends/rapidpro/channel_log.go | 16 +++++----- backends/rapidpro/contact.go | 10 +++--- backends/rapidpro/msg.go | 6 ++-- backends/rapidpro/status.go | 18 +++++------ backends/rapidpro/urn.go | 8 ++--- 7 files changed, 56 insertions(+), 58 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 6ff73654c..7ee1dfb74 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -6,6 +6,7 @@ import ( "database/sql" "encoding/json" "fmt" + "log/slog" "net/url" "path" "path/filepath" @@ -29,7 +30,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/redisx" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // the name for our message queue @@ -105,10 +105,10 @@ func newBackend(cfg *courier.Config) courier.Backend { // Start starts our RapidPro backend, this tests our various connections and starts our spool flushers func (b *backend) Start() error { // parse and test our redis config - log := logrus.WithFields(logrus.Fields{ - "comp": "backend", - "state": "starting", - }) + log := slog.With( + "comp", "backend", + "state", "starting", + ) log.Info("starting backend") // parse and test our db config @@ -137,7 +137,7 @@ func (b *backend) Start() error { err = b.db.PingContext(ctx) cancel() if err != nil { - log.WithError(err).Error("db not reachable") + log.Error("db not reachable", "error", err) } else { log.Info("db ok") } @@ -183,7 +183,7 @@ func (b *backend) Start() error { defer conn.Close() _, err = conn.Do("PING") if err != nil { - log.WithError(err).Error("redis not reachable") + log.Error("redis not reachable", "error", err) } else { log.Info("redis ok") } @@ -221,12 +221,12 @@ func (b *backend) Start() error { // check our storages if err := checkStorage(b.attachmentStorage); err != nil { - log.WithError(err).Error(b.attachmentStorage.Name() + " attachment storage not available") + log.Error(b.attachmentStorage.Name()+" attachment storage not available", "error", err) } else { log.Info(b.attachmentStorage.Name() + " attachment storage ok") } if err := checkStorage(b.logStorage); err != nil { - log.WithError(err).Error(b.logStorage.Name() + " log storage not available") + log.Error(b.logStorage.Name()+" log storage not available", "error", err) } else { log.Info(b.logStorage.Name() + " log storage ok") } @@ -240,7 +240,7 @@ func (b *backend) Start() error { err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "events") } if err != nil { - log.WithError(err).Error("spool directories not writable") + log.Error("spool directories not writable", "error", err) } else { log.Info("spool directories ok") } @@ -260,7 +260,7 @@ func (b *backend) Start() error { courier.RegisterFlusher(path.Join(b.config.SpoolDir, "statuses"), b.flushStatusFile) courier.RegisterFlusher(path.Join(b.config.SpoolDir, "events"), b.flushChannelEventFile) - logrus.WithFields(logrus.Fields{"comp": "backend", "state": "started"}).Info("backend started") + slog.Info("backend started", "comp", "backend", "state", "started") return nil } @@ -496,14 +496,14 @@ func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.MsgOu rc.Send("expire", dateKey, 60*60*24*2) _, err := rc.Do("") if err != nil { - logrus.WithError(err).WithField("sent_msgs_key", dateKey).Error("unable to add new unsent message") + slog.Error("unable to add new unsent message", "error", err, "sent_msgs_key", dateKey) } // if our msg has an associated session and timeout, update that if dbMsg.SessionWaitStartedOn_ != nil { err = updateSessionTimeout(ctx, b, dbMsg.SessionID_, *dbMsg.SessionWaitStartedOn_, dbMsg.SessionTimeout_) if err != nil { - logrus.WithError(err).WithField("session_id", dbMsg.SessionID_).Error("unable to update session timeout") + slog.Error("unable to update session timeout", "error", err, "session_id", dbMsg.SessionID_) } } } @@ -529,7 +529,7 @@ func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalI // WriteStatusUpdate writes the passed in MsgStatus to our store func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { - log := logrus.WithFields(logrus.Fields{"msg_id": status.MsgID(), "msg_external_id": status.ExternalID(), "status": status.Status()}) + log := slog.With("msg_id", status.MsgID(), "msg_external_id", status.ExternalID(), "status", status.Status()) su := status.(*StatusUpdate) if status.MsgID() == courier.NilMsgID && status.ExternalID() == "" { @@ -553,7 +553,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.MsgID())) if err != nil { - log.WithError(err).Error("error recording external id") + log.Error("error recording external id", "error", err) } } @@ -561,7 +561,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp if status.Status() == courier.MsgStatusErrored { err := b.ClearMsgSent(ctx, status.MsgID()) if err != nil { - log.WithError(err).Error("error clearing sent flags") + log.Error("error clearing sent flags", "error", err) } } } @@ -799,16 +799,14 @@ func (b *backend) Heartbeat() error { analytics.Gauge("courier.bulk_queue", float64(bulkSize)) analytics.Gauge("courier.priority_queue", float64(prioritySize)) - logrus.WithFields(logrus.Fields{ - "db_busy": dbStats.InUse, - "db_idle": dbStats.Idle, - "db_wait_time": dbWaitDurationInPeriod, - "db_wait_count": dbWaitCountInPeriod, - "redis_wait_time": dbWaitDurationInPeriod, - "redis_wait_count": dbWaitCountInPeriod, - "priority_size": prioritySize, - "bulk_size": bulkSize, - }).Info("current analytics") + slog.Info("current analytics", "db_busy", dbStats.InUse, + "db_idle", dbStats.Idle, + "db_wait_time", dbWaitDurationInPeriod, + "db_wait_count", dbWaitCountInPeriod, + "redis_wait_time", dbWaitDurationInPeriod, + "redis_wait_count", dbWaitCountInPeriod, + "priority_size", prioritySize, + "bulk_size", bulkSize) return nil } diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index 8cb530dc9..e9b58c022 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log" + "log/slog" "os" "strconv" "time" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/null/v3" - "github.com/sirupsen/logrus" ) // ChannelEventID is the type of our channel event ids @@ -124,7 +124,7 @@ func writeChannelEvent(ctx context.Context, b *backend, event courier.ChannelEve // failed writing, write to our spool instead if err != nil { - logrus.WithError(err).WithField("channel_id", dbEvent.ChannelID).WithField("event_type", dbEvent.EventType_).Error("error writing channel event to db") + slog.Error("error writing channel event to db", "error", err, "channel_id", dbEvent.ChannelID, "event_type", dbEvent.EventType_) } if err != nil { @@ -171,7 +171,7 @@ func writeChannelEventToDB(ctx context.Context, b *backend, e *ChannelEvent, clo // if we had a problem queueing the event, log it err = queueChannelEvent(rc, contact, e) if err != nil { - logrus.WithError(err).WithField("evt_id", e.ID_).Error("error queueing channel event") + slog.Error("error queueing channel event", "error", err, "evt_id", e.ID_) } return nil diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 3aba20d34..8c0fbce13 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "path" "sync" "time" @@ -15,7 +16,6 @@ import ( "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/storage" "github.com/nyaruka/gocommon/syncx" - "github.com/sirupsen/logrus" ) const sqlInsertChannelLog = ` @@ -57,7 +57,7 @@ type channelError struct { // queues the passed in channel log to a writer func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) { - log := logrus.WithFields(logrus.Fields{"log_uuid": clog.UUID(), "log_type": clog.Type(), "channel_uuid": clog.Channel().UUID()}) + log := slog.With("log_uuid", clog.UUID(), "log_type", clog.Type(), "channel_uuid", clog.Channel().UUID()) dbChan := clog.Channel().(*Channel) // so that we don't save null @@ -79,7 +79,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) // if log is attached to a call or message, only write to storage if clog.Attached() { - log = log.WithField("storage", "s3") + log = log.With("storage", "s3") v := &stChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -94,7 +94,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) } } else { // otherwise write to database so it's retrievable - log = log.WithField("storage", "db") + log = log.With("storage", "db") v := &dbChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -136,14 +136,14 @@ func writeDBChannelLogs(ctx context.Context, db *sqlx.DB, batch []*dbChannelLog) for _, v := range batch { err = dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, []*dbChannelLog{v}) if err != nil { - log := logrus.WithField("comp", "log writer").WithField("log_uuid", v.UUID) + log := slog.With("comp", "log writer", "log_uuid", v.UUID) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + log = log.With("sql", query, "sql_params", params) } - log.WithError(err).Error("error writing channel log") + log.Error("error writing channel log", "error", err) } } } @@ -174,6 +174,6 @@ func writeStorageChannelLogs(ctx context.Context, st storage.Storage, batch []*s } } if err := st.BatchPut(ctx, uploads); err != nil { - logrus.WithField("comp", "storage log writer").Error("error writing channel logs") + slog.Error("error writing channel logs", "comp", "storage log writer") } } diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 80f5cc208..1d7f6d162 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "log/slog" "strconv" "time" "unicode/utf8" @@ -16,7 +17,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // used by unit tests to slow down urn operations to test races @@ -107,7 +107,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, contact := &Contact{} err := b.db.GetContext(ctx, contact, lookupContactFromURNSQL, urn.Identity(), org) if err != nil && err != sql.ErrNoRows { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) return nil, errors.Wrap(err, "error looking up contact by URN") } @@ -116,13 +116,13 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, // insert it tx, err := b.db.BeginTxx(ctx, nil) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) return nil, errors.Wrap(err, "error beginning transaction") } err = setDefaultURN(tx, channel, contact, urn, authTokens) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) tx.Rollback() return nil, errors.Wrap(err, "error setting default URN for contact") } @@ -148,7 +148,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, // in the case of errors, we log the error but move onwards anyways if err != nil { - logrus.WithField("channel_uuid", channel.UUID()).WithField("channel_type", channel.ChannelType()).WithField("urn", urn).WithError(err).Error("unable to describe URN") + slog.Error("unable to describe URN", "error", err, "channel_uuid", channel.UUID(), "channel_type", channel.ChannelType(), "urn", urn) } else { name = attrs["name"] } diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 11cb6aa87..cd56e70d9 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "log" + "log/slog" "os" "strings" "time" @@ -22,7 +23,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" filetype "gopkg.in/h2non/filetype.v1" ) @@ -228,7 +228,7 @@ func writeMsg(ctx context.Context, b *backend, msg courier.MsgIn, clog *courier. // fail? log if err != nil { - logrus.WithError(err).WithField("msg", m.UUID()).Error("error writing to db") + slog.Error("error writing to db", "error", err, "msg", m.UUID()) } // if we failed write to spool @@ -282,7 +282,7 @@ func writeMsgToDB(ctx context.Context, b *backend, m *Msg, clog *courier.Channel // if we had a problem queueing the handling, log it, but our message is written, it'll // get picked up by our rapidpro catch-all after a period if err != nil { - logrus.WithError(err).WithField("msg_id", m.ID_).Error("error queueing msg handling") + slog.Error("error queueing msg handling", "error", err, "msg_id", m.ID_) } return nil diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 16243e6e6..5793049b8 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" "strconv" "sync" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/gocommon/syncx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // StatusUpdate represents a status update on a message @@ -121,7 +121,7 @@ func (b *backend) flushStatusFile(filename string, contents []byte) error { status := &StatusUpdate{} err := json.Unmarshal(contents, status) if err != nil { - logrus.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) + slog.Info(fmt.Sprintf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err)) os.Rename(filename, fmt.Sprintf("%s.error", filename)) return nil } @@ -182,7 +182,7 @@ func NewStatusWriter(b *backend, spoolDir string, wg *sync.WaitGroup) *StatusWri // tries to write a batch of message statuses to the database and spools those that fail func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batch []*StatusUpdate) { - log := logrus.WithField("comp", "status writer") + log := slog.With("comp", "status writer") unresolved, err := b.writeStatusUpdatesToDB(ctx, batch) @@ -191,24 +191,24 @@ func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batc for _, s := range batch { _, err = b.writeStatusUpdatesToDB(ctx, []*StatusUpdate{s}) if err != nil { - log := log.WithField("msg_id", s.MsgID()) + log := log.With("msg_id", s.MsgID()) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + log = log.With("sql", query, "sql_params", params) } - log.WithError(err).Error("error writing msg status") + log.Error("error writing msg status", "error", err) err := courier.WriteToSpool(spoolDir, "statuses", s) if err != nil { - log.WithError(err).Error("error writing status to spool") // just have to log and move on + log.Error("error writing status to spool", "error", err) // just have to log and move on } } } } else { for _, s := range unresolved { - log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) + log.Warn(fmt.Sprintf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_)) } } } @@ -268,7 +268,7 @@ func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*Sta cachedIDs, err := b.sentExternalIDs.MGet(rc, chAndExtKeys...) if err != nil { // log error but we continue and try to get ids from the database - logrus.WithError(err).Error("error looking up sent message ids in redis") + slog.Error("error looking up sent message ids in redis", "error", err) } // collect the statuses that couldn't be resolved from cache, update the ones that could diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 46d814d83..07717754d 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -4,6 +4,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "log/slog" "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" @@ -11,7 +12,6 @@ import ( "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // ContactURNID represents a contact urn's id @@ -98,7 +98,7 @@ func setDefaultURN(db *sqlx.Tx, channel *Channel, contact *Contact, urn urns.URN scheme := urn.Scheme() contactURNs, err := getURNsForContact(db, contact.ID_) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("channel_id", channel.ID()).Error("error looking up contact urns") + slog.Error("error looking up contact urns", "error", err, "urn", urn.Identity(), "channel_id", channel.ID()) return err } @@ -248,7 +248,7 @@ UPDATE contacts_contacturn func updateContactURN(db *sqlx.Tx, urn *ContactURN) error { rows, err := db.NamedQuery(sqlUpdateURN, urn) if err != nil { - logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") + slog.Error("error updating contact urn", "error", err, "urn_id", urn.ID) return err } defer rows.Close() @@ -263,7 +263,7 @@ func updateContactURN(db *sqlx.Tx, urn *ContactURN) error { func fullyUpdateContactURN(db *sqlx.Tx, urn *ContactURN) error { rows, err := db.NamedQuery(sqlFullyUpdateURN, urn) if err != nil { - logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") + slog.Error("error updating contact urn", "error", err, "urn_id", urn.ID) return err } defer rows.Close() From 56a4ad4f21800e96ec25d926ab432531c9998e05 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 11:36:05 +0200 Subject: [PATCH 135/170] Discard logs for testing --- backends/rapidpro/backend_test.go | 3 +-- handlers/jiochat/handler_test.go | 4 ++-- handlers/test.go | 4 ++-- handlers/wechat/handler_test.go | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 2b7231497..97ee68d50 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -26,7 +26,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/nyaruka/redisx/assertredis" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" ) @@ -47,7 +46,7 @@ func (ts *BackendTestSuite) SetupSuite() { storageDir = "_test_storage" // turn off logging - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) b, err := courier.NewBackend(testConfig()) if err != nil { diff --git a/handlers/jiochat/handler_test.go b/handlers/jiochat/handler_test.go index 865d902e1..77af22f35 100644 --- a/handlers/jiochat/handler_test.go +++ b/handlers/jiochat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log" "log/slog" "net/http" "net/http/httptest" @@ -19,7 +20,6 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -262,7 +262,7 @@ func buildMockJCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" config.Redis = "redis://localhost:6379/0" diff --git a/handlers/test.go b/handlers/test.go index ec8693b88..ff46c2a9c 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log" "log/slog" "mime/multipart" "net/http" @@ -21,7 +22,6 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -149,7 +149,7 @@ func testHandlerRequest(tb testing.TB, s courier.Server, path string, headers ma func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.FacebookWebhookSecret = "fb_webhook_secret" diff --git a/handlers/wechat/handler_test.go b/handlers/wechat/handler_test.go index b784ec8fc..843e238e4 100644 --- a/handlers/wechat/handler_test.go +++ b/handlers/wechat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log" "log/slog" "net/http" "net/http/httptest" @@ -19,7 +20,6 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -214,7 +214,7 @@ func buildMockWCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" config.Redis = "redis://localhost:6379/0" From b4ea6aba57345def3524c6794293bb9bbf653540 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 13:58:38 +0200 Subject: [PATCH 136/170] Replace logrus with slog in handlers packages --- handlers/facebook_legacy/handler.go | 4 ++-- handlers/hormuud/handler.go | 4 ++-- handlers/messagebird/handler.go | 4 ++-- handlers/twiml/handlers.go | 4 ++-- handlers/whatsapp_legacy/handler.go | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index c5ca9e671..6c075869a 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -16,7 +17,6 @@ import ( "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // Endpoints we hit @@ -124,7 +124,7 @@ func (h *handler) subscribeToEvents(ctx context.Context, channel courier.Channel // log if we get any kind of error success, _ := jsonparser.GetBoolean(respBody, "success") if err != nil || resp.StatusCode/100 != 2 || !success { - logrus.WithField("channel_uuid", channel.UUID()).Error("error subscribing to Facebook page events") + slog.Error("error subscribing to Facebook page events", "channel_uuid", channel.UUID()) } h.Backend().WriteChannelLog(ctx, clog) diff --git a/handlers/hormuud/handler.go b/handlers/hormuud/handler.go index 31dcac41c..88f8390a6 100644 --- a/handlers/hormuud/handler.go +++ b/handlers/hormuud/handler.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) var ( @@ -181,7 +181,7 @@ func (h *handler) FetchToken(ctx context.Context, channel courier.Channel, msg c conn.Close() if err != nil { - logrus.WithError(err).Error("error caching HM access token") + slog.Error("error caching HM access token", "error", err) } return token, nil diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 4b01076aa..1981be45e 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -9,6 +9,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "log/slog" "strconv" "fmt" @@ -22,7 +23,6 @@ import ( "github.com/nyaruka/courier/handlers" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" ) var ( @@ -113,7 +113,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if receivedStatus.Reference != "" { msgID, err := strconv.ParseInt(receivedStatus.Reference, 10, 64) if err != nil { - logrus.WithError(err).WithField("id", receivedStatus.Reference).Error("error converting Messagebird status id to integer") + slog.Error("error converting Messagebird status id to integer", "error", err, "id", receivedStatus.Reference) } else { status = h.Backend().NewStatusUpdate(channel, courier.MsgID(msgID), msgStatus, clog) } diff --git a/handlers/twiml/handlers.go b/handlers/twiml/handlers.go index 4d12f6553..24298b361 100644 --- a/handlers/twiml/handlers.go +++ b/handlers/twiml/handlers.go @@ -12,6 +12,7 @@ import ( _ "embed" "encoding/base64" "fmt" + "log/slog" "net/http" "net/url" "sort" @@ -25,7 +26,6 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) const ( @@ -181,7 +181,7 @@ func (h *handler) receiveStatus(ctx context.Context, channel courier.Channel, w if idString != "" { msgID, err := strconv.ParseInt(idString, 10, 64) if err != nil { - logrus.WithError(err).WithField("id", idString).Error("error converting twilio callback id to integer") + slog.Error("error converting twilio callback id to integer", "error", err, "id", idString) } else { status = h.Backend().NewStatusUpdate(channel, courier.MsgID(msgID), msgStatus, clog) } diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index b1de6774b..d03e70250 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "net/url" "strconv" @@ -23,7 +24,6 @@ import ( "github.com/nyaruka/redisx" "github.com/patrickmn/go-cache" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "golang.org/x/mod/semver" ) @@ -577,7 +577,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] mimeType, mediaURL := handlers.SplitAttachment(attachment) mediaID, err := h.fetchMediaID(msg, mimeType, mediaURL, clog) if err != nil { - logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("error while uploading media to whatsapp") + slog.Error("error while uploading media to whatsapp", "error", err, "channel_uuid", msg.Channel().UUID()) } fileURL := mediaURL if err == nil && mediaID != "" { @@ -604,7 +604,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] // Logging error if err != nil { - logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("Error while parsing the media URL") + slog.Error("Error while parsing the media URL", "error", err, "channel_uuid", msg.Channel().UUID()) } payload.Document = mediaPayload payloads = append(payloads, payload) From def5d3de60de9dc90409da1ac2c1662630210b1d Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 18:01:45 +0200 Subject: [PATCH 137/170] Remove use of logrus and use slog with sentry --- Dockerfile | 2 +- cmd/courier/main.go | 50 +++++++++++++----------- go.mod | 19 +++++----- go.sum | 57 +++++++++++++--------------- utils/logrus.go | 92 --------------------------------------------- 5 files changed, 64 insertions(+), 156 deletions(-) delete mode 100644 utils/logrus.go diff --git a/Dockerfile b/Dockerfile index a2cf86075..b8738962b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20 +FROM golang:1.21 WORKDIR /usr/src/app diff --git a/cmd/courier/main.go b/cmd/courier/main.go index be2563ac0..5f6c9d622 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -1,16 +1,17 @@ package main import ( + "log" "log/slog" "os" "os/signal" "syscall" + "time" - "github.com/evalphobia/logrus_sentry" + "github.com/getsentry/sentry-go" _ "github.com/lib/pq" "github.com/nyaruka/courier" - "github.com/nyaruka/courier/utils" - "github.com/sirupsen/logrus" + slogsentry "github.com/samber/slog-sentry" // load channel handler packages _ "github.com/nyaruka/courier/handlers/africastalking" @@ -87,51 +88,56 @@ func main() { } // configure our logger - logrus.SetOutput(os.Stdout) - level, err := logrus.ParseLevel(config.LogLevel) + loggerLevel := new(slog.LevelVar) + logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: loggerLevel}) + slog.SetDefault(slog.New(logHandler)) + + var level slog.Level + err := level.UnmarshalText([]byte(config.LogLevel)) if err != nil { - slog.Error("invalid log level", "level", level) + log.Fatalf("invalid log level %s", level) os.Exit(1) } - logrus.SetLevel(level) - - // configure golang std structured logging to route to logrus - slog.SetDefault(slog.New(utils.NewLogrusHandler(logrus.StandardLogger()))) + loggerLevel.Set(level) - log := slog.With("comp", "main") - log.Info("starting courier", "version", version) + logger := slog.With("comp", "main") + logger.Info("starting courier", "version", version) // if we have a DSN entry, try to initialize it if config.SentryDSN != "" { - hook, err := logrus_sentry.NewSentryHook(config.SentryDSN, []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}) - hook.Timeout = 0 - hook.StacktraceConfiguration.Enable = true - hook.StacktraceConfiguration.Skip = 4 - hook.StacktraceConfiguration.Context = 5 + err := sentry.Init(sentry.ClientOptions{ + Dsn: config.SentryDSN, + EnableTracing: false, + }) if err != nil { - log.Error("unable to configure sentry hook", "dsn", config.SentryDSN, "error", err) + log.Fatalf("error initiating sentry client, error %s, dsn %s", err, config.SentryDSN) os.Exit(1) } - logrus.StandardLogger().Hooks.Add(hook) + + defer sentry.Flush(2 * time.Second) + + logger = slog.New(slogsentry.Option{Level: slog.LevelError}.NewSentryHandler()) + logger = logger.With("release", version) + slog.SetDefault(logger) } // load our backend backend, err := courier.NewBackend(config) if err != nil { - log.Error("error creating backend", "error", err) + logger.Error("error creating backend", "error", err) os.Exit(1) } server := courier.NewServer(config, backend) err = server.Start() if err != nil { - log.Error("unable to start server", "error", err) + logger.Error("unable to start server", "error", err) os.Exit(1) } ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - slog.Info("stopping", "comp", "main", "signal", <-ch) + logger.Info("stopping", "comp", "main", "signal", <-ch) server.Stop() diff --git a/go.mod b/go.mod index c258c73ec..cccd70d25 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.21 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.45.0 + github.com/aws/aws-sdk-go v1.45.24 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 - github.com/evalphobia/logrus_sentry v0.8.2 + github.com/getsentry/sentry-go v0.22.0 github.com/go-chi/chi v4.1.2+incompatible github.com/golang-jwt/jwt/v5 v5.0.0 github.com/gomodule/redigo v1.8.9 @@ -15,26 +15,24 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.41.1 + github.com/nyaruka/gocommon v1.42.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.3 + github.com/samber/slog-sentry v1.2.2 github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/mod v0.12.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/mod v0.13.0 gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/h2non/filetype.v1 v1.0.5 ) require ( github.com/antchfx/xpath v1.2.4 // indirect - github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/getsentry/raven-go v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.1 // indirect @@ -46,10 +44,11 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect - github.com/nyaruka/librato v1.0.0 // indirect + github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/samber/lo v1.38.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/go.sum b/go.sum index b4a707b82..fe23d7bff 100644 --- a/go.sum +++ b/go.sum @@ -2,28 +2,26 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.45.0 h1:qoVOQHuLacxJMO71T49KeE70zm+Tk3vtrl7XO4VUPZc= -github.com/aws/aws-sdk-go v1.45.0/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.45.24 h1:TZx/CizkmCQn8Rtsb11iLYutEQVGK5PK9wAhwouELBo= +github.com/aws/aws-sdk-go v1.45.24/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dghubble/oauth1 v0.7.2 h1:pwcinOZy8z6XkNxvPmUDY52M7RDPxt0Xw1zgZ6Cl5JA= github.com/dghubble/oauth1 v0.7.2/go.mod h1:9erQdIhqhOHG/7K9s/tgh9Ks/AfoyrO5mW/43Lu2+kE= -github.com/evalphobia/logrus_sentry v0.8.2 h1:dotxHq+YLZsT1Bb45bB5UQbfCh3gM/nFFetyN46VoDQ= -github.com/evalphobia/logrus_sentry v0.8.2/go.mod h1:pKcp+vriitUqu9KiWj/VRFbRfFNUwz95/UkgG8a6MNc= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM= +github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -46,8 +44,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -56,7 +54,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= @@ -72,10 +69,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.41.1 h1:SpIXqLCBF3Un/AjzIiqC/DO4jU7Zt7SyDh/t9SyjIrQ= -github.com/nyaruka/gocommon v1.41.1/go.mod h1:cJ2XmEX+FDOzBvE19IW+hG8EFVsSrNgCp7NrxAlP4Xg= -github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= -github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= +github.com/nyaruka/gocommon v1.42.0 h1:lJtIJ+1fehx8DWrxFegR0OtH1BjKIZs8/y/zaLrCmgA= +github.com/nyaruka/gocommon v1.42.0/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= +github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= +github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= @@ -86,21 +83,21 @@ github.com/nyaruka/redisx v0.5.0 h1:XH1pjG17lhj2DZJbrrZ2yZuPLAXrrHidXVA7cIuQq4g= github.com/nyaruka/redisx v0.5.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= +github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -112,11 +109,11 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -127,11 +124,9 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -165,9 +160,9 @@ gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+a gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y= gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utils/logrus.go b/utils/logrus.go deleted file mode 100644 index 2650bf9bb..000000000 --- a/utils/logrus.go +++ /dev/null @@ -1,92 +0,0 @@ -// Structured logging handler for logrus so we can rewrite code to use slog package incrementally. Once all logging is -// happening via slog, we just need to hook up Sentry directly to that, and then we can get rid of this file. -package utils - -import ( - "context" - "log/slog" - "slices" - "strings" - - "github.com/sirupsen/logrus" -) - -var levels = map[slog.Level]logrus.Level{ - slog.LevelError: logrus.ErrorLevel, - slog.LevelWarn: logrus.WarnLevel, - slog.LevelInfo: logrus.InfoLevel, - slog.LevelDebug: logrus.DebugLevel, -} - -type LogrusHandler struct { - logger *logrus.Logger - groups []string - attrs []slog.Attr -} - -func NewLogrusHandler(logger *logrus.Logger) *LogrusHandler { - return &LogrusHandler{logger: logger} -} - -func (l *LogrusHandler) clone() *LogrusHandler { - return &LogrusHandler{ - logger: l.logger, - groups: slices.Clip(l.groups), - attrs: slices.Clip(l.attrs), - } -} - -func (l *LogrusHandler) Enabled(ctx context.Context, level slog.Level) bool { - return levels[level] <= l.logger.GetLevel() -} - -func (l *LogrusHandler) Handle(ctx context.Context, r slog.Record) error { - log := logrus.NewEntry(l.logger) - if r.Time.IsZero() { - log = log.WithTime(r.Time) - } - - f := logrus.Fields{} - for _, a := range l.attrs { - if a.Key != "" { - f[a.Key] = a.Value - } - } - log = log.WithFields(f) - - r.Attrs(func(attr slog.Attr) bool { - if attr.Key == "" { - return true - } - log = log.WithField(attr.Key, attr.Value) - return true - }) - log.Logf(levels[r.Level], r.Message) - return nil -} - -func (l *LogrusHandler) groupPrefix() string { - if len(l.groups) > 0 { - return strings.Join(l.groups, ":") + ":" - } - return "" -} - -func (l *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - newHandler := l.clone() - for _, a := range attrs { - newHandler.attrs = append(newHandler.attrs, slog.Attr{ - Key: l.groupPrefix() + a.Key, - Value: a.Value, - }) - } - return newHandler -} - -func (l *LogrusHandler) WithGroup(name string) slog.Handler { - newHandler := l.clone() - newHandler.groups = append(newHandler.groups, name) - return newHandler -} - -var _ slog.Handler = &LogrusHandler{} From 3f64d8324969caeb8d50754c0dd7cea125a7a1bd Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Fri, 13 Oct 2023 12:21:02 +0200 Subject: [PATCH 138/170] Use multi slog handlers when we have sentry DSN configured --- cmd/courier/main.go | 8 +++++++- go.mod | 1 + go.sum | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 5f6c9d622..47100c3ca 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -11,6 +11,7 @@ import ( "github.com/getsentry/sentry-go" _ "github.com/lib/pq" "github.com/nyaruka/courier" + slogmulti "github.com/samber/slog-multi" slogsentry "github.com/samber/slog-sentry" // load channel handler packages @@ -116,7 +117,12 @@ func main() { defer sentry.Flush(2 * time.Second) - logger = slog.New(slogsentry.Option{Level: slog.LevelError}.NewSentryHandler()) + logger = slog.New( + slogmulti.Fanout( + logHandler, + slogsentry.Option{Level: slog.LevelError}.NewSentryHandler(), + ), + ) logger = logger.With("release", version) slog.SetDefault(logger) } diff --git a/go.mod b/go.mod index cccd70d25..8dc2cdbc4 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.38.1 // indirect + github.com/samber/slog-multi v1.0.2 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/go.sum b/go.sum index fe23d7bff..7096f2d4a 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= +github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= From b65220a7278b48f831751bcda8c3482c769d377b Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Fri, 13 Oct 2023 15:45:49 +0200 Subject: [PATCH 139/170] Create slog handler with the configured parsed log level --- cmd/courier/main.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/courier/main.go b/cmd/courier/main.go index 47100c3ca..b8085d6c2 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -88,18 +88,16 @@ func main() { config.Version = version } - // configure our logger - loggerLevel := new(slog.LevelVar) - logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: loggerLevel}) - slog.SetDefault(slog.New(logHandler)) - var level slog.Level err := level.UnmarshalText([]byte(config.LogLevel)) if err != nil { log.Fatalf("invalid log level %s", level) os.Exit(1) } - loggerLevel.Set(level) + + // configure our logger + logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}) + slog.SetDefault(slog.New(logHandler)) logger := slog.With("comp", "main") logger.Info("starting courier", "version", version) From 6f6f5b9f6f6d7c5f7de937752718e2af38dc249b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Oct 2023 14:56:24 -0500 Subject: [PATCH 140/170] Update docker image to go 1.21 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a2cf86075..b8738962b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20 +FROM golang:1.21 WORKDIR /usr/src/app From 654728090b8f78247e74935cddbe96c4dc7c0852 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Oct 2023 16:01:53 -0500 Subject: [PATCH 141/170] Update CHANGELOG.md for v8.3.25 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c49246001..026d04c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.25 (2023-10-25) +------------------------- + * Update docker image to go 1.21 + * Remove use of logrus and use slog with sentry + * Bump golang.org/x/net from 0.14.0 to 0.17.0 + v8.3.24 (2023-10-10) ------------------------- * Fix handling IG like hearts From 3a53db439bf4b8c735c6437ab151bcbdd4b66db8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Oct 2023 09:38:57 -0500 Subject: [PATCH 142/170] Update to latest gocommon --- attachments.go | 2 +- go.mod | 4 ++-- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/attachments.go b/attachments.go index e6b587fcd..829ed0957 100644 --- a/attachments.go +++ b/attachments.go @@ -99,7 +99,7 @@ func FetchAndStoreAttachment(ctx context.Context, b Backend, channel Channel, at // if we got a non-200 response, return the attachment with a pseudo content type which tells the caller // to continue without the attachment - if trace.Response == nil || trace.Response.StatusCode/100 != 2 { + if trace.Response == nil || trace.Response.StatusCode/100 != 2 || err == httpx.ErrResponseSize || err == httpx.ErrAccessConfig { return &Attachment{ContentType: "unavailable", URL: attURL}, nil } } diff --git a/go.mod b/go.mod index 8dc2cdbc4..82af11240 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,12 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.42.0 + github.com/nyaruka/gocommon v1.42.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 + github.com/samber/slog-multi v1.0.2 github.com/samber/slog-sentry v1.2.2 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20231006140011-7918f672742d @@ -49,7 +50,6 @@ require ( github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.38.1 // indirect - github.com/samber/slog-multi v1.0.2 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/go.sum b/go.sum index 7096f2d4a..3d0ea5ae1 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.42.0 h1:lJtIJ+1fehx8DWrxFegR0OtH1BjKIZs8/y/zaLrCmgA= -github.com/nyaruka/gocommon v1.42.0/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= +github.com/nyaruka/gocommon v1.42.1 h1:BIS+RpgG06Vl4nzrPLxuFFVF+KTP7PZ+V1xJE1ksLBo= +github.com/nyaruka/gocommon v1.42.1/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 8f8ef1b2934c252101c23ad4d4c7ff822c21ae16 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Oct 2023 09:39:28 -0500 Subject: [PATCH 143/170] Update CHANGELOG.md for v8.3.26 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 026d04c73..77c4af216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.26 (2023-10-30) +------------------------- + * Update to latest gocommon + v8.3.25 (2023-10-25) ------------------------- * Update docker image to go 1.21 From eb08b7c66cb12f109ab816940511a920a7f68016 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Oct 2023 14:10:36 -0500 Subject: [PATCH 144/170] Prevent all courier HTTP requests from accessing local networks --- attachments.go | 2 +- backend.go | 6 +++ backends/rapidpro/backend.go | 35 ++++++++++++++ config.go | 56 +++++++++++++++++----- go.mod | 2 +- go.sum | 4 +- handlers/africastalking/handler.go | 2 +- handlers/arabiacell/handler.go | 2 +- handlers/bandwidth/handler.go | 2 +- handlers/base.go | 32 +++++++++++++ handlers/{http_test.go => base_test.go} | 12 +++-- handlers/bongolive/handler.go | 2 +- handlers/burstsms/handler.go | 2 +- handlers/clickatell/handler.go | 2 +- handlers/clickmobile/handler.go | 2 +- handlers/clicksend/handler.go | 2 +- handlers/dart/handler.go | 2 +- handlers/dialog360/handler.go | 24 +++++----- handlers/discord/handler.go | 2 +- handlers/dmark/handler.go | 2 +- handlers/external/handler.go | 2 +- handlers/facebook_legacy/handler.go | 6 +-- handlers/facebook_legacy/handler_test.go | 1 + handlers/firebase/handler.go | 2 +- handlers/freshchat/handler.go | 2 +- handlers/globe/handler.go | 2 +- handlers/highconnection/handler.go | 2 +- handlers/hormuud/handler.go | 4 +- handlers/http.go | 38 --------------- handlers/i2sms/handler.go | 2 +- handlers/infobip/handler.go | 2 +- handlers/jasmin/handler.go | 2 +- handlers/jiochat/handler.go | 6 +-- handlers/justcall/handler.go | 2 +- handlers/kaleyra/handler.go | 6 +-- handlers/kannel/handler.go | 4 +- handlers/line/handler.go | 4 +- handlers/m3tech/handler.go | 2 +- handlers/macrokiosk/handler.go | 2 +- handlers/mblox/handler.go | 2 +- handlers/messagebird/handler.go | 2 +- handlers/messangi/handler.go | 2 +- handlers/meta/facebook_test.go | 2 +- handlers/meta/handlers.go | 26 +++++------ handlers/meta/instagram_test.go | 2 +- handlers/mtarget/handler.go | 2 +- handlers/mtn/handler.go | 4 +- handlers/nexmo/handler.go | 2 +- handlers/novo/handler.go | 2 +- handlers/playmobile/handler.go | 2 +- handlers/plivo/handler.go | 2 +- handlers/redrabbit/handler.go | 2 +- handlers/rocketchat/handler.go | 2 +- handlers/shaqodoon/handler.go | 2 +- handlers/slack/handler.go | 22 ++++----- handlers/slack/handler_test.go | 1 + handlers/smscentral/handler.go | 2 +- handlers/start/handler.go | 2 +- handlers/telegram/handler.go | 4 +- handlers/telesom/handler.go | 2 +- handlers/thinq/handler.go | 4 +- handlers/twiml/handlers.go | 2 +- handlers/twitter/handler.go | 21 ++++----- handlers/viber/handler.go | 10 ++-- handlers/vk/handler.go | 41 ++++++++-------- handlers/vk/handler_test.go | 1 + handlers/wavy/handler.go | 2 +- handlers/wechat/handler.go | 6 +-- handlers/whatsapp_legacy/handler.go | 20 ++++---- handlers/yo/handler.go | 2 +- handlers/zenvia/handlers.go | 2 +- server.go | 3 -- test/backend.go | 12 ++++- test/server.go | 59 ++++++++++++++++++++++++ utils/http.go | 51 -------------------- utils/http_test.go | 24 ---------- 76 files changed, 346 insertions(+), 289 deletions(-) rename handlers/{http_test.go => base_test.go} (85%) delete mode 100644 handlers/http.go create mode 100644 test/server.go delete mode 100644 utils/http.go delete mode 100644 utils/http_test.go diff --git a/attachments.go b/attachments.go index 829ed0957..caa5935c4 100644 --- a/attachments.go +++ b/attachments.go @@ -93,7 +93,7 @@ func FetchAndStoreAttachment(ctx context.Context, b Backend, channel Channel, at return nil, errors.Wrap(err, "unable to create attachment request") } - trace, err := httpx.DoTrace(utils.GetHTTPClient(), attRequest, nil, nil, maxAttBodyReadBytes) + trace, err := httpx.DoTrace(b.HttpClient(true), attRequest, nil, b.HttpAccess(), maxAttBodyReadBytes) if trace != nil { clog.HTTP(trace) diff --git a/backend.go b/backend.go index 2ab025e2a..0479ffb0a 100644 --- a/backend.go +++ b/backend.go @@ -3,9 +3,11 @@ package courier import ( "context" "fmt" + "net/http" "strings" "github.com/gomodule/redigo/redis" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" ) @@ -88,6 +90,10 @@ type Backend interface { // ResolveMedia resolves an outgoing attachment URL to a media object ResolveMedia(context.Context, string) (Media, error) + // HttpClient returns an HTTP client for making external requests + HttpClient(bool) *http.Client + HttpAccess() *httpx.AccessConfig + // Health returns a string describing any health problems the backend has, or empty string if all is well Health() string diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 7ee1dfb74..3a8f0a5eb 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -3,10 +3,12 @@ package rapidpro import ( "bytes" "context" + "crypto/tls" "database/sql" "encoding/json" "fmt" "log/slog" + "net/http" "net/url" "path" "path/filepath" @@ -23,6 +25,7 @@ import ( "github.com/nyaruka/courier/queue" "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/gocommon/dbutil" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/storage" "github.com/nyaruka/gocommon/syncx" @@ -66,6 +69,10 @@ type backend struct { stopChan chan bool waitGroup *sync.WaitGroup + httpClient *http.Client + httpClientInsecure *http.Client + httpAccess *httpx.AccessConfig + mediaCache *redisx.IntervalHash mediaMutexes syncx.HashMutex @@ -85,9 +92,26 @@ type backend struct { // NewBackend creates a new RapidPro backend func newBackend(cfg *courier.Config) courier.Backend { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.MaxIdleConns = 64 + transport.MaxIdleConnsPerHost = 8 + transport.IdleConnTimeout = 15 * time.Second + + insecureTransport := http.DefaultTransport.(*http.Transport).Clone() + insecureTransport.MaxIdleConns = 64 + insecureTransport.MaxIdleConnsPerHost = 8 + insecureTransport.IdleConnTimeout = 15 * time.Second + insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + disallowedIPs, disallowedNets, _ := cfg.ParseDisallowedNetworks() + return &backend{ config: cfg, + httpClient: &http.Client{Transport: transport, Timeout: 30 * time.Second}, + httpClientInsecure: &http.Client{Transport: insecureTransport, Timeout: 30 * time.Second}, + httpAccess: httpx.NewAccessConfig(10*time.Second, disallowedIPs, disallowedNets), + stopChan: make(chan bool), waitGroup: &sync.WaitGroup{}, @@ -720,6 +744,17 @@ func (b *backend) ResolveMedia(ctx context.Context, mediaUrl string) (courier.Me return media, nil } +func (b *backend) HttpClient(secure bool) *http.Client { + if secure { + return b.httpClient + } + return b.httpClientInsecure +} + +func (b *backend) HttpAccess() *httpx.AccessConfig { + return b.httpAccess +} + // Health returns the health of this backend as a string, returning "" if all is well func (b *backend) Health() string { // test redis diff --git a/config.go b/config.go index ea5056fa0..e546cdb53 100644 --- a/config.go +++ b/config.go @@ -1,7 +1,15 @@ package courier import ( + "encoding/csv" + "io" + "net" + "strings" + + "github.com/nyaruka/courier/utils" "github.com/nyaruka/ezconf" + "github.com/nyaruka/gocommon/httpx" + "github.com/pkg/errors" ) // Config is our top level configuration object @@ -30,15 +38,16 @@ type Config struct { FacebookWebhookSecret string `help:"the secret for Facebook webhook URL verification"` WhatsappAdminSystemUserToken string `help:"the token of the admin system user for WhatsApp"` - MediaDomain string `help:"the domain on which we'll try to resolve outgoing media URLs"` - MaxWorkers int `help:"the maximum number of go routines that will be used for sending (set to 0 to disable sending)"` - LibratoUsername string `help:"the username that will be used to authenticate to Librato"` - LibratoToken string `help:"the token that will be used to authenticate to Librato"` - StatusUsername string `help:"the username that is needed to authenticate against the /status endpoint"` - StatusPassword string `help:"the password that is needed to authenticate against the /status endpoint"` - AuthToken string `help:"the authentication token need to access non-channel endpoints"` - LogLevel string `help:"the logging level courier should use"` - Version string `help:"the version that will be used in request and response headers"` + DisallowedNetworks string `help:"comma separated list of IP addresses and networks which we disallow fetching attachments from"` + MediaDomain string `help:"the domain on which we'll try to resolve outgoing media URLs"` + MaxWorkers int `help:"the maximum number of go routines that will be used for sending (set to 0 to disable sending)"` + LibratoUsername string `help:"the username that will be used to authenticate to Librato"` + LibratoToken string `help:"the token that will be used to authenticate to Librato"` + StatusUsername string `help:"the username that is needed to authenticate against the /status endpoint"` + StatusPassword string `help:"the password that is needed to authenticate against the /status endpoint"` + AuthToken string `help:"the authentication token need to access non-channel endpoints"` + LogLevel string `help:"the logging level courier should use"` + Version string `help:"the version that will be used in request and response headers"` // IncludeChannels is the list of channels to enable, empty means include all IncludeChannels []string @@ -73,9 +82,10 @@ func NewConfig() *Config { FacebookWebhookSecret: "missing_facebook_webhook_secret", WhatsappAdminSystemUserToken: "missing_whatsapp_admin_system_user_token", - MaxWorkers: 32, - LogLevel: "error", - Version: "Dev", + DisallowedNetworks: `127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,fe80::/10`, + MaxWorkers: 32, + LogLevel: "error", + Version: "Dev", } } @@ -91,3 +101,25 @@ func LoadConfig(filename string) *Config { loader.MustLoad() return config } + +// Validate validates the config +func (c *Config) Validate() error { + if err := utils.Validate(c); err != nil { + return err + } + + if _, _, err := c.ParseDisallowedNetworks(); err != nil { + return errors.Wrap(err, "unable to parse 'DisallowedNetworks'") + } + return nil +} + +// ParseDisallowedNetworks parses the list of IPs and IP networks (written in CIDR notation) +func (c *Config) ParseDisallowedNetworks() ([]net.IP, []*net.IPNet, error) { + addrs, err := csv.NewReader(strings.NewReader(c.DisallowedNetworks)).Read() + if err != nil && err != io.EOF { + return nil, nil, err + } + + return httpx.ParseNetworks(addrs...) +} diff --git a/go.mod b/go.mod index 82af11240..82a51bd56 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.42.1 + github.com/nyaruka/gocommon v1.42.2 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 3d0ea5ae1..5fc6d4c03 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.42.1 h1:BIS+RpgG06Vl4nzrPLxuFFVF+KTP7PZ+V1xJE1ksLBo= -github.com/nyaruka/gocommon v1.42.1/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= +github.com/nyaruka/gocommon v1.42.2 h1:VGJ/h7WNmCyQ6wNYClJfFkXkU7ZZn+Aiz9xoKJHVRH4= +github.com/nyaruka/gocommon v1.42.2/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= diff --git a/handlers/africastalking/handler.go b/handlers/africastalking/handler.go index 1726947b8..697e1e8db 100644 --- a/handlers/africastalking/handler.go +++ b/handlers/africastalking/handler.go @@ -150,7 +150,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("apikey", apiKey) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/arabiacell/handler.go b/handlers/arabiacell/handler.go index 5b708fde4..ce0f33417 100644 --- a/handlers/arabiacell/handler.go +++ b/handlers/arabiacell/handler.go @@ -96,7 +96,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/xml") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/bandwidth/handler.go b/handlers/bandwidth/handler.go index 51aa15f31..2441270f0 100644 --- a/handlers/bandwidth/handler.go +++ b/handlers/bandwidth/handler.go @@ -227,7 +227,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.SetBasicAuth(username, password) - resp, respBody, _ := handlers.RequestHTTP(req, clog) + resp, respBody, _ := h.RequestHTTP(req, clog) response := &mtResponse{} err = json.Unmarshal(respBody, response) diff --git a/handlers/base.go b/handlers/base.go index d76e873f2..a0fe32797 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -2,10 +2,12 @@ package handlers import ( "context" + "fmt" "net/http" "github.com/go-chi/chi" "github.com/nyaruka/courier" + "github.com/nyaruka/gocommon/httpx" ) var defaultRedactConfigKeys = []string{courier.ConfigAuthToken, courier.ConfigAPIKey, courier.ConfigSecret, courier.ConfigPassword, courier.ConfigSendAuthorization} @@ -98,6 +100,36 @@ func (h *BaseHandler) GetChannel(ctx context.Context, r *http.Request) (courier. return h.backend.GetChannel(ctx, h.ChannelType(), uuid) } +// RequestHTTP does the given request, logging the trace, and returns the response +func (h *BaseHandler) RequestHTTP(req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { + return h.RequestHTTPWithClient(h.backend.HttpClient(true), req, clog) +} + +// RequestHTTP does the given request, logging the trace, and returns the response +func (h *BaseHandler) RequestHTTPInsecure(req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { + return h.RequestHTTPWithClient(h.backend.HttpClient(false), req, clog) +} + +// RequestHTTP does the given request using the given client, logging the trace, and returns the response +func (h *BaseHandler) RequestHTTPWithClient(client *http.Client, req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { + var resp *http.Response + var body []byte + + req.Header.Set("User-Agent", fmt.Sprintf("Courier/%s", h.server.Config().Version)) + + trace, err := httpx.DoTrace(client, req, nil, h.backend.HttpAccess(), 0) + if trace != nil { + clog.HTTP(trace) + resp = trace.Response + body = trace.ResponseBody + } + if err != nil { + return nil, nil, err + } + + return resp, body, nil +} + // WriteStatusSuccessResponse writes a success response for the statuses func (h *BaseHandler) WriteStatusSuccessResponse(ctx context.Context, w http.ResponseWriter, statuses []courier.StatusUpdate) error { return courier.WriteStatusSuccess(w, statuses) diff --git a/handlers/http_test.go b/handlers/base_test.go similarity index 85% rename from handlers/http_test.go rename to handlers/base_test.go index 1e9257b9b..bc6934a12 100644 --- a/handlers/http_test.go +++ b/handlers/base_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDoHTTPRequest(t *testing.T) { +func TestRequestHTTP(t *testing.T) { httpx.SetRequestor(httpx.NewMockRequestor(map[string][]*httpx.MockResponse{ "https://api.messages.com/send.json": { httpx.NewMockResponse(200, nil, []byte(`{"status":"success"}`)), @@ -26,8 +26,14 @@ func TestDoHTTPRequest(t *testing.T) { mm := mb.NewOutgoingMsg(mc, 123, urns.URN("tel:+1234"), "Hello World", false, nil, "", "", courier.MsgOriginChat, nil) clog := courier.NewChannelLogForSend(mm, nil) + config := courier.NewConfig() + server := test.NewMockServer(config, mb) + + h := handlers.NewBaseHandler("NX", "Test") + h.SetServer(server) + req, _ := http.NewRequest("POST", "https://api.messages.com/send.json", nil) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) assert.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, []byte(`{"status":"success"}`), respBody) @@ -38,7 +44,7 @@ func TestDoHTTPRequest(t *testing.T) { assert.Equal(t, "https://api.messages.com/send.json", hlog1.URL) req, _ = http.NewRequest("POST", "https://api.messages.com/send.json", nil) - resp, _, err = handlers.RequestHTTP(req, clog) + resp, _, err = h.RequestHTTP(req, clog) assert.NoError(t, err) assert.Equal(t, 400, resp.StatusCode) assert.Len(t, clog.HTTPLogs(), 2) diff --git a/handlers/bongolive/handler.go b/handlers/bongolive/handler.go index a697dea84..eead3510d 100644 --- a/handlers/bongolive/handler.go +++ b/handlers/bongolive/handler.go @@ -165,7 +165,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, err := handlers.RequestHTTPInsecure(req, clog) + resp, respBody, err := h.RequestHTTPInsecure(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/burstsms/handler.go b/handlers/burstsms/handler.go index 8a4fe17c3..9f8a244de 100644 --- a/handlers/burstsms/handler.go +++ b/handlers/burstsms/handler.go @@ -84,7 +84,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/clickatell/handler.go b/handlers/clickatell/handler.go index 7ffbafe95..a5ad4be5b 100644 --- a/handlers/clickatell/handler.go +++ b/handlers/clickatell/handler.go @@ -180,7 +180,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/clickmobile/handler.go b/handlers/clickmobile/handler.go index 441e71b4d..c0ac4b0cf 100644 --- a/handlers/clickmobile/handler.go +++ b/handlers/clickmobile/handler.go @@ -156,7 +156,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/clicksend/handler.go b/handlers/clicksend/handler.go index a238232e0..ce8f1610d 100644 --- a/handlers/clicksend/handler.go +++ b/handlers/clicksend/handler.go @@ -93,7 +93,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.SetBasicAuth(username, password) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/dart/handler.go b/handlers/dart/handler.go index 8bc5a8eeb..9e91bfead 100644 --- a/handlers/dart/handler.go +++ b/handlers/dart/handler.go @@ -186,7 +186,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index 742c9606f..dafe6623f 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -149,21 +149,21 @@ func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Ch text = msg.Text.Body } else if msg.Type == "audio" && msg.Audio != nil { text = msg.Audio.Caption - mediaURL, err = resolveMediaURL(channel, msg.Audio.ID, clog) + mediaURL, err = h.resolveMediaURL(channel, msg.Audio.ID, clog) } else if msg.Type == "voice" && msg.Voice != nil { text = msg.Voice.Caption - mediaURL, err = resolveMediaURL(channel, msg.Voice.ID, clog) + mediaURL, err = h.resolveMediaURL(channel, msg.Voice.ID, clog) } else if msg.Type == "button" && msg.Button != nil { text = msg.Button.Text } else if msg.Type == "document" && msg.Document != nil { text = msg.Document.Caption - mediaURL, err = resolveMediaURL(channel, msg.Document.ID, clog) + mediaURL, err = h.resolveMediaURL(channel, msg.Document.ID, clog) } else if msg.Type == "image" && msg.Image != nil { text = msg.Image.Caption - mediaURL, err = resolveMediaURL(channel, msg.Image.ID, clog) + mediaURL, err = h.resolveMediaURL(channel, msg.Image.ID, clog) } else if msg.Type == "video" && msg.Video != nil { text = msg.Video.Caption - mediaURL, err = resolveMediaURL(channel, msg.Video.ID, clog) + mediaURL, err = h.resolveMediaURL(channel, msg.Video.ID, clog) } else if msg.Type == "location" && msg.Location != nil { mediaURL = fmt.Sprintf("geo:%f,%f", msg.Location.Latitude, msg.Location.Longitude) } else if msg.Type == "interactive" && msg.Interactive.Type == "button_reply" { @@ -244,14 +244,13 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, // set the access token as the authorization header req, _ := http.NewRequest(http.MethodGet, attachmentURL, nil) - req.Header.Set("User-Agent", utils.HTTPUserAgent) req.Header.Set(d3AuthorizationKey, token) return req, nil } var _ courier.AttachmentRequestBuilder = (*handler)(nil) -func resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.ChannelLog) (string, error) { +func (h *handler) resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.ChannelLog) (string, error) { // sometimes WA will send an attachment with status=undownloaded and no ID if mediaID == "" { return "", nil @@ -272,10 +271,9 @@ func resolveMediaURL(channel courier.Channel, mediaID string, clog *courier.Chan mediaURL := url.ResolveReference(mediaPath).String() req, _ := http.NewRequest(http.MethodGet, mediaURL, nil) - req.Header.Set("User-Agent", utils.HTTPUserAgent) req.Header.Set(d3AuthorizationKey, token) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", fmt.Errorf("failed to request media URL for D3C channel: %s", err) } @@ -509,7 +507,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch zeroIndex = true } payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} - status, err := requestD3C(payloadAudio, accessToken, status, sendURL, zeroIndex, clog) + status, err := h.requestD3C(payloadAudio, accessToken, status, sendURL, zeroIndex, clog) if err != nil { return status, nil } @@ -578,7 +576,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch zeroIndex = true } - status, err := requestD3C(payload, accessToken, status, sendURL, zeroIndex, clog) + status, err := h.requestD3C(payload, accessToken, status, sendURL, zeroIndex, clog) if err != nil { return status, err } @@ -590,7 +588,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, nil } -func requestD3C(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { +func (h *handler) requestD3C(payload whatsapp.SendRequest, accessToken string, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (courier.StatusUpdate, error) { jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) @@ -602,7 +600,7 @@ func requestD3C(payload whatsapp.SendRequest, accessToken string, status courier req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - _, respBody, _ := handlers.RequestHTTP(req, clog) + _, respBody, _ := h.RequestHTTP(req, clog) respPayload := &whatsapp.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { diff --git a/handlers/discord/handler.go b/handlers/discord/handler.go index ef238dd68..a7a6c0b35 100644 --- a/handlers/discord/handler.go +++ b/handlers/discord/handler.go @@ -197,7 +197,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Authorization", authorization) } - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/dmark/handler.go b/handlers/dmark/handler.go index fa00d86d1..02b442047 100644 --- a/handlers/dmark/handler.go +++ b/handlers/dmark/handler.go @@ -134,7 +134,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Token %s", auth)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/external/handler.go b/handlers/external/handler.go index c7a494c3f..99aecfd24 100644 --- a/handlers/external/handler.go +++ b/handlers/external/handler.go @@ -364,7 +364,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set(hKey, fmt.Sprint(hValue)) } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/facebook_legacy/handler.go b/handlers/facebook_legacy/handler.go index 6c075869a..45683f28b 100644 --- a/handlers/facebook_legacy/handler.go +++ b/handlers/facebook_legacy/handler.go @@ -119,7 +119,7 @@ func (h *handler) subscribeToEvents(ctx context.Context, channel courier.Channel req, _ := http.NewRequest(http.MethodPost, subscribeURL, strings.NewReader(form.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) // log if we get any kind of error success, _ := jsonparser.GetBoolean(respBody, "success") @@ -545,7 +545,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -627,7 +627,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn u.RawQuery = query.Encode() req, _ := http.NewRequest(http.MethodGet, u.String(), nil) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up contact data") } diff --git a/handlers/facebook_legacy/handler_test.go b/handlers/facebook_legacy/handler_test.go index 81d2f524c..58c7cfe31 100644 --- a/handlers/facebook_legacy/handler_test.go +++ b/handlers/facebook_legacy/handler_test.go @@ -632,6 +632,7 @@ func TestDescribeURN(t *testing.T) { channel := testChannels[0] handler := newHandler() + handler.Initialize(test.NewMockServer(courier.NewConfig(), test.NewMockBackend())) clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) tcs := []struct { diff --git a/handlers/firebase/handler.go b/handlers/firebase/handler.go index 672cbc6cb..a38276673 100644 --- a/handlers/firebase/handler.go +++ b/handlers/firebase/handler.go @@ -197,7 +197,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("key=%s", fcmKey)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/freshchat/handler.go b/handlers/freshchat/handler.go index 8cfd0cb4a..827aaa76b 100644 --- a/handlers/freshchat/handler.go +++ b/handlers/freshchat/handler.go @@ -159,7 +159,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch var bearer = "Bearer " + authToken req.Header.Set("Authorization", bearer) - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/globe/handler.go b/handlers/globe/handler.go index de7548084..2f39be542 100644 --- a/handlers/globe/handler.go +++ b/handlers/globe/handler.go @@ -157,7 +157,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/highconnection/handler.go b/handlers/highconnection/handler.go index 70286e849..77cf30c68 100644 --- a/handlers/highconnection/handler.go +++ b/handlers/highconnection/handler.go @@ -166,7 +166,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/hormuud/handler.go b/handlers/hormuud/handler.go index 88f8390a6..2e650a2aa 100644 --- a/handlers/hormuud/handler.go +++ b/handlers/hormuud/handler.go @@ -107,7 +107,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -162,7 +162,7 @@ func (h *handler) FetchToken(ctx context.Context, channel courier.Channel, msg c req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", errors.Wrapf(err, "error making token request") } diff --git a/handlers/http.go b/handlers/http.go deleted file mode 100644 index 0aed1a72c..000000000 --- a/handlers/http.go +++ /dev/null @@ -1,38 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/utils" - "github.com/nyaruka/gocommon/httpx" -) - -// RequestHTTP does the given request, logging the trace, and returns the response -func RequestHTTP(req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { - return RequestHTTPWithClient(utils.GetHTTPClient(), req, clog) -} - -// RequestHTTPInsecure does the given request using an insecure client that does not validate SSL certificates, -// logging the trace, and returns the response -func RequestHTTPInsecure(req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { - return RequestHTTPWithClient(utils.GetInsecureHTTPClient(), req, clog) -} - -// RequestHTTP does the given request using the given client, logging the trace, and returns the response -func RequestHTTPWithClient(client *http.Client, req *http.Request, clog *courier.ChannelLog) (*http.Response, []byte, error) { - var resp *http.Response - var body []byte - - trace, err := httpx.DoTrace(client, req, nil, nil, 0) - if trace != nil { - clog.HTTP(trace) - resp = trace.Response - body = trace.ResponseBody - } - if err != nil { - return nil, nil, err - } - - return resp, body, nil -} diff --git a/handlers/i2sms/handler.go b/handlers/i2sms/handler.go index ce3eda937..543139963 100644 --- a/handlers/i2sms/handler.go +++ b/handlers/i2sms/handler.go @@ -117,7 +117,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/infobip/handler.go b/handlers/infobip/handler.go index 0ceed91ec..1f6ab666b 100644 --- a/handlers/infobip/handler.go +++ b/handlers/infobip/handler.go @@ -211,7 +211,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/jasmin/handler.go b/handlers/jasmin/handler.go index 4ae2e1977..b54365a83 100644 --- a/handlers/jasmin/handler.go +++ b/handlers/jasmin/handler.go @@ -162,7 +162,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/jiochat/handler.go b/handlers/jiochat/handler.go index eb3c1b6c1..058343edc 100644 --- a/handlers/jiochat/handler.go +++ b/handlers/jiochat/handler.go @@ -186,7 +186,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -216,7 +216,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn req, _ := http.NewRequest(http.MethodGet, reqURL.String(), nil) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up contact data") } @@ -304,7 +304,7 @@ func (h *handler) fetchAccessToken(ctx context.Context, channel courier.Channel, req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", 0, err } diff --git a/handlers/justcall/handler.go b/handlers/justcall/handler.go index c42aa1f95..2f89150f7 100644 --- a/handlers/justcall/handler.go +++ b/handlers/justcall/handler.go @@ -190,7 +190,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("%s:%s", apiKey, apiSecret)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/kaleyra/handler.go b/handlers/kaleyra/handler.go index f52fcc4c1..13d116934 100644 --- a/handlers/kaleyra/handler.go +++ b/handlers/kaleyra/handler.go @@ -158,7 +158,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch // download media req, _ := http.NewRequest(http.MethodGet, attachmentURL, nil) - resp, attBody, err := handlers.RequestHTTP(req, clog) + resp, attBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { kwaErr = errors.New("unable to fetch media") break @@ -203,7 +203,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch // send multipart form req, _ = http.NewRequest(http.MethodPost, sendURL, body) req.Header.Set("Content-Type", writer.FormDataContentType()) - kwaResp, kwaRespBody, kwaErr = handlers.RequestHTTP(req, clog) + kwaResp, kwaRespBody, kwaErr = h.RequestHTTP(req, clog) } } else { form := url.Values{} @@ -219,7 +219,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req, _ := http.NewRequest(http.MethodPost, sendURL, strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - kwaResp, kwaRespBody, kwaErr = handlers.RequestHTTP(req, clog) + kwaResp, kwaRespBody, kwaErr = h.RequestHTTP(req, clog) } if kwaErr != nil || kwaResp.StatusCode/100 != 2 { diff --git a/handlers/kannel/handler.go b/handlers/kannel/handler.go index b29f1bef4..c249a6fef 100644 --- a/handlers/kannel/handler.go +++ b/handlers/kannel/handler.go @@ -201,9 +201,9 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch var resp *http.Response if verifySSL { - resp, _, err = handlers.RequestHTTP(req, clog) + resp, _, err = h.RequestHTTP(req, clog) } else { - resp, _, err = handlers.RequestHTTPInsecure(req, clog) + resp, _, err = h.RequestHTTPInsecure(req, clog) } status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) diff --git a/handlers/line/handler.go b/handlers/line/handler.go index e802b922e..fce0a690e 100644 --- a/handlers/line/handler.go +++ b/handlers/line/handler.go @@ -360,7 +360,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, err } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err == nil && resp.StatusCode/100 == 2 { batch = []string{} @@ -382,7 +382,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, err } - resp, respBody, _ := handlers.RequestHTTP(req, clog) + resp, respBody, _ := h.RequestHTTP(req, clog) respPayload := &mtResponse{} err = json.Unmarshal(respBody, respPayload) diff --git a/handlers/m3tech/handler.go b/handlers/m3tech/handler.go index bfb51dcbf..8cdfb39db 100644 --- a/handlers/m3tech/handler.go +++ b/handlers/m3tech/handler.go @@ -113,7 +113,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { break } diff --git a/handlers/macrokiosk/handler.go b/handlers/macrokiosk/handler.go index 60897611f..aa4ffb5dd 100644 --- a/handlers/macrokiosk/handler.go +++ b/handlers/macrokiosk/handler.go @@ -189,7 +189,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/mblox/handler.go b/handlers/mblox/handler.go index a49683da1..3d1254c6d 100644 --- a/handlers/mblox/handler.go +++ b/handlers/mblox/handler.go @@ -141,7 +141,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", password)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/messagebird/handler.go b/handlers/messagebird/handler.go index 1981be45e..401e1a008 100644 --- a/handlers/messagebird/handler.go +++ b/handlers/messagebird/handler.go @@ -228,7 +228,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch var bearer = "AccessKey " + authToken req.Header.Set("Authorization", bearer) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/messangi/handler.go b/handlers/messangi/handler.go index 9fb69961b..8fb105c58 100644 --- a/handlers/messangi/handler.go +++ b/handlers/messangi/handler.go @@ -96,7 +96,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index 36ef60036..b3b7be0e1 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -281,7 +281,7 @@ func TestFacebookDescribeURN(t *testing.T) { channel := facebookTestChannels[0] handler := newHandler("FBA", "Facebook") - handler.Initialize(courier.NewServer(courier.NewConfig(), nil)) + handler.Initialize(test.NewMockServer(courier.NewConfig(), test.NewMockBackend())) clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) tcs := []struct { diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index f70e32ccf..9d39c3bdc 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -188,7 +188,7 @@ func (h *handler) receiveVerify(ctx context.Context, channel courier.Channel, w return nil, err } -func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (string, error) { +func (h *handler) resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (string, error) { if token == "" { return "", fmt.Errorf("missing token for WA channel") } @@ -202,7 +202,7 @@ func resolveMediaURL(mediaID string, token string, clog *courier.ChannelLog) (st //req.Header.Set("User-Agent", utils.HTTPUserAgent) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", errors.New("error resolving media URL") } @@ -297,21 +297,21 @@ func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Ch text = msg.Text.Body } else if msg.Type == "audio" && msg.Audio != nil { text = msg.Audio.Caption - mediaURL, err = resolveMediaURL(msg.Audio.ID, token, clog) + mediaURL, err = h.resolveMediaURL(msg.Audio.ID, token, clog) } else if msg.Type == "voice" && msg.Voice != nil { text = msg.Voice.Caption - mediaURL, err = resolveMediaURL(msg.Voice.ID, token, clog) + mediaURL, err = h.resolveMediaURL(msg.Voice.ID, token, clog) } else if msg.Type == "button" && msg.Button != nil { text = msg.Button.Text } else if msg.Type == "document" && msg.Document != nil { text = msg.Document.Caption - mediaURL, err = resolveMediaURL(msg.Document.ID, token, clog) + mediaURL, err = h.resolveMediaURL(msg.Document.ID, token, clog) } else if msg.Type == "image" && msg.Image != nil { text = msg.Image.Caption - mediaURL, err = resolveMediaURL(msg.Image.ID, token, clog) + mediaURL, err = h.resolveMediaURL(msg.Image.ID, token, clog) } else if msg.Type == "video" && msg.Video != nil { text = msg.Video.Caption - mediaURL, err = resolveMediaURL(msg.Video.ID, token, clog) + mediaURL, err = h.resolveMediaURL(msg.Video.ID, token, clog) } else if msg.Type == "location" && msg.Location != nil { mediaURL = fmt.Sprintf("geo:%f,%f", msg.Location.Latitude, msg.Location.Longitude) } else if msg.Type == "interactive" && msg.Interactive.Type == "button_reply" { @@ -721,7 +721,7 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - _, respBody, _ := handlers.RequestHTTP(req, clog) + _, respBody, _ := h.RequestHTTP(req, clog) respPayload := &messenger.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { @@ -997,7 +997,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog zeroIndex = true } payloadAudio = whatsapp.SendRequest{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &whatsapp.Media{Link: attURL}} - err := requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := h.requestWAC(payloadAudio, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil } @@ -1066,7 +1066,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog zeroIndex = true } - err := requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) + err := h.requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, err } @@ -1078,7 +1078,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog return status, nil } -func 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, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) error { jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) @@ -1090,7 +1090,7 @@ func requestWAC(payload whatsapp.SendRequest, accessToken string, status courier req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - _, respBody, _ := handlers.RequestHTTP(req, clog) + _, respBody, _ := h.RequestHTTP(req, clog) respPayload := &whatsapp.SendResponse{} err = json.Unmarshal(respBody, respPayload) if err != nil { @@ -1143,7 +1143,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn u.RawQuery = query.Encode() req, _ := http.NewRequest(http.MethodGet, u.String(), nil) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up contact data") } diff --git a/handlers/meta/instagram_test.go b/handlers/meta/instagram_test.go index 078e32483..9fadd3853 100644 --- a/handlers/meta/instagram_test.go +++ b/handlers/meta/instagram_test.go @@ -394,7 +394,7 @@ func TestInstagramDescribeURN(t *testing.T) { channel := instgramTestChannels[0] handler := newHandler("IG", "Instagram") - handler.Initialize(courier.NewServer(courier.NewConfig(), nil)) + handler.Initialize(test.NewMockServer(courier.NewConfig(), test.NewMockBackend())) clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) tcs := []struct { diff --git a/handlers/mtarget/handler.go b/handlers/mtarget/handler.go index a1453ffbb..9f79ea345 100644 --- a/handlers/mtarget/handler.go +++ b/handlers/mtarget/handler.go @@ -179,7 +179,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/mtn/handler.go b/handlers/mtn/handler.go index 8ee9a1b68..560712425 100644 --- a/handlers/mtn/handler.go +++ b/handlers/mtn/handler.go @@ -154,7 +154,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -225,7 +225,7 @@ func (h *handler) fetchAccessToken(ctx context.Context, channel courier.Channel, req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", 0, err } diff --git a/handlers/nexmo/handler.go b/handlers/nexmo/handler.go index 1e7dc00dd..585f9a3ec 100644 --- a/handlers/nexmo/handler.go +++ b/handlers/nexmo/handler.go @@ -216,7 +216,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, requestErr = handlers.RequestHTTP(req, clog) + resp, respBody, requestErr = h.RequestHTTP(req, clog) matched := throttledRE.FindAllStringSubmatch(string(respBody), -1) if len(matched) > 0 && len(matched[0]) > 0 { sleepTime, _ := strconv.Atoi(matched[0][1]) diff --git a/handlers/novo/handler.go b/handlers/novo/handler.go index 7f07ea51c..6d10b5bc7 100644 --- a/handlers/novo/handler.go +++ b/handlers/novo/handler.go @@ -110,7 +110,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/playmobile/handler.go b/handlers/playmobile/handler.go index ecce2fff0..91bf0d10d 100644 --- a/handlers/playmobile/handler.go +++ b/handlers/playmobile/handler.go @@ -193,7 +193,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/plivo/handler.go b/handlers/plivo/handler.go index 9ed6e092c..48f0dce8f 100644 --- a/handlers/plivo/handler.go +++ b/handlers/plivo/handler.go @@ -170,7 +170,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.SetBasicAuth(authID, authToken) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/redrabbit/handler.go b/handlers/redrabbit/handler.go index 7d34d242b..6543ae342 100644 --- a/handlers/redrabbit/handler.go +++ b/handlers/redrabbit/handler.go @@ -72,7 +72,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return nil, err } - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/rocketchat/handler.go b/handlers/rocketchat/handler.go index 2937bdd41..a36eecd3c 100644 --- a/handlers/rocketchat/handler.go +++ b/handlers/rocketchat/handler.go @@ -132,7 +132,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Token %s", secret)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/shaqodoon/handler.go b/handlers/shaqodoon/handler.go index d6fe3a0df..6b77f3ef8 100644 --- a/handlers/shaqodoon/handler.go +++ b/handlers/shaqodoon/handler.go @@ -115,7 +115,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) - resp, _, err := handlers.RequestHTTPInsecure(req, clog) + resp, _, err := h.RequestHTTPInsecure(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index 41020f85e..d1152c166 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -116,7 +116,7 @@ func (h *handler) resolveFile(ctx context.Context, channel courier.Channel, file req.Header.Add("Content-Type", "application/json; charset=utf-8") req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", userToken)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", errors.New("unable to resolve file") } @@ -154,14 +154,14 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusErrored, clog) for _, attachment := range msg.Attachments() { - fileAttachment, err := parseAttachmentToFileParams(msg, attachment, clog) + fileAttachment, err := h.parseAttachmentToFileParams(msg, attachment, clog) if err != nil { clog.RawError(err) return status, nil } if fileAttachment != nil { - err = sendFilePart(msg, botToken, fileAttachment, clog) + err = h.sendFilePart(msg, botToken, fileAttachment, clog) if err != nil { clog.RawError(err) return status, nil @@ -170,7 +170,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } if msg.Text() != "" { - err := sendTextMsgPart(msg, botToken, clog) + err := h.sendTextMsgPart(msg, botToken, clog) if err != nil { clog.RawError(err) return status, nil @@ -181,7 +181,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, nil } -func sendTextMsgPart(msg courier.MsgOut, token string, clog *courier.ChannelLog) error { +func (h *handler) sendTextMsgPart(msg courier.MsgOut, token string, clog *courier.ChannelLog) error { sendURL := apiURL + "/chat.postMessage" msgPayload := &mtPayload{ @@ -201,7 +201,7 @@ func sendTextMsgPart(msg courier.MsgOut, token string, clog *courier.ChannelLog) req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return errors.New("error sending message") } @@ -221,7 +221,7 @@ func sendTextMsgPart(msg courier.MsgOut, token string, clog *courier.ChannelLog) return nil } -func parseAttachmentToFileParams(msg courier.MsgOut, attachment string, clog *courier.ChannelLog) (*FileParams, error) { +func (h *handler) parseAttachmentToFileParams(msg courier.MsgOut, attachment string, clog *courier.ChannelLog) (*FileParams, error) { _, attURL := handlers.SplitAttachment(attachment) req, err := http.NewRequest(http.MethodGet, attURL, nil) @@ -229,7 +229,7 @@ func parseAttachmentToFileParams(msg courier.MsgOut, attachment string, clog *co return nil, errors.Wrapf(err, "error building file request") } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("error fetching attachment") } @@ -241,7 +241,7 @@ func parseAttachmentToFileParams(msg courier.MsgOut, attachment string, clog *co return &FileParams{File: respBody, FileName: filename, Channels: msg.URN().Path()}, nil } -func sendFilePart(msg courier.MsgOut, token string, fileParams *FileParams, clog *courier.ChannelLog) error { +func (h *handler) sendFilePart(msg courier.MsgOut, token string, fileParams *FileParams, clog *courier.ChannelLog) error { uploadURL := apiURL + "/files.upload" body := &bytes.Buffer{} @@ -273,7 +273,7 @@ func sendFilePart(msg courier.MsgOut, token string, fileParams *FileParams, clog req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Add("Content-Type", writer.FormDataContentType()) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return errors.New("error uploading file to slack") } @@ -304,7 +304,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn q.Add("user", urn.Path()) req.URL.RawQuery = q.Encode() - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up user info") } diff --git a/handlers/slack/handler_test.go b/handlers/slack/handler_test.go index 246b02aed..1f24fde97 100644 --- a/handlers/slack/handler_test.go +++ b/handlers/slack/handler_test.go @@ -360,6 +360,7 @@ func TestDescribeURN(t *testing.T) { defer server.Close() handler := newHandler() + handler.Initialize(test.NewMockServer(courier.NewConfig(), test.NewMockBackend())) clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, testChannels[0], handler.RedactValues(testChannels[0])) urn, _ := urns.NewURNFromParts(urns.SlackScheme, "U012345", "", "") diff --git a/handlers/smscentral/handler.go b/handlers/smscentral/handler.go index 250265104..51750406c 100644 --- a/handlers/smscentral/handler.go +++ b/handlers/smscentral/handler.go @@ -90,7 +90,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/start/handler.go b/handlers/start/handler.go index b349c1996..67336b83f 100644 --- a/handlers/start/handler.go +++ b/handlers/start/handler.go @@ -164,7 +164,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/xml; charset=utf8") req.SetBasicAuth(username, password) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/telegram/handler.go b/handlers/telegram/handler.go index 4ab58a2c8..a824a29cf 100644 --- a/handlers/telegram/handler.go +++ b/handlers/telegram/handler.go @@ -157,7 +157,7 @@ func (h *handler) sendMsgPart(msg courier.MsgOut, token string, path string, for } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, _ := handlers.RequestHTTP(req, clog) + resp, respBody, _ := h.RequestHTTP(req, clog) response := &mtResponse{} err = json.Unmarshal(respBody, response) @@ -346,7 +346,7 @@ func (h *handler) resolveFileID(ctx context.Context, channel courier.Channel, fi courier.LogRequestError(req, channel, err) } - resp, respBody, _ := handlers.RequestHTTP(req, clog) + resp, respBody, _ := h.RequestHTTP(req, clog) respPayload := &fileResponse{} err = json.Unmarshal(respBody, respPayload) diff --git a/handlers/telesom/handler.go b/handlers/telesom/handler.go index 9c306bc13..7f9e86e1e 100644 --- a/handlers/telesom/handler.go +++ b/handlers/telesom/handler.go @@ -114,7 +114,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/thinq/handler.go b/handlers/thinq/handler.go index 6bfca6309..3cac20b1d 100644 --- a/handlers/thinq/handler.go +++ b/handlers/thinq/handler.go @@ -172,7 +172,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.SetBasicAuth(tokenUser, token) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -205,7 +205,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.SetBasicAuth(tokenUser, token) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/twiml/handlers.go b/handlers/twiml/handlers.go index 24298b361..7d54e40bf 100644 --- a/handlers/twiml/handlers.go +++ b/handlers/twiml/handlers.go @@ -286,7 +286,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil { return status, nil } diff --git a/handlers/twitter/handler.go b/handlers/twitter/handler.go index a5ee3d19f..29b4fcaa0 100644 --- a/handlers/twitter/handler.go +++ b/handlers/twitter/handler.go @@ -19,7 +19,6 @@ import ( "github.com/dghubble/oauth1" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" @@ -288,7 +287,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch mimeType, s3url := handlers.SplitAttachment(attachment) mediaID := "" if strings.HasPrefix(mimeType, "image") || strings.HasPrefix(mimeType, "video") { - mediaID, err = uploadMediaToTwitter(msg, mediaURL, mimeType, s3url, client, clog) + mediaID, err = h.uploadMediaToTwitter(msg, mediaURL, mimeType, s3url, client, clog) if err != nil { clog.RawError(errors.Wrap(err, "unable to upload media to Twitter server")) } @@ -328,7 +327,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTPWithClient(client, req, clog) + resp, respBody, err := h.RequestHTTPWithClient(client, req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -360,11 +359,11 @@ func generateSignature(secret string, content string) string { return base64.StdEncoding.EncodeToString(h.Sum(nil)) } -func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeType string, attachmentURL string, client *http.Client, clog *courier.ChannelLog) (string, error) { +func (h *handler) uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeType string, attachmentURL string, client *http.Client, clog *courier.ChannelLog) (string, error) { // retrieve the media to be sent from S3 req, _ := http.NewRequest(http.MethodGet, attachmentURL, nil) - s3Resp, s3RespBody, err := handlers.RequestHTTP(req, clog) + s3Resp, s3RespBody, err := h.RequestHTTP(req, clog) if err != nil || s3Resp.StatusCode/100 != 2 { return "", err } @@ -392,9 +391,8 @@ func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeTyp twReq, _ := http.NewRequest(http.MethodPost, mediaUrl, strings.NewReader(form.Encode())) twReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") twReq.Header.Set("Accept", "application/json") - twReq.Header.Set("User-Agent", utils.HTTPUserAgent) - twResp, twRespBody, err := handlers.RequestHTTPWithClient(client, twReq, clog) + twResp, twRespBody, err := h.RequestHTTPWithClient(client, twReq, clog) if err != nil || twResp.StatusCode/100 != 2 { return "", err } @@ -435,9 +433,8 @@ func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeTyp twReq, _ = http.NewRequest(http.MethodPost, mediaUrl, bytes.NewReader(body.Bytes())) twReq.Header.Set("Content-Type", contentType) twReq.Header.Set("Accept", "application/json") - twReq.Header.Set("User-Agent", utils.HTTPUserAgent) - twResp, twRespBody, err = handlers.RequestHTTPWithClient(client, twReq, clog) + twResp, twRespBody, err = h.RequestHTTPWithClient(client, twReq, clog) if err != nil || twResp.StatusCode/100 != 2 { return "", err } @@ -454,9 +451,8 @@ func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeTyp twReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") twReq.Header.Set("Accept", "application/json") - twReq.Header.Set("User-Agent", utils.HTTPUserAgent) - twResp, twRespBody, err = handlers.RequestHTTPWithClient(client, twReq, clog) + twResp, twRespBody, err = h.RequestHTTPWithClient(client, twReq, clog) if err != nil || twResp.StatusCode/100 != 2 { return "", err } @@ -485,9 +481,8 @@ func uploadMediaToTwitter(msg courier.MsgOut, mediaUrl string, attachmentMimeTyp twReq, _ = http.NewRequest(http.MethodGet, statusURL.String(), nil) twReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") twReq.Header.Set("Accept", "application/json") - twReq.Header.Set("User-Agent", utils.HTTPUserAgent) - twResp, twRespBody, err = handlers.RequestHTTPWithClient(client, twReq, clog) + twResp, twRespBody, err = h.RequestHTTPWithClient(client, twReq, clog) if err != nil || twResp.StatusCode/100 != 2 { return "", err } diff --git a/handlers/viber/handler.go b/handlers/viber/handler.go index 86d04414b..38f7ab4f4 100644 --- a/handlers/viber/handler.go +++ b/handlers/viber/handler.go @@ -385,7 +385,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch case "video": msgType = "video" attURL = mediaURL - attSize, err = getAttachmentSize(mediaURL, clog) + attSize, err = h.getAttachmentSize(mediaURL, clog) if err != nil { return nil, err } @@ -394,7 +394,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch case "audio": msgType = "file" attURL = mediaURL - attSize, err = getAttachmentSize(mediaURL, clog) + attSize, err = h.getAttachmentSize(mediaURL, clog) if err != nil { return nil, err } @@ -438,7 +438,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { clog.Error(courier.ErrorResponseStatusCode()) return status, nil @@ -466,13 +466,13 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, nil } -func getAttachmentSize(u string, clog *courier.ChannelLog) (int, error) { +func (h *handler) getAttachmentSize(u string, clog *courier.ChannelLog) (int, error) { req, err := http.NewRequest(http.MethodHead, u, nil) if err != nil { return 0, err } - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return 0, errors.New("unable to get attachment size") } diff --git a/handlers/vk/handler.go b/handlers/vk/handler.go index 19d8e0022..dc7e191b1 100644 --- a/handlers/vk/handler.go +++ b/handlers/vk/handler.go @@ -16,7 +16,6 @@ import ( "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/courier/utils" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" @@ -273,7 +272,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn req.URL.RawQuery = params.Encode() - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up user info") } @@ -381,7 +380,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch params.Set(paramUserId, msg.URN().Path()) params.Set(paramRandomId, msg.ID().String()) - text, attachments := buildTextAndAttachmentParams(msg, clog) + text, attachments := h.buildTextAndAttachmentParams(msg, clog) params.Set(paramMessage, text) params.Set(paramAttachments, attachments) @@ -399,7 +398,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.URL.RawQuery = params.Encode() - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -415,8 +414,8 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch return status, nil } -// buildTextAndAttachmentParams builds msg text with attachment links (if needed) and attachments list param, also returns the errors that occurred -func buildTextAndAttachmentParams(msg courier.MsgOut, clog *courier.ChannelLog) (string, string) { +// builds msg text with attachment links (if needed) and attachments list param, also returns the errors that occurred +func (h *handler) buildTextAndAttachmentParams(msg courier.MsgOut, clog *courier.ChannelLog) (string, string) { var msgAttachments []string textBuf := bytes.Buffer{} @@ -434,7 +433,7 @@ func buildTextAndAttachmentParams(msg courier.MsgOut, clog *courier.ChannelLog) switch mediaType { case mediaTypeImage: - if attachment, err := handleMediaUploadAndGetAttachment(msg.Channel(), mediaTypeImage, mediaExt, mediaURL, clog); err == nil { + if attachment, err := h.handleMediaUploadAndGetAttachment(msg.Channel(), mediaTypeImage, mediaExt, mediaURL, clog); err == nil { msgAttachments = append(msgAttachments, attachment) } else { clog.RawError(err) @@ -448,24 +447,24 @@ func buildTextAndAttachmentParams(msg courier.MsgOut, clog *courier.ChannelLog) return textBuf.String(), strings.Join(msgAttachments, ",") } -// handleMediaUploadAndGetAttachment handles media downloading, uploading, saving information and returns the attachment string -func handleMediaUploadAndGetAttachment(channel courier.Channel, mediaType, mediaExt, mediaURL string, clog *courier.ChannelLog) (string, error) { +// handles media downloading, uploading, saving information and returns the attachment string +func (h *handler) handleMediaUploadAndGetAttachment(channel courier.Channel, mediaType, mediaExt, mediaURL string, clog *courier.ChannelLog) (string, error) { switch mediaType { case mediaTypeImage: uploadKey := "photo" // initialize server URL to upload photos if URLPhotoUploadServer == "" { - if serverURL, err := getUploadServerURL(channel, apiBaseURL+actionGetPhotoUploadServer, clog); err == nil { + if serverURL, err := h.getUploadServerURL(channel, apiBaseURL+actionGetPhotoUploadServer, clog); err == nil { URLPhotoUploadServer = serverURL } } - download, err := downloadMedia(mediaURL) + download, err := h.downloadMedia(mediaURL) if err != nil { return "", err } - uploadResponse, err := uploadMedia(URLPhotoUploadServer, uploadKey, mediaExt, download, clog) + uploadResponse, err := h.uploadMedia(URLPhotoUploadServer, uploadKey, mediaExt, download, clog) if err != nil { return "", err @@ -476,7 +475,7 @@ func handleMediaUploadAndGetAttachment(channel courier.Channel, mediaType, media return "", err } serverId := strconv.FormatInt(payload.ServerId, 10) - info, err := saveUploadedMediaInfo(channel, apiBaseURL+actionSaveUploadedPhotoInfo, serverId, payload.Hash, uploadKey, payload.Photo, clog) + info, err := h.saveUploadedMediaInfo(channel, apiBaseURL+actionSaveUploadedPhotoInfo, serverId, payload.Hash, uploadKey, payload.Photo, clog) if err != nil { return "", err @@ -491,7 +490,7 @@ func handleMediaUploadAndGetAttachment(channel courier.Channel, mediaType, media } // getUploadServerURL gets VK's media upload server -func getUploadServerURL(channel courier.Channel, sendURL string, clog *courier.ChannelLog) (string, error) { +func (h *handler) getUploadServerURL(channel courier.Channel, sendURL string, clog *courier.ChannelLog) (string, error) { req, err := http.NewRequest(http.MethodPost, sendURL, nil) if err != nil { @@ -500,7 +499,7 @@ func getUploadServerURL(channel courier.Channel, sendURL string, clog *courier.C params := buildApiBaseParams(channel) req.URL.RawQuery = params.Encode() - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", errors.New("unable to get upload server URL") } @@ -514,13 +513,13 @@ func getUploadServerURL(channel courier.Channel, sendURL string, clog *courier.C } // downloadMedia GET request to given media URL -func downloadMedia(mediaURL string) (io.Reader, error) { +func (h *handler) downloadMedia(mediaURL string) (io.Reader, error) { req, err := http.NewRequest(http.MethodGet, mediaURL, nil) if err != nil { return nil, err } - if res, err := utils.GetHTTPClient().Do(req); err == nil { + if res, err := h.Backend().HttpClient(true).Do(req); err == nil { return res.Body, nil } else { return nil, err @@ -528,7 +527,7 @@ func downloadMedia(mediaURL string) (io.Reader, error) { } // uploadMedia multiform request that passes file key as uploadKey and file value as media to upload server -func uploadMedia(serverURL, uploadKey, mediaExt string, media io.Reader, clog *courier.ChannelLog) ([]byte, error) { +func (h *handler) uploadMedia(serverURL, uploadKey, mediaExt string, media io.Reader, clog *courier.ChannelLog) ([]byte, error) { body := &bytes.Buffer{} writer := multipart.NewWriter(body) fileName := fmt.Sprintf("%s.%s", uploadKey, mediaExt) @@ -555,7 +554,7 @@ func uploadMedia(serverURL, uploadKey, mediaExt string, media io.Reader, clog *c req.Header.Set("Content-Type", writer.FormDataContentType()) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to upload media") } @@ -563,7 +562,7 @@ func uploadMedia(serverURL, uploadKey, mediaExt string, media io.Reader, clog *c } // saveUploadedMediaInfo saves uploaded media info and returns an object containing media/owner id -func saveUploadedMediaInfo(channel courier.Channel, sendURL, serverId, hash, mediaKey, mediaValue string, clog *courier.ChannelLog) (*mediaUploadInfoPayload, error) { +func (h *handler) saveUploadedMediaInfo(channel courier.Channel, sendURL, serverId, hash, mediaKey, mediaValue string, clog *courier.ChannelLog) (*mediaUploadInfoPayload, error) { params := buildApiBaseParams(channel) params.Set(paramServerId, serverId) params.Set(paramHash, hash) @@ -576,7 +575,7 @@ func saveUploadedMediaInfo(channel courier.Channel, sendURL, serverId, hash, med req.URL.RawQuery = params.Encode() - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to save uploaded media info") } diff --git a/handlers/vk/handler_test.go b/handlers/vk/handler_test.go index e208a9694..d1c9af0b6 100644 --- a/handlers/vk/handler_test.go +++ b/handlers/vk/handler_test.go @@ -369,6 +369,7 @@ func TestDescribeURN(t *testing.T) { defer server.Close() handler := newHandler() + handler.Initialize(test.NewMockServer(courier.NewConfig(), test.NewMockBackend())) clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, testChannels[0], handler.RedactValues(testChannels[0])) urn, _ := urns.NewURNFromParts(urns.VKScheme, "123456789", "", "") data := map[string]string{"name": "John Doe"} diff --git a/handlers/wavy/handler.go b/handlers/wavy/handler.go index 073315425..e4531ff37 100644 --- a/handlers/wavy/handler.go +++ b/handlers/wavy/handler.go @@ -148,7 +148,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("username", username) req.Header.Set("authenticationtoken", token) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/wechat/handler.go b/handlers/wechat/handler.go index d745b209c..d104d6d4e 100644 --- a/handlers/wechat/handler.go +++ b/handlers/wechat/handler.go @@ -209,7 +209,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - resp, _, err := handlers.RequestHTTP(req, clog) + resp, _, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } @@ -239,7 +239,7 @@ func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn req, _ := http.NewRequest(http.MethodGet, reqURL.String(), nil) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("unable to look up contact data") } @@ -326,7 +326,7 @@ func (h *handler) fetchAccessToken(ctx context.Context, channel courier.Channel, req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return "", 0, err } diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index d03e70250..de11ebcd9 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -313,7 +313,6 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, // set the access token as the authorization header req, _ := http.NewRequest(http.MethodGet, attachmentURL, nil) - req.Header.Set("User-Agent", utils.HTTPUserAgent) setWhatsAppAuthHeader(&req.Header, channel) return req, nil } @@ -524,7 +523,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch for i, payload := range payloads { externalID := "" - wppID, externalID, err = sendWhatsAppMsg(conn, msg, sendPath, payload, clog) + wppID, externalID, err = h.sendWhatsAppMsg(conn, msg, sendPath, payload, clog) if err != nil { break } @@ -866,7 +865,7 @@ func (h *handler) fetchMediaID(msg courier.MsgOut, mimeType, mediaURL string, cl return "", errors.Wrapf(err, "error building media request") } - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { failedMediaCache.Set(failKey, true, cache.DefaultExpiration) return "", nil @@ -888,7 +887,7 @@ func (h *handler) fetchMediaID(msg courier.MsgOut, mimeType, mediaURL string, cl mediaType, _ := httpx.DetectContentType(respBody) req.Header.Add("Content-Type", mediaType) - resp, respBody, err = handlers.RequestHTTP(req, clog) + resp, respBody, err = h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { failedMediaCache.Set(failKey, true, cache.DefaultExpiration) return "", errors.Wrapf(err, "error uploading media to whatsapp") @@ -909,13 +908,13 @@ func (h *handler) fetchMediaID(msg courier.MsgOut, mimeType, mediaURL string, cl return mediaID, nil } -func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { +func (h *handler) sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, payload any, clog *courier.ChannelLog) (string, string, error) { jsonBody := jsonx.MustMarshal(payload) req, _ := http.NewRequest(http.MethodPost, sendPath.String(), bytes.NewReader(jsonBody)) req.Header = buildWhatsAppHeaders(msg.Channel()) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil { return "", "", err } @@ -955,7 +954,7 @@ func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, paylo } // check contact baseURL := fmt.Sprintf("%s://%s", sendPath.Scheme, sendPath.Host) - checkResp, err := checkWhatsAppContact(msg.Channel(), baseURL, msg.URN(), clog) + checkResp, err := h.checkWhatsAppContact(msg.Channel(), baseURL, msg.URN(), clog) if checkResp == nil { return "", "", err } @@ -1009,7 +1008,7 @@ func sendWhatsAppMsg(rc redis.Conn, msg courier.MsgOut, sendPath *url.URL, paylo reqRetry.URL.RawQuery = fmt.Sprintf("%s=1", retryParam) } - retryResp, retryRespBody, err := handlers.RequestHTTP(reqRetry, clog) + retryResp, retryRespBody, err := h.RequestHTTP(reqRetry, clog) if err != nil || retryResp.StatusCode/100 != 2 { return "", "", errors.New("error making retry request") } @@ -1041,7 +1040,6 @@ func buildWhatsAppHeaders(channel courier.Channel) http.Header { header := http.Header{ "Content-Type": []string{"application/json"}, "Accept": []string{"application/json"}, - "User-Agent": []string{utils.HTTPUserAgent}, } setWhatsAppAuthHeader(&header, channel) return header @@ -1079,7 +1077,7 @@ type mtContactCheckPayload struct { ForceCheck bool `json:"force_check"` } -func checkWhatsAppContact(channel courier.Channel, baseURL string, urn urns.URN, clog *courier.ChannelLog) ([]byte, error) { +func (h *handler) checkWhatsAppContact(channel courier.Channel, baseURL string, urn urns.URN, clog *courier.ChannelLog) ([]byte, error) { payload := mtContactCheckPayload{ Blocking: "wait", Contacts: []string{fmt.Sprintf("+%s", urn.Path())}, @@ -1090,7 +1088,7 @@ func checkWhatsAppContact(channel courier.Channel, baseURL string, urn urns.URN, req, _ := http.NewRequest(http.MethodPost, sendURL, bytes.NewReader(reqBody)) req.Header = buildWhatsAppHeaders(channel) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return nil, errors.New("error checking contact") } diff --git a/handlers/yo/handler.go b/handlers/yo/handler.go index 49160ddd1..d225b08c0 100644 --- a/handlers/yo/handler.go +++ b/handlers/yo/handler.go @@ -130,7 +130,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/handlers/zenvia/handlers.go b/handlers/zenvia/handlers.go index 392e8d300..173beb857 100644 --- a/handlers/zenvia/handlers.go +++ b/handlers/zenvia/handlers.go @@ -238,7 +238,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch req.Header.Set("Accept", "application/json") req.Header.Set("X-API-TOKEN", token) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return status, nil } diff --git a/server.go b/server.go index 6210e3c3e..f080a1c73 100644 --- a/server.go +++ b/server.go @@ -91,9 +91,6 @@ func NewServerWithLogger(config *Config, backend Backend, logger *slog.Logger) S // if it encounters any unrecoverable (or ignorable) error, though its bias is to move forward despite // connection errors func (s *server) Start() error { - // set our user agent, needs to happen before we do anything so we don't change have threading issues - utils.HTTPUserAgent = fmt.Sprintf("Courier/%s", s.config.Version) - // configure librato if we have configuration options for it host, _ := os.Hostname() if s.config.LibratoUsername != "" { diff --git a/test/backend.go b/test/backend.go index 94d9d6e67..b4ba905c9 100644 --- a/test/backend.go +++ b/test/backend.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/http" "sync" "time" @@ -11,6 +12,7 @@ import ( _ "github.com/lib/pq" "github.com/nyaruka/courier" "github.com/nyaruka/courier/utils" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/pkg/errors" @@ -352,11 +354,19 @@ func (mb *MockBackend) ResolveMedia(ctx context.Context, mediaUrl string) (couri return media, nil } -// Health gives a string representing our health, empty for our mock func (mb *MockBackend) Health() string { return "" } +// Health gives a string representing our health, empty for our mock +func (mb *MockBackend) HttpClient(bool) *http.Client { + return http.DefaultClient +} + +func (mb *MockBackend) HttpAccess() *httpx.AccessConfig { + return nil +} + // Status returns a string describing the status of the service, queue size etc.. func (mb *MockBackend) Status() string { return "ALL GOOD" diff --git a/test/server.go b/test/server.go new file mode 100644 index 000000000..9f31b92d8 --- /dev/null +++ b/test/server.go @@ -0,0 +1,59 @@ +package test + +import ( + "sync" + + "github.com/go-chi/chi" + "github.com/nyaruka/courier" +) + +type MockServer struct { + backend courier.Backend + config *courier.Config + + stopChan chan bool + stopped bool +} + +func NewMockServer(config *courier.Config, backend courier.Backend) courier.Server { + return &MockServer{ + backend: backend, + config: config, + stopChan: make(chan bool), + } +} + +func (ms *MockServer) Config() *courier.Config { + return ms.config +} + +func (ms *MockServer) AddHandlerRoute(handler courier.ChannelHandler, method string, action string, logType courier.ChannelLogType, handlerFunc courier.ChannelHandleFunc) { + +} +func (ms *MockServer) GetHandler(courier.Channel) courier.ChannelHandler { + return nil +} + +func (ms *MockServer) Backend() courier.Backend { + return ms.backend +} + +func (ms *MockServer) WaitGroup() *sync.WaitGroup { + return nil +} +func (ms *MockServer) StopChan() chan bool { + return ms.stopChan +} +func (ms *MockServer) Stopped() bool { + return ms.stopped +} + +func (ms *MockServer) Router() chi.Router { + return nil +} + +func (ms *MockServer) Start() error { return nil } +func (ms *MockServer) Stop() error { + ms.stopped = true + return nil +} diff --git a/utils/http.go b/utils/http.go deleted file mode 100644 index db49c0ad1..000000000 --- a/utils/http.go +++ /dev/null @@ -1,51 +0,0 @@ -package utils - -import ( - "crypto/tls" - "net/http" - "sync" - "time" -) - -// GetHTTPClient returns the shared HTTP client used by all Courier threads -func GetHTTPClient() *http.Client { - once.Do(func() { - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.MaxIdleConns = 64 - transport.MaxIdleConnsPerHost = 8 - transport.IdleConnTimeout = 15 * time.Second - client = &http.Client{ - Transport: transport, - Timeout: 30 * time.Second, - } - }) - - return client -} - -// GetInsecureHTTPClient returns the shared HTTP client used by all Courier threads -func GetInsecureHTTPClient() *http.Client { - insecureOnce.Do(func() { - insecureTransport := http.DefaultTransport.(*http.Transport).Clone() - insecureTransport.MaxIdleConns = 64 - insecureTransport.MaxIdleConnsPerHost = 8 - insecureTransport.IdleConnTimeout = 15 * time.Second - insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - insecureClient = &http.Client{ - Transport: insecureTransport, - Timeout: 30 * time.Second, - } - }) - - return insecureClient -} - -var ( - client *http.Client - once sync.Once - - insecureClient *http.Client - insecureOnce sync.Once - - HTTPUserAgent = "Courier/vDev" -) diff --git a/utils/http_test.go b/utils/http_test.go deleted file mode 100644 index 160ef0480..000000000 --- a/utils/http_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package utils - -import "testing" - -func TestClient(t *testing.T) { - client := GetHTTPClient() - if client == nil { - t.Error("Client should not be nil") - } - - insecureClient := GetInsecureHTTPClient() - if insecureClient == nil { - t.Error("Insecure client should not be nil") - } - - if client == insecureClient || client.Transport == insecureClient.Transport { - t.Error("Client and insecure client should not be the same") - } - - client2 := GetHTTPClient() - if client != client2 { - t.Error("GetHTTPClient should always return same client") - } -} From 18a8dc69da29f702582a71e12c29c58dd8b232dd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 31 Oct 2023 14:02:29 -0500 Subject: [PATCH 145/170] Update CHANGELOG.md for v8.3.27 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c4af216..021befbdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.27 (2023-10-31) +------------------------- + * Prevent all courier HTTP requests from accessing local networks + v8.3.26 (2023-10-30) ------------------------- * Update to latest gocommon From 0da943d79adc021b524b0add7c3a923766ae78c7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 23 Nov 2023 10:09:33 -0500 Subject: [PATCH 146/170] Logging tweak --- backends/rapidpro/contact.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 1d7f6d162..923363716 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -103,26 +103,28 @@ WHERE // contactForURN first tries to look up a contact for the passed in URN, if not finding one then creating one func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, urn urns.URN, authTokens map[string]string, name string, clog *courier.ChannelLog) (*Contact, error) { + log := slog.With("org_id", org, "urn", urn.Identity(), "channel_uuid", channel.UUID(), "log_uuid", clog.UUID()) + // try to look up our contact by URN contact := &Contact{} err := b.db.GetContext(ctx, contact, lookupContactFromURNSQL, urn.Identity(), org) if err != nil && err != sql.ErrNoRows { - slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) + log.Error("error looking up contact by URN", "error", err) return nil, errors.Wrap(err, "error looking up contact by URN") } // we found it, return it if err != sql.ErrNoRows { - // insert it tx, err := b.db.BeginTxx(ctx, nil) if err != nil { - slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) + log.Error("error beginning transaction", "error", err) return nil, errors.Wrap(err, "error beginning transaction") } + // update contact's URNs so this URN has priority err = setDefaultURN(tx, channel, contact, urn, authTokens) if err != nil { - slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) + log.Error("error updating default URN for contact", "error", err) tx.Rollback() return nil, errors.Wrap(err, "error setting default URN for contact") } @@ -148,7 +150,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, // in the case of errors, we log the error but move onwards anyways if err != nil { - slog.Error("unable to describe URN", "error", err, "channel_uuid", channel.UUID(), "channel_type", channel.ChannelType(), "urn", urn) + log.Error("unable to describe URN", "error", err) } else { name = attrs["name"] } From 9a1b3e630fee4e0e345735fb5cc678e4980167bd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 23 Nov 2023 10:11:20 -0500 Subject: [PATCH 147/170] Update CHANGELOG.md for v8.3.28 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 021befbdd..073307c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.28 (2023-11-23) +------------------------- + * Logging tweak + v8.3.27 (2023-10-31) ------------------------- * Prevent all courier HTTP requests from accessing local networks From b328ce3eeec64ef6a83a9f806c441c12d5603658 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 Nov 2023 16:03:01 -0500 Subject: [PATCH 148/170] Update to latest gocommon and phonenumbers --- go.mod | 4 ++-- go.sum | 43 ++++++++++++++++++------------------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 82a51bd56..7e6397069 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.42.2 + github.com/nyaruka/gocommon v1.42.6 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -47,7 +47,7 @@ require ( github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.1.8 // indirect + github.com/nyaruka/phonenumbers v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.38.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect diff --git a/go.sum b/go.sum index 5fc6d4c03..2c377bdb3 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245Pp github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.45.24 h1:TZx/CizkmCQn8Rtsb11iLYutEQVGK5PK9wAhwouELBo= -github.com/aws/aws-sdk-go v1.45.24/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.48.0 h1:1SeJ8agckRDQvnSCt1dGZYAwUaoD2Ixj6IaXB4LCv8Q= +github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -39,8 +39,6 @@ github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -69,16 +67,16 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.42.2 h1:VGJ/h7WNmCyQ6wNYClJfFkXkU7ZZn+Aiz9xoKJHVRH4= -github.com/nyaruka/gocommon v1.42.2/go.mod h1:JuphjZr/q+GYycaXSQ1WmXzJdbqkbm0iMBlqxxVcF8M= +github.com/nyaruka/gocommon v1.42.6 h1:3FMJ2IHRfHfNMHsdSVx3yzXZpKsHnmNsIu5xUS3Rgg4= +github.com/nyaruka/gocommon v1.42.6/go.mod h1:qdAi1VfyotMr9jO4cgy9WDZ0LDhJJigxDSVxijfWR7c= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= -github.com/nyaruka/phonenumbers v1.1.8/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs= +github.com/nyaruka/phonenumbers v1.2.2 h1:OwVjf7Y4uHoK9VJUrA8ebR0ha2yc6sEYbfrwkq0asCY= +github.com/nyaruka/phonenumbers v1.2.2/go.mod h1:wzk2qq7qwsaBKrfbkWKdgHYOOH+QFTesSpIq53ELw8M= github.com/nyaruka/redisx v0.5.0 h1:XH1pjG17lhj2DZJbrrZ2yZuPLAXrrHidXVA7cIuQq4g= github.com/nyaruka/redisx v0.5.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -109,20 +107,19 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -130,28 +127,24 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 503a4a99ae0be4055ae2efe32880d1c55f1f78e3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 Nov 2023 16:04:20 -0500 Subject: [PATCH 149/170] Update deps --- go.mod | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 7e6397069..2e04e2415 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.45.24 + github.com/aws/aws-sdk-go v1.48.0 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 github.com/getsentry/sentry-go v0.22.0 @@ -23,8 +23,8 @@ require ( github.com/samber/slog-multi v1.0.2 github.com/samber/slog-sentry v1.2.2 github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d - golang.org/x/mod v0.13.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/mod v0.14.0 gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/h2non/filetype.v1 v1.0.5 ) @@ -39,7 +39,6 @@ require ( github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -51,10 +50,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.38.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect From 715182083cf3b73a2171084778dfb80ce040087f Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Wed, 29 Nov 2023 19:51:15 +0200 Subject: [PATCH 150/170] Remove support for HSM template support --- handlers/whatsapp_legacy/handler.go | 43 ++++++++---------------- handlers/whatsapp_legacy/handler_test.go | 25 -------------- 2 files changed, 14 insertions(+), 54 deletions(-) diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index de11ebcd9..40fffe999 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -724,39 +724,24 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] return nil, errors.Errorf("cannot send template message without Facebook namespace for channel: %s", msg.Channel().UUID()) } - if msg.Channel().BoolConfigForKey(configHSMSupport, false) { - payload := hsmPayload{ - To: msg.URN().Path(), - Type: "hsm", - } - payload.HSM.Namespace = namespace - payload.HSM.ElementName = templating.Template.Name - payload.HSM.Language.Policy = "deterministic" - payload.HSM.Language.Code = langCode - for _, v := range templating.Variables { - payload.HSM.LocalizableParams = append(payload.HSM.LocalizableParams, LocalizableParam{Default: v}) - } - payloads = append(payloads, payload) - } else { + payload := templatePayload{ + To: msg.URN().Path(), + Type: "template", + } + payload.Template.Namespace = namespace + payload.Template.Name = templating.Template.Name + payload.Template.Language.Policy = "deterministic" + payload.Template.Language.Code = langCode - payload := templatePayload{ - To: msg.URN().Path(), - Type: "template", - } - payload.Template.Namespace = namespace - payload.Template.Name = templating.Template.Name - payload.Template.Language.Policy = "deterministic" - payload.Template.Language.Code = langCode + component := &Component{Type: "body"} - component := &Component{Type: "body"} + for _, v := range templating.Variables { + component.Parameters = append(component.Parameters, Param{Type: "text", Text: v}) + } + payload.Template.Components = append(payload.Template.Components, *component) - for _, v := range templating.Variables { - component.Parameters = append(component.Parameters, Param{Type: "text", Text: v}) - } - payload.Template.Components = append(payload.Template.Components, *component) + payloads = append(payloads, payload) - payloads = append(payloads, payload) - } } else { if isInteractiveMsg { diff --git a/handlers/whatsapp_legacy/handler_test.go b/handlers/whatsapp_legacy/handler_test.go index f2183103d..33a308d24 100644 --- a/handlers/whatsapp_legacy/handler_test.go +++ b/handlers/whatsapp_legacy/handler_test.go @@ -1070,21 +1070,6 @@ var mediaCacheSendTestCases = []OutgoingTestCase{ }, } -var hsmSupportSendTestCases = []OutgoingTestCase{ - { - Label: "Template Send", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "language": "eng", "variables": ["Chef", "tomorrow"]}}`), - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 200, - ExpectedRequestBody: `{"to":"250788123123","type":"hsm","hsm":{"namespace":"waba_namespace","element_name":"revive_issue","language":{"policy":"deterministic","code":"en"},"localizable_params":[{"default":"Chef"},{"default":"tomorrow"}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, -} - func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) @@ -1108,15 +1093,6 @@ func TestOutgoing(t *testing.T) { "version": "v2.35.2", }) - var hsmSupportChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WA", "250788383383", "US", - map[string]any{ - "auth_token": "token123", - "base_url": "https://foo.bar/", - "fb_namespace": "waba_namespace", - "hsm_support": true, - "version": "v2.35.2", - }) - var d3Channel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "D3", "250788383383", "US", map[string]any{ "auth_token": "token123", @@ -1134,7 +1110,6 @@ func TestOutgoing(t *testing.T) { }) RunOutgoingTestCases(t, defaultChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), defaultSendTestCases, []string{"token123"}, nil) - RunOutgoingTestCases(t, hsmSupportChannel, newWAHandler(courier.ChannelType("WA"), "WhatsApp"), hsmSupportSendTestCases, []string{"token123"}, nil) RunOutgoingTestCases(t, d3Channel, newWAHandler(courier.ChannelType("D3"), "360Dialog"), defaultSendTestCases, []string{"token123"}, nil) RunOutgoingTestCases(t, txwChannel, newWAHandler(courier.ChannelType("TXW"), "TextIt"), defaultSendTestCases, []string{"token123"}, nil) From 20411409c8ea20b215f7c286147b6b2d95a56662 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Mon, 4 Dec 2023 14:19:54 +0200 Subject: [PATCH 151/170] Fix FBA timestamps that sometimes are in seconds instead of milliseconds --- handlers/meta/facebook_test.go | 11 ++++++++ handlers/meta/handlers.go | 11 ++++++-- .../meta/testdata/fba/referral_seconds.json | 27 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 handlers/meta/testdata/fba/referral_seconds.json diff --git a/handlers/meta/facebook_test.go b/handlers/meta/facebook_test.go index b3b7be0e1..c6fdb6f34 100644 --- a/handlers/meta/facebook_test.go +++ b/handlers/meta/facebook_test.go @@ -183,6 +183,17 @@ var facebookIncomingTests = []IncomingTestCase{ }, PrepRequest: addValidSignature, }, + { + Label: "Receive Referral timestamp seconds", + URL: "/c/fba/receive", + Data: string(test.ReadFile("./testdata/fba/referral_seconds.json")), + ExpectedRespStatus: 200, + ExpectedBodyContains: `"referrer_id":"referral id"`, + ExpectedEvents: []ExpectedEvent{ + {Type: courier.EventTypeReferral, URN: "facebook:5678", Time: time.Date(2023, 12, 3, 10, 25, 11, 0, time.UTC), Extra: map[string]string{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}}, + }, + PrepRequest: addValidSignature, + }, { Label: "Receive Fallback Attachment", URL: "/c/fba/receive", diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 9d39c3bdc..a63f85d68 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -409,8 +409,15 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c continue } - // create our date from the timestamp (they give us millis, arg is nanos) - date := time.Unix(0, msg.Timestamp*1000000).UTC() + var date time.Time + + if msg.Timestamp >= 10_000_000_000 { + // create our date from the timestamp (they give us millis, arg is nanos) + date = time.Unix(0, msg.Timestamp*1000000).UTC() + } else { + // create our date from the timestamp (they give us seconds) + date = time.Unix(msg.Timestamp, 0).UTC() + } sender := msg.Sender.UserRef if sender == "" { diff --git a/handlers/meta/testdata/fba/referral_seconds.json b/handlers/meta/testdata/fba/referral_seconds.json new file mode 100644 index 000000000..3b3d76460 --- /dev/null +++ b/handlers/meta/testdata/fba/referral_seconds.json @@ -0,0 +1,27 @@ +{ + "object": "page", + "entry": [ + { + "id": "12345", + "messaging": [ + { + "referral": { + "ref": "referral id", + "ad_id": "ad id", + "source": "referral source", + "type": "referral type" + }, + "recipient": { + "id": "12345" + }, + "sender": { + "id": "5678", + "user_ref": "5678" + }, + "timestamp": 1701599111 + } + ], + "time": 1701599116216 + } + ] +} \ No newline at end of file From 9ccd1e96ae544e65404d934b9ab0b6a75aa2cf50 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 4 Dec 2023 09:52:29 -0500 Subject: [PATCH 152/170] Update CHANGELOG.md for v8.3.29 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 073307c64..a99c48586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v8.3.29 (2023-12-04) +------------------------- + * Fix FBA timestamps that sometimes are in seconds instead of milliseconds + * Remove support for HSM template support + * Update to latest gocommon and phonenumbers + v8.3.28 (2023-11-23) ------------------------- * Logging tweak From 98ea509590007e10a95cf489f7057685e5c3a1cc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 4 Dec 2023 10:16:47 -0500 Subject: [PATCH 153/170] Cleanup parsing of FBA timestamps --- handlers/meta/handlers.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index a63f85d68..362cc0d65 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -279,7 +279,7 @@ func (h *handler) processWhatsAppPayload(ctx context.Context, channel courier.Ch if err != nil { return nil, nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, fmt.Errorf("invalid timestamp: %s", msg.Timestamp)) } - date := time.Unix(ts, 0).UTC() + date := parseTimestamp(ts) urn, err := urns.NewWhatsAppURN(msg.From) if err != nil { @@ -409,15 +409,7 @@ func (h *handler) processFacebookInstagramPayload(ctx context.Context, channel c continue } - var date time.Time - - if msg.Timestamp >= 10_000_000_000 { - // create our date from the timestamp (they give us millis, arg is nanos) - date = time.Unix(0, msg.Timestamp*1000000).UTC() - } else { - // create our date from the timestamp (they give us seconds) - date = time.Unix(msg.Timestamp, 0).UTC() - } + date := parseTimestamp(msg.Timestamp) sender := msg.Sender.UserRef if sender == "" { @@ -1227,3 +1219,11 @@ func (h *handler) BuildAttachmentRequest(ctx context.Context, b courier.Backend, } var _ courier.AttachmentRequestBuilder = (*handler)(nil) + +func parseTimestamp(ts int64) time.Time { + // sometimes Facebook sends timestamps in seconds rather than milliseconds + if ts >= 1_000_000_000_000 { + return time.Unix(0, ts*1000000).UTC() + } + return time.Unix(ts, 0).UTC() +} From 27ab88fa4220a050c23317a40a6c9b82eeafb7cf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 4 Dec 2023 13:54:26 -0500 Subject: [PATCH 154/170] Change channel events so that created_on is db time and is included in queued task payload --- backends/rapidpro/backend_test.go | 12 ++++-- backends/rapidpro/channel_event.go | 16 ++++---- backends/rapidpro/task.go | 65 +++++------------------------- 3 files changed, 27 insertions(+), 66 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 97ee68d50..2ddf5c777 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/buger/jsonparser" "github.com/gomodule/redigo/redis" "github.com/lib/pq" "github.com/nyaruka/courier" @@ -22,6 +23,7 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" @@ -1313,7 +1315,8 @@ func (ts *BackendTestSuite) TestMailroomEvents() { clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) urn, _ := urns.NewTelURNForCountry("12065551616", channel.Country()) - event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog).WithExtra(map[string]string{"ref_id": "12345"}). + event := ts.b.NewChannelEvent(channel, courier.EventTypeReferral, urn, clog). + WithExtra(map[string]string{"ref_id": "12345"}). WithContactName("kermit frog"). WithOccurredOn(time.Date(2020, 8, 5, 13, 30, 0, 123456789, time.UTC)) err := ts.b.WriteChannelEvent(ctx, event, clog) @@ -1322,6 +1325,7 @@ func (ts *BackendTestSuite) TestMailroomEvents() { contact, err := contactForURN(ctx, ts.b, channel.OrgID_, channel, urn, nil, "", clog) ts.NoError(err) ts.Equal(null.String("kermit frog"), contact.Name_) + ts.False(contact.IsNew_) dbE := event.(*ChannelEvent) dbE = readChannelEventFromDB(ts.b, dbE.ID_) @@ -1449,9 +1453,11 @@ func (ts *BackendTestSuite) assertQueuedContactTask(contactID ContactID, expecte data, err := redis.Bytes(rc.Do("LPOP", fmt.Sprintf("c:1:%d", contactID))) ts.NoError(err) + // created_on is usually DB time so exclude it from task body comparison + data = jsonparser.Delete(data, "task", "created_on") + var body map[string]any - err = json.Unmarshal(data, &body) - ts.NoError(err) + jsonx.MustUnmarshal(data, &body) ts.Equal(expectedType, body["type"]) ts.Equal(expectedBody, body["task"]) } diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index e9b58c022..3e86a8b58 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -62,7 +62,6 @@ type ChannelEvent struct { // newChannelEvent creates a new channel event func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType, urn urns.URN, clog *courier.ChannelLog) *ChannelEvent { dbChannel := channel.(*Channel) - now := time.Now().In(time.UTC) return &ChannelEvent{ ChannelUUID_: dbChannel.UUID_, @@ -70,8 +69,7 @@ func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType ChannelID_: dbChannel.ID_, URN_: urn, EventType_: eventType, - OccurredOn_: now, - CreatedOn_: now, + OccurredOn_: time.Now().In(time.UTC), LogUUIDs: []string{string(clog.UUID())}, channel: dbChannel, @@ -136,11 +134,11 @@ func writeChannelEvent(ctx context.Context, b *backend, event courier.ChannelEve const sqlInsertChannelEvent = ` INSERT INTO - channels_channelevent( org_id, channel_id, contact_id, contact_urn_id, event_type, optin_id, extra, occurred_on, created_on, log_uuids) - VALUES(:org_id, :channel_id, :contact_id, :contact_urn_id, :event_type, :optin_id, :extra, :occurred_on, :created_on, :log_uuids) -RETURNING id` + channels_channelevent( org_id, channel_id, contact_id, contact_urn_id, event_type, optin_id, extra, occurred_on, created_on, log_uuids) + VALUES(:org_id, :channel_id, :contact_id, :contact_urn_id, :event_type, :optin_id, :extra, :occurred_on, NOW(), :log_uuids) +RETURNING id, created_on` -// writeChannelEventToDB writes the passed in msg status to our db +// writeChannelEventToDB writes the passed in channel event to our db func writeChannelEventToDB(ctx context.Context, b *backend, e *ChannelEvent, clog *courier.ChannelLog) error { // grab the contact for this event contact, err := contactForURN(ctx, b, e.OrgID_, e.channel, e.URN_, e.URNAuthTokens_, e.ContactName_, clog) @@ -159,8 +157,8 @@ func writeChannelEventToDB(ctx context.Context, b *backend, e *ChannelEvent, clo defer rows.Close() rows.Next() - err = rows.Scan(&e.ID_) - if err != nil { + + if err = rows.Scan(&e.ID_, &e.CreatedOn_); err != nil { return err } diff --git a/backends/rapidpro/task.go b/backends/rapidpro/task.go index 76dc5ad11..f9967a355 100644 --- a/backends/rapidpro/task.go +++ b/backends/rapidpro/task.go @@ -31,73 +31,30 @@ func queueMsgHandling(rc redis.Conn, c *Contact, m *Msg) error { } func queueChannelEvent(rc redis.Conn, c *Contact, e *ChannelEvent) error { - // queue to mailroom + body := map[string]any{ + "org_id": e.OrgID_, + "contact_id": e.ContactID_, + "urn_id": e.ContactURNID_, + "channel_id": e.ChannelID_, + "extra": e.Extra(), + "new_contact": c.IsNew_, + "occurred_on": e.OccurredOn_, + "created_on": e.CreatedOn_, + } + switch e.EventType() { case courier.EventTypeStopContact: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "stop_contact", e.OrgID_, e.ContactID_, body) - case courier.EventTypeWelcomeMessage: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "urn_id": e.ContactURNID_, - "channel_id": e.ChannelID_, - "new_contact": c.IsNew_, - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "welcome_message", e.OrgID_, e.ContactID_, body) - case courier.EventTypeReferral: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "urn_id": e.ContactURNID_, - "channel_id": e.ChannelID_, - "extra": e.Extra(), - "new_contact": c.IsNew_, - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "referral", e.OrgID_, e.ContactID_, body) - case courier.EventTypeNewConversation: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "urn_id": e.ContactURNID_, - "channel_id": e.ChannelID_, - "extra": e.Extra(), - "new_contact": c.IsNew_, - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "new_conversation", e.OrgID_, e.ContactID_, body) - case courier.EventTypeOptIn: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "urn_id": e.ContactURNID_, - "channel_id": e.ChannelID_, - "extra": e.Extra(), - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "optin", e.OrgID_, e.ContactID_, body) - case courier.EventTypeOptOut: - body := map[string]any{ - "org_id": e.OrgID_, - "contact_id": e.ContactID_, - "urn_id": e.ContactURNID_, - "channel_id": e.ChannelID_, - "extra": e.Extra(), - "occurred_on": e.OccurredOn_, - } return queueMailroomTask(rc, "optout", e.OrgID_, e.ContactID_, body) - default: return fmt.Errorf("unknown event type: %s", e.EventType()) } From f58fa2a689082523d0e5a2795d1fc00dbf8fd808 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 4 Dec 2023 14:15:54 -0500 Subject: [PATCH 155/170] Update CHANGELOG.md for v8.3.30 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a99c48586..3fcb393d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.30 (2023-12-04) +------------------------- + * Change channel events so that created_on is db time and is included in queued task payload + v8.3.29 (2023-12-04) ------------------------- * Fix FBA timestamps that sometimes are in seconds instead of milliseconds From f7056ae664469a6ec5dc877b7a2667843b4cc802 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Dec 2023 16:36:20 -0500 Subject: [PATCH 156/170] Use language value in templating metadata instead of trying to match --- handlers/dialog360/handler.go | 5 +- handlers/dialog360/handler_test.go | 28 +----- handlers/i18n.go | 25 ++++++ handlers/i18n_test.go | 16 ++++ handlers/meta/handlers.go | 5 +- handlers/meta/whataspp_test.go | 28 +----- handlers/meta/whatsapp/languages.go | 107 ----------------------- handlers/meta/whatsapp/languages_test.go | 27 ------ handlers/meta/whatsapp/templates.go | 1 + 9 files changed, 48 insertions(+), 194 deletions(-) create mode 100644 handlers/i18n.go create mode 100644 handlers/i18n_test.go delete mode 100644 handlers/meta/whatsapp/languages.go delete mode 100644 handlers/meta/whatsapp/languages_test.go diff --git a/handlers/dialog360/handler.go b/handlers/dialog360/handler.go index dafe6623f..36f58610e 100644 --- a/handlers/dialog360/handler.go +++ b/handlers/dialog360/handler.go @@ -316,8 +316,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) } qrs := msg.QuickReplies() - lang := whatsapp.GetSupportedLanguage(msg.Locale()) - menuButton := whatsapp.GetMenuButton(lang) + menuButton := handlers.GetText("Menu", msg.Locale()) var payloadAudio whatsapp.SendRequest @@ -334,7 +333,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch payload.Type = "template" - template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: lang}} + template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: templating.Language}} payload.Template = &template component := &whatsapp.Component{Type: "body"} diff --git a/handlers/dialog360/handler_test.go b/handlers/dialog360/handler_test.go index 24fb14dd0..ceef82189 100644 --- a/handlers/dialog360/handler_test.go +++ b/handlers/dialog360/handler_test.go @@ -409,38 +409,12 @@ var SendTestCasesD3C = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"], "language": "en_US"}}`), ExpectedMsgStatus: "W", ExpectedExternalID: "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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - SendPrep: setSendURL, - }, - { - Label: "Template Country Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), - 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"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Template Invalid Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "bnt", - MsgMetadata: json.RawMessage(`{"templating": { "template": { "name": "revive_issue", "uuid": "8ca114b4-bee2-4d3b-aaf1-9aa6b48d41e8" }, "variables": ["Chef", "tomorrow"]}}`), - 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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", SendPrep: setSendURL, }, { diff --git a/handlers/i18n.go b/handlers/i18n.go new file mode 100644 index 000000000..0088d9b4e --- /dev/null +++ b/handlers/i18n.go @@ -0,0 +1,25 @@ +package handlers + +import "github.com/nyaruka/gocommon/i18n" + +func GetText(text string, locale i18n.Locale) string { + if set, ok := translations[text]; ok { + lang, _ := locale.Split() + if trans := set[lang]; trans != "" { + return trans + } + } + return text +} + +var translations = map[string]map[i18n.Language]string{ + "Menu": { + "afr": "Kieslys", + "ara": "قائمة", + "zho": "菜单", + "heb": "תפריט", + "gle": "Roghchlár", + "spa": "Menú", + "swa": "Menyu", + }, +} diff --git a/handlers/i18n_test.go b/handlers/i18n_test.go new file mode 100644 index 000000000..f0f0bf98c --- /dev/null +++ b/handlers/i18n_test.go @@ -0,0 +1,16 @@ +package handlers_test + +import ( + "testing" + + "github.com/nyaruka/courier/handlers" + "github.com/stretchr/testify/assert" +) + +func TestGetText(t *testing.T) { + assert.Equal(t, "Menu", handlers.GetText("Menu", "eng")) + assert.Equal(t, "Menú", handlers.GetText("Menu", "spa")) + assert.Equal(t, "Menú", handlers.GetText("Menu", "spa-MX")) + assert.Equal(t, "Menyu", handlers.GetText("Menu", "swa")) + assert.Equal(t, "Foo", handlers.GetText("Foo", "eng")) +} diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index 362cc0d65..c0549b85d 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -805,8 +805,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) } qrs := msg.QuickReplies() - lang := whatsapp.GetSupportedLanguage(msg.Locale()) - menuButton := whatsapp.GetMenuButton(lang) + menuButton := handlers.GetText("Menu", msg.Locale()) var payloadAudio whatsapp.SendRequest @@ -823,7 +822,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog payload.Type = "template" - template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: lang}} + template := whatsapp.Template{Name: templating.Template.Name, Language: &whatsapp.Language{Policy: "deterministic", Code: templating.Language}} payload.Template = &template component := &whatsapp.Component{Type: "body"} diff --git a/handlers/meta/whataspp_test.go b/handlers/meta/whataspp_test.go index f62585b70..e660131e6 100644 --- a/handlers/meta/whataspp_test.go +++ b/handlers/meta/whataspp_test.go @@ -373,38 +373,12 @@ var whatsappOutgoingTests = []OutgoingTestCase{ MsgText: "templated message", MsgURN: "whatsapp:250788123123", MsgLocale: "eng", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), + MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"], "language": "en_US"}}`), ExpectedMsgStatus: "W", ExpectedExternalID: "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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - SendPrep: setSendURL, - }, - { - Label: "Template Country Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), - 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"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Template Invalid Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "bnt", - MsgMetadata: json.RawMessage(`{"templating": { "template": { "name": "revive_issue", "uuid": "8ca114b4-bee2-4d3b-aaf1-9aa6b48d41e8" }, "variables": ["Chef", "tomorrow"]}}`), - 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"},"components":[{"type":"body","sub_type":"","index":"","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", SendPrep: setSendURL, }, { diff --git a/handlers/meta/whatsapp/languages.go b/handlers/meta/whatsapp/languages.go deleted file mode 100644 index 978eaca2c..000000000 --- a/handlers/meta/whatsapp/languages.go +++ /dev/null @@ -1,107 +0,0 @@ -package whatsapp - -import "github.com/nyaruka/gocommon/i18n" - -func GetSupportedLanguage(lc i18n.Locale) string { - if lc == i18n.NilLocale { - return "en" - } - return supportedLanguages.ForLocales(lc, "en") -} - -// see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates/ -var supportedLanguages = i18n.NewBCP47Matcher( - "af", // Afrikaans - "sq", // Albanian - "ar", // Arabic - "az", // Azerbaijani - "bn", // Bengali - "bg", // Bulgarian - "ca", // Catalan - "zh_CN", // Chinese (CHN) - "zh_HK", // Chinese (HKG) - "zh_TW", // Chinese (TAI) - "hr", // Croatian - "cs", // Czech - "da", // Danish - "nl", // Dutch - "en", // English - "en_GB", // English (UK) - "en_US", // English (US) - "et", // Estonian - "fil", // Filipino - "fi", // Finnish - "fr", // French - "ka", // Georgian - "de", // German - "el", // Greek - "gu", // Gujarati - "ha", // Hausa - "he", // Hebrew - "hi", // Hindi - "hu", // Hungarian - "id", // Indonesian - "ga", // Irish - "it", // Italian - "ja", // Japanese - "kn", // Kannada - "kk", // Kazakh - "rw_RW", // Kinyarwanda - "ko", // Korean - "ky_KG", // Kyrgyzstan - "lo", // Lao - "lv", // Latvian - "lt", // Lithuanian - "mk", // Macedonian - "ms", // Malay - "ml", // Malayalam - "mr", // Marathi - "nb", // Norwegian - "fa", // Persian - "pl", // Polish - "pt_BR", // Portuguese (BR) - "pt_PT", // Portuguese (POR) - "pa", // Punjabi - "ro", // Romanian - "ru", // Russian - "sr", // Serbian - "sk", // Slovak - "sl", // Slovenian - "es", // Spanish - "es_AR", // Spanish (ARG) - "es_ES", // Spanish (SPA) - "es_MX", // Spanish (MEX) - "sw", // Swahili - "sv", // Swedish - "ta", // Tamil - "te", // Telugu - "th", // Thai - "tr", // Turkish - "uk", // Ukrainian - "ur", // Urdu - "uz", // Uzbek - "vi", // Vietnamese - "zu", // Zulu -) - -func GetMenuButton(lang string) string { - if trans := menuTranslations[lang]; trans != "" { - return trans - } - return "Menu" -} - -var menuTranslations = map[string]string{ - "af": "Kieslys", - "ar": "قائمة", - "zh_CN": "菜单", - "zh_HK": "菜单", - "zh_TW": "菜单", - "he": "תפריט", - "ga": "Roghchlár", - "es": "Menú", - "es_AR": "Menú", - "es_ES": "Menú", - "es_MX": "Menú", - "sw": "Menyu", -} diff --git a/handlers/meta/whatsapp/languages_test.go b/handlers/meta/whatsapp/languages_test.go deleted file mode 100644 index 981a5ec73..000000000 --- a/handlers/meta/whatsapp/languages_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package whatsapp_test - -import ( - "testing" - - "github.com/nyaruka/courier/handlers/meta/whatsapp" - "github.com/nyaruka/gocommon/i18n" - "github.com/stretchr/testify/assert" -) - -func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, "en", whatsapp.GetSupportedLanguage(i18n.NilLocale)) - assert.Equal(t, "en", whatsapp.GetSupportedLanguage("eng")) - assert.Equal(t, "en_US", whatsapp.GetSupportedLanguage("eng-US")) - assert.Equal(t, "pt_BR", whatsapp.GetSupportedLanguage("por")) - assert.Equal(t, "pt_PT", whatsapp.GetSupportedLanguage("por-PT")) - assert.Equal(t, "pt_BR", whatsapp.GetSupportedLanguage("por-BR")) - assert.Equal(t, "fil", whatsapp.GetSupportedLanguage("fil")) - assert.Equal(t, "fr", whatsapp.GetSupportedLanguage("fra-CA")) - assert.Equal(t, "en", whatsapp.GetSupportedLanguage("run")) -} - -func TestGetMenuButton(t *testing.T) { - assert.Equal(t, "Menu", whatsapp.GetMenuButton("en")) - assert.Equal(t, "Menú", whatsapp.GetMenuButton("es_MX")) - assert.Equal(t, "Menyu", whatsapp.GetMenuButton("sw")) -} diff --git a/handlers/meta/whatsapp/templates.go b/handlers/meta/whatsapp/templates.go index c4638c779..fa0b30414 100644 --- a/handlers/meta/whatsapp/templates.go +++ b/handlers/meta/whatsapp/templates.go @@ -15,6 +15,7 @@ type MsgTemplating struct { } `json:"template" validate:"required,dive"` Namespace string `json:"namespace"` Variables []string `json:"variables"` + Language string `json:"language"` } func GetTemplating(msg courier.MsgOut) (*MsgTemplating, error) { From 86ae0808de1db70e28c0df16f9be10af3fc4c3d6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 6 Dec 2023 11:03:16 -0500 Subject: [PATCH 157/170] Update CHANGELOG.md for v8.3.31 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fcb393d2..85586d97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.31 (2023-12-06) +------------------------- + * Use language value in templating metadata instead of trying to match + v8.3.30 (2023-12-04) ------------------------- * Change channel events so that created_on is db time and is included in queued task payload From bd58136b261564b34568adf0200052414e5b6625 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 12 Dec 2023 10:42:43 -0500 Subject: [PATCH 158/170] Update deps --- go.mod | 28 ++++++++++++++-------------- go.sum | 55 ++++++++++++++++++++++++++++--------------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 2e04e2415..35c988d9a 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,19 @@ module github.com/nyaruka/courier go 1.21 require ( - github.com/antchfx/xmlquery v1.3.17 - github.com/aws/aws-sdk-go v1.48.0 + github.com/antchfx/xmlquery v1.3.18 + github.com/aws/aws-sdk-go v1.49.0 github.com/buger/jsonparser v1.1.1 github.com/dghubble/oauth1 v0.7.2 - github.com/getsentry/sentry-go v0.22.0 + github.com/getsentry/sentry-go v0.25.0 github.com/go-chi/chi v4.1.2+incompatible - github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/gomodule/redigo v1.8.9 - github.com/gorilla/schema v1.2.0 + github.com/gorilla/schema v1.2.1 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.42.6 + github.com/nyaruka/gocommon v1.42.7 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -23,20 +23,20 @@ require ( github.com/samber/slog-multi v1.0.2 github.com/samber/slog-sentry v1.2.2 github.com/stretchr/testify v1.8.4 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/mod v0.14.0 gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/h2non/filetype.v1 v1.0.5 ) require ( - github.com/antchfx/xpath v1.2.4 // indirect + github.com/antchfx/xpath v1.2.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.1 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -46,13 +46,13 @@ require ( github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.2.2 // indirect + github.com/nyaruka/phonenumbers v1.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/samber/lo v1.38.1 // indirect + github.com/samber/lo v1.39.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 2c377bdb3..5792adea7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ -github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245PpTk= -github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= +github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= +github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/aws/aws-sdk-go v1.48.0 h1:1SeJ8agckRDQvnSCt1dGZYAwUaoD2Ixj6IaXB4LCv8Q= -github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/antchfx/xpath v1.2.5 h1:hqZ+wtQ+KIOV/S3bGZcIhpgYC26um2bZYP2KVGcR7VY= +github.com/antchfx/xpath v1.2.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= +github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,8 +17,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM= -github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -28,14 +29,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -44,8 +45,8 @@ github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs0 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= -github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM= +github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -67,16 +68,16 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.42.6 h1:3FMJ2IHRfHfNMHsdSVx3yzXZpKsHnmNsIu5xUS3Rgg4= -github.com/nyaruka/gocommon v1.42.6/go.mod h1:qdAi1VfyotMr9jO4cgy9WDZ0LDhJJigxDSVxijfWR7c= +github.com/nyaruka/gocommon v1.42.7 h1:4U7Ta1LIHVc/uv8sfqmmV5oRiFU8TcJM9a7QjxVoaeA= +github.com/nyaruka/gocommon v1.42.7/go.mod h1:DMj0TJPT2zi6eoXrBSsJTGBxSAUkpBk+UzcMyAbq5DA= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.2.2 h1:OwVjf7Y4uHoK9VJUrA8ebR0ha2yc6sEYbfrwkq0asCY= -github.com/nyaruka/phonenumbers v1.2.2/go.mod h1:wzk2qq7qwsaBKrfbkWKdgHYOOH+QFTesSpIq53ELw8M= +github.com/nyaruka/phonenumbers v1.2.3 h1:xjbKWbTk+tTKU+FsHPBhRNZY0Kszk+1+K+fpvdPDLcg= +github.com/nyaruka/phonenumbers v1.2.3/go.mod h1:Jv2/XnmnjYDo3rW3/CSkH0zZB6Gl4RsDmlUKZV0JMW8= github.com/nyaruka/redisx v0.5.0 h1:XH1pjG17lhj2DZJbrrZ2yZuPLAXrrHidXVA7cIuQq4g= github.com/nyaruka/redisx v0.5.0/go.mod h1:v3PY8t0gyf/0E7S0Cxb1RpCCxYo9GUFAIQdF/RufsVw= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -87,8 +88,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= @@ -107,10 +108,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -118,8 +119,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -128,8 +129,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= From aee22cfdd81041b092b650c409a368f89737e846 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 12 Dec 2023 11:32:37 -0500 Subject: [PATCH 159/170] Update CHANGELOG.md for v8.3.32 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85586d97c..bc3247382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v8.3.32 (2023-12-12) +------------------------- + * Update deps + v8.3.31 (2023-12-06) ------------------------- * Use language value in templating metadata instead of trying to match From 3109d0e4202f247ab70fcedcf193438af94193fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:06:41 +0000 Subject: [PATCH 160/170] Bump golang.org/x/crypto from 0.16.0 to 0.17.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 35c988d9a..84f0c2ad0 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.39.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 5792adea7..7f26cddc6 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From 778560fa8929e52685647aaf157e52692b2627d4 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 5 Jan 2024 11:21:17 -0500 Subject: [PATCH 161/170] Tweak README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb23ef1c8..c95d4b803 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ environment variables and parameters and for more details on each option. ### RapidPro -For use with RapidPro, you will want to configure these settings: +For use with RapidPro/TextIt, you will want to configure these settings: * `COURIER_DOMAIN`: The root domain which courier is exposed as (ex `textit.in`) * `COURIER_SPOOL_DIR`: A local path where courier can spool files if the database is down, should be writable. (ex: `/home/courier/spool`) From d19befe5827f8d4ecc33342ace580df4f8eec2b7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 5 Jan 2024 11:21:45 -0500 Subject: [PATCH 162/170] Update CHANGELOG.md for v9.0.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3247382..e2ea469ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.0.0 (2024-01-05) +------------------------- + * Bump golang.org/x/crypto from 0.16.0 to 0.17.0 + v8.3.32 (2023-12-12) ------------------------- * Update deps From 2ac235a538716e6bb2a72f04384294cc92e9794c Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Sun, 7 Jan 2024 10:47:23 +0200 Subject: [PATCH 163/170] Fix sending bandwidth MMS without text --- handlers/bandwidth/handler.go | 5 +++++ handlers/bandwidth/handler_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/handlers/bandwidth/handler.go b/handlers/bandwidth/handler.go index 2441270f0..98e76bf95 100644 --- a/handlers/bandwidth/handler.go +++ b/handlers/bandwidth/handler.go @@ -199,7 +199,12 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch msgParts := make([]string, 0) if msg.Text() != "" { msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) + } else { + if len(msg.Attachments()) > 0 { + msgParts = append(msgParts, "") + } } + for i, part := range msgParts { payload := &mtPayload{} payload.ApplicationID = applicationID diff --git a/handlers/bandwidth/handler_test.go b/handlers/bandwidth/handler_test.go index 5707ae0d2..76d96b682 100644 --- a/handlers/bandwidth/handler_test.go +++ b/handlers/bandwidth/handler_test.go @@ -293,6 +293,27 @@ var defaultSendTestCases = []OutgoingTestCase{ ExpectedExternalID: "55555", SendPrep: setSendURL, }, + { + Label: "Send Attachment no text", + MsgText: "", + MsgURN: "tel:+12067791234", + MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + MockResponseBody: `{"id": "55555"}`, + MockResponseStatus: 200, + ExpectedRequests: []ExpectedRequest{ + { + Headers: map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic dXNlcjE6cGFzczE=", + }, + Body: `{"applicationId":"application-id","to":["+12067791234"],"from":"2020","text":"","media":["https://foo.bar/image.jpg"]}`, + }, + }, + ExpectedMsgStatus: "W", + ExpectedExternalID: "55555", + SendPrep: setSendURL, + }, { Label: "No External ID", MsgText: "No External ID", From 57c4b1129a1d40e0361dc92ffd386adbe5887215 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 8 Jan 2024 08:46:09 -0500 Subject: [PATCH 164/170] Update CHANGELOG.md for v9.0.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ea469ee..48800d3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.0.1 (2024-01-08) +------------------------- + * Fix sending bandwidth MMS without text + v9.0.0 (2024-01-05) ------------------------- * Bump golang.org/x/crypto from 0.16.0 to 0.17.0 From 9ca5aedf988cad85b38744fa431040b540d6ac1e Mon Sep 17 00:00:00 2001 From: Robi9 Date: Thu, 5 Dec 2024 16:35:53 -0300 Subject: [PATCH 165/170] Fix bugs and test --- cmd/courier/main.go | 1 - go.mod | 11 +- go.sum | 31 +- handlers/facebookapp/facebookapp_test.go | 1607 ---------------------- handlers/meta/handlers.go | 28 +- handlers/meta/messenger/api.go | 9 + handlers/meta/whatsapp/api.go | 4 + handlers/slack/handler.go | 6 +- handlers/teams/teams.go | 414 ------ handlers/teams/teams_test.go | 348 ----- handlers/weniwebchat/weniwebchat.go | 26 +- handlers/weniwebchat/weniwebchat_test.go | 46 +- handlers/whatsapp_legacy/handler.go | 6 +- utils/misc.go | 2 +- utils/misc_test.go | 2 +- 15 files changed, 74 insertions(+), 2467 deletions(-) delete mode 100644 handlers/facebookapp/facebookapp_test.go delete mode 100644 handlers/teams/teams.go delete mode 100644 handlers/teams/teams_test.go diff --git a/cmd/courier/main.go b/cmd/courier/main.go index dfe58e505..e0720d866 100644 --- a/cmd/courier/main.go +++ b/cmd/courier/main.go @@ -61,7 +61,6 @@ import ( _ "github.com/nyaruka/courier/handlers/slack" _ "github.com/nyaruka/courier/handlers/smscentral" _ "github.com/nyaruka/courier/handlers/start" - _ "github.com/nyaruka/courier/handlers/teams" _ "github.com/nyaruka/courier/handlers/telegram" _ "github.com/nyaruka/courier/handlers/telesom" _ "github.com/nyaruka/courier/handlers/thinq" diff --git a/go.mod b/go.mod index 4e5edfc67..d0397e6cf 100644 --- a/go.mod +++ b/go.mod @@ -32,30 +32,21 @@ require ( require ( github.com/gabriel-vasile/mimetype v1.4.3 - github.com/golang-jwt/jwt/v4 v4.4.1 - github.com/lestrrat-go/jwx v1.2.25 github.com/sirupsen/logrus v1.9.0 ) require ( github.com/antchfx/xpath v1.2.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect github.com/fatih/structs v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/goccy/go-json v0.9.11 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.0 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.1 // indirect - github.com/lestrrat-go/option v1.0.0 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect @@ -73,4 +64,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/nyaruka/gocommon => github.com/Ilhasoft/gocommon v1.37.0-weni +replace github.com/nyaruka/gocommon => github.com/Ilhasoft/gocommon v1.42.7-weni diff --git a/go.sum b/go.sum index 6dd81803d..82cdc3fa9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Ilhasoft/gocommon v1.42.7-weni h1:7V5i59zAjexVkDtX20cBcIAYvYlCLEs97vEivZs1T7E= +github.com/Ilhasoft/gocommon v1.42.7-weni/go.mod h1:DMj0TJPT2zi6eoXrBSsJTGBxSAUkpBk+UzcMyAbq5DA= github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= @@ -10,9 +12,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/dghubble/oauth1 v0.7.2 h1:pwcinOZy8z6XkNxvPmUDY52M7RDPxt0Xw1zgZ6Cl5JA= github.com/dghubble/oauth1 v0.7.2/go.mod h1:9erQdIhqhOHG/7K9s/tgh9Ks/AfoyrO5mW/43Lu2+kE= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -36,13 +35,8 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -65,18 +59,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= -github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= -github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= -github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA= -github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -89,8 +71,6 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.42.7 h1:4U7Ta1LIHVc/uv8sfqmmV5oRiFU8TcJM9a7QjxVoaeA= -github.com/nyaruka/gocommon v1.42.7/go.mod h1:DMj0TJPT2zi6eoXrBSsJTGBxSAUkpBk+UzcMyAbq5DA= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -122,8 +102,6 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -133,7 +111,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= @@ -143,7 +120,6 @@ golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= @@ -152,7 +128,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -165,7 +140,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -186,7 +160,6 @@ gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+a gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y= gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go deleted file mode 100644 index e40e9bc36..000000000 --- a/handlers/facebookapp/facebookapp_test.go +++ /dev/null @@ -1,1607 +0,0 @@ -package facebookapp - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/handlers" - . "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" -) - -var testChannelsFBA = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "FBA", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), -} - -var testChannelsIG = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "IG", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), -} - -var testChannelsWAC = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "WAC", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}), -} - -var testCasesFBA = []ChannelHandleTestCase{ - { - Label: "Receive Message FBA", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/helloMsgFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Signature", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/helloMsgFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - PrepRequest: addInvalidSignature, - }, - { - Label: "No Duplicate Receive Message", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/duplicateMsgFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Attachment", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/attachmentFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"https://image-url/foo.png"}, - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Location", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/locationAttachment.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"geo:1.200000,-1.300000"}, - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Thumbs Up", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/thumbsUp.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("👍"), - ExpectedURN: "facebook:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive OptIn UserRef", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/optInUserRef.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:ref:optin_user_ref", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive OptIn", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/optIn.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "optin_ref"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Get Started", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postbackGetStarted.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral Postback", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postback.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "postback payload", "referrer_id": "postback ref", "source": "postback source", "type": "postback type"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/postbackReferral.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"title": "postback title", "payload": "get_started", "referrer_id": "postback ref", "source": "postback source", "type": "postback type", "ad_id": "ad id"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Referral", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/referral.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"referrer_id":"referral id"`, - ExpectedURN: "facebook:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.Referral, - ExpectedEventExtra: map[string]interface{}{"referrer_id": "referral id", "source": "referral source", "type": "referral type", "ad_id": "ad id"}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive DLR", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/dlr.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgStatus: courier.MsgDelivered, - ExpectedExternalID: "mid.1458668856218:ed81099e15d3f4f233", - PrepRequest: addValidSignature, - }, - { - Label: "Different Page", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/differentPageFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"data":[]`, - PrepRequest: addValidSignature, - }, - { - Label: "Echo", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/echoFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring echo`, - PrepRequest: addValidSignature, - }, - { - Label: "Not Page", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/notPage.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notpage", - PrepRequest: addValidSignature, - }, - { - Label: "No Entries", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/noEntriesFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "no entries found", - PrepRequest: addValidSignature, - }, - { - Label: "No Messaging Entries", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/noMessagingEntriesFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Unknown Messaging Entry", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/unknownMessagingEntryFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Not JSON", - URL: "/c/fba/receive", - Data: "not JSON", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse request JSON", - PrepRequest: addValidSignature, - }, - { - Label: "Invalid URN", - URL: "/c/fba/receive", - Data: string(test.ReadFile("./testdata/fba/invalidURNFBA.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid facebook id", - PrepRequest: addValidSignature, - }, -} - -var testCasesIG = []ChannelHandleTestCase{ - { - Label: "Receive Message", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/helloMsgIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Signature", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/helloMsgIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - PrepRequest: addInvalidSignature, - }, - { - Label: "No Duplicate Receive Message", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/duplicateMsgIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Attachment", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/attachmentIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"https://image-url/foo.png"}, - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Like Heart", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/like_heart.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp(""), - ExpectedURN: "instagram:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Icebreaker Get Started", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/icebreakerGetStarted.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedURN: "instagram:5678", - ExpectedDate: time.Date(2016, 4, 7, 1, 11, 27, 970000000, time.UTC), - ExpectedEvent: courier.NewConversation, - ExpectedEventExtra: map[string]interface{}{"title": "icebreaker question", "payload": "get_started"}, - PrepRequest: addValidSignature, - }, - { - Label: "Different Page", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/differentPageIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"data":[]`, - PrepRequest: addValidSignature, - }, - { - Label: "Echo", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/echoIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring echo`, - PrepRequest: addValidSignature, - }, - { - Label: "No Entries", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/noEntriesIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "no entries found", - PrepRequest: addValidSignature, - }, - { - Label: "Not Instagram", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/notInstagram.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "object expected 'page', 'instagram' or 'whatsapp_business_account', found notinstagram", - PrepRequest: addValidSignature, - }, - { - Label: "No Messaging Entries", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/noMessagingEntriesIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Unknown Messaging Entry", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unknownMessagingEntryIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - PrepRequest: addValidSignature, - }, - { - Label: "Not JSON", - URL: "/c/ig/receive", - Data: "not JSON", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse request JSON", - PrepRequest: addValidSignature, - }, - { - Label: "Invalid URN", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/invalidURNIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid instagram id", - PrepRequest: addValidSignature, - }, - { - Label: "Story Mention", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/storyMentionIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `ignoring story_mention`, - PrepRequest: addValidSignature, - }, - { - Label: "Message unsent", - URL: "/c/ig/receive", - Data: string(test.ReadFile("./testdata/ig/unsentMsgIG.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `msg deleted`, - PrepRequest: addValidSignature, - }, -} - -func addValidSignature(r *http.Request) { - body, _ := ReadBody(r, maxRequestBodyBytes) - sig, _ := fbCalculateSignature("fb_app_secret", body) - r.Header.Set(signatureHeader, fmt.Sprintf("sha256=%s", string(sig))) -} - -func addValidSignatureWAC(r *http.Request) { - body, _ := handlers.ReadBody(r, 100000) - sig, _ := fbCalculateSignature("wac_app_secret", body) - r.Header.Set(signatureHeader, fmt.Sprintf("sha1=%s", string(sig))) -} - -func addInvalidSignature(r *http.Request) { - r.Header.Set(signatureHeader, "invalidsig") -} - -// mocks the call to the Facebook graph API -func buildMockFBGraphFBA(testCases []ChannelHandleTestCase) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.URL.Query().Get("access_token") - defer r.Body.Close() - - // invalid auth token - if accessToken != "a123" { - http.Error(w, "invalid auth token", 403) - } - - // user has a name - if strings.HasSuffix(r.URL.Path, "1337") { - w.Write([]byte(`{ "first_name": "John", "last_name": "Doe"}`)) - return - } - // no name - w.Write([]byte(`{ "first_name": "", "last_name": ""}`)) - })) - graphURL = server.URL - - return server -} - -// mocks the call to the Facebook graph API -func buildMockFBGraphIG(testCases []ChannelHandleTestCase) *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.URL.Query().Get("access_token") - defer r.Body.Close() - - // invalid auth token - if accessToken != "a123" { - http.Error(w, "invalid auth token", 403) - } - - // user has a name - if strings.HasSuffix(r.URL.Path, "1337") { - w.Write([]byte(`{ "name": "John Doe"}`)) - return - } - - // no name - w.Write([]byte(`{ "name": ""}`)) - })) - graphURL = server.URL - - return server -} - -func TestDescribeURNForFBA(t *testing.T) { - fbGraph := buildMockFBGraphFBA(testCasesFBA) - defer fbGraph.Close() - - channel := testChannelsFBA[0] - handler := newHandler("FBA", "Facebook", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"facebook:1337", map[string]string{"name": "John Doe"}}, - {"facebook:4567", map[string]string{"name": ""}}, - {"facebook:ref:1337", map[string]string{}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -func TestDescribeURNForIG(t *testing.T) { - fbGraph := buildMockFBGraphIG(testCasesIG) - defer fbGraph.Close() - - channel := testChannelsIG[0] - handler := newHandler("IG", "Instagram", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"instagram:1337", map[string]string{"name": "John Doe"}}, - {"instagram:4567", map[string]string{"name": ""}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), channel, tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -func TestDescribeURNForWAC(t *testing.T) { - channel := testChannelsWAC[0] - handler := newHandler("WAC", "Cloud API WhatsApp", false) - handler.Initialize(newServer(nil)) - clog := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, handler.RedactValues(channel)) - - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{ - {"whatsapp:1337", map[string]string{}}, - {"whatsapp:4567", map[string]string{}}, - } - - for _, tc := range tcs { - metadata, _ := handler.(courier.URNDescriber).DescribeURN(context.Background(), testChannelsWAC[0], tc.urn, clog) - assert.Equal(t, metadata, tc.expectedMetadata) - } - - AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"}) -} - -var wacReceiveURL = "/c/wac/receive" - -var testCasesWAC = []ChannelHandleTestCase{ - { - Label: "Receive Message WAC", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/helloWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Duplicate Valid Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/duplicateWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Voice Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/voiceWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp(""), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Voice"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Button Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("No"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Document Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/documentWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("80skaraokesonglistartist"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Document"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Image Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/imageWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Image"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Video Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/videoWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Video"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Audio Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/audioWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Check out my new phone!"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedAttachments: []string{"https://foo.bar/attachmentURL_Audio"}, - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Location Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/locationWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"msg"`, - ExpectedMsgText: Sp(""), - ExpectedAttachments: []string{"geo:0.000000,1.000000"}, - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid JSON", - URL: wacReceiveURL, - Data: "not json", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unable to parse", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid JSON", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidFrom.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid whatsapp id", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid JSON", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidTimestamp.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid timestamp", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Message WAC invalid signature", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/helloWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "invalid request signature", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - PrepRequest: addInvalidSignature, - }, - { - Label: "Receive Message WAC with error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorMsg.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131051", "Unsupported message type")}, - NoInvalidChannelCheck: true, - PrepRequest: addValidSignature, - }, - { - Label: "Receive error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorErrors.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("0", "We were unable to authenticate the app user")}, - NoInvalidChannelCheck: true, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/validStatusWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "S", - ExpectedExternalID: "external_id", - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Status with error message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/errorStatus.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"type":"status"`, - ExpectedMsgStatus: "F", - ExpectedExternalID: "external_id", - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("131014", "Request for url https://URL.jpg failed with error: 404 (Not Found)")}, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Invalid Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/invalidStatusWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"unknown status: in_orbit"`, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Ignore Status", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/ignoreStatusWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: `"ignoring status: deleted"`, - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Interactive Button Reply Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/buttonReplyWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Yes"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, - { - Label: "Receive Valid Interactive List Reply Message", - URL: wacReceiveURL, - Data: string(test.ReadFile("./testdata/wac/listReplyWAC.json")), - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - ExpectedMsgText: Sp("Yes"), - ExpectedURN: "whatsapp:5678", - ExpectedExternalID: "external_id", - ExpectedDate: time.Date(2016, 1, 30, 1, 57, 9, 0, time.UTC), - PrepRequest: addValidSignature, - }, -} - -func TestHandler(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.Header.Get("Authorization") - defer r.Body.Close() - - // invalid auth token - if accessToken != "Bearer a123" && accessToken != "Bearer wac_admin_system_user_token" { - fmt.Printf("Access token: %s\n", accessToken) - http.Error(w, "invalid auth token", 403) - return - } - - if strings.HasSuffix(r.URL.Path, "image") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Image"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "audio") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Audio"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "voice") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Voice"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "video") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Video"}`)) - return - } - - if strings.HasSuffix(r.URL.Path, "document") { - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL_Document"}`)) - return - } - - // valid token - w.Write([]byte(`{"url": "https://foo.bar/attachmentURL"}`)) - - })) - graphURL = server.URL - - RunChannelTestCases(t, testChannelsWAC, newHandler("WAC", "Cloud API WhatsApp", false), testCasesWAC) - RunChannelTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) - RunChannelTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) -} - -func BenchmarkHandler(b *testing.B) { - fbService := buildMockFBGraphFBA(testCasesFBA) - - RunChannelBenchmarks(b, testChannelsFBA, newHandler("FBA", "Facebook", false), testCasesFBA) - fbService.Close() - - fbServiceIG := buildMockFBGraphIG(testCasesIG) - - RunChannelBenchmarks(b, testChannelsIG, newHandler("IG", "Instagram", false), testCasesIG) - fbServiceIG.Close() -} - -func TestVerify(t *testing.T) { - RunChannelTestCases(t, testChannelsFBA, newHandler("FBA", "Facebook", false), []ChannelHandleTestCase{ - { - Label: "Valid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - }, - { - Label: "Verify No Mode", - URL: "/c/fba/receive", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unknown request", - }, - { - Label: "Verify No Secret", - URL: "/c/fba/receive?hub.mode=subscribe", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - }, - { - Label: "Invalid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=blah", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - }, - { - Label: "Valid Secret", - URL: "/c/fba/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - }, - }) - - RunChannelTestCases(t, testChannelsIG, newHandler("IG", "Instagram", false), []ChannelHandleTestCase{ - { - Label: "Valid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - NoQueueErrorCheck: true, - NoInvalidChannelCheck: true, - }, - { - Label: "Verify No Mode", - URL: "/c/ig/receive", - ExpectedRespStatus: 200, - ExpectedBodyContains: "unknown request", - }, - { - Label: "Verify No Secret", - URL: "/c/ig/receive?hub.mode=subscribe", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - }, - { - Label: "Invalid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=blah", - ExpectedRespStatus: 200, - ExpectedBodyContains: "token does not match secret", - }, - { - Label: "Valid Secret", - URL: "/c/ig/receive?hub.mode=subscribe&hub.verify_token=fb_webhook_secret&hub.challenge=yarchallenge", - ExpectedRespStatus: 200, - ExpectedBodyContains: "yarchallenge", - }, - }) -} - -// 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.Msg) { - sendURL = s.URL - graphURL = s.URL -} - -var SendTestCasesFBA = []ChannelSendTestCase{ - { - 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", - ExpectedExternalID: "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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response using referal URN", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "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", - ExpectedErrors: []*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", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "facebook:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedErrors: []*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, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -var SendTestCasesIG = []ChannelSendTestCase{ - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Text only flow response", - MsgText: "Simple Message", - 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", - ExpectedExternalID: "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"}, - 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", - ExpectedExternalID: "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", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "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", - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - 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", - ExpectedExternalID: "mid.133", - SendPrep: setSendURL, - }, - { - Label: "Response doesn't contain message id", - MsgText: "ID Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 200, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response status code is non-200", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "is_error": true }`, - MockResponseStatus: 403, - ExpectedMsgStatus: "E", - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseValueMissing("message_id")}, - SendPrep: setSendURL, - }, - { - Label: "Response is invalid JSON", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `bad json`, - MockResponseStatus: 200, - ExpectedErrors: []*courier.ChannelError{courier.ErrorResponseUnparseable("JSON")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, - { - Label: "Response is channel specific error", - MsgText: "Error", - MsgURN: "instagram:12345", - MockResponseBody: `{ "error": {"message": "The image size is too large.","code": 36000 }}`, - MockResponseStatus: 400, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("36000", "The image size is too large.")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -var SendTestCasesWAC = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Simple Message","preview_url":false}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Unicode Send", - MsgText: "☺", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"☺","preview_url":false}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Audio Send", - MsgText: "audio caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"audio caption","preview_url":false}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Document Send", - MsgText: "document caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Image Send", - MsgText: "image caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Video Send", - MsgText: "video caption", - MsgURN: "whatsapp:250788123123", - MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "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" }, "variables": ["Chef", "tomorrow"]}}`), - ExpectedMsgStatus: "W", - ExpectedExternalID: "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"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - SendPrep: setSendURL, - }, - { - Label: "Template Country Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "eng-US", - MsgMetadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "variables": ["Chef", "tomorrow"]}}`), - 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","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Template Invalid Language", - MsgText: "templated message", - MsgURN: "whatsapp:250788123123", - MsgLocale: "bnt", - MsgMetadata: json.RawMessage(`{"templating": { "template": { "name": "revive_issue", "uuid": "8ca114b4-bee2-4d3b-aaf1-9aa6b48d41e8" }, "variables": ["Chef", "tomorrow"]}}`), - 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"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]}]}}`, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - 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", - ExpectedExternalID: "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", - ExpectedExternalID: "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", - ExpectedExternalID: "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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - 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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - 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, - ExpectedRequestBody: `{"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"}}]}}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Interactive Button Message Send with audio attachment", - MsgText: "Interactive Button Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3"}, - MsgAttachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - 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"}}]}}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Interactive List Message Send with attachment", - MsgText: "Interactive List Msg", - MsgURN: "whatsapp:250788123123", - MsgQuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, - MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - MockResponses: map[MockedRequest]*httpx.MockResponse{ - { - Method: "POST", - Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - { - Method: "POST", - Path: "/12345_ID/messages", - 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"}]}]}}}`, - }: httpx.NewMockResponse(201, nil, []byte(`{ "messages": [{"id": "157b5e14568e8"}] }`)), - }, - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Link Sending", - MsgText: "Link Sending https://link.com", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, - MockResponseStatus: 201, - ExpectedRequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"text","text":{"body":"Link Sending https://link.com","preview_url":true}}`, - ExpectedRequestPath: "/12345_ID/messages", - ExpectedMsgStatus: "W", - ExpectedExternalID: "157b5e14568e8", - SendPrep: setSendURL, - }, - { - Label: "Error Bad JSON", - MsgText: "Error", - MsgURN: "whatsapp:250788123123", - MockResponseBody: `bad json`, - MockResponseStatus: 403, - ExpectedErrors: []*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, - ExpectedErrors: []*courier.ChannelError{courier.ErrorExternal("130429", "(#130429) Rate limit hit")}, - ExpectedMsgStatus: "E", - SendPrep: setSendURL, - }, -} - -func TestSending(t *testing.T) { - // shorter max msg length for testing - maxMsgLengthFBA = 100 - maxMsgLengthIG = 100 - maxMsgLengthWAC = 100 - - var ChannelFBA = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "FBA", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) - var ChannelIG = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "IG", "12345", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) - var ChannelWAC = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "WAC", "12345_ID", "", map[string]interface{}{courier.ConfigAuthToken: "a123"}) - - checkRedacted := []string{"wac_admin_system_user_token", "missing_facebook_app_secret", "missing_facebook_webhook_secret", "a123"} - - RunChannelSendTestCases(t, ChannelFBA, newHandler("FBA", "Facebook", false), SendTestCasesFBA, checkRedacted, nil) - RunChannelSendTestCases(t, ChannelIG, newHandler("IG", "Instagram", false), SendTestCasesIG, checkRedacted, nil) - RunChannelSendTestCases(t, ChannelWAC, newHandler("WAC", "Cloud API WhatsApp", false), SendTestCasesWAC, checkRedacted, nil) -} - -func TestSigning(t *testing.T) { - tcs := []struct { - Body string - Signature string - }{ - { - "hello world", - "f39034b29165ec6a5104d9aef27266484ab26c8caa7bca8bcb2dd02e8be61b17", - }, - { - "hello world2", - "60905fdf409d0b4f721e99f6f25b31567a68a6b45e933d814e17a246be4c5a53", - }, - } - - for i, tc := range tcs { - sig, err := fbCalculateSignature("sesame", []byte(tc.Body)) - assert.NoError(t, err) - assert.Equal(t, tc.Signature, sig, "%d: mismatched signature", i) - } -} - -func newServer(backend courier.Backend) courier.Server { - config := courier.NewConfig() - config.WhatsappAdminSystemUserToken = "wac_admin_system_user_token" - return courier.NewServer(config, backend) -} - -func TestBuildAttachmentRequest(t *testing.T) { - mb := test.NewMockBackend() - s := newServer(mb) - wacHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("WAC"), "WhatsApp Cloud", false, nil)} - wacHandler.Initialize(s) - req, _ := wacHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsWAC[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, "Bearer wac_admin_system_user_token", req.Header.Get("Authorization")) - - fbaHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("FBA"), "Facebook", false, nil)} - fbaHandler.Initialize(s) - req, _ = fbaHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsFBA[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, http.Header{}, req.Header) - - igHandler := &handler{NewBaseHandlerWithParams(courier.ChannelType("IG"), "Instagram", false, nil)} - igHandler.Initialize(s) - req, _ = igHandler.BuildAttachmentRequest(context.Background(), mb, testChannelsFBA[0], "https://example.org/v1/media/41", nil) - assert.Equal(t, "https://example.org/v1/media/41", req.URL.String()) - assert.Equal(t, http.Header{}, req.Header) -} - -func TestGetSupportedLanguage(t *testing.T) { - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.NilLocale)) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("eng"))) - assert.Equal(t, languageInfo{"en_US", "Menu"}, getSupportedLanguage(courier.Locale("eng-US"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por"))) - assert.Equal(t, languageInfo{"pt_PT", "Menu"}, getSupportedLanguage(courier.Locale("por-PT"))) - assert.Equal(t, languageInfo{"pt_BR", "Menu"}, getSupportedLanguage(courier.Locale("por-BR"))) - assert.Equal(t, languageInfo{"fil", "Menu"}, getSupportedLanguage(courier.Locale("fil"))) - assert.Equal(t, languageInfo{"fr", "Menu"}, getSupportedLanguage(courier.Locale("fra-CA"))) - assert.Equal(t, languageInfo{"en", "Menu"}, getSupportedLanguage(courier.Locale("run"))) -} diff --git a/handlers/meta/handlers.go b/handlers/meta/handlers.go index d26e1ea28..a9d1670ae 100644 --- a/handlers/meta/handlers.go +++ b/handlers/meta/handlers.go @@ -35,9 +35,7 @@ var ( maxRequestBodyBytes int64 = 1024 * 1024 // max for the body - maxMsgLengthIG = 1000 - maxMsgLengthFBA = 2000 - maxMsgLengthWAC = 4096 + maxMsgLength = 1000 // Sticker ID substitutions stickerIDToEmoji = map[int64]string{ @@ -836,11 +834,11 @@ func (h *handler) sendFacebookInstagramMsg(ctx context.Context, msg courier.MsgO } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - _, _, err = handlers.RequestHTTP(req, clog) + _, _, err = h.RequestHTTP(req, clog) if err != nil { return status, nil } - status.SetStatus(courier.MsgWired) + status.SetStatus(courier.MsgStatusWired) } return status, nil } @@ -974,7 +972,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog msgParts := make([]string, 0) if msg.Text() != "" { - msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLengthWAC) + msgParts = handlers.SplitMsgByChannel(msg.Channel(), msg.Text(), maxMsgLength) } qrs := msg.QuickReplies() menuButton := handlers.GetText("Menu", msg.Locale()) @@ -1214,7 +1212,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog 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, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, nil } @@ -1299,7 +1297,7 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog zeroIndex = true } - err := h.requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) + respPayload, err := h.requestWAC(payload, accessToken, status, wacPhoneURL, zeroIndex, clog) if err != nil { return status, err } @@ -1311,13 +1309,14 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog if err != nil { return status, nil } - err = status.SetUpdatedURN(msg.URN(), toUpdateURN) + err = status.SetURNUpdate(msg.URN(), toUpdateURN) if err != nil { clog.Error(courier.ErrorResponseUnexpected("unable to update contact URN for a new based on wa_id")) } hasNewURN = true } } + if hasTemplate && len(msg.Attachments()) > 0 || hasCaption { break } @@ -1325,12 +1324,12 @@ func (h *handler) sendWhatsAppMsg(ctx context.Context, msg courier.MsgOut, clog return status, 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, status courier.StatusUpdate, wacPhoneURL *url.URL, zeroIndex bool, clog *courier.ChannelLog) (*whatsapp.SendResponse, error) { jsonBody := jsonx.MustMarshal(payload) req, err := http.NewRequest(http.MethodPost, wacPhoneURL.String(), bytes.NewReader(jsonBody)) if err != nil { - return err + return nil, err } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) @@ -1342,21 +1341,22 @@ func (h *handler) requestWAC(payload whatsapp.SendRequest, accessToken string, s err = json.Unmarshal(respBody, respPayload) if err != nil { clog.Error(courier.ErrorResponseUnparseable("JSON")) - return nil + return respPayload, nil } if respPayload.Error.Code != 0 { clog.Error(courier.ErrorExternal(strconv.Itoa(respPayload.Error.Code), respPayload.Error.Message)) - return nil + return respPayload, nil } externalID := respPayload.Messages[0].ID if zeroIndex && externalID != "" { status.SetExternalID(externalID) } + // this was wired successfully status.SetStatus(courier.MsgStatusWired) - return nil + return respPayload, nil } // DescribeURN looks up URN metadata for new contacts diff --git a/handlers/meta/messenger/api.go b/handlers/meta/messenger/api.go index 65b13e505..603e74524 100644 --- a/handlers/meta/messenger/api.go +++ b/handlers/meta/messenger/api.go @@ -58,6 +58,15 @@ type SendResponse struct { } `json:"error"` } +type FeedbackQuestion struct { + Type string `json:"type"` + Payload string `json:"payload"` + FollowUp *struct { + Type string `json:"type"` + Payload string `json:"payload"` + } `json:"follow_up"` +} + // see https://developers.facebook.com/docs/messenger-platform/webhooks/#event-notifications type Messaging struct { Sender *struct { diff --git a/handlers/meta/whatsapp/api.go b/handlers/meta/whatsapp/api.go index 6f9998783..57468d3cf 100644 --- a/handlers/meta/whatsapp/api.go +++ b/handlers/meta/whatsapp/api.go @@ -237,4 +237,8 @@ type SendResponse struct { Message string `json:"message"` Code int `json:"code"` } `json:"error"` + Contacts []*struct { + Input string `json:"input,omitempty"` + WaID string `json:"wa_id,omitempty"` + } `json:"contacts,omitempty"` } diff --git a/handlers/slack/handler.go b/handlers/slack/handler.go index 5bf357f28..db4dcda1b 100644 --- a/handlers/slack/handler.go +++ b/handlers/slack/handler.go @@ -170,7 +170,7 @@ func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.Ch } if len(msg.QuickReplies()) != 0 { - _, err := sendQuickReplies(msg, botToken, clog) + _, err := h.sendQuickReplies(msg, botToken, clog) if err != nil { clog.RawError(err) return status, nil @@ -297,7 +297,7 @@ func (h *handler) sendFilePart(msg courier.MsgOut, token string, fileParams *Fil return nil } -func sendQuickReplies(msg courier.Msg, botToken string, clog *courier.ChannelLog) (*courier.ChannelLog, error) { +func (h *handler) sendQuickReplies(msg courier.MsgOut, botToken string, clog *courier.ChannelLog) (*courier.ChannelLog, error) { sendURL := apiURL + "/chat.postMessage" payload := &mtPayload{ @@ -345,7 +345,7 @@ func sendQuickReplies(msg courier.Msg, botToken string, clog *courier.ChannelLog req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", botToken)) - resp, respBody, err := handlers.RequestHTTP(req, clog) + resp, respBody, err := h.RequestHTTP(req, clog) if err != nil || resp.StatusCode/100 != 2 { return clog, errors.New("error uploading file to slack") } diff --git a/handlers/teams/teams.go b/handlers/teams/teams.go deleted file mode 100644 index ae18d2d62..000000000 --- a/handlers/teams/teams.go +++ /dev/null @@ -1,414 +0,0 @@ -package teams - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - "time" - - "github.com/buger/jsonparser" - "github.com/golang-jwt/jwt/v4" - "github.com/lestrrat-go/jwx/jwk" - "github.com/nyaruka/courier" - "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/courier/utils" - "github.com/nyaruka/gocommon/urns" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var ( - jv = JwtTokenValidator{} - AllowedSigningAlgorithms = []string{"RS256", "RS384", "RS512"} - ToBotFromChannelTokenIssuer = "https://api.botframework.com" - jwks_uri = "https://login.botframework.com/v1/.well-known/keys" -) - -const fetchTimeout = 20 - -func init() { - courier.RegisterHandler(newHandler()) -} - -type handler struct { - handlers.BaseHandler -} - -func newHandler() courier.ChannelHandler { - return &handler{handlers.NewBaseHandler(courier.ChannelType("TM"), "Teams")} -} - -func (h *handler) Initialize(s courier.Server) error { - h.SetServer(s) - s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMsgReceive, h.receiveEvent) - return nil -} - -type metadata struct { - JwksURI string `json:"jwks_uri"` -} - -type Keys struct { - Keys struct { - Kty string `json:"kty"` - Kid string `json:"kid"` - Endorsements []string `json:"endorsements"` - } -} - -// AuthCache is a general purpose cache -type AuthCache struct { - Keys interface{} - Expiry time.Time -} - -// JwtTokenValidator is the default implementation of TokenValidator. -type JwtTokenValidator struct { - AuthCache -} - -// IsExpired checks if the Keys have expired. -// Compares Expiry time with current time. -func (cache *AuthCache) IsExpired() bool { - - if diff := time.Now().Sub(cache.Expiry).Hours(); diff > 0 { - return true - } - return false -} - -func validateToken(channel courier.Channel, w http.ResponseWriter, r *http.Request) error { - tokenH := r.Header.Get("Authorization") - tokenHeader := strings.Replace(tokenH, "Bearer ", "", 1) - getKey := func(token *jwt.Token) (interface{}, error) { - // Get new JWKs if the cache is expired - if jv.AuthCache.IsExpired() { - - ctx, cancel := context.WithTimeout(context.Background(), fetchTimeout*time.Second) - defer cancel() - set, err := jwk.Fetch(ctx, jwks_uri) - if err != nil { - return nil, err - } - // Update the cache - // The expiry time is set to be of 5 days - jv.AuthCache = AuthCache{ - Keys: set, - Expiry: time.Now().Add(time.Hour * 24 * 5), - } - } - - keyID, ok := token.Header["kid"].(string) - if !ok { - return nil, fmt.Errorf("Expecting JWT header to have string kid") - } - - // Return cached JWKs - key, ok := jv.AuthCache.Keys.(jwk.Set).LookupKeyID(keyID) - if ok { - var rawKey interface{} - err := key.Raw(&rawKey) - if err != nil { - return nil, err - } - return rawKey, nil - } - return nil, fmt.Errorf("Could not find public key") - } - - token, _ := jwt.Parse(tokenHeader, getKey) - - // Check allowed signing algorithms - alg := token.Header["alg"] - isAllowed := func() bool { - for _, allowed := range AllowedSigningAlgorithms { - if allowed == alg { - return true - } - } - return false - }() - if !isAllowed { - return fmt.Errorf("Unauthorized. Invalid signing algorithm") - } - - issuer := token.Claims.(jwt.MapClaims)["iss"].(string) - - if issuer != ToBotFromChannelTokenIssuer { - return fmt.Errorf("Unauthorized, invalid token issuer") - } - - audience := token.Claims.(jwt.MapClaims)["aud"].(string) - appID := channel.StringConfigForKey("appID", "") - - if audience != appID { - return fmt.Errorf("Unauthorized: invalid AppId passed on token") - } - - return nil -} - -func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, clog *courier.ChannelLog) ([]courier.Event, error) { - payload := &Activity{} - err := handlers.DecodeAndValidateJSON(payload, r) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - err = validateToken(channel, w, r) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - path := strings.Split(payload.ServiceURL, "//") - serviceURL := path[1] - - var urn urns.URN - - // the list of events we deal with - events := make([]courier.Event, 0, 2) - - // the list of data we will return in our response - data := make([]interface{}, 0, 2) - - date, err := time.Parse(time.RFC3339, payload.Timestamp) - if err != nil { - return nil, err - } - - if payload.Type == "message" { - sender := strings.Split(payload.Conversation.ID, "a:") - - urn, err = urns.NewTeamsURN(sender[1] + ":" + path[1]) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - text := payload.Text - attachmentURLs := make([]string, 0, 2) - - for _, att := range payload.Attachments { - if att.ContentType != "" && att.ContentURL != "" { - attachmentURLs = append(attachmentURLs, att.ContentURL) - } - } - - event := h.Backend().NewIncomingMsg(channel, urn, text, payload.ID, clog).WithReceivedOn(date) - - // add any attachment URL found - for _, attURL := range attachmentURLs { - event.WithAttachment(attURL) - } - - err := h.Backend().WriteMsg(ctx, event, clog) - if err != nil { - return nil, err - } - - events = append(events, event) - data = append(data, courier.NewMsgReceiveData(event)) - } - - if payload.Type == "conversationUpdate" { - userID := payload.MembersAdded[0].ID - - if userID == "" { - return nil, nil - } - - bot := ChannelAccount{} - - bot.ID = channel.StringConfigForKey("botID", "") - bot.Role = "bot" - - members := []ChannelAccount{} - - members = append(members, ChannelAccount{ID: userID, Role: payload.MembersAdded[0].Role}) - - ConversationJson := &mtPayload{ - Bot: bot, - Members: members, - IsGroup: false, - } - jsonBody, err := json.Marshal(ConversationJson) - if err != nil { - return nil, err - } - token := channel.StringConfigForKey(courier.ConfigAuthToken, "") - req, err := http.NewRequest(http.MethodPost, payload.ServiceURL+"/v3/conversations", bytes.NewReader(jsonBody)) - - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - - resp, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil || resp.StatusCode/100 != 2 { - return nil, errors.New("unable to look up contact data") - } - - var body ConversationAccount - - err = json.Unmarshal(respBody, &body) - if err != nil { - return nil, err - } - conversationID := strings.Split(body.ID, "a:") - urn, err = urns.NewTeamsURN(conversationID[1] + ":" + serviceURL) - if err != nil { - return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err) - } - - event := h.Backend().NewChannelEvent(channel, courier.NewConversation, urn, clog).WithOccurredOn(date) - events = append(events, event) - data = append(data, courier.NewEventReceiveData(event)) - } - // Ignore activity of type messageReaction - if payload.Type == "messageReaction" { - data = append(data, courier.NewInfoData("ignoring messageReaction")) - } - - return events, courier.WriteDataResponse(w, http.StatusOK, "Events Handled", data) -} - -type mtPayload struct { - Activity Activity `json:"activity"` - TopicName string `json:"topicname,omitempty"` - Bot ChannelAccount `json:"bot,omitempty"` - Members []ChannelAccount `json:"members,omitempty"` - IsGroup bool `json:"isGroup,omitempty"` - TenantId string `json:"tenantId,omitempty"` - ChannelData ChannelData `json:"channelData,omitempty"` -} - -type ChannelData struct { - AadObjectId string `json:"aadObjectId"` - Tenant struct { - ID string `json:"id"` - } `json:"tenant"` -} - -type ChannelAccount struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Role string `json:"role"` - AadObjectId string `json:"aadObjectId,omitempty"` -} - -type ConversationAccount struct { - ID string `json:"id"` - ConversationType string `json:"conversationType"` - TenantID string `json:"tenantId"` - Role string `json:"role"` - Name string `json:"name"` - IsGroup bool `json:"isGroup"` - AadObjectId string `json:"aadObjectId"` -} - -type mtAttachment struct { - ContentType string `json:"contentType"` - ContentURL string `json:"contentUrl"` - Name string `json:"name,omitempty"` -} - -type Activity struct { - Action string `json:"action,omitempty"` - Attachments []mtAttachment `json:"attachments,omitempty"` - ChannelID string `json:"channelId,omitempty"` - Conversation ConversationAccount `json:"conversation,omitempty"` - ID string `json:"id,omitempty"` - MembersAdded []ChannelAccount `json:"membersAdded,omitempty"` - Name string `json:"name,omitempty"` - Recipient ChannelAccount `json:"recipient,omitempty"` - ServiceURL string `json:"serviceUrl,omitempty"` - Text string `json:"text"` - Type string `json:"type"` - Timestamp string `json:"timestamp,omitempty"` -} - -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - - token := msg.Channel().StringConfigForKey(courier.ConfigAuthToken, "") - if token == "" { - return nil, fmt.Errorf("missing token for TM channel") - } - - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored, clog) - - payload := Activity{} - - path := strings.Split(msg.URN().Path(), ":") - conversationID := path[1] - - msgURL := msg.URN().TeamsServiceURL() + "v3/conversations/a:" + conversationID + "/activities" - - for _, attachment := range msg.Attachments() { - attType, attURL := handlers.SplitAttachment(attachment) - filename, err := utils.BasePathForURL(attURL) - if err != nil { - logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("Error while parsing the media URL") - } - payload.Attachments = append(payload.Attachments, mtAttachment{attType, attURL, filename}) - } - - if msg.Text() != "" { - payload.Type = "message" - payload.Text = msg.Text() - } - - jsonBody, err := json.Marshal(payload) - if err != nil { - return status, err - } - - req, err := http.NewRequest(http.MethodPost, msgURL, bytes.NewReader(jsonBody)) - - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - - _, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil { - return status, err - } - status.SetStatus(courier.MsgWired) - externalID, err := jsonparser.GetString(respBody, "id") - if err != nil { - logrus.WithError(errors.Errorf("unable to get message_id from body")) - return status, nil - } - status.SetExternalID(externalID) - return status, nil -} - -func (h *handler) DescribeURN(ctx context.Context, channel courier.Channel, urn urns.URN, clog *courier.ChannelLog) (map[string]string, error) { - - accessToken := channel.StringConfigForKey(courier.ConfigAuthToken, "") - if accessToken == "" { - return nil, fmt.Errorf("missing access token") - } - - // build a request to lookup the stats for this contact - pathSplit := strings.Split(urn.Path(), ":") - conversationID := pathSplit[1] - url := urn.TeamsServiceURL() + "v3/conversations/a:" + conversationID + "/members" - - req, _ := http.NewRequest(http.MethodGet, url, nil) - req.Header.Set("Authorization", "Bearer "+accessToken) - resp, respBody, err := handlers.RequestHTTP(req, clog) - if err != nil { - return nil, fmt.Errorf("unable to look up contact data:%v\n%v", err, resp) - } - - // read our first and last name - givenName, _ := jsonparser.GetString(respBody, "[0]", "givenName") - surname, _ := jsonparser.GetString(respBody, "[0]", "surname") - - return map[string]string{"name": utils.JoinNonEmpty(" ", givenName, surname)}, nil -} diff --git a/handlers/teams/teams_test.go b/handlers/teams/teams_test.go deleted file mode 100644 index 20b819892..000000000 --- a/handlers/teams/teams_test.go +++ /dev/null @@ -1,348 +0,0 @@ -//go:build ignore - -package teams - -import ( - "context" - "io" - "log" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/buger/jsonparser" - "github.com/nyaruka/courier" - . "github.com/nyaruka/courier/handlers" - "github.com/nyaruka/courier/test" - "github.com/nyaruka/gocommon/urns" - "gopkg.in/go-playground/assert.v1" -) - -var access_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYzEyMyJ9.eyJpc3MiOiJodHRwczovL2FwaS5ib3RmcmFtZXdvcmsuY29tIiwic2VydmljZXVybCI6Imh0dHBzOi8vc21iYS50cmFmZmljbWFuYWdlci5uZXQvYnIvIiwiYXVkIjoiMTU5NiJ9.hqKdNdlB0NX6jtwkN96jI-kIiWTWPDIA1K7oo56tVsRBmMycyNNHrsGbKrEw7dccLjATmimpk4x0J_umaJZ5mcK5S5F7b4hkGHFIRWc4vaMjxCl6VSJ6E6DTRnQwfrfTF0AerHSO1iABI2YAlbdMV3ahxGzzNkaqnIX496G2IKwiYziOumo4M0gfOt-MqNkOJKvnSRfB7pikSATaSQiaFmrA5A8bH0AbaM9znPIRxHyrKqlFlrpWkPSiUPOS3aHQeD8kVGk7RNEWtOk26sXfUIjHp8ZYExIClBEmc6QPAf2-FAuwsw-S8YDLwsiycJ0gEO8MYPZWn8gXR_sVIwLMMg" - -var testChannels = []courier.Channel{ - test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c568c", "TM", "2022", "US", map[string]interface{}{"auth_token": access_token, "tenantID": "cba321", "botID": "0123", "appID": "1596"}), -} - -var helloMsg = `{ - "channelId": "msteams", - "conversation": { - "converstaionType": "personal", - "id": "a:2811", - "tenantId": "cba321" - }, - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "text":"Hello World", - "type":"message" -}` - -var attachment = `{ - "channelId": "msteams", - "conversation": { - "converstaionType": "personal", - "id": "a:2811", - "tenantId": "cba321" - }, - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "text":"Hello World", - "type":"message", - "attachments":[ - { - "contentType": "image", - "contentUrl": "https://image-url/foo.png", - "name": "foo.png" - } - ] -}` - -var attachmentVideo = `{ - "channelId": "msteams", - "conversation": { - "converstaionType": "personal", - "id": "a:2811", - "tenantId": "cba321" - }, - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "text":"Hello World", - "type":"message", - "attachments":[ - { - "contentType": "video/mp4", - "contentUrl": "https://video-url/foo.mp4", - "name": "foo.png" - } - ] -}` - -var attachmentDocument = `{ - "channelId": "msteams", - "conversation": { - "converstaionType": "personal", - "id": "a:2811", - "tenantId": "cba321" - }, - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "text":"Hello World", - "type":"message", - "attachments":[ - { - "contentType": "application/pdf", - "contentUrl": "https://document-url/foo.pdf", - "name": "foo.png" - } - ] -}` - -var conversationUpdate = `{ - "channelId": "msteams", - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "type":"conversationUpdate", - "membersAdded": [{ - "id":"4569", - "name": "Joe", - "role": "user" - }] -}` - -var messageReaction = `{ - "channelId": "msteams", - "id": "56834", - "timestamp": "2022-06-06T16:51:00.0000000Z", - "serviceUrl": "https://smba.trafficmanager.net/br/", - "type":"messageReaction" -}` - -var testCases = []ChannelHandleTestCase{ - { - Label: "Receive Message", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: helloMsg, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedURN: "teams:2811:smba.trafficmanager.net/br/", - ExpectedExternalID: "56834", - ExpectedDate: time.Date(2022, 6, 6, 16, 51, 00, 0000000, time.UTC), - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, - { - Label: "Receive Attachment Image", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: attachment, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedAttachments: []string{"https://image-url/foo.png"}, - ExpectedURN: "teams:2811:smba.trafficmanager.net/br/", - ExpectedExternalID: "56834", - ExpectedDate: time.Date(2022, 6, 6, 16, 51, 00, 0000000, time.UTC), - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, - { - Label: "Receive Attachment Video", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: attachmentVideo, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedAttachments: []string{"https://video-url/foo.mp4"}, - ExpectedURN: "teams:2811:smba.trafficmanager.net/br/", - ExpectedExternalID: "56834", - ExpectedDate: time.Date(2022, 6, 6, 16, 51, 00, 0000000, time.UTC), - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, - { - Label: "Receive Attachment Document", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: attachmentDocument, - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - ExpectedMsgText: Sp("Hello World"), - ExpectedAttachments: []string{"https://document-url/foo.pdf"}, - ExpectedURN: "teams:2811:smba.trafficmanager.net/br/", - ExpectedExternalID: "56834", - ExpectedDate: time.Date(2022, 6, 6, 16, 51, 00, 0000000, time.UTC), - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, - { - Label: "Receive Message Reaction", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: messageReaction, - ExpectedRespStatus: 200, - ExpectedURN: "", - ExpectedBodyContains: "ignoring messageReaction", - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, - { - Label: "Receive Conversation Update", - URL: "/c/tm/8eb23e93-5ecb-45ba-b726-3b064e0c568c/receive", - Data: "", - ExpectedRespStatus: 200, - ExpectedBodyContains: "Handled", - Headers: map[string]string{"Authorization": "Bearer " + access_token}, - NoQueueErrorCheck: true, - }, -} - -func TestHandler(t *testing.T) { - tmService := buildMockTeams() - newTestCases := newConversationUpdateTC(tmService.URL, testCases) - jwks_url := buildMockJwksURL() - RunChannelTestCases(t, testChannels, newHandler(), newTestCases) - jwks_url.Close() - tmService.Close() - -} - -func buildMockJwksURL() *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") - w.Write([]byte(`{"keys":[{"kty":"RSA","use":"sig","kid":"abc123","x5t":"abc123","n":"abcd","e":"AQAB","endorsements":["msteams"]}]}`)) - })) - - jwks_uri = server.URL - - return server -} - -func buildMockTeams() *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accessToken := r.Header.Get("Authorization") - tokenH := strings.Replace(accessToken, "Bearer ", "", 1) - // payload := r.GetBody - defer r.Body.Close() - - // invalid auth token - if tokenH != access_token { - http.Error(w, "invalid auth token", 400) - } - - if r.URL.Path == "/v3/conversations" { - w.Header().Add("Content-Type", "application/json") - w.Write([]byte(`{"id":"a:2811"}`)) - } - - if r.URL.Path == "/v3/conversations/a:2022/activities" { - byteBody, err := io.ReadAll(r.Body) - if err != nil { - log.Fatal(err) - } - text, err := jsonparser.GetString(byteBody, "text") - if err != nil { - log.Fatal(err) - } - if text == "Error" { - w.Header().Add("Content-Type", "application/json") - w.Write([]byte(`{"is_error": true}`)) - } - w.Header().Add("Content-Type", "application/json") - w.Write([]byte(`{"id":"1234567890"}`)) - } - - if r.URL.Path == "/v3/conversations/a:2022/members" { - w.Write([]byte(`[{"givenName": "John","surname": "Doe"}]`)) - } - })) - - return server -} - -func newConversationUpdateTC(newUrl string, testCase []ChannelHandleTestCase) []ChannelHandleTestCase { - casesWithMockedUrls := make([]ChannelHandleTestCase, len(testCases)) - for i, tc := range testCases { - mockedCase := tc - if mockedCase.Label == "Receive Conversation Update" { - mockedCase.Data = strings.Replace(conversationUpdate, "https://smba.trafficmanager.net/br/", newUrl, 1) - } - casesWithMockedUrls[i] = mockedCase - } - return casesWithMockedUrls -} - -var defaultSendTestCases = []ChannelSendTestCase{ - { - Label: "Plain Send", - MsgText: "Simple Message", - MsgURN: "teams:2022:https://smba.trafficmanager.net/br/", - ExpectedMsgStatus: "W", ExpectedExternalID: "1234567890", - MockResponseBody: `{id:"1234567890"}`, MockResponseStatus: 200, - }, - {Label: "Send Photo", - MsgURN: "teams:2022:https://smba.trafficmanager.net/br/", MsgAttachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - ExpectedMsgStatus: "W", ExpectedExternalID: "1234567890", - MockResponseBody: `{"id": "1234567890"}`, MockResponseStatus: 200, - }, - {Label: "Send Video", - MsgURN: "teams:2022:https://smba.trafficmanager.net/br/", MsgAttachments: []string{"video/mp4:https://foo.bar/video.mp4"}, - ExpectedMsgStatus: "W", ExpectedExternalID: "1234567890", - MockResponseBody: `{"id": "1234567890"}`, MockResponseStatus: 200, - }, - {Label: "Send Document", - MsgURN: "teams:2022:https://smba.trafficmanager.net/br/", MsgAttachments: []string{"application/pdf:https://foo.bar/document.pdf"}, - ExpectedMsgStatus: "W", ExpectedExternalID: "1234567890", - MockResponseBody: `{"id": "1234567890"}`, MockResponseStatus: 200, - }, - {Label: "ID Error", - MsgText: "Error", MsgURN: "teams:2022:smba.trafficmanager.net/br/", - ExpectedMsgStatus: "E", - MockResponseBody: `{"is_error": true}`, MockResponseStatus: 200, - }, -} - -func newSendTestCases(testSendCases []ChannelSendTestCase, url string) []ChannelSendTestCase { - var newtestSendCases []ChannelSendTestCase - for _, tc := range testSendCases { - spTC := strings.Split(tc.MsgURN, ":") - newURN := spTC[0] + ":" + spTC[1] + ":" + url + "/" - tc.MsgURN = newURN - newtestSendCases = append(newtestSendCases, tc) - } - return newtestSendCases -} - -func TestSending(t *testing.T) { - var defaultChannel = test.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "TM", "2022", "US", - map[string]interface{}{courier.ConfigAuthToken: access_token, "tenantID": "cba321", "botID": "0123", "appID": "1596"}) - - serviceTM := buildMockTeams() - url := strings.Split(serviceTM.URL, "http://") - newSendTestCases := newSendTestCases(defaultSendTestCases, url[1]) - RunChannelSendTestCases(t, defaultChannel, newHandler(), newSendTestCases, nil, nil) - serviceTM.Close() -} - -func TestDescribe(t *testing.T) { - server := buildMockTeams() - url := strings.Split(server.URL, "http://") - - channel := testChannels[0] - handler := newHandler().(courier.URNDescriber) - logger := courier.NewChannelLog(courier.ChannelLogTypeUnknown, channel, nil) - tcs := []struct { - urn urns.URN - expectedMetadata map[string]string - }{{urns.URN("teams:2022:" + string(url[1]) + "/"), map[string]string{"name": "John Doe"}}} - - for _, tc := range tcs { - metadata, _ := handler.DescribeURN(context.Background(), testChannels[0], tc.urn, logger) - assert.Equal(t, metadata, tc.expectedMetadata) - } - server.Close() -} diff --git a/handlers/weniwebchat/weniwebchat.go b/handlers/weniwebchat/weniwebchat.go index ba21bea84..95b18f47c 100644 --- a/handlers/weniwebchat/weniwebchat.go +++ b/handlers/weniwebchat/weniwebchat.go @@ -97,7 +97,7 @@ func (h *handler) receiveEvent(ctx context.Context, channel courier.Channel, w h msg.WithAttachment(mediaURL) } - return handlers.WriteMsgsAndResponse(ctx, h, []courier.Msg{msg}, w, r, clog) + return handlers.WriteMsgsAndResponse(ctx, h, []courier.MsgIn{msg}, w, r, clog) } var timestamp = "" @@ -120,12 +120,12 @@ type moMessage struct { QuickReplies []string `json:"quick_replies,omitempty"` } -func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.ChannelLog) (courier.MsgStatus, error) { - status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgSent, clog) +func (h *handler) Send(ctx context.Context, msg courier.MsgOut, clog *courier.ChannelLog) (courier.StatusUpdate, error) { + status := h.Backend().NewStatusUpdate(msg.Channel(), msg.ID(), courier.MsgStatusSent, clog) baseURL := msg.Channel().StringConfigForKey(courier.ConfigBaseURL, "") if baseURL == "" { - return nil, errors.New("blank base_url") + return status, errors.New("blank base_url") } sendURL := fmt.Sprintf("%s/send", baseURL) @@ -161,7 +161,7 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann } } else { logrus.WithField("channel_uuid", msg.Channel().UUID()).Error("unknown attachment mime type: ", mimeType) - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) break attachmentsLoop } @@ -180,18 +180,18 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann body, err := json.Marshal(&payload) if err != nil { logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("Error sending message") - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) break attachmentsLoop } req, _ := http.NewRequest(http.MethodPost, sendURL, bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") - _, _, err = handlers.RequestHTTP(req, clog) + _, _, err = h.RequestHTTP(req, clog) if err != nil { logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("Message Send Error") - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) } if err != nil { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) break attachmentsLoop } } @@ -206,14 +206,14 @@ func (h *handler) Send(ctx context.Context, msg courier.Msg, clog *courier.Chann body, err := json.Marshal(&payload) if err != nil { logrus.WithField("channel_uuid", msg.Channel().UUID()).WithError(err).Error("Message Send Error") - status.SetStatus(courier.MsgFailed) - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) + status.SetStatus(courier.MsgStatusFailed) } else { req, _ := http.NewRequest(http.MethodPost, sendURL, bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") - _, _, err := handlers.RequestHTTP(req, clog) + _, _, err := h.RequestHTTP(req, clog) if err != nil { - status.SetStatus(courier.MsgFailed) + status.SetStatus(courier.MsgStatusFailed) } } diff --git a/handlers/weniwebchat/weniwebchat_test.go b/handlers/weniwebchat/weniwebchat_test.go index 037a23c8a..301d2cb82 100644 --- a/handlers/weniwebchat/weniwebchat_test.go +++ b/handlers/weniwebchat/weniwebchat_test.go @@ -74,7 +74,7 @@ const ( ` ) -var testCases = []ChannelHandleTestCase{ +var testCases = []IncomingTestCase{ { Label: "Receive Valid Text Msg", URL: receiveURL, @@ -135,8 +135,8 @@ var testCases = []ChannelHandleTestCase{ }, } -func TestHandler(t *testing.T) { - RunChannelTestCases(t, testChannels, newHandler(), testCases) +func TestIncoming(t *testing.T) { + RunIncomingTestCases(t, testChannels, newHandler(), testCases) } func BenchmarkHandler(b *testing.B) { @@ -145,13 +145,13 @@ func BenchmarkHandler(b *testing.B) { // SendMsg test -func prepareSendMsg(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { +func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(courier.ConfigBaseURL, s.URL) timestamp = "1616700878" } -func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTestCase) []ChannelSendTestCase { - casesWithMockedUrls := make([]ChannelSendTestCase, len(testCases)) +func mockAttachmentURLs(mediaServer *httptest.Server, testCases []OutgoingTestCase) []OutgoingTestCase { + casesWithMockedUrls := make([]OutgoingTestCase, len(testCases)) for i, testCase := range testCases { mockedCase := testCase @@ -164,38 +164,38 @@ func mockAttachmentURLs(mediaServer *httptest.Server, testCases []ChannelSendTes return casesWithMockedUrls } -var sendTestCases = []ChannelSendTestCase{ +var sendTestCases = []OutgoingTestCase{ { Label: "Plain Send", MsgText: "Simple Message", MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, ExpectedRequestPath: "/send", ExpectedHeaders: map[string]string{"Content-type": "application/json"}, ExpectedRequestBody: `{"type":"message","to":"371298371241","from":"250788383383","message":{"type":"text","timestamp":"1616700878","text":"Simple Message"}}`, MockResponseStatus: 200, - SendPrep: prepareSendMsg, + SendPrep: setSendURL, }, { Label: "Unicode Send", MsgText: "☺", MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, ExpectedRequestPath: "/send", ExpectedHeaders: map[string]string{"Content-type": "application/json"}, ExpectedRequestBody: `{"type":"message","to":"371298371241","from":"250788383383","message":{"type":"text","timestamp":"1616700878","text":"☺"}}`, MockResponseStatus: 200, - SendPrep: prepareSendMsg, + SendPrep: setSendURL, }, { Label: "invalid Text Send", MsgText: "Error", MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, ExpectedRequestPath: "/send", ExpectedHeaders: map[string]string{"Content-type": "application/json"}, ExpectedRequestBody: `{"type":"message","to":"371298371241","from":"250788383383","message":{"type":"text","timestamp":"1616700878","text":"Error"}}`, - SendPrep: prepareSendMsg, + SendPrep: setSendURL, }, { Label: "Medias Send", @@ -207,41 +207,41 @@ var sendTestCases = []ChannelSendTestCase{ "video/mp4:https://foo.bar/video.mp4", }, MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, MockResponseStatus: 200, - SendPrep: prepareSendMsg, + SendPrep: setSendURL, }, { Label: "Invalid Media Type Send", MsgText: "Medias", MsgAttachments: []string{"foo/bar:https://foo.bar/foo.bar"}, MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgFailed, + ExpectedMsgStatus: courier.MsgStatusFailed, MockResponseStatus: 400, - SendPrep: prepareSendMsg, + SendPrep: setSendURL, }, { Label: "Invalid Media Send", MsgText: "Medias", MsgAttachments: []string{"image/png:https://foo.bar/image.png"}, MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgFailed, - SendPrep: prepareSendMsg, + ExpectedMsgStatus: courier.MsgStatusFailed, + SendPrep: setSendURL, }, { Label: "No Timestamp Prepare", MsgText: "No prepare", MsgURN: "ext:371298371241", - ExpectedMsgStatus: courier.MsgSent, + ExpectedMsgStatus: courier.MsgStatusSent, MockResponseStatus: 200, - SendPrep: func(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { + SendPrep: func(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.MsgOut) { c.(*test.MockChannel).SetConfig(courier.ConfigBaseURL, s.URL) timestamp = "" }, }, } -func TestSending(t *testing.T) { +func TestOutgoing(t *testing.T) { mediaServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() res.WriteHeader(200) @@ -251,5 +251,5 @@ func TestSending(t *testing.T) { mockedSendTestCases := mockAttachmentURLs(mediaServer, sendTestCases) mediaServer.Close() - RunChannelSendTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, nil, nil) + RunOutgoingTestCases(t, testChannels[0], newHandler(), mockedSendTestCases, nil, nil) } diff --git a/handlers/whatsapp_legacy/handler.go b/handlers/whatsapp_legacy/handler.go index 7de7eeacc..f70089b6d 100644 --- a/handlers/whatsapp_legacy/handler.go +++ b/handlers/whatsapp_legacy/handler.go @@ -183,12 +183,12 @@ type eventsPayload struct { func checkBlockedContact(payload *eventsPayload, ctx context.Context, channel courier.Channel, h *handler, clog *courier.ChannelLog) error { if len(payload.Contacts) > 0 { if contactURN, err := urns.NewWhatsAppURN(payload.Contacts[0].WaID); err == nil { - if contact, err := h.Backend().GetContact(ctx, channel, contactURN, channel.StringConfigForKey(courier.ConfigAuthToken, ""), payload.Contacts[0].Profile.Name, clog); err == nil { + if contact, err := h.Backend().GetContact(ctx, channel, contactURN, nil, payload.Contacts[0].Profile.Name, clog); err == nil { c, err := json.Marshal(contact) if err != nil { return err } - var dbc rapidpro.DBContact + var dbc rapidpro.Contact if err = json.Unmarshal(c, &dbc); err != nil { return err } @@ -893,7 +893,7 @@ func buildPayloads(msg courier.MsgOut, h *handler, clog *courier.ChannelLog) ([] payload.Interactive.Type = "list" payload.Interactive.Body.Text = part if msg.Locale() != "" { - payload.Interactive.Action.Button = langCode + payload.Interactive.Action.Button = "Menu" } else { payload.Interactive.Action.Button = "Menu" } diff --git a/utils/misc.go b/utils/misc.go index 8312921f8..a3d650951 100644 --- a/utils/misc.go +++ b/utils/misc.go @@ -155,4 +155,4 @@ func MapUpdate[K comparable, V comparable, M ~map[K]V](m1 M, m2 M) { m1[k] = v } } -} +} \ No newline at end of file diff --git a/utils/misc_test.go b/utils/misc_test.go index 996abc332..987d8b25d 100644 --- a/utils/misc_test.go +++ b/utils/misc_test.go @@ -144,4 +144,4 @@ func TestMapUpdate(t *testing.T) { utils.MapUpdate(tc.m1, tc.m2) assert.Equal(t, tc.updated, tc.m1) } -} +} \ No newline at end of file From c89aa081bfd3705e3522839673a7036fa5c209f5 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 6 Dec 2024 15:16:19 -0300 Subject: [PATCH 166/170] Adjust build workflow --- .github/workflows/build-courier-push-tag-sp-india-ire.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-courier-push-tag-sp-india-ire.yaml b/.github/workflows/build-courier-push-tag-sp-india-ire.yaml index 5803bedcf..671e23b19 100644 --- a/.github/workflows/build-courier-push-tag-sp-india-ire.yaml +++ b/.github/workflows/build-courier-push-tag-sp-india-ire.yaml @@ -16,7 +16,6 @@ jobs: if grep -qs -e '^.*.*-develop' <<< "${TAG}" ; then echo "Found environment: DEVELOP - ${TAG}" echo "ENVIRONMENT=develop" | tee -a "${GITHUB_ENV}" - exit 1 # stop action elif grep -qs -e '^.*.*-staging' <<< "${TAG}" ; then echo "Found environment: STAGING - ${TAG}" echo "ENVIRONMENT=staging" | tee -a "${GITHUB_ENV}" From e18b73fb4050d2dea1921173af1018d62f47f49a Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 6 Dec 2024 15:42:43 -0300 Subject: [PATCH 167/170] Change golang version --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5a08e40ea..902f8e3f8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.9-alpine3.17 +FROM golang:1.21.5-alpine WORKDIR /app From a5695c5f6581d7790de7e8452689df3bad9a467c Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 6 Dec 2024 15:46:18 -0300 Subject: [PATCH 168/170] Change dockerfile --- docker/Dockerfile | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 902f8e3f8..20e59ac75 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,20 +1,19 @@ -FROM golang:1.21.5-alpine +FROM golang:1.23-bookworm AS builder -WORKDIR /app +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download -x -RUN apk update \ - && apk add --virtual build-deps gcc git \ - && rm -rf /var/cache/apk/* +COPY . ./ -RUN addgroup -S golang \ - && adduser -S -G golang golang +RUN GOOS=linux GOARCH=amd64 go build -o /bin/mailroom ./cmd/mailroom/*.go -COPY . . +FROM gcr.io/distroless/base-debian12 -RUN go install -v ./cmd/... -RUN chown -R golang /app +WORKDIR /app -USER golang +COPY --from=builder bin/mailroom ./ -EXPOSE 8080 -ENTRYPOINT ["courier"] +EXPOSE 8090 +ENTRYPOINT ["./courier"] From bcbc60be6e44c9ff89de0ed7713beccb2cd128a9 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 6 Dec 2024 15:49:26 -0300 Subject: [PATCH 169/170] Fix dockerfile --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 20e59ac75..0aa7adace 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,13 +7,13 @@ RUN go mod download -x COPY . ./ -RUN GOOS=linux GOARCH=amd64 go build -o /bin/mailroom ./cmd/mailroom/*.go +RUN GOOS=linux GOARCH=amd64 go build -o /bin/courier ./cmd/courier/*.go FROM gcr.io/distroless/base-debian12 WORKDIR /app -COPY --from=builder bin/mailroom ./ +COPY --from=builder bin/courier ./ -EXPOSE 8090 +EXPOSE 8080 ENTRYPOINT ["./courier"] From fc06adf8623abca41c00127bfe778105b6c4630a Mon Sep 17 00:00:00 2001 From: Robi9 Date: Thu, 12 Dec 2024 14:26:35 -0300 Subject: [PATCH 170/170] Update weni-changelog --- WENI-CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WENI-CHANGELOG.md b/WENI-CHANGELOG.md index 7f6f3c39e..d77e9fc89 100644 --- a/WENI-CHANGELOG.md +++ b/WENI-CHANGELOG.md @@ -1,3 +1,8 @@ +1.5.8-courier-9.0.1 +---------- + * Update to v9.0.1 + * Update dockerfile + 1.5.7-courier-8.2.1 ---------- * Update to v8.2.1