feat: harden sessions and track downstream acknowledgements

This commit is contained in:
hectorzhao
2026-07-14 14:18:43 +08:00
parent 3d37adcc9f
commit 8c03663f24
43 changed files with 1733 additions and 150 deletions
+77 -3
View File
@@ -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 {