-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ROX-23252: Connect email sender service to the endpoint (#1848)
* Add SendRawEmail method * Connect email sender service to the endpoint * Update emailsender/cmd/app/main.go Co-authored-by: Johannes Malsam <[email protected]> --------- Co-authored-by: Johannes Malsam <[email protected]>
- Loading branch information
1 parent
4306c60
commit 929ec23
Showing
7 changed files
with
266 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ import ( | |
"github.com/golang/glog" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/config" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/pkg/api" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/pkg/email" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/pkg/metrics" | ||
) | ||
|
||
|
@@ -39,9 +40,19 @@ func main() { | |
|
||
ctx := context.Background() | ||
|
||
// initialize components | ||
sesClient, err := email.NewSES(ctx) | ||
if err != nil { | ||
glog.Errorf("Failed to initialise SES Client: %v", err) | ||
os.Exit(1) | ||
} | ||
temporarySenderName := "[email protected]" | ||
emailSender := email.NewEmailSender(temporarySenderName, sesClient) | ||
emailHandler := api.NewEmailHandler(emailSender) | ||
|
||
// base router | ||
router := mux.NewRouter() | ||
api.SetupRoutes(router) | ||
api.SetupRoutes(router, emailHandler) | ||
|
||
server := http.Server{Addr: cfg.ServerAddress, Handler: router} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/golang/glog" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/pkg/email" | ||
"net/http" | ||
) | ||
|
||
type EmailHandler struct { | ||
emailSender email.Sender | ||
} | ||
|
||
// SendEmailRequest represents API requests for sending email | ||
type SendEmailRequest struct { | ||
To []string | ||
RawMessage []byte | ||
} | ||
|
||
type Envelope map[string]interface{} | ||
|
||
func NewEmailHandler(emailSender email.Sender) *EmailHandler { | ||
return &EmailHandler{ | ||
emailSender: emailSender, | ||
} | ||
} | ||
|
||
func (eh *EmailHandler) SendEmail(w http.ResponseWriter, r *http.Request) { | ||
var request SendEmailRequest | ||
|
||
jsonDecoder := json.NewDecoder(r.Body) | ||
jsonDecoder.DisallowUnknownFields() | ||
|
||
if err := jsonDecoder.Decode(&request); err != nil { | ||
eh.errorResponse(w, "Cannot decode send email request payload", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
if err := eh.emailSender.Send(r.Context(), request.To, request.RawMessage); err != nil { | ||
eh.errorResponse(w, "Cannot send email", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
envelope := Envelope{ | ||
"status": "sent", | ||
} | ||
if err := eh.jsonResponse(w, envelope, http.StatusOK); err != nil { | ||
glog.Errorf("Failed creating json response: %v", err) | ||
http.Error(w, "Cannot create json response", http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
func (eh *EmailHandler) jsonResponse(w http.ResponseWriter, envelop Envelope, statusCode int) error { | ||
j, err := json.Marshal(envelop) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal: %v", err) | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(statusCode) | ||
|
||
_, err = w.Write(j) | ||
if err != nil { | ||
return fmt.Errorf("failed to write json response: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (eh *EmailHandler) errorResponse(w http.ResponseWriter, message string, statusCode int) { | ||
envelope := Envelope{ | ||
"error": message, | ||
} | ||
|
||
if err := eh.jsonResponse(w, envelope, statusCode); err != nil { | ||
glog.Errorf("Failed creating error json response: %v", err) | ||
http.Error(w, "Can not create error json response", http.StatusInternalServerError) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package api | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"github.com/stackrox/acs-fleet-manager/emailsender/pkg/email" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
type MockEmailSender struct { | ||
SendFunc func(ctx context.Context, to []string, rawMessage []byte) error | ||
} | ||
|
||
func (m *MockEmailSender) Send(ctx context.Context, to []string, rawMessage []byte) error { | ||
return m.SendFunc(ctx, to, rawMessage) | ||
} | ||
|
||
var simpleEmailSender = &MockEmailSender{ | ||
SendFunc: func(ctx context.Context, to []string, rawMessage []byte) error { | ||
return nil | ||
}, | ||
} | ||
|
||
func TestEmailHandler_SendEmail(t *testing.T) { | ||
subject := "Test subject" | ||
textBody := "text body" | ||
var messageBuf bytes.Buffer | ||
messageBuf.WriteString(fmt.Sprintf("Subject: %s\r\n", subject)) | ||
messageBuf.WriteString(textBody) | ||
rawMessage := messageBuf.Bytes() | ||
|
||
sendEmailRequest := SendEmailRequest{ | ||
To: []string{"[email protected]", "[email protected]"}, | ||
RawMessage: rawMessage, | ||
} | ||
jsonReq, _ := json.Marshal(sendEmailRequest) | ||
invalidJsonReq, _ := json.Marshal(map[string]string{ | ||
"invalid": "JSON", | ||
}) | ||
|
||
tests := []struct { | ||
name string | ||
emailSender email.Sender | ||
req *http.Request | ||
wantCode int | ||
wantBody string | ||
}{ | ||
{ | ||
name: "should return JSON response with StatusOK to a valid email request", | ||
emailSender: simpleEmailSender, | ||
req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonReq)), | ||
wantCode: http.StatusOK, | ||
wantBody: `{"status":"sent"}`, | ||
}, | ||
{ | ||
name: "should return JSON error with StatusBadRequest when cannot decode request", | ||
emailSender: simpleEmailSender, | ||
req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(invalidJsonReq)), | ||
wantCode: http.StatusBadRequest, | ||
wantBody: `{"error":"Cannot decode send email request payload"}`, | ||
}, | ||
{ | ||
name: "should return JSON error with StatusInternalServerError when cannot send email", | ||
emailSender: &MockEmailSender{ | ||
SendFunc: func(ctx context.Context, to []string, rawMessage []byte) error { | ||
return errors.New("failed to send email") | ||
}, | ||
}, | ||
req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonReq)), | ||
wantCode: http.StatusInternalServerError, | ||
wantBody: `{"error":"Cannot send email"}`, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
eh := &EmailHandler{ | ||
emailSender: tt.emailSender, | ||
} | ||
resp := httptest.NewRecorder() | ||
eh.SendEmail(resp, tt.req) | ||
|
||
if resp.Result().StatusCode != tt.wantCode { | ||
t.Errorf("expected status code %d, got %d", tt.wantCode, resp.Result().StatusCode) | ||
} | ||
|
||
if resp.Body.String() != tt.wantBody { | ||
t.Errorf("expected body %s, got %s", tt.wantBody, resp.Body.String()) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package email | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/golang/glog" | ||
) | ||
|
||
type Sender interface { | ||
Send(ctx context.Context, to []string, rawMessage []byte) error | ||
} | ||
|
||
type MailSender struct { | ||
from string | ||
ses *SES | ||
} | ||
|
||
func NewEmailSender(from string, ses *SES) *MailSender { | ||
return &MailSender{ | ||
from: from, | ||
ses: ses, | ||
} | ||
} | ||
|
||
func (s *MailSender) Send(ctx context.Context, to []string, rawMessage []byte) error { | ||
_, err := s.ses.SendRawEmail(ctx, s.from, to, rawMessage) | ||
if err != nil { | ||
glog.Errorf("Failed sending email: %v", err) | ||
return fmt.Errorf("failed to send email: %v", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package email | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go-v2/service/ses" | ||
) | ||
|
||
func TestSend_Success(t *testing.T) { | ||
from := "[email protected]" | ||
to := []string{"[email protected]", "[email protected]"} | ||
subject := "Test subject" | ||
textBody := "text body" | ||
var messageBuf bytes.Buffer | ||
messageBuf.WriteString(fmt.Sprintf("Subject: %s\r\n", subject)) | ||
messageBuf.WriteString(textBody) | ||
rawMessage := messageBuf.Bytes() | ||
called := false | ||
|
||
mockClient := &MockSESClient{ | ||
SendRawEmailFunc: func(ctx context.Context, params *ses.SendRawEmailInput, optFns ...func(*ses.Options)) (*ses.SendRawEmailOutput, error) { | ||
called = true | ||
return &ses.SendRawEmailOutput{ | ||
MessageId: aws.String("test-message-id"), | ||
}, nil | ||
}, | ||
} | ||
mockedSES := &SES{sesClient: mockClient} | ||
mockedSender := MailSender{ | ||
from, | ||
mockedSES, | ||
} | ||
|
||
err := mockedSender.Send(context.Background(), to, rawMessage) | ||
|
||
assert.NoError(t, err) | ||
assert.True(t, called) | ||
} |