Skip to content

Commit

Permalink
Merge pull request #10 from MikeMwita/indempontency-and-retries
Browse files Browse the repository at this point in the history
Better Retries with Exponential Backoff and jitter
  • Loading branch information
MikeMwita authored Jul 10, 2024
2 parents 5640cd4 + ed9adad commit 018a2e5
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .idea/GitLink.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 14 additions & 9 deletions pkg/sms/sms_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sms
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -143,17 +144,21 @@ func (s *SmsSender) SendSMS() (SmsSenderResponse, error) {

return smsSenderResponse, fmt.Errorf("status code: %d", res.StatusCode)
}
// Retry sends an SMS with exponential backoff

func (s *SmsSender) RetrySendSMS(maxRetries int) (SmsSenderResponse, error) {
for retry := 0; retry < maxRetries; retry++ {
response, err := s.SendSMS()
if err == nil {
return response, nil
}
response, err := s.SendSMS()
if err == nil {
return response, nil
}

delay := time.Duration(1<<uint(retry)) * time.Second
time.Sleep(delay)
}
delay := time.Duration(1<<uint(retry)) * time.Second

// Add jitter (randomness) to the delay
jitter := time.Duration(rand.Intn(int(delay))) * time.Millisecond
waitTime := delay + jitter

time.Sleep(waitTime)
}
return SmsSenderResponse{}, fmt.Errorf("max retries reached")
}
}
103 changes: 103 additions & 0 deletions pkg/sms/sms_sender_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sms

import (
"fmt"
"testing"
)

Expand Down Expand Up @@ -30,3 +31,105 @@ func TestSendSMS(t *testing.T) {
t.Errorf("empty message received in response")
}
}

func (s *SmsSender) MockSendSMS() (SmsSenderResponse, error) {
// Todo: Implement mock behavior
return SmsSenderResponse{
SmsMessageData: SmsMessageData{
Message: "Mocked success message",
Recipients: []Recipient{
{
Key: "mock-recipient-key",
Cost: "0.05",
SmsKey: "mock-sms-key",
MessageId: "mock-message-id",
MessagePart: 1,
Number: "+254745617596",
Status: "Success",
StatusCode: "200",
},
},
},
}, nil
}

func TestRetrySendSMS(t *testing.T) {
sender := SmsSender{
ApiKey: "your-api-key",
ApiUser: "your-api-user",
Recipients: []string{"+254745617596"},
Message: "Hello, this is a test message.",
Sender: "YourSenderID",
}

maxRetries := 5

sender.SendSMS = sender.MockSendSMS

response, err := sender.RetrySendSMS(maxRetries)

if err != nil {
t.Errorf("unexpected error: %v", err)
}

if response.SmsMessageData.Message != "Mocked success message" {
t.Errorf("unexpected message received in response")
}
}

func TestRetrySendSMS_Success(t *testing.T) {
sender := SmsSender{
ApiKey: "your-api-key",
ApiUser: "your-api-user",
Recipients: []string{"+254745617596"},
Message: "Hello, this is a test message.",
Sender: "YourSenderID",
}

maxRetries := 5

retryCount := 0
sender.SendSMS = func() (SmsSenderResponse, error) {
if retryCount < 3 {
retryCount++
return SmsSenderResponse{}, fmt.Errorf("mocked error")
}
return SmsSenderResponse{
SmsMessageData: SmsMessageData{
Message: "Success!",
},
}, nil
}

response, err := sender.RetrySendSMS(maxRetries)

if err != nil {
t.Errorf("unexpected error: %v", err)
}

if response.SmsMessageData.Message != "Success!" {
t.Errorf("unexpected message received in response")
}
}

func TestRetrySendSMS_Failure(t *testing.T) {
sender := SmsSender{
ApiKey: "your-api-key",
ApiUser: "your-api-user",
Recipients: []string{"+254745617596"},
Message: "Hello, this is a test message.",
Sender: "YourSenderID",
}

maxRetries := 3

sender.SendSMS = func() (SmsSenderResponse, error) {
return SmsSenderResponse{}, fmt.Errorf("mocked error")
}

_, err := sender.RetrySendSMS(maxRetries)

if err == nil {
t.Errorf("expected error, got nil")
}
}

0 comments on commit 018a2e5

Please sign in to comment.