package email import ( "context" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" ) func TestCloudflareSenderSend(t *testing.T) { var got cloudflareRequest var gotAuthorization string var gotPath string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuthorization = r.Header.Get("Authorization") gotPath = r.URL.Path body, _ := io.ReadAll(r.Body) _ = json.Unmarshal(body, &got) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"errors":[],"messages":[],"result":{"delivered":["alice@example.com"],"message_id":"message-1","permanent_bounces":[],"queued":[]}}`)) })) defer server.Close() sender := NewCloudflareSender("account-1", "token-1", "notifications@example.com", server.URL, testLogger(t)) if err := sender.Send(context.Background(), []string{"alice@example.com"}, "Hello", "World"); err != nil { t.Fatalf("Send: %v", err) } if gotAuthorization != "Bearer token-1" { t.Errorf("authorization = %q", gotAuthorization) } if gotPath != "/accounts/account-1/email/sending/send" { t.Errorf("path = %q", gotPath) } if got.From != "notifications@example.com" || got.Subject != "Hello" || got.Text != "World" { t.Errorf("request = %#v", got) } if len(got.To) != 1 || got.To[0] != "alice@example.com" { t.Errorf("to = %#v", got.To) } } func TestCloudflareSenderRetryableResponses(t *testing.T) { for _, status := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} { t.Run(http.StatusText(status), func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) _, _ = w.Write([]byte(`{"success":false}`)) })) defer server.Close() sender := NewCloudflareSender("account", "token", "from@example.com", server.URL, testLogger(t)) err := sender.Send(context.Background(), []string{"to@example.com"}, "subject", "body") var temporary *TemporaryError if !errors.As(err, &temporary) { t.Fatalf("error = %T %v, want TemporaryError", err, err) } }) } } func TestCloudflareSenderPermanentRejection(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(`{"success":false,"errors":[{"code":1000,"message":"Sender domain not verified"}]}`)) })) defer server.Close() sender := NewCloudflareSender("account", "token", "from@example.com", server.URL, testLogger(t)) err := sender.Send(context.Background(), []string{"to@example.com"}, "subject", "body") if err == nil || !strings.Contains(err.Error(), "400") { t.Fatalf("error = %v, want permanent 400 rejection", err) } var temporary *TemporaryError if errors.As(err, &temporary) { t.Fatalf("error = %T, should not be retryable", err) } } func TestCloudflareSenderAPIErrorAndBounce(t *testing.T) { tests := []struct { name string body string want string }{ {"api error", `{"success":false,"errors":[{"code":1000,"message":"Sender domain not verified"}],"result":null}`, "Sender domain not verified"}, {"bounce", `{"success":true,"errors":[],"result":{"delivered":[],"message_id":"message-1","permanent_bounces":["to@example.com"],"queued":[]}}`, "permanently bounced"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(test.body)) })) defer server.Close() sender := NewCloudflareSender("account", "token", "from@example.com", server.URL, testLogger(t)) err := sender.Send(context.Background(), []string{"to@example.com"}, "subject", "body") if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want substring %q", err, test.want) } }) } } func TestCloudflareSenderValidation(t *testing.T) { tests := []struct { name string accountID string token string from string to []string }{ {"account", "", "token", "from@example.com", []string{"to@example.com"}}, {"token", "account", "", "from@example.com", []string{"to@example.com"}}, {"from", "account", "token", "", []string{"to@example.com"}}, {"recipient", "account", "token", "from@example.com", nil}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { sender := NewCloudflareSender(test.accountID, test.token, test.from, "", testLogger(t)) if err := sender.Send(context.Background(), test.to, "subject", "body"); err == nil { t.Fatal("expected validation error") } }) } }