fix: close receipt delivery workflows
This commit is contained in:
@@ -4,8 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cmpp "github.com/bigwhite/gocmpp"
|
||||
)
|
||||
|
||||
func TestSubmitResponseProtocolLoggerEmitsActualPacketDirection(t *testing.T) {
|
||||
@@ -50,3 +53,38 @@ func TestSubmitResponseProtocolLoggerEmitsActualPacketDirection(t *testing.T) {
|
||||
t.Fatal("timed out waiting for protocol event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownstreamDeliverProtocolLoggerEmitsReceiptPacket(t *testing.T) {
|
||||
events := make(chan protocolLogEvent, 1)
|
||||
session := &downstreamSession{
|
||||
account: "607532", tenantID: "tenant-1", applicationID: "app-1",
|
||||
messageID: "MSG-LONG-1", phoneNumber: "18821203795", mu: &sync.Mutex{},
|
||||
protocolLog: func(event protocolLogEvent) { events <- event },
|
||||
}
|
||||
session.recordDownstreamProtocol(
|
||||
&cmpp.Cmpp2DeliverReqPkt{
|
||||
MsgId: 736078096490905600, SrcTerminalId: "18821203795", RegisterDelivery: 1,
|
||||
},
|
||||
"delivery-1",
|
||||
71,
|
||||
736078096490905600,
|
||||
"success",
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
select {
|
||||
case event := <-events:
|
||||
if event.Protocol != "cmpp" || event.Direction != "platform_to_client" || event.EventType != "deliver_receipt" {
|
||||
t.Fatalf("unexpected protocol event: %+v", event)
|
||||
}
|
||||
if event.TenantID != "tenant-1" || event.ApplicationID != "app-1" || event.Account != "607532" {
|
||||
t.Fatalf("unexpected application identifiers: %+v", event)
|
||||
}
|
||||
if event.MessageID != "MSG-LONG-1" || event.GatewayMessageID != "736078096490905600" || event.Phone != "18821203795" {
|
||||
t.Fatalf("unexpected message identifiers: %+v", event)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting for downstream deliver protocol event")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ type downstreamConnectionEvent struct {
|
||||
type downstreamSession struct {
|
||||
messageID string
|
||||
account string
|
||||
tenantID string
|
||||
applicationID string
|
||||
enterpriseCode string
|
||||
protocol string
|
||||
srcID string
|
||||
@@ -188,6 +190,7 @@ type downstreamSession struct {
|
||||
instanceID string
|
||||
report func(*downstreamSession, string, string)
|
||||
deliveryReport func(downstreamDeliveryLifecycleEvent)
|
||||
protocolLog func(protocolLogEvent)
|
||||
}
|
||||
|
||||
var downstreamRegistry = struct {
|
||||
@@ -244,6 +247,8 @@ func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger
|
||||
now := time.Now().UTC()
|
||||
session := &downstreamSession{
|
||||
account: strings.TrimSpace(defaultString(auth.Account, account)),
|
||||
tenantID: strings.TrimSpace(auth.TenantID),
|
||||
applicationID: strings.TrimSpace(auth.ApplicationID),
|
||||
enterpriseCode: strings.TrimSpace(auth.EnterpriseCode),
|
||||
protocol: cmppVersionName(req.Version),
|
||||
srcID: strings.TrimSpace(auth.Account),
|
||||
@@ -256,6 +261,7 @@ func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger
|
||||
instanceID: s.gatewayInstanceID(),
|
||||
report: s.reportConnection,
|
||||
deliveryReport: s.reportDownstreamDelivery,
|
||||
protocolLog: s.emitProtocolLog,
|
||||
}
|
||||
if !rememberAccount(session, auth.MaxConnections) {
|
||||
logger.Printf("cmpp inbound auth failed account=%s remote=%s err=connection limit exceeded max=%d", account, packet.Conn.Conn.RemoteAddr(), auth.MaxConnections)
|
||||
@@ -372,6 +378,8 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
|
||||
rememberDownstream(downstreamSession{
|
||||
messageID: acceptedMessage.MessageID,
|
||||
account: account,
|
||||
tenantID: result.TenantID,
|
||||
applicationID: result.ApplicationID,
|
||||
enterpriseCode: session.enterpriseCode,
|
||||
protocol: clientProtocol,
|
||||
srcID: strings.TrimSpace(req.srcID),
|
||||
@@ -386,6 +394,7 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
|
||||
instanceID: s.gatewayInstanceID(),
|
||||
report: session.report,
|
||||
deliveryReport: session.deliveryReport,
|
||||
protocolLog: session.protocolLog,
|
||||
})
|
||||
}
|
||||
if current := findSessionByConn(packet.Conn); current != nil && current.report != nil {
|
||||
@@ -1282,12 +1291,14 @@ func sendDownstream(session *downstreamSession, deliver cmpp.Packer, deliveryID
|
||||
tracker := registerDownstreamAck(session, deliveryID, sequenceID, messageID, ackDeadlineAt)
|
||||
if err := session.conn.SendPkt(deliver, sequenceID); err != nil {
|
||||
removeDownstreamAck(tracker)
|
||||
session.recordDownstreamProtocol(deliver, deliveryID, sequenceID, messageID, "failed", "SEND_FAILED", err)
|
||||
if session.report != nil {
|
||||
go session.report(session, "disconnected", err.Error())
|
||||
}
|
||||
forgetDownstream(session)
|
||||
return DownstreamSendResult{}, err
|
||||
}
|
||||
session.recordDownstreamProtocol(deliver, deliveryID, sequenceID, messageID, "success", "", nil)
|
||||
result := DownstreamSendResult{
|
||||
Sent: true, ConnectionID: session.connectionID,
|
||||
SequenceID: strconv.FormatUint(uint64(sequenceID), 10), MessageID: strconv.FormatUint(messageID, 10),
|
||||
@@ -1306,6 +1317,56 @@ func sendDownstream(session *downstreamSession, deliver cmpp.Packer, deliveryID
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (session *downstreamSession) recordDownstreamProtocol(
|
||||
deliver cmpp.Packer,
|
||||
deliveryID string,
|
||||
sequenceID uint32,
|
||||
messageID uint64,
|
||||
status string,
|
||||
resultCode string,
|
||||
sendErr error,
|
||||
) {
|
||||
if session == nil || session.protocolLog == nil {
|
||||
return
|
||||
}
|
||||
eventType, phone := downstreamDeliverMetadata(deliver)
|
||||
detail := map[string]any{"sequenceId": sequenceID, "deliveryId": deliveryID}
|
||||
if sendErr != nil {
|
||||
detail["error"] = sendErr.Error()
|
||||
}
|
||||
session.protocolLog(protocolLogEvent{
|
||||
Protocol: "cmpp",
|
||||
Direction: "platform_to_client",
|
||||
EventType: eventType,
|
||||
Status: status,
|
||||
TenantID: session.tenantID,
|
||||
ApplicationID: session.applicationID,
|
||||
Account: session.account,
|
||||
MessageID: session.messageID,
|
||||
GatewayMessageID: strconv.FormatUint(messageID, 10),
|
||||
Phone: defaultString(phone, session.phoneNumber),
|
||||
ResultCode: resultCode,
|
||||
Detail: detail,
|
||||
})
|
||||
}
|
||||
|
||||
func downstreamDeliverMetadata(deliver cmpp.Packer) (string, string) {
|
||||
switch packet := deliver.(type) {
|
||||
case *cmpp.Cmpp2DeliverReqPkt:
|
||||
if packet.RegisterDelivery == 1 {
|
||||
return "deliver_receipt", packet.SrcTerminalId
|
||||
}
|
||||
return "deliver_uplink", packet.SrcTerminalId
|
||||
case *cmpp.Cmpp3DeliverReqPkt:
|
||||
if packet.RegisterDelivery == 1 {
|
||||
return "deliver_receipt", packet.SrcTerminalId
|
||||
}
|
||||
return "deliver_uplink", packet.SrcTerminalId
|
||||
default:
|
||||
return "deliver", ""
|
||||
}
|
||||
}
|
||||
|
||||
func downstreamDeliverMessageID(deliver cmpp.Packer) uint64 {
|
||||
switch packet := deliver.(type) {
|
||||
case *cmpp.Cmpp2DeliverReqPkt:
|
||||
@@ -1389,6 +1450,26 @@ func handleDownstreamAcknowledgement(conn *cmpp.Conn, sequenceID uint32, message
|
||||
SequenceID: sequenceID, MessageID: messageID, Result: result, ObservedAt: time.Now().UTC(),
|
||||
})
|
||||
}
|
||||
if tracker.session != nil && tracker.session.protocolLog != nil {
|
||||
status := "success"
|
||||
if result != 0 {
|
||||
status = "failed"
|
||||
}
|
||||
tracker.session.protocolLog(protocolLogEvent{
|
||||
Protocol: "cmpp",
|
||||
Direction: "client_to_platform",
|
||||
EventType: "deliver_resp",
|
||||
Status: status,
|
||||
TenantID: tracker.session.tenantID,
|
||||
ApplicationID: tracker.session.applicationID,
|
||||
Account: tracker.session.account,
|
||||
MessageID: tracker.session.messageID,
|
||||
GatewayMessageID: strconv.FormatUint(messageID, 10),
|
||||
Phone: tracker.session.phoneNumber,
|
||||
ResultCode: strconv.FormatUint(uint64(result), 10),
|
||||
Detail: map[string]any{"sequenceId": sequenceID, "deliveryId": tracker.deliveryID},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func downstreamAckTimeout() time.Duration {
|
||||
|
||||
@@ -912,10 +912,14 @@ func TestDownstreamDeliveryRequiresAcknowledgement(t *testing.T) {
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
events := make(chan downstreamDeliveryLifecycleEvent, 1)
|
||||
protocolEvents := make(chan protocolLogEvent, 1)
|
||||
conn := &cmpp.Conn{}
|
||||
session := &downstreamSession{
|
||||
conn: conn, connectionID: "conn-1",
|
||||
deliveryReport: func(event downstreamDeliveryLifecycleEvent) { events <- event },
|
||||
tenantID: "tenant-1", applicationID: "app-1", account: "607532",
|
||||
messageID: "MSG-LONG-1", phoneNumber: "18821203795",
|
||||
protocolLog: func(event protocolLogEvent) { protocolEvents <- event },
|
||||
}
|
||||
registerDownstreamAck(session, "delivery-1", 37, 9016479179509871733, time.Now().Add(time.Second))
|
||||
handleDownstreamAcknowledgement(conn, 37, 9016479179509871733, 0, log.Default())
|
||||
@@ -928,6 +932,17 @@ func TestDownstreamDeliveryRequiresAcknowledgement(t *testing.T) {
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting acknowledgement event")
|
||||
}
|
||||
select {
|
||||
case event := <-protocolEvents:
|
||||
if event.Protocol != "cmpp" || event.Direction != "client_to_platform" || event.EventType != "deliver_resp" {
|
||||
t.Fatalf("unexpected acknowledgement protocol event: %+v", event)
|
||||
}
|
||||
if event.MessageID != "MSG-LONG-1" || event.GatewayMessageID != "9016479179509871733" || event.ResultCode != "0" {
|
||||
t.Fatalf("unexpected acknowledgement identifiers: %+v", event)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting acknowledgement protocol event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiptLookupDoesNotFallbackToAccountBeforeSubmitMappingExists(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user