Skip to content

chore(api-error): change unauthorized msg and add tests #2190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func NewBadRequestError(err error) *APIError {
func NewUnauthorizedError(err error) *APIError {
return &APIError{
StatusCode: http.StatusUnauthorized,
Message: err.Error(),
Message: "Unauthorized",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When dealing with 401's we shouldn't be exposing any context around what happened.

err: err,
}
}
Expand Down
264 changes: 264 additions & 0 deletions api/types/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAppendFieldError(t *testing.T) {
Expand Down Expand Up @@ -51,3 +55,263 @@ func TestAppendFieldError(t *testing.T) {
})
}
}

func TestNewAPIError(t *testing.T) {
tests := []struct {
name string
err error
newFunction func(err error) *APIError
want struct {
statusCode int
message string
}
}{
{
name: "BadRequestError",
err: errors.New("bad request"),
newFunction: NewBadRequestError,
want: struct {
statusCode int
message string
}{
statusCode: http.StatusBadRequest,
message: "bad request",
},
},
{
name: "UnauthorizedError",
err: errors.New("auth failed"),
newFunction: NewUnauthorizedError,
want: struct {
statusCode int
message string
}{
statusCode: http.StatusUnauthorized,
message: "Unauthorized",
},
},
{
name: "InternalServerError",
newFunction: NewInternalServerError,
err: errors.New("internal server error"),
want: struct {
statusCode int
message string
}{
statusCode: http.StatusInternalServerError,
message: "internal server error",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
apiErr := tt.newFunction(tt.err)

assert.Equal(t, tt.want.statusCode, apiErr.StatusCode, "StatusCode should match")
assert.Equal(t, tt.want.message, apiErr.Message, "Message should match")
assert.Equal(t, tt.err, apiErr.err, "Original error should be stored")
})
}
}

func TestAPIError_Error(t *testing.T) {
tests := []struct {
name string
apiError *APIError
expected string
}{
{
name: "nil error",
apiError: nil,
expected: "",
},
{
name: "error with message, no sub-errors",
apiError: &APIError{
Message: "main error message",
Errors: []*APIError{},
},
expected: "main error message",
},
{
name: "error with message and one sub-error",
apiError: &APIError{
Message: "main error message",
Errors: []*APIError{
{Message: "sub-error 1"},
},
},
expected: "main error message: sub-error 1",
},
{
name: "error with message and multiple sub-errors",
apiError: &APIError{
Message: "main error message",
Errors: []*APIError{
{Message: "sub-error 1"},
{Message: "sub-error 2"},
{Message: "sub-error 3"},
},
},
expected: "main error message: sub-error 1; sub-error 2; sub-error 3",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.apiError.Error()
assert.Equal(t, tt.expected, got, "Error() should return the expected string")
})
}
}

func TestAPIError_ErrorOrNil(t *testing.T) {
tests := []struct {
name string
err *APIError
wantNil bool
}{
{
name: "nil error",
err: (*APIError)(nil),
wantNil: true,
},
{
name: "error without child errors",
err: &APIError{
StatusCode: http.StatusBadRequest,
Message: "bad request",
Errors: nil,
},
wantNil: true,
},
{
name: "error with empty errors slice",
err: &APIError{
StatusCode: http.StatusBadRequest,
Message: "bad request",
Errors: []*APIError{},
},
wantNil: true,
},
{
name: "error with child errors",
err: &APIError{
StatusCode: http.StatusBadRequest,
Message: "validation failed",
Errors: []*APIError{
{
Message: "field1: invalid value",
Field: "field1",
},
},
},
wantNil: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.err.ErrorOrNil()

if tt.wantNil {
assert.Nil(t, result, "ErrorOrNil() should return nil")
} else {
assert.NotNil(t, result, "ErrorOrNil() should not return nil")
assert.Equal(t, tt.err, result, "ErrorOrNil() should return the error itself")
}
})
}
}

func TestAPIError_JSON(t *testing.T) {
tests := []struct {
name string
apiErr *APIError
wantCode int
wantJSON map[string]any
}{
{
name: "simple error",
apiErr: &APIError{
StatusCode: http.StatusInternalServerError,
Message: "invalid request",
},
wantCode: http.StatusInternalServerError,
wantJSON: map[string]any{
"status_code": float64(http.StatusInternalServerError),
"message": "invalid request",
},
},
{
name: "field error",
apiErr: &APIError{
StatusCode: http.StatusBadRequest,
Message: "validation error",
Field: "username",
},
wantCode: http.StatusBadRequest,
wantJSON: map[string]any{
"status_code": float64(http.StatusBadRequest),
"message": "validation error",
"field": "username",
},
},
{
name: "error with nested errors",
apiErr: &APIError{
StatusCode: http.StatusBadRequest,
Message: "multiple validation errors",
Errors: []*APIError{
{
Message: "field1 is required",
Field: "field1",
},
{
Message: "field2 must be a number",
Field: "field2",
},
},
},
wantCode: http.StatusBadRequest,
wantJSON: map[string]any{
"status_code": float64(http.StatusBadRequest),
"message": "multiple validation errors",
"errors": []any{
map[string]any{
"message": "field1 is required",
"field": "field1",
},
map[string]any{
"message": "field2 must be a number",
"field": "field2",
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a mock HTTP response recorder
rec := httptest.NewRecorder()

// Call the JSON method
tt.apiErr.JSON(rec)

// Check status code
assert.Equal(t, tt.wantCode, rec.Code, "Status code should match")

// Check content type header
contentType := rec.Header().Get("Content-Type")
assert.Equal(t, "application/json", contentType, "Content-Type header should be application/json")

// Parse and check the JSON response
var gotJSON map[string]any
err := json.Unmarshal(rec.Body.Bytes(), &gotJSON)
assert.NoError(t, err, "Should be able to parse the JSON response")
assert.Equal(t, tt.wantJSON, gotJSON, "JSON response should match expected structure")
})
}
}
Loading