Skip to content

Commit

Permalink
DE-1201/subaccounts crud and on-behalf-on-header support
Browse files Browse the repository at this point in the history
  • Loading branch information
sadbeam committed Jan 8, 2024
1 parent 0d5a003 commit 88d455c
Show file tree
Hide file tree
Showing 7 changed files with 569 additions and 2 deletions.
4 changes: 2 additions & 2 deletions httphelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/pkg/errors"
)

var validURL = regexp.MustCompile(`/v[2-4].*`)
var validURL = regexp.MustCompile(`/v[2-5].*`)

type httpRequest struct {
URL string
Expand Down Expand Up @@ -364,7 +364,7 @@ func (r *httpRequest) curlString(req *http.Request, p payload) string {
parts = append(parts, fmt.Sprintf("-H \"Host: %s\"", req.Host))
}

//parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))
// parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))

if p != nil {
if p.getContentType() == "application/json" {
Expand Down
24 changes: 24 additions & 0 deletions mailgun.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ const (
listsEndpoint = "lists"
basicAuthUser = "api"
templatesEndpoint = "templates"
accountsEndpoint = "accounts"
subaccountsEndpoint = "subaccounts"
OnBehalfOfHeader = "X-Mailgun-On-Behalf-Of"
)

// Mailgun defines the supported subset of the Mailgun API.
Expand Down Expand Up @@ -246,6 +249,12 @@ type Mailgun interface {
UpdateTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error
DeleteTemplateVersion(ctx context.Context, templateName, tag string) error
ListTemplateVersions(templateName string, opts *ListOptions) *TemplateVersionsIterator

ListSubaccounts(opts *ListSubaccountsOptions) *SubaccountsIterator
CreateSubaccount(ctx context.Context, subaccountName string) (SubaccountResponse, error)
SubaccountDetails(ctx context.Context, subaccountId string) (SubaccountResponse, error)
EnableSubaccount(ctx context.Context, subaccountId string) (SubaccountResponse, error)
DisableSubaccount(ctx context.Context, subaccountId string) (SubaccountResponse, error)
}

// MailgunImpl bundles data needed by a large number of methods in order to interact with the Mailgun API.
Expand Down Expand Up @@ -317,6 +326,17 @@ func (mg *MailgunImpl) SetClient(c *http.Client) {
mg.client = c
}

// SetOnBehalfOfSubaccount sets X-Mailgun-On-Behalf-Of header to SUBACCOUNT_ACCOUNT_ID in order to perform API request
// on behalf of subaccount.
func (mg *MailgunImpl) SetOnBehalfOfSubaccount(subaccountId string) {
mg.AddOverrideHeader(OnBehalfOfHeader, subaccountId)
}

// RemoveOnBehalfOfSubaccount remove X-Mailgun-On-Behalf-Of header for primary usage.
func (mg *MailgunImpl) RemoveOnBehalfOfSubaccount() {
delete(mg.overrideHeaders, OnBehalfOfHeader)
}

// SetAPIBase updates the API Base URL for this client.
// // For EU Customers
// mg.SetAPIBase(mailgun.APIBaseEU)
Expand Down Expand Up @@ -402,6 +422,10 @@ func generatePublicApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/%s", m.APIBase(), endpoint)
}

func generateSubaccountsApiUrl(m Mailgun) string {
return fmt.Sprintf("%s/%s/%s", m.APIBase(), accountsEndpoint, subaccountsEndpoint)
}

// generateParameterizedUrl works as generateApiUrl, but supports query parameters.
func generateParameterizedUrl(m Mailgun, endpoint string, payload payload) (string, error) {
paramBuffer, err := payload.getPayloadBuffer()
Expand Down
39 changes: 39 additions & 0 deletions messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,45 @@ func TestAddOverrideHeader(t *testing.T) {
ensure.StringContains(t, mg.GetCurlOutput(), "Host:")
}

func TestOnBehalfOfSubaccount(t *testing.T) {
const (
exampleDomain = "testDomain"
exampleAPIKey = "testAPIKey"
toUser = "[email protected]"
exampleMessage = "Queue. Thank you"
exampleID = "<[email protected]>"
)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ensure.DeepEqual(t, req.Method, http.MethodPost)
ensure.DeepEqual(t, req.URL.Path, fmt.Sprintf("/v3/%s/messages", exampleDomain))
ensure.DeepEqual(t, req.Header.Get("CustomHeader"), "custom-value")
ensure.DeepEqual(t, req.Host, "example.com")
ensure.DeepEqual(t, req.Header.Get(mailgun.OnBehalfOfHeader), "mailgun.subaccount")

rsp := fmt.Sprintf(`{"message":"%s", "id":"%s"}`, exampleMessage, exampleID)
fmt.Fprint(w, rsp)
}))
defer srv.Close()

mg := mailgun.NewMailgun(exampleDomain, exampleAPIKey)
mg.SetAPIBase(srv.URL + "/v3")
mg.AddOverrideHeader("Host", "example.com")
mg.AddOverrideHeader("CustomHeader", "custom-value")
mg.SetOnBehalfOfSubaccount("mailgun.subaccount")
ctx := context.Background()

m := mg.NewMessage(fromUser, exampleSubject, exampleText, toUser)
m.SetRequireTLS(true)
m.SetSkipVerification(true)

msg, id, err := mg.Send(ctx, m)
ensure.Nil(t, err)
ensure.DeepEqual(t, msg, exampleMessage)
ensure.DeepEqual(t, id, exampleID)

ensure.StringContains(t, mg.GetCurlOutput(), "Host:")
}

func TestCaptureCurlOutput(t *testing.T) {
const (
exampleDomain = "testDomain"
Expand Down
9 changes: 9 additions & 0 deletions mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type MockServer interface {
Events() []Event
Webhooks() WebHooksListResponse
Templates() []Template
SubaccountList() []Subaccount
}

// A mailgun api mock suitable for testing
Expand All @@ -47,6 +48,7 @@ type mockServer struct {
credentials []Credential
stats []Stats
tags []Tag
subaccountList []Subaccount
webhooks WebHooksListResponse
mutex sync.Mutex
}
Expand Down Expand Up @@ -105,6 +107,12 @@ func (ms *mockServer) Unsubscribes() []Unsubscribe {
return ms.unsubscribes
}

func (ms *mockServer) SubaccountList() []Subaccount {
defer ms.mutex.Unlock()
ms.mutex.Lock()
return ms.subaccountList
}

// Create a new instance of the mailgun API mock server
func NewMockServer() MockServer {
ms := mockServer{}
Expand All @@ -129,6 +137,7 @@ func NewMockServer() MockServer {
ms.addCredentialsRoutes(r)
ms.addStatsRoutes(r)
ms.addTagsRoutes(r)
ms.addSubaccountRoutes(r)
})
ms.addValidationRoutes(r)

Expand Down
127 changes: 127 additions & 0 deletions mock_subaccounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package mailgun

import (
"net/http"

"github.com/go-chi/chi/v5"
)

func (ms *mockServer) addSubaccountRoutes(r chi.Router) {
ms.subaccountList = append(ms.subaccountList, Subaccount{
Id: "enabled.subaccount",
Name: "mailgun.test",
Status: "enabled",
}, Subaccount{
Id: "disabled.subaccount",
Name: "mailgun.test",
Status: "disabled",
})

r.Get("/accounts/subaccounts", ms.listSubaccounts)
r.Post("/accounts/subaccounts", ms.createSubaccount)

r.Get("/accounts/subaccounts/{subaccountID}", ms.getSubaccount)
r.Post("/accounts/subaccounts/{subaccountID}/enable", ms.enableSubaccount)
r.Post("/accounts/subaccounts/{subaccountID}/disable", ms.disableSubaccount)
}

func (ms *mockServer) listSubaccounts(w http.ResponseWriter, r *http.Request) {
defer ms.mutex.Unlock()
ms.mutex.Lock()

var list subaccountsListResponse
for _, subaccount := range ms.subaccountList {
list.Items = append(list.Items, subaccount)
}

skip := stringToInt(r.FormValue("skip"))
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}

if skip > len(list.Items) {
skip = len(list.Items)
}

end := limit + skip
if end > len(list.Items) {
end = len(list.Items)
}

// If we are at the end of the list
if skip == end {
toJSON(w, subaccountsListResponse{
Total: len(list.Items),
Items: []Subaccount{},
})
return
}

toJSON(w, subaccountsListResponse{
Total: len(list.Items),
Items: list.Items[skip:end],
})
}

func (ms *mockServer) getSubaccount(w http.ResponseWriter, r *http.Request) {
defer ms.mutex.Unlock()
ms.mutex.Lock()

for _, s := range ms.subaccountList {
if s.Id == chi.URLParam(r, "subaccountID") {
toJSON(w, SubaccountResponse{Item: s})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "Not Found"})
}

func (ms *mockServer) createSubaccount(w http.ResponseWriter, r *http.Request) {
defer ms.mutex.Unlock()
ms.mutex.Lock()

ms.subaccountList = append(ms.subaccountList, Subaccount{
Id: "test",
Name: r.FormValue("name"),
Status: "active",
})
toJSON(w, okResp{Message: "Subaccount has been created"})
}

func (ms *mockServer) enableSubaccount(w http.ResponseWriter, r *http.Request) {
defer ms.mutex.Unlock()
ms.mutex.Lock()

for _, subaccount := range ms.subaccountList {
if subaccount.Id == chi.URLParam(r, "subaccountID") && subaccount.Status == "disabled" {
subaccount.Status = "enabled"
toJSON(w, SubaccountResponse{Item: subaccount})
return
}
if subaccount.Id == chi.URLParam(r, "subaccountID") && subaccount.Status == "enabled" {
toJSON(w, okResp{Message: "subaccount is already enabled"})
return
}
}
toJSON(w, okResp{Message: "Not Found"})
}

func (ms *mockServer) disableSubaccount(w http.ResponseWriter, r *http.Request) {
defer ms.mutex.Unlock()
ms.mutex.Lock()

for _, subaccount := range ms.subaccountList {
if subaccount.Id == chi.URLParam(r, "subaccountID") && subaccount.Status == "enabled" {
subaccount.Status = "disabled"
toJSON(w, SubaccountResponse{Item: subaccount})
return
}
if subaccount.Id == chi.URLParam(r, "subaccountID") && subaccount.Status == "disabled" {
toJSON(w, okResp{Message: "subaccount is already disabled"})
return
}
}
toJSON(w, okResp{Message: "Not Found"})
}
Loading

0 comments on commit 88d455c

Please sign in to comment.