// 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(""), 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"]) } }) } }