Repository: mailgun/mailgun-go
Pull request #280 (2021-12-24 09:36:06)

Add bounces mock and use them in the tests.


Patch:
diff --git a/mock.go b/mock.go
index c10462c..241d67e 100644
--- a/mock.go
+++ b/mock.go
@@ -43,6 +43,7 @@ type mockServer struct {
 	templateVersions map[string][]TemplateVersion
 	unsubscribes     []Unsubscribe
 	complaints       []Complaint
+	bounces          []Bounce
 	webhooks         WebHooksListResponse
 	mutex            sync.Mutex
 }
@@ -121,6 +122,7 @@ func NewMockServer() MockServer {
 		ms.addTemplateVersionRoutes(r)
 		ms.addUnsubscribesRoutes(r)
 		ms.addComplaintsRoutes(r)
+		ms.addBouncesRoutes(r)
 	}(r.PathPrefix("/v3").Subrouter())
 	ms.addValidationRoutes(r)
 
diff --git a/mock_bounces.go b/mock_bounces.go
new file mode 100644
index 0000000..b835e37
--- /dev/null
+++ b/mock_bounces.go
@@ -0,0 +1,196 @@
+package mailgun
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"time"
+
+	"github.com/gorilla/mux"
+)
+
+func (ms *mockServer) addBouncesRoutes(r *mux.Router) {
+	r.HandleFunc("/{domain}/bounces", ms.listBounces).Methods(http.MethodGet)
+	r.HandleFunc("/{domain}/bounces/{address}", ms.getBounce).Methods(http.MethodGet)
+	r.HandleFunc("/{domain}/bounces/{address}", ms.deleteBounce).Methods(http.MethodDelete)
+	r.HandleFunc("/{domain}/bounces", ms.deleteBouncesList).Methods(http.MethodDelete)
+	r.HandleFunc("/{domain}/bounces", ms.createBounce).Methods(http.MethodPost)
+
+	ms.bounces = append(ms.bounces, Bounce{
+		CreatedAt: RFC2822Time(time.Now()),
+		Error:     "invalid address",
+		Code:      "INVALID",
+		Address:   "foo@mailgun.test",
+	})
+
+	ms.bounces = append(ms.bounces, Bounce{
+		CreatedAt: RFC2822Time(time.Now()),
+		Error:     "non existing address",
+		Code:      "NOT_EXIST",
+		Address:   "alice@example.com",
+	})
+}
+
+func (ms *mockServer) listBounces(w http.ResponseWriter, r *http.Request) {
+	defer ms.mutex.Unlock()
+	ms.mutex.Lock()
+
+	var idx []string
+	for _, t := range ms.bounces {
+		idx = append(idx, t.Address)
+	}
+
+	limit := stringToInt(r.FormValue("limit"))
+	if limit == 0 {
+		limit = 100
+	}
+
+	page := r.FormValue("page")
+	var pivot string
+	if len(page) != 0 {
+		pivot = r.FormValue("p")
+		if pivot == "" {
+			w.WriteHeader(http.StatusBadRequest)
+			w.Write([]byte("{\"message\": \"Invalid parameter: pivot \"}"))
+			return
+		}
+	}
+	start, end := pageOffsets(idx, page, pivot, limit)
+	var nextAddress, prevAddress string
+	var results []Bounce
+
+	if start != end {
+		results = ms.bounces[start:end]
+		nextAddress = results[len(results)-1].Address
+		prevAddress = results[0].Address
+	} else {
+		results = []Bounce{}
+		nextAddress = pivot
+		prevAddress = pivot
+	}
+
+	toJSON(w, bouncesListResponse{
+		Paging: Paging{
+			First: getPageURL(r, url.Values{
+				"page": []string{"first"},
+			}),
+			Last: getPageURL(r, url.Values{
+				"page": []string{"last"},
+			}),
+			Next: getPageURL(r, url.Values{
+				"page": []string{"next"},
+				"p":    []string{nextAddress},
+			}),
+			Previous: getPageURL(r, url.Values{
+				"page": []string{"prev"},
+				"p":    []string{prevAddress},
+			}),
+		},
+		Items: results,
+	})
+}
+
+func (ms *mockServer) getBounce(w http.ResponseWriter, r *http.Request) {
+	defer ms.mutex.Unlock()
+	ms.mutex.Lock()
+
+	for _, bounce := range ms.bounces {
+		if bounce.Address == mux.Vars(r)["address"] {
+			toJSON(w, bounce)
+			return
+		}
+	}
+	w.WriteHeader(http.StatusNotFound)
+	w.Write([]byte("{\"message\": \"Address not found in bounces table\"}"))
+}
+
+func (ms *mockServer) createBounce(w http.ResponseWriter, r *http.Request) {
+	defer ms.mutex.Unlock()
+	ms.mutex.Lock()
+
+	var bounces []Bounce
+	if r.Header.Get("Content-Type") == "application/json" {
+		body, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			w.Write([]byte("{\"message\": \"Can't read request body\"}"))
+			return
+		}
+
+		err = json.Unmarshal(body, &bounces)
+		if err != nil {
+			w.WriteHeader(http.StatusBadRequest)
+			w.Write([]byte(fmt.Sprintf("{\"message\": \"Invalid json: %s\"}", err.Error())))
+			return
+		}
+	} else {
+		if err := r.ParseForm(); err != nil {
+			w.WriteHeader(http.StatusInternalServerError)
+			w.Write([]byte(err.Error()))
+			return
+		}
+
+		bounceError := r.FormValue("error")
+		code := r.FormValue("code")
+
+		address := r.FormValue("address")
+		if len(address) == 0 {
+			w.WriteHeader(http.StatusBadRequest)
+			w.Write([]byte("{\"message\": \"Invalid format for parameter address: \"}"))
+			return
+		}
+
+		bounces = append(bounces, Bounce{Address: address, Code: code, Error: bounceError})
+	}
+
+	for _, bounce := range bounces {
+		var addressExist bool
+		for _, existingBounce := range ms.bounces {
+			if existingBounce.Address == bounce.Address {
+				addressExist = true
+			}
+		}
+
+		if !addressExist {
+			ms.bounces = append(ms.bounces, bounce)
+		}
+	}
+
+	toJSON(w, map[string]interface{}{
+		"message": "Address has been added to the bounces table",
+		"address": fmt.Sprint(bounces),
+	})
+}
+
+func (ms *mockServer) deleteBounce(w http.ResponseWriter, r *http.Request) {
+	defer ms.mutex.Unlock()
+	ms.mutex.Lock()
+
+	for i, bounce := range ms.bounces {
+		if bounce.Address == mux.Vars(r)["address"] {
+			ms.bounces = append(ms.bounces[:i], ms.bounces[i+1:len(ms.bounces)]...)
+
+			toJSON(w, map[string]interface{}{
+				"message": "Bounce has been removed",
+			})
+			return
+		}
+	}
+
+	toJSON(w, map[string]interface{}{
+		"message": "Address not found in bounces table",
+	})
+}
+
+func (ms *mockServer) deleteBouncesList(w http.ResponseWriter, r *http.Request) {
+	defer ms.mutex.Unlock()
+	ms.mutex.Lock()
+
+	ms.bounces = []Bounce{}
+
+	toJSON(w, map[string]interface{}{
+		"message": "All bounces has been deleted",
+	})
+}


Test patch:
diff --git a/bounces_test.go b/bounces_test.go
index 1648189..d9079b4 100644
--- a/bounces_test.go
+++ b/bounces_test.go
@@ -1,4 +1,4 @@
-package mailgun
+package mailgun_test
 
 import (
 	"context"
@@ -9,21 +9,19 @@ import (
 	"testing"
 	"time"
 
+	"github.com/mailgun/mailgun-go/v4"
+
 	"github.com/facebookgo/ensure"
 )
 
 func TestGetBounces(t *testing.T) {
-	if reason := SkipNetworkTest(); reason != "" {
-		t.Skip(reason)
-	}
-
-	mg, err := NewMailgunFromEnv()
-	ensure.Nil(t, err)
+	mg := mailgun.NewMailgun(testDomain, testKey)
+	mg.SetAPIBase(server.URL())
 
 	ctx := context.Background()
 	it := mg.ListBounces(nil)
 
-	var page []Bounce
+	var page []mailgun.Bounce
 	for it.Next(ctx, &page) {
 		for _, bounce := range page {
 			t.Logf("Bounce: %+v\n", bounce)
@@ -33,37 +31,28 @@ func TestGetBounces(t *testing.T) {
 }
 
 func TestGetSingleBounce(t *testing.T) {
-	if reason := SkipNetworkTest(); reason != "" {
-		t.Skip(reason)
-	}
-
-	mg, err := NewMailgunFromEnv()
-	ensure.Nil(t, err)
+	mg := mailgun.NewMailgun(testDomain, testKey)
+	mg.SetAPIBase(server.URL())
 
 	ctx := context.Background()
 	exampleEmail := fmt.Sprintf("%s@%s", strings.ToLower(randomString(64, "")),
 		os.Getenv("MG_DOMAIN"))
-	_, err = mg.GetBounce(ctx, exampleEmail)
+	_, err := mg.GetBounce(ctx, exampleEmail)
 	ensure.NotNil(t, err)
 
-	ure, ok := err.(*UnexpectedResponseError)
+	ure, ok := err.(*mailgun.UnexpectedResponseError)
 	ensure.True(t, ok)
 	ensure.DeepEqual(t, ure.Actual, http.StatusNotFound)
 }
 
 func TestAddDelBounces(t *testing.T) {
-	if reason := SkipNetworkTest(); reason != "" {
-		t.Skip(reason)
-	}
-
-	domain := os.Getenv("MG_DOMAIN")
-	mg, err := NewMailgunFromEnv()
+	mg := mailgun.NewMailgun(testDomain, testKey)
+	mg.SetAPIBase(server.URL())
 	ctx := context.Background()
-	ensure.Nil(t, err)
 
 	findBounce := func(address string) bool {
 		it := mg.ListBounces(nil)
-		var page []Bounce
+		var page []mailgun.Bounce
 		for it.Next(ctx, &page) {
 			ensure.True(t, len(page) != 0)
 			for _, bounce := range page {
@@ -83,7 +72,7 @@ func TestAddDelBounces(t *testing.T) {
 	exampleEmail := fmt.Sprintf("%s@%s", strings.ToLower(randomString(8, "bounce")), domain)
 
 	// Add the bounce for our address.
-	err = mg.AddBounce(ctx, exampleEmail, "550", "TestAddDelBounces-generated error")
+	err := mg.AddBounce(ctx, exampleEmail, "550", "TestAddDelBounces-generated error")
 	ensure.Nil(t, err)
 
 	// Give API some time to refresh cache
@@ -115,18 +104,14 @@ func TestAddDelBounces(t *testing.T) {
 }
 
 func TestDelBounceList(t *testing.T) {
-	if reason := SkipNetworkTest(); reason != "" {
-		t.Skip(reason)
-	}
+	mg := mailgun.NewMailgun(testDomain, testKey)
+	mg.SetAPIBase(server.URL())
 
-	domain := os.Getenv("MG_DOMAIN")
-	mg, err := NewMailgunFromEnv()
 	ctx := context.Background()
-	ensure.Nil(t, err)
 
 	findBounce := func(address string) bool {
 		it := mg.ListBounces(nil)
-		var page []Bounce
+		var page []mailgun.Bounce
 		for it.Next(ctx, &page) {
 			ensure.True(t, len(page) != 0)
 			for _, bounce := range page {
@@ -147,7 +132,7 @@ func TestDelBounceList(t *testing.T) {
 		exampleEmail := fmt.Sprintf("%s@%s", strings.ToLower(randomString(8, "bounce")), domain)
 
 		// Add the bounce for our address.
-		err = mg.AddBounce(ctx, exampleEmail, "550", "TestAddDelBounces-generated error")
+		err := mg.AddBounce(ctx, exampleEmail, "550", "TestAddDelBounces-generated error")
 		ensure.Nil(t, err)
 
 		// Give API some time to refresh cache
@@ -160,11 +145,11 @@ func TestDelBounceList(t *testing.T) {
 	}
 
 	// Delete the bounce list.  This should put us back the way we were.
-	err = mg.DeleteBounceList(ctx)
+	err := mg.DeleteBounceList(ctx)
 	ensure.Nil(t, err)
 
 	it := mg.ListBounces(nil)
-	var page []Bounce
+	var page []mailgun.Bounce
 	if it.Next(ctx, &page) {
 		t.Fatalf("Expected no item in the bounce list")
 	}