Skip to content

Commit

Permalink
Merge b7071c5 into f68e4f2
Browse files Browse the repository at this point in the history
  • Loading branch information
norkans7 committed Feb 16, 2018
2 parents f68e4f2 + b7071c5 commit 5b0a001
Show file tree
Hide file tree
Showing 2 changed files with 319 additions and 0 deletions.
184 changes: 184 additions & 0 deletions handlers/highconnection/highconnection.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,192 @@
package highconnection

import (
"context"
"fmt"
"net/http"
"net/url"
"time"

"github.com/nyaruka/courier"
"github.com/nyaruka/courier/handlers"
"github.com/nyaruka/courier/utils"
"github.com/nyaruka/gocommon/urns"
)

/*
GET /handlers/hcnx/status/uuid?push_id=1164711372&status=6&to=%2B33611441111&ret_id=19128317&text=Msg
POST /handlers/hcnx/receive/uuid?FROM=+33644961111
ID=1164708294&FROM=%2B33644961111&TO=36105&MESSAGE=Msg&VALIDITY_DATE=2017-05-03T21%3A13%3A13&GET_STATUS=0&CLIENT=LEANCONTACTFAST&CLASS_TYPE=0&RECEPTION_DATE=2017-05-02T21%3A13%3A13&TO_OP_ID=20810&INITIAL_OP_ID=20810&STATUS=POSTING_30179_1410&EMAIL=&BINARY=0&PARAM=%7C%7C%7C%7CP223%2F03%2F03&USER_DATA=LEANCONTACTFAST&USER_DATA_2=jours+pas+r%E9gl%E9&BULK_ID=0&MO_ID=0&APPLICATION_ID=0&ACCOUNT_ID=39&GW_MESSAGE_ID=0&READ_STATUS=0&TARIFF=0&REQUEST_ID=33609002123&TAC=%28null%29&REASON=2017-05-02+23%3A13%3A13&FORMAT=&MVNO=&ORIG_ID=1164708215&ORIG_MESSAGE=Msg&RET_ID=123456&ORIG_DATE=2017-05-02T21%3A11%3A44
*/

var sendURL = "https://highpushfastapi-v2.hcnx.eu/api"
var maxMsgLength = 1500

func init() {
courier.RegisterHandler(newHandler())
}

type handler struct {
handlers.BaseHandler
}

func newHandler() courier.ChannelHandler {
return &handler{handlers.NewBaseHandler(courier.ChannelType("HX"), "High Connection")}
}

// Initialize is called by the engine once everything is loaded
func (h *handler) Initialize(s courier.Server) error {
h.SetServer(s)
err := s.AddHandlerRoute(h, http.MethodGet, "receive", h.ReceiveMessage)
if err != nil {
return err
}
err = s.AddHandlerRoute(h, http.MethodPost, "receive", h.ReceiveMessage)
if err != nil {
return err
}

err = s.AddHandlerRoute(h, http.MethodPost, "status", h.StatusMessage)
if err != nil {
return err
}

return s.AddHandlerRoute(h, http.MethodGet, "status", h.StatusMessage)

}

type moMsg struct {
To string `name:"TO" validate:"required"`
From string `name:"FROM" validate:"required"`
Message string `name:"MESSAGE" validate:"required"`
ReceiveDate string `name:"RECEPTION_DATE"`
}

// 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) ([]courier.Event, error) {
hxRequest := &moMsg{}
err := handlers.DecodeAndValidateForm(hxRequest, r)
if err != nil {
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err)
}

date := time.Now()
if hxRequest.ReceiveDate != "" {
date, err = time.Parse("2006-01-02T15:04:05", hxRequest.ReceiveDate)
if err != nil {
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err)
}
}

// create our URN
urn := urns.NewTelURNForCountry(hxRequest.From, channel.Country())

// build our infobipMessage
msg := h.Backend().NewIncomingMsg(channel, urn, hxRequest.Message).WithReceivedOn(date.UTC())

// and write it
err = h.Backend().WriteMsg(ctx, msg)
if err != nil {
return nil, err
}
return []courier.Event{msg}, courier.WriteMsgSuccess(ctx, w, r, []courier.Msg{msg})

}

type moStatus struct {
RetID int64 `name:"ret_id" validate:"required"`
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,
}

// 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) ([]courier.Event, error) {
hxRequest := &moStatus{}
err := handlers.DecodeAndValidateForm(hxRequest, r)
if err != nil {
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err)
}

msgStatus, found := statusMapping[hxRequest.Status]
if !found {
return nil, fmt.Errorf("unknown status '%d', must be one of 2, 4, 6, 11, 12, 13, 14, 15 or 16", hxRequest.Status)
}

// write our status
status := h.Backend().NewMsgStatusForID(channel, courier.NewMsgID(hxRequest.RetID), msgStatus)
err = h.Backend().WriteMsgStatus(ctx, status)
if err == courier.ErrMsgNotFound {
return nil, courier.WriteAndLogStatusMsgNotFound(ctx, w, r, channel)
}

if err != nil {
return nil, err
}

return []courier.Event{status}, courier.WriteStatusSuccess(ctx, w, r, []courier.MsgStatus{status})

}

// SendMsg sends the passed in message, returning any error
func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStatus, error) {
username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "")
if username == "" {
return nil, fmt.Errorf("no username set for HX channel")
}

password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "")
if password == "" {
return nil, fmt.Errorf("no password set for HX channel")
}

callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain)
statusURL := fmt.Sprintf("https://%s%s%s/status", callbackDomain, "/c/hx/", msg.Channel().UUID())
receiveURL := fmt.Sprintf("https://%s%s%s/receive", callbackDomain, "/c/hx/", msg.Channel().UUID())

status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored)
parts := handlers.SplitMsg(handlers.GetTextAndAttachments(msg), maxMsgLength)
for _, part := range parts {

form := url.Values{
"accountid": []string{username},
"password": []string{password},
"text": []string{part},
"to": []string{msg.URN().Path()},
"ret_id": []string{msg.ID().String()},
"datacoding": []string{"8"},
"userdata": []string{"textit"},
"ret_url": []string{statusURL},
"ret_mo_url": []string{receiveURL},
}

msgURL, _ := url.Parse(sendURL)
msgURL.RawQuery = form.Encode()

req, err := http.NewRequest(http.MethodPost, msgURL.String(), nil)
rr, err := utils.MakeHTTPRequest(req)

// record our status and log
log := courier.NewChannelLogFromRR("Message Sent", msg.Channel(), msg.ID(), rr).WithError("Message Send Error", err)
status.AddLog(log)
if err != nil {
return status, nil
}

status.SetStatus(courier.MsgWired)

}

return status, nil
}
135 changes: 135 additions & 0 deletions handlers/highconnection/highconnection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package highconnection

import (
"net/http/httptest"
"testing"
"time"

"github.com/nyaruka/courier"
. "github.com/nyaruka/courier/handlers"
)

var testChannels = []courier.Channel{
courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US", nil),
}

var (
receiveURL = "/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/"
statusURL = "/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/"

validReceive = receiveURL + "?FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14:26:06"
invalidDateReceive = receiveURL + "?FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14:26"
validStatus = statusURL + "?ret_id=12345&status=6"
)

var testCases = []ChannelHandleTestCase{
{Label: "Receive Valid Message", URL: validReceive, Status: 200, Response: "Accepted",
Text: Sp("Hello World"), URN: Sp("tel:+33610346460"),
Date: Tp(time.Date(2015, 04, 02, 14, 26, 06, 0, time.UTC))},
{Label: "Receive Missing Params", URL: receiveURL, Status: 400, Response: "validation for 'Message' failed"},
{Label: "Receive Invalid Date", URL: invalidDateReceive, Status: 400, Response: "cannot parse"},
{Label: "Status Missing Params", URL: statusURL, Status: 400, Response: "validation for 'Status' failed"},
{Label: "Status Delivered", URL: validStatus, Status: 200, Response: `"status":"D"`},
}

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",
Text: "Simple Message",
URN: "tel:+250788383383",
Status: "W",
URLParams: map[string]string{
"accountid": "Username",
"password": "Password",
"text": "Simple Message",
"to": "+250788383383",
"ret_id": "10",
"datacoding": "8",
"userdata": "textit",
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status",
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive",
},
ResponseStatus: 200,
SendPrep: setSendURL},
{Label: "Unicode Send",
Text: "☺",
URN: "tel:+250788383383",
Status: "W",
URLParams: map[string]string{
"accountid": "Username",
"password": "Password",
"text": "☺",
"to": "+250788383383",
"ret_id": "10",
"datacoding": "8",
"userdata": "textit",
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status",
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive",
},
ResponseStatus: 200,
SendPrep: setSendURL},
{Label: "Long Send",
Text: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work",
URN: "tel:+250788383383",
Status: "W",
URLParams: map[string]string{
"accountid": "Username",
"password": "Password",
"text": "I need to keep adding more things to make it work",
"to": "+250788383383",
"ret_id": "10",
"datacoding": "8",
"userdata": "textit",
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status",
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive",
},
ResponseStatus: 200,
SendPrep: setSendURL},
{Label: "Send Attachement",
Text: "My pic!",
Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"},
URN: "tel:+250788383383",
Status: "W",
URLParams: map[string]string{
"accountid": "Username",
"password": "Password",
"text": "My pic!\nhttps://foo.bar/image.jpg",
"to": "+250788383383",
"ret_id": "10",
"datacoding": "8",
"userdata": "textit",
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status",
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive",
},
ResponseStatus: 200,
SendPrep: setSendURL},

{Label: "Error Sending",
Text: "Error Sending", URN: "tel:+250788383383",
Status: "E",
ResponseStatus: 403,
SendPrep: setSendURL},
}

func TestSending(t *testing.T) {
maxMsgLength = 160
var defaultChannel = courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US",
map[string]interface{}{
courier.ConfigPassword: "Password",
courier.ConfigUsername: "Username",
})

RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, nil)
}

0 comments on commit 5b0a001

Please sign in to comment.