monorepo/cloud/maplefile-backend/pkg/httperror/httperror_test.go

328 lines
7.5 KiB
Go

// File Path: monorepo/cloud/maplefile-backend/pkg/httperror/httperror_test.go
package httperror
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestNew(t *testing.T) {
tests := []struct {
name string
code int
errors map[string]string
wantCode int
}{
{
name: "basic error",
code: http.StatusBadRequest,
errors: map[string]string{"field": "error message"},
wantCode: http.StatusBadRequest,
},
{
name: "empty errors map",
code: http.StatusNotFound,
errors: map[string]string{},
wantCode: http.StatusNotFound,
},
{
name: "multiple errors",
code: http.StatusBadRequest,
errors: map[string]string{"field1": "error1", "field2": "error2"},
wantCode: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := New(tt.code, &tt.errors)
httpErr, ok := err.(HTTPError)
if !ok {
t.Fatal("expected HTTPError type")
}
if httpErr.Code != tt.wantCode {
t.Errorf("Code = %v, want %v", httpErr.Code, tt.wantCode)
}
for k, v := range tt.errors {
if (*httpErr.Errors)[k] != v {
t.Errorf("Errors[%s] = %v, want %v", k, (*httpErr.Errors)[k], v)
}
}
})
}
}
func TestNewForBadRequest(t *testing.T) {
tests := []struct {
name string
errors map[string]string
}{
{
name: "single error",
errors: map[string]string{"field": "error"},
},
{
name: "multiple errors",
errors: map[string]string{"field1": "error1", "field2": "error2"},
},
{
name: "empty errors",
errors: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := NewForBadRequest(&tt.errors)
httpErr, ok := err.(HTTPError)
if !ok {
t.Fatal("expected HTTPError type")
}
if httpErr.Code != http.StatusBadRequest {
t.Errorf("Code = %v, want %v", httpErr.Code, http.StatusBadRequest)
}
for k, v := range tt.errors {
if (*httpErr.Errors)[k] != v {
t.Errorf("Errors[%s] = %v, want %v", k, (*httpErr.Errors)[k], v)
}
}
})
}
}
func TestNewForSingleField(t *testing.T) {
tests := []struct {
name string
code int
field string
message string
}{
{
name: "basic error",
code: http.StatusBadRequest,
field: "test",
message: "error",
},
{
name: "empty field",
code: http.StatusNotFound,
field: "",
message: "error",
},
{
name: "empty message",
code: http.StatusBadRequest,
field: "field",
message: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := NewForSingleField(tt.code, tt.field, tt.message)
httpErr, ok := err.(HTTPError)
if !ok {
t.Fatal("expected HTTPError type")
}
if httpErr.Code != tt.code {
t.Errorf("Code = %v, want %v", httpErr.Code, tt.code)
}
if (*httpErr.Errors)[tt.field] != tt.message {
t.Errorf("Errors[%s] = %v, want %v", tt.field, (*httpErr.Errors)[tt.field], tt.message)
}
})
}
}
func TestError(t *testing.T) {
tests := []struct {
name string
errors map[string]string
wantErr bool
}{
{
name: "valid json",
errors: map[string]string{"field": "error"},
wantErr: false,
},
{
name: "empty map",
errors: map[string]string{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := HTTPError{
Code: http.StatusBadRequest,
Errors: &tt.errors,
}
errStr := err.Error()
var jsonMap map[string]string
if jsonErr := json.Unmarshal([]byte(errStr), &jsonMap); (jsonErr != nil) != tt.wantErr {
t.Errorf("Error() json.Unmarshal error = %v, wantErr %v", jsonErr, tt.wantErr)
return
}
if !tt.wantErr {
for k, v := range tt.errors {
if jsonMap[k] != v {
t.Errorf("Error() jsonMap[%s] = %v, want %v", k, jsonMap[k], v)
}
}
}
})
}
}
func TestResponseError(t *testing.T) {
tests := []struct {
name string
err error
wantCode int
wantContent string
}{
{
name: "http error",
err: NewForBadRequestWithSingleField("field", "invalid"),
wantCode: http.StatusBadRequest,
wantContent: `{"field":"invalid"}`,
},
{
name: "standard error",
err: fmt.Errorf("standard error"),
wantCode: http.StatusInternalServerError,
wantContent: `"standard error"`,
},
{
name: "nil error",
err: errors.New("<nil>"),
wantCode: http.StatusInternalServerError,
wantContent: `"\u003cnil\u003e"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rr := httptest.NewRecorder()
ResponseError(rr, tt.err)
// Check status code
if rr.Code != tt.wantCode {
t.Errorf("ResponseError() code = %v, want %v", rr.Code, tt.wantCode)
}
// Check content type
if ct := rr.Header().Get("Content-Type"); ct != "Application/json" {
t.Errorf("ResponseError() Content-Type = %v, want Application/json", ct)
}
// Trim newline from response for comparison
got := rr.Body.String()
got = got[:len(got)-1] // Remove trailing newline added by json.Encoder
if got != tt.wantContent {
t.Errorf("ResponseError() content = %v, want %v", got, tt.wantContent)
}
})
}
}
func TestErrorWrapping(t *testing.T) {
originalErr := errors.New("original error")
wrappedErr := fmt.Errorf("wrapped: %w", originalErr)
httpErr := NewForBadRequestWithSingleField("field", wrappedErr.Error())
// Test error unwrapping
if !errors.Is(httpErr, httpErr) {
t.Error("errors.Is failed for same error")
}
var targetErr HTTPError
if !errors.As(httpErr, &targetErr) {
t.Error("errors.As failed to get HTTPError")
}
}
// Test all convenience constructors
func TestConvenienceConstructors(t *testing.T) {
tests := []struct {
name string
create func() error
wantCode int
}{
{
name: "NewForBadRequestWithSingleField",
create: func() error {
return NewForBadRequestWithSingleField("field", "message")
},
wantCode: http.StatusBadRequest,
},
{
name: "NewForNotFoundWithSingleField",
create: func() error {
return NewForNotFoundWithSingleField("field", "message")
},
wantCode: http.StatusNotFound,
},
{
name: "NewForServiceUnavailableWithSingleField",
create: func() error {
return NewForServiceUnavailableWithSingleField("field", "message")
},
wantCode: http.StatusServiceUnavailable,
},
{
name: "NewForLockedWithSingleField",
create: func() error {
return NewForLockedWithSingleField("field", "message")
},
wantCode: http.StatusLocked,
},
{
name: "NewForForbiddenWithSingleField",
create: func() error {
return NewForForbiddenWithSingleField("field", "message")
},
wantCode: http.StatusForbidden,
},
{
name: "NewForUnauthorizedWithSingleField",
create: func() error {
return NewForUnauthorizedWithSingleField("field", "message")
},
wantCode: http.StatusUnauthorized,
},
{
name: "NewForGoneWithSingleField",
create: func() error {
return NewForGoneWithSingleField("field", "message")
},
wantCode: http.StatusGone,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.create()
httpErr, ok := err.(HTTPError)
if !ok {
t.Fatal("expected HTTPError type")
}
if httpErr.Code != tt.wantCode {
t.Errorf("Code = %v, want %v", httpErr.Code, tt.wantCode)
}
if (*httpErr.Errors)["field"] != "message" {
t.Errorf("Error message = %v, want 'message'", (*httpErr.Errors)["field"])
}
})
}
}