229 lines
5.7 KiB
Go
229 lines
5.7 KiB
Go
|
|
package sendamatic
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestAPIError_Error(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
apiErr *APIError
|
||
|
|
wantText string
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "simple error",
|
||
|
|
apiErr: &APIError{
|
||
|
|
StatusCode: 400,
|
||
|
|
Message: "Invalid request",
|
||
|
|
},
|
||
|
|
wantText: "sendamatic api error (status 400): Invalid request",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "error with validation details",
|
||
|
|
apiErr: &APIError{
|
||
|
|
StatusCode: 422,
|
||
|
|
Message: "Validation failed",
|
||
|
|
ValidationErrors: "sender is required",
|
||
|
|
JSONPath: "$.sender",
|
||
|
|
},
|
||
|
|
wantText: "sendamatic api error (status 422): sender is required (path: $.sender)",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "error with SMTP code",
|
||
|
|
apiErr: &APIError{
|
||
|
|
StatusCode: 500,
|
||
|
|
Message: "SMTP error",
|
||
|
|
SMTPCode: 550,
|
||
|
|
Sender: "test@example.com",
|
||
|
|
},
|
||
|
|
wantText: "sendamatic api error (status 500): SMTP error",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
got := tt.apiErr.Error()
|
||
|
|
if got != tt.wantText {
|
||
|
|
t.Errorf("Error() = %q, want %q", got, tt.wantText)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseErrorResponse_ValidJSON(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
statusCode int
|
||
|
|
body string
|
||
|
|
want *APIError
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "simple error",
|
||
|
|
statusCode: 400,
|
||
|
|
body: `{"error": "Invalid API key"}`,
|
||
|
|
want: &APIError{
|
||
|
|
StatusCode: 400,
|
||
|
|
Message: "Invalid API key",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "validation error",
|
||
|
|
statusCode: 422,
|
||
|
|
body: `{"error": "Validation failed", "validation_errors": "sender is required", "json_path": "$.sender"}`,
|
||
|
|
want: &APIError{
|
||
|
|
StatusCode: 422,
|
||
|
|
Message: "Validation failed",
|
||
|
|
ValidationErrors: "sender is required",
|
||
|
|
JSONPath: "$.sender",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "smtp error",
|
||
|
|
statusCode: 500,
|
||
|
|
body: `{"error": "SMTP error", "smtp_code": 550, "sender": "test@example.com"}`,
|
||
|
|
want: &APIError{
|
||
|
|
StatusCode: 500,
|
||
|
|
Message: "SMTP error",
|
||
|
|
SMTPCode: 550,
|
||
|
|
Sender: "test@example.com",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
err := parseErrorResponse(tt.statusCode, []byte(tt.body))
|
||
|
|
|
||
|
|
apiErr, ok := err.(*APIError)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("parseErrorResponse returned %T, want *APIError", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.StatusCode != tt.want.StatusCode {
|
||
|
|
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, tt.want.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.Message != tt.want.Message {
|
||
|
|
t.Errorf("Message = %q, want %q", apiErr.Message, tt.want.Message)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.ValidationErrors != tt.want.ValidationErrors {
|
||
|
|
t.Errorf("ValidationErrors = %q, want %q", apiErr.ValidationErrors, tt.want.ValidationErrors)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.JSONPath != tt.want.JSONPath {
|
||
|
|
t.Errorf("JSONPath = %q, want %q", apiErr.JSONPath, tt.want.JSONPath)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.SMTPCode != tt.want.SMTPCode {
|
||
|
|
t.Errorf("SMTPCode = %d, want %d", apiErr.SMTPCode, tt.want.SMTPCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.Sender != tt.want.Sender {
|
||
|
|
t.Errorf("Sender = %q, want %q", apiErr.Sender, tt.want.Sender)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseErrorResponse_InvalidJSON(t *testing.T) {
|
||
|
|
statusCode := 500
|
||
|
|
body := []byte("Internal Server Error - not JSON")
|
||
|
|
|
||
|
|
err := parseErrorResponse(statusCode, body)
|
||
|
|
|
||
|
|
apiErr, ok := err.(*APIError)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("parseErrorResponse returned %T, want *APIError", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.StatusCode != statusCode {
|
||
|
|
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, statusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
// When JSON parsing fails, the raw body should be used as the message
|
||
|
|
if apiErr.Message != string(body) {
|
||
|
|
t.Errorf("Message = %q, want %q", apiErr.Message, string(body))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseErrorResponse_EmptyBody(t *testing.T) {
|
||
|
|
statusCode := 404
|
||
|
|
body := []byte("")
|
||
|
|
|
||
|
|
err := parseErrorResponse(statusCode, body)
|
||
|
|
|
||
|
|
apiErr, ok := err.(*APIError)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("parseErrorResponse returned %T, want *APIError", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.StatusCode != statusCode {
|
||
|
|
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, statusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseErrorResponse_MalformedJSON(t *testing.T) {
|
||
|
|
statusCode := 400
|
||
|
|
body := []byte(`{"error": "Missing closing brace"`)
|
||
|
|
|
||
|
|
err := parseErrorResponse(statusCode, body)
|
||
|
|
|
||
|
|
apiErr, ok := err.(*APIError)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("parseErrorResponse returned %T, want *APIError", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if apiErr.StatusCode != statusCode {
|
||
|
|
t.Errorf("StatusCode = %d, want %d", apiErr.StatusCode, statusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Should fall back to raw body as message
|
||
|
|
if apiErr.Message != string(body) {
|
||
|
|
t.Errorf("Message = %q, want %q", apiErr.Message, string(body))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAPIError_JSONRoundtrip(t *testing.T) {
|
||
|
|
original := &APIError{
|
||
|
|
StatusCode: 422,
|
||
|
|
Message: "Validation error",
|
||
|
|
ValidationErrors: "sender format invalid",
|
||
|
|
JSONPath: "$.sender",
|
||
|
|
Sender: "invalid@",
|
||
|
|
SMTPCode: 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Marshal to JSON
|
||
|
|
data, err := json.Marshal(original)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Marshal failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unmarshal back
|
||
|
|
var decoded APIError
|
||
|
|
err = json.Unmarshal(data, &decoded)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Unmarshal failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// StatusCode should not be in JSON (json:"-" tag)
|
||
|
|
if strings.Contains(string(data), "StatusCode") {
|
||
|
|
t.Error("StatusCode should not be marshaled to JSON")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compare fields (except StatusCode which has json:"-")
|
||
|
|
if decoded.Message != original.Message {
|
||
|
|
t.Errorf("Message = %q, want %q", decoded.Message, original.Message)
|
||
|
|
}
|
||
|
|
|
||
|
|
if decoded.ValidationErrors != original.ValidationErrors {
|
||
|
|
t.Errorf("ValidationErrors = %q, want %q", decoded.ValidationErrors, original.ValidationErrors)
|
||
|
|
}
|
||
|
|
|
||
|
|
if decoded.JSONPath != original.JSONPath {
|
||
|
|
t.Errorf("JSONPath = %q, want %q", decoded.JSONPath, original.JSONPath)
|
||
|
|
}
|
||
|
|
}
|