406 lines
11 KiB
Go
406 lines
11 KiB
Go
package sendamatic
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNewClient(t *testing.T) {
|
|
client := NewClient("test-user", "test-pass")
|
|
|
|
if client == nil {
|
|
t.Fatal("NewClient returned nil")
|
|
}
|
|
|
|
expectedAPIKey := "test-user-test-pass"
|
|
if client.apiKey != expectedAPIKey {
|
|
t.Errorf("apiKey = %q, want %q", client.apiKey, expectedAPIKey)
|
|
}
|
|
|
|
if client.baseURL != defaultBaseURL {
|
|
t.Errorf("baseURL = %q, want %q", client.baseURL, defaultBaseURL)
|
|
}
|
|
|
|
if client.httpClient == nil {
|
|
t.Fatal("httpClient is nil")
|
|
}
|
|
|
|
if client.httpClient.Timeout != defaultTimeout {
|
|
t.Errorf("httpClient.Timeout = %v, want %v", client.httpClient.Timeout, defaultTimeout)
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_Success(t *testing.T) {
|
|
// Create a test server that returns a successful response
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify request method
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("Method = %s, want POST", r.Method)
|
|
}
|
|
|
|
// Verify request path
|
|
if r.URL.Path != "/send" {
|
|
t.Errorf("Path = %s, want /send", r.URL.Path)
|
|
}
|
|
|
|
// Verify headers
|
|
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
|
|
t.Errorf("Content-Type = %s, want application/json", ct)
|
|
}
|
|
|
|
if apiKey := r.Header.Get("x-api-key"); apiKey != "user-pass" {
|
|
t.Errorf("x-api-key = %s, want user-pass", apiKey)
|
|
}
|
|
|
|
// Verify request body
|
|
body, _ := io.ReadAll(r.Body)
|
|
var msg Message
|
|
if err := json.Unmarshal(body, &msg); err != nil {
|
|
t.Errorf("Failed to unmarshal request body: %v", err)
|
|
}
|
|
|
|
// Send successful response
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := map[string][2]interface{}{
|
|
"recipient@example.com": {float64(200), "msg-12345"},
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
resp, err := client.Send(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("Send() error = %v, want nil", err)
|
|
}
|
|
|
|
if !resp.IsSuccess() {
|
|
t.Error("Expected successful response")
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("StatusCode = %d, want 200", resp.StatusCode)
|
|
}
|
|
|
|
msgID, ok := resp.GetMessageID("recipient@example.com")
|
|
if !ok {
|
|
t.Error("Expected to find message ID")
|
|
}
|
|
if msgID != "msg-12345" {
|
|
t.Errorf("MessageID = %q, want %q", msgID, "msg-12345")
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_ValidationError(t *testing.T) {
|
|
client := NewClient("user", "pass")
|
|
|
|
// Create an invalid message (no recipients)
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
_, err := client.Send(context.Background(), msg)
|
|
if err == nil {
|
|
t.Fatal("Expected validation error, got nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "validation failed") {
|
|
t.Errorf("Error message = %q, want to contain 'validation failed'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_APIError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statusCode int
|
|
responseBody string
|
|
wantErrMessage string
|
|
}{
|
|
{
|
|
name: "400 bad request",
|
|
statusCode: 400,
|
|
responseBody: `{"error": "Invalid request"}`,
|
|
wantErrMessage: "sendamatic api error (status 400): Invalid request",
|
|
},
|
|
{
|
|
name: "401 unauthorized",
|
|
statusCode: 401,
|
|
responseBody: `{"error": "Invalid API key"}`,
|
|
wantErrMessage: "sendamatic api error (status 401): Invalid API key",
|
|
},
|
|
{
|
|
name: "422 validation error",
|
|
statusCode: 422,
|
|
responseBody: `{"error": "Validation failed", "validation_errors": "sender is required", "json_path": "$.sender"}`,
|
|
wantErrMessage: "sendamatic api error (status 422): sender is required (path: $.sender)",
|
|
},
|
|
{
|
|
name: "500 server error",
|
|
statusCode: 500,
|
|
responseBody: `{"error": "Internal server error"}`,
|
|
wantErrMessage: "sendamatic api error (status 500): Internal server error",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(tt.statusCode)
|
|
w.Write([]byte(tt.responseBody))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
_, err := client.Send(context.Background(), msg)
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nil")
|
|
}
|
|
|
|
var apiErr *APIError
|
|
if !errors.As(err, &apiErr) {
|
|
t.Fatalf("Error type = %T, want *APIError", err)
|
|
}
|
|
|
|
if apiErr.StatusCode != tt.statusCode {
|
|
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, tt.statusCode)
|
|
}
|
|
|
|
if err.Error() != tt.wantErrMessage {
|
|
t.Errorf("Error message = %q, want %q", err.Error(), tt.wantErrMessage)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_ContextTimeout(t *testing.T) {
|
|
// Create a server that delays the response
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"recipient@example.com": [200, "msg-12345"]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
// Create a context that times out quickly
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
defer cancel()
|
|
|
|
_, err := client.Send(ctx, msg)
|
|
if err == nil {
|
|
t.Fatal("Expected timeout error, got nil")
|
|
}
|
|
|
|
if !errors.Is(err, context.DeadlineExceeded) && !strings.Contains(err.Error(), "context deadline exceeded") {
|
|
t.Errorf("Expected context deadline exceeded error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_ContextCancellation(t *testing.T) {
|
|
// Create a server that delays the response
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"recipient@example.com": [200, "msg-12345"]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
// Cancel the context immediately
|
|
cancel()
|
|
|
|
_, err := client.Send(ctx, msg)
|
|
if err == nil {
|
|
t.Fatal("Expected cancellation error, got nil")
|
|
}
|
|
|
|
if !errors.Is(err, context.Canceled) && !strings.Contains(err.Error(), "context canceled") {
|
|
t.Errorf("Expected context canceled error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_MultipleRecipients(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := map[string][2]interface{}{
|
|
"recipient1@example.com": {float64(200), "msg-11111"},
|
|
"recipient2@example.com": {float64(200), "msg-22222"},
|
|
"recipient3@example.com": {float64(550), "msg-33333"}, // Failed delivery
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient1@example.com").
|
|
AddTo("recipient2@example.com").
|
|
AddTo("recipient3@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
resp, err := client.Send(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("Send() error = %v, want nil", err)
|
|
}
|
|
|
|
// Check each recipient
|
|
for email, expected := range map[string]struct {
|
|
status int
|
|
msgID string
|
|
}{
|
|
"recipient1@example.com": {200, "msg-11111"},
|
|
"recipient2@example.com": {200, "msg-22222"},
|
|
"recipient3@example.com": {550, "msg-33333"},
|
|
} {
|
|
status, ok := resp.GetStatus(email)
|
|
if !ok {
|
|
t.Errorf("Expected to find status for %s", email)
|
|
continue
|
|
}
|
|
if status != expected.status {
|
|
t.Errorf("Status for %s = %d, want %d", email, status, expected.status)
|
|
}
|
|
|
|
msgID, ok := resp.GetMessageID(email)
|
|
if !ok {
|
|
t.Errorf("Expected to find message ID for %s", email)
|
|
continue
|
|
}
|
|
if msgID != expected.msgID {
|
|
t.Errorf("MessageID for %s = %q, want %q", email, msgID, expected.msgID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_InvalidJSON(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`not valid json`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
_, err := client.Send(context.Background(), msg)
|
|
if err == nil {
|
|
t.Fatal("Expected error for invalid JSON, got nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "unmarshal") {
|
|
t.Errorf("Error should mention unmarshal, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_EmptyResponse(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body")
|
|
|
|
resp, err := client.Send(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("Send() error = %v, want nil", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("StatusCode = %d, want 200", resp.StatusCode)
|
|
}
|
|
|
|
if len(resp.Recipients) != 0 {
|
|
t.Errorf("Recipients length = %d, want 0", len(resp.Recipients))
|
|
}
|
|
}
|
|
|
|
func TestClient_Send_WithAttachments(t *testing.T) {
|
|
var receivedMsg Message
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
json.Unmarshal(body, &receivedMsg)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"recipient@example.com": [200, "msg-12345"]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("user", "pass", WithBaseURL(server.URL))
|
|
|
|
msg := NewMessage().
|
|
SetSender("sender@example.com").
|
|
AddTo("recipient@example.com").
|
|
SetSubject("Test").
|
|
SetTextBody("Body").
|
|
AttachFile("test.txt", "text/plain", []byte("test content"))
|
|
|
|
_, err := client.Send(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("Send() error = %v, want nil", err)
|
|
}
|
|
|
|
// Verify attachment was sent
|
|
if len(receivedMsg.Attachments) != 1 {
|
|
t.Fatalf("Attachments length = %d, want 1", len(receivedMsg.Attachments))
|
|
}
|
|
|
|
if receivedMsg.Attachments[0].Filename != "test.txt" {
|
|
t.Errorf("Filename = %q, want %q", receivedMsg.Attachments[0].Filename, "test.txt")
|
|
}
|
|
}
|