package inbound import ( "encoding/json" cmpp "github.com/bigwhite/gocmpp" "net" "sync" "testing" "time" ) func TestZeroSequenceReceiptRecoversAndAcknowledgesOverTCP(t *testing.T) { for _, version := range []cmpp.Type{cmpp.V20, cmpp.V30} { t.Run(version.String(), func(t *testing.T) { resetDownstreamRegistry() defer resetDownstreamRegistry() listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer listener.Close() tcp, err := net.Dial("tcp", listener.Addr().String()) if err != nil { t.Fatal(err) } remote, err := listener.Accept() if err != nil { t.Fatal(err) } server, client := cmpp.NewConn(remote, version), cmpp.NewConn(tcp, version) server.SetState(cmpp.CONN_AUTHOK) client.SetState(cmpp.CONN_AUTHOK) defer server.Close() defer client.Close() acknowledged := make(chan downstreamDeliveryLifecycleEvent, 2) session := &downstreamSession{account: "qa", conn: server, protocol: version.String(), connectionID: "reconnected", mu: &sync.Mutex{}, deliveryReport: func(e downstreamDeliveryLifecycleEvent) { if e.Kind == "acknowledged" { acknowledged <- e } }} // Simulate a fresh connection with no in-memory original Submit mapping. downstreamRegistry.Lock() downstreamRegistry.byAccount["qa"] = session downstreamRegistry.Unlock() var event DownstreamReceipt if err = json.Unmarshal([]byte(`{"deliveryId":"qa-zero","account":"qa","messageId":"original","submitSequenceId":0,"receiptStatus":"delivered","phoneNumber":"13800138000"}`), &event); err != nil { t.Fatal(err) } result, err := PushReceiptWithResult(event) if err != nil || !result.Sent { t.Fatalf("zero cannot recover: %+v %v", result, err) } p, err := client.RecvAndUnpackPkt(time.Second) if err != nil { t.Fatal(err) } var content string var seq uint32 var msgID uint64 switch pkt := p.(type) { case *cmpp.Cmpp2DeliverReqPkt: content, seq, msgID = pkt.MsgContent, pkt.SeqId, pkt.MsgId case *cmpp.Cmpp3DeliverReqPkt: content, seq, msgID = pkt.MsgContent, pkt.SeqId, pkt.MsgId default: t.Fatalf("bad packet %T", p) } var receipt cmpp.CmppReceiptPkt if err = receipt.UnpackVersion([]byte(content), version); err != nil { t.Fatal(err) } if receipt.MsgId != messageIDFrom("original", 0) || msgID != receipt.MsgId { t.Fatalf("wrong original Msg_Id: %d", receipt.MsgId) } var response cmpp.Packer = &cmpp.Cmpp3DeliverRspPkt{MsgId: msgID, Result: 0} if version == cmpp.V20 { response = &cmpp.Cmpp2DeliverRspPkt{MsgId: msgID, Result: 0} } if err = client.SendPkt(response, seq); err != nil { t.Fatal(err) } p, err = server.RecvAndUnpackPkt(time.Second) if err != nil { t.Fatal(err) } switch pkt := p.(type) { case *cmpp.Cmpp2DeliverRspPkt: handleDownstreamAcknowledgement(server, pkt.SeqId, pkt.MsgId, uint32(pkt.Result), nil) case *cmpp.Cmpp3DeliverRspPkt: handleDownstreamAcknowledgement(server, pkt.SeqId, pkt.MsgId, pkt.Result, nil) } select { case ack := <-acknowledged: if ack.Result != 0 || ack.MessageID != receipt.MsgId { t.Fatal("wrong ACK") } case <-time.After(time.Second): t.Fatal("missing ACK") } raw, err := json.Marshal(submitRequest{SequenceID: 0}) if err != nil { t.Fatal(err) } var fields map[string]any json.Unmarshal(raw, &fields) if value, ok := fields["sequenceId"]; !ok || value != float64(0) { t.Fatal("zero omitted from Submit callback") } }) } } func TestDownstreamAckCollisionDoesNotReplacePendingDelivery(t *testing.T) { conn := &cmpp.Conn{} session := &downstreamSession{conn: conn} for _, sequence := range []uint32{^uint32(0), 0} { first := registerDownstreamAck(session, "first", "a", sequence, 1, time.Now().Add(time.Minute)) if first == nil { t.Fatal("first registration failed") } if registerDownstreamAck(session, "second", "b", sequence, 2, time.Now().Add(time.Minute)) != nil { t.Fatal("overwrote pending delivery") } if takeDownstreamAck(conn, sequence) != first { t.Fatal("lost original delivery") } } } func TestAbsentAndZeroSubmitSequenceRemainDistinct(t *testing.T) { resetDownstreamRegistry() defer resetDownstreamRegistry() for _, payload := range []string{`{}`, `{"submitSequenceId":null}`, `{"submitSequenceId":0}`} { event := DownstreamReceipt{Account: "qa", MessageID: "original"} if err := json.Unmarshal([]byte(payload), &event); err != nil { t.Fatal(err) } result, err := PushReceiptWithResult(event) if err != nil { t.Fatal(err) } if result.Retryable != (event.SubmitSequenceID != nil) { t.Fatalf("missing confused with zero: %s %+v", payload, result) } } }