Skip to content

Commit

Permalink
added basic unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexjlockwood committed Apr 1, 2014
1 parent a4d10e3 commit b0afad5
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 2 deletions.
7 changes: 5 additions & 2 deletions sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const (
maxBackoffDelay = 1024000
)

// Declared as a mutable variable for testing purposes.
var gcmSendEndpoint = GcmSendEndpoint

// Sender abstracts the interaction between the application server and the
// GCM server. The developer must obtain an API key from the Google APIs
// Console page and pass it to the Sender so that it can perform authorized
Expand Down Expand Up @@ -60,7 +63,7 @@ func (s *Sender) SendNoRetry(msg *Message) (*Response, error) {
return nil, err
}

req, err := http.NewRequest("POST", GcmSendEndpoint, bytes.NewBuffer(data))
req, err := http.NewRequest("POST", gcmSendEndpoint, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -114,7 +117,7 @@ func (s *Sender) Send(msg *Message, retries int) (*Response, error) {
regIDs := msg.RegistrationIDs
allResults := make(map[string]Result, len(regIDs))
backoff := backoffInitialDelay
for i := 0; updateStatus(msg, resp, allResults) > 0 || i < retries; i++ {
for i := 0; updateStatus(msg, resp, allResults) > 0 && i < retries; i++ {
sleepTime := backoff/2 + rand.Intn(backoff)
time.Sleep(time.Duration(sleepTime) * time.Millisecond)
backoff = min(2*backoff, maxBackoffDelay)
Expand Down
162 changes: 162 additions & 0 deletions sender_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package gcm

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

type testResponse struct {
StatusCode int
Response *Response
}

func startTestServer(t *testing.T, responses ...*testResponse) *httptest.Server {
i := 0
handler := func(w http.ResponseWriter, r *http.Request) {
if i >= len(responses) {
t.Fatalf("server received %d requests, expected %d", i+1, len(responses))
}
resp := responses[i]
status := resp.StatusCode
if status == 0 || status == http.StatusOK {
w.Header().Set("Content-Type", "application/json")
respBytes, _ := json.Marshal(resp.Response)
fmt.Fprint(w, string(respBytes))
} else {
w.WriteHeader(status)
}
i++
}
server := httptest.NewServer(http.HandlerFunc(handler))
gcmSendEndpoint = server.URL
return server
}

func TestSendNoRetryInvalidApiKey(t *testing.T) {
server := startTestServer(t)
defer server.Close()
sender := &Sender{ApiKey: ""}
if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}}); err == nil {
t.Fatal("test should fail when sender's ApiKey is \"\"")
}
}

func TestSendInvalidApiKey(t *testing.T) {
server := startTestServer(t)
defer server.Close()
sender := &Sender{ApiKey: ""}
if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}}, 0); err == nil {
t.Fatal("test should fail when sender's ApiKey is \"\"")
}
}

func TestSendNoRetryInvalidMessage(t *testing.T) {
server := startTestServer(t)
defer server.Close()
sender := &Sender{ApiKey: "test"}
if _, err := sender.SendNoRetry(nil); err == nil {
t.Fatal("test should fail when message is nil")
}
if _, err := sender.SendNoRetry(&Message{}); err == nil {
t.Fatal("test should fail when message RegistrationIDs field is nil")
}
if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{}}); err == nil {
t.Fatal("test should fail when message RegistrationIDs field is an empty slice")
}
if _, err := sender.SendNoRetry(&Message{RegistrationIDs: make([]string, 1001)}); err == nil {
t.Fatal("test should fail when more than 1000 RegistrationIDs are specified")
}
if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}, TimeToLive: -1}); err == nil {
t.Fatal("test should fail when message TimeToLive field is negative")
}
if _, err := sender.SendNoRetry(&Message{RegistrationIDs: []string{"1"}, TimeToLive: 2419201}); err == nil {
t.Fatal("test should fail when message TimeToLive field is greater than 2419200")
}
}

func TestSendInvalidMessage(t *testing.T) {
server := startTestServer(t)
defer server.Close()
sender := &Sender{ApiKey: "test"}
if _, err := sender.Send(nil, 0); err == nil {
t.Fatal("test should fail when message is nil")
}
if _, err := sender.Send(&Message{}, 0); err == nil {
t.Fatal("test should fail when message RegistrationIDs field is nil")
}
if _, err := sender.Send(&Message{RegistrationIDs: []string{}}, 0); err == nil {
t.Fatal("test should fail when message RegistrationIDs field is an empty slice")
}
if _, err := sender.Send(&Message{RegistrationIDs: make([]string, 1001)}, 0); err == nil {
t.Fatal("test should fail when more than 1000 RegistrationIDs are specified")
}
if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}, TimeToLive: -1}, 0); err == nil {
t.Fatal("test should fail when message TimeToLive field is negative")
}
if _, err := sender.Send(&Message{RegistrationIDs: []string{"1"}, TimeToLive: 2419201}, 0); err == nil {
t.Fatal("test should fail when message TimeToLive field is greater than 2419200")
}
}

func TestSendNoRetrySuccess(t *testing.T) {
server := startTestServer(t, &testResponse{Response: &Response{}})
defer server.Close()
sender := &Sender{ApiKey: "test"}
msg := NewMessage(map[string]interface{}{"key": "value"}, "1")
if _, err := sender.SendNoRetry(msg); err != nil {
t.Fatalf("test failed with error: %s", err)
}
}

func TestSendNoRetryNonrecoverableFailure(t *testing.T) {
server := startTestServer(t, &testResponse{StatusCode: http.StatusBadRequest})
defer server.Close()
sender := &Sender{ApiKey: "test"}
msg := NewMessage(map[string]interface{}{"key": "value"}, "1")
if _, err := sender.SendNoRetry(msg); err == nil {
t.Fatal("test expected non-recoverable error")
}
}

func TestSendOneRetrySuccess(t *testing.T) {
server := startTestServer(t,
&testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}},
&testResponse{Response: &Response{Success: 1, Results: []Result{{MessageID: "id"}}}},
)
defer server.Close()
sender := &Sender{ApiKey: "test"}
msg := NewMessage(map[string]interface{}{"key": "value"}, "1")
if _, err := sender.Send(msg, 1); err != nil {
t.Fatal("send should succeed after one retry")
}
}

func TestSendOneRetryFailure(t *testing.T) {
server := startTestServer(t,
&testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}},
&testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}},
)
defer server.Close()
sender := &Sender{ApiKey: "test"}
msg := NewMessage(map[string]interface{}{"key": "value"}, "1")
resp, err := sender.Send(msg, 1)
if err != nil || resp.Failure != 1 {
t.Fatal("send should return response with one failure")
}
}

func TestSendOneRetryNonrecoverableFailure(t *testing.T) {
server := startTestServer(t,
&testResponse{Response: &Response{Failure: 1, Results: []Result{{Error: "Unavailable"}}}},
&testResponse{StatusCode: http.StatusBadRequest},
)
defer server.Close()
sender := &Sender{ApiKey: "test"}
msg := NewMessage(map[string]interface{}{"key": "value"}, "1")
if _, err := sender.Send(msg, 1); err == nil {
t.Fatal("send should fail after one retry")
}
}

0 comments on commit b0afad5

Please sign in to comment.