feat: harden sessions and track downstream acknowledgements
This commit is contained in:
@@ -100,6 +100,7 @@ func TestInboundServerAuthenticatesAndSubmits(t *testing.T) {
|
||||
var gotAuth authRequest
|
||||
var gotSubmit submitRequest
|
||||
connectionEvents := make(chan downstreamConnectionEvent, 8)
|
||||
acknowledgements := make(chan map[string]any, 1)
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/gateway/events/inbound/authenticate":
|
||||
@@ -121,6 +122,15 @@ func TestInboundServerAuthenticatesAndSubmits(t *testing.T) {
|
||||
}
|
||||
connectionEvents <- event
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/api/gateway/events/downstream/sent":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/api/gateway/events/downstream/acknowledged":
|
||||
var event map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||
t.Fatalf("decode acknowledgement: %v", err)
|
||||
}
|
||||
acknowledgements <- event
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
t.Fatalf("unexpected api path: %s", r.URL.Path)
|
||||
}
|
||||
@@ -176,14 +186,15 @@ func TestInboundServerAuthenticatesAndSubmits(t *testing.T) {
|
||||
if rsp.Result != 0 || rsp.MsgId == 0 {
|
||||
t.Fatalf("unexpected submit response: %+v", rsp)
|
||||
}
|
||||
delivered, err := PushReceipt(DownstreamReceipt{
|
||||
sendResult, err := PushReceiptWithResult(DownstreamReceipt{
|
||||
DeliveryID: "delivery-1",
|
||||
MessageID: "MSG-1",
|
||||
PhoneNumber: "13500002696",
|
||||
ReceiptStatus: "delivered",
|
||||
DeliveredAt: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
})
|
||||
if err != nil || !delivered {
|
||||
t.Fatalf("push receipt delivered=%v err=%v", delivered, err)
|
||||
if err != nil || !sendResult.Sent {
|
||||
t.Fatalf("push receipt sent=%v err=%v", sendResult.Sent, err)
|
||||
}
|
||||
deliver := recvDeliver(t, client)
|
||||
if deliver.RegisterDelivery != 1 {
|
||||
@@ -196,6 +207,17 @@ func TestInboundServerAuthenticatesAndSubmits(t *testing.T) {
|
||||
if receipt.Stat != "DELIVRD" || receipt.DestTerminalId != "13500002696" {
|
||||
t.Fatalf("unexpected pushed receipt: %+v", receipt)
|
||||
}
|
||||
if err := client.SendRspPkt(&cmpp.Cmpp3DeliverRspPkt{MsgId: deliver.MsgId, Result: 0}, deliver.SeqId); err != nil {
|
||||
t.Fatalf("send deliver response: %v", err)
|
||||
}
|
||||
select {
|
||||
case event := <-acknowledgements:
|
||||
if event["id"] != "delivery-1" || event["result"] != float64(0) {
|
||||
t.Fatalf("unexpected acknowledgement callback: %+v", event)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("expected downstream acknowledgement callback")
|
||||
}
|
||||
if gotAuth.Account != account || gotAuth.AuthSource == "" || gotAuth.RemoteIP == "" {
|
||||
t.Fatalf("unexpected auth payload: %+v", gotAuth)
|
||||
}
|
||||
@@ -537,6 +559,50 @@ func TestRememberAndForgetAccountUpdatesPresenceStore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownstreamDeliveryRequiresAcknowledgement(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
events := make(chan downstreamDeliveryLifecycleEvent, 1)
|
||||
conn := &cmpp.Conn{}
|
||||
session := &downstreamSession{
|
||||
conn: conn, connectionID: "conn-1",
|
||||
deliveryReport: func(event downstreamDeliveryLifecycleEvent) { events <- event },
|
||||
}
|
||||
registerDownstreamAck(session, "delivery-1", 37, 9016479179509871733, time.Now().Add(time.Second))
|
||||
handleDownstreamAcknowledgement(conn, 37, 9016479179509871733, 0, log.Default())
|
||||
|
||||
select {
|
||||
case event := <-events:
|
||||
if event.Kind != "acknowledged" || event.DeliveryID != "delivery-1" || event.Result != 0 || event.SequenceID != 37 {
|
||||
t.Fatalf("unexpected acknowledgement event: %+v", event)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting acknowledgement event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownstreamDeliveryReportsAckTimeout(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
events := make(chan downstreamDeliveryLifecycleEvent, 1)
|
||||
session := &downstreamSession{
|
||||
conn: &cmpp.Conn{}, connectionID: "conn-1",
|
||||
deliveryReport: func(event downstreamDeliveryLifecycleEvent) { events <- event },
|
||||
}
|
||||
registerDownstreamAck(session, "delivery-timeout", 38, 9017467844344255865, time.Now().Add(20*time.Millisecond))
|
||||
|
||||
select {
|
||||
case event := <-events:
|
||||
if event.Kind != "failed" || event.FailureType != "ack_timeout" || event.DeliveryID != "delivery-timeout" {
|
||||
t.Fatalf("unexpected timeout event: %+v", event)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting acknowledgement timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func recvDeliver(t *testing.T, client *cmpp.Client) *cmpp.Cmpp3DeliverReqPkt {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
@@ -575,6 +641,14 @@ func resetDownstreamRegistry() {
|
||||
downstreamRegistry.byAccount = make(map[string]*downstreamSession)
|
||||
downstreamRegistry.byMessageID = make(map[string]*downstreamSession)
|
||||
downstreamRegistry.byConn = make(map[*cmpp.Conn]*downstreamSession)
|
||||
downstreamAckRegistry.Lock()
|
||||
for _, tracker := range downstreamAckRegistry.items {
|
||||
if tracker.timer != nil {
|
||||
tracker.timer.Stop()
|
||||
}
|
||||
}
|
||||
downstreamAckRegistry.items = make(map[string]*downstreamAckTracker)
|
||||
downstreamAckRegistry.Unlock()
|
||||
}
|
||||
|
||||
func reserveTCPAddr(t *testing.T) string {
|
||||
|
||||
Reference in New Issue
Block a user