feat: make error handling more verbose and collect more error information from Unifi Controller, support API V2 error format
190 lines
4.8 KiB
Go
190 lines
4.8 KiB
Go
package unifi //nolint: testpackage
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestServerError_Error(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
input ServerError
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Error with message and validation error",
|
|
input: ServerError{
|
|
StatusCode: 400,
|
|
RequestMethod: "GET",
|
|
RequestURL: "http://example.com",
|
|
Message: "Bad Request",
|
|
Details: []ServerErrorDetails{
|
|
{
|
|
Message: "Invalid input",
|
|
ValidationError: ServerValidationError{
|
|
Field: "username",
|
|
Pattern: "^[a-zA-Z0-9]+$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: "Server error (400) for GET http://example.com: Bad Request\nInvalid input: field 'username' should match '^[a-zA-Z0-9]+$'",
|
|
},
|
|
{
|
|
name: "Error with message only",
|
|
input: ServerError{
|
|
StatusCode: 404,
|
|
RequestMethod: "POST",
|
|
RequestURL: "http://example.com",
|
|
Message: "Not Found",
|
|
},
|
|
expected: "Server error (404) for POST http://example.com: Not Found",
|
|
},
|
|
{
|
|
name: "Error with multiple validation errors",
|
|
input: ServerError{
|
|
StatusCode: 422,
|
|
RequestMethod: "PUT",
|
|
RequestURL: "http://example.com",
|
|
Message: "Unprocessable Entity",
|
|
Details: []ServerErrorDetails{
|
|
{
|
|
Message: "Invalid username",
|
|
ValidationError: ServerValidationError{
|
|
Field: "username",
|
|
Pattern: "^[a-zA-Z0-9]+$",
|
|
},
|
|
},
|
|
{
|
|
Message: "Invalid email",
|
|
ValidationError: ServerValidationError{
|
|
Field: "email",
|
|
Pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: "Server error (422) for PUT http://example.com: Unprocessable Entity\nInvalid username: field 'username' should match '^[a-zA-Z0-9]+$'\nInvalid email: field 'email' should match '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'",
|
|
},
|
|
{
|
|
name: "Error with no details",
|
|
input: ServerError{
|
|
StatusCode: 500,
|
|
RequestMethod: "DELETE",
|
|
RequestURL: "http://example.com",
|
|
Message: "Internal Server Error",
|
|
},
|
|
expected: "Server error (500) for DELETE http://example.com: Internal Server Error",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
a := assert.New(t)
|
|
a.Equal(tt.expected, tt.input.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleError(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
responseBody string
|
|
statusCode int
|
|
expectedError *ServerError
|
|
}{
|
|
{
|
|
name: "V2 error with invalid fields",
|
|
responseBody: `{
|
|
"code": "error_code",
|
|
"message": "error message",
|
|
"details": {
|
|
"invalid_fields": ["field1", "field2"]
|
|
}
|
|
}`,
|
|
statusCode: 400,
|
|
expectedError: &ServerError{
|
|
StatusCode: 400,
|
|
RequestMethod: "GET",
|
|
RequestURL: "http://example.com",
|
|
Message: "error message",
|
|
ErrorCode: "error_code",
|
|
Details: []ServerErrorDetails{
|
|
{ValidationError: ServerValidationError{Field: "field1"}},
|
|
{ValidationError: ServerValidationError{Field: "field2"}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "V1 error with validation error",
|
|
responseBody: `{
|
|
"Meta": {"rc": "error", "msg": "meta error message"},
|
|
"data": [{
|
|
"Meta": {"rc": "error", "msg": "data meta error message"},
|
|
"validationError": {"field": "field1", "pattern": "pattern1"},
|
|
"rc": "error",
|
|
"msg": "data error message"
|
|
}]
|
|
}`,
|
|
statusCode: 400,
|
|
expectedError: &ServerError{
|
|
StatusCode: 400,
|
|
RequestMethod: "GET",
|
|
RequestURL: "http://example.com",
|
|
Message: "meta error message",
|
|
ErrorCode: "error",
|
|
Details: []ServerErrorDetails{
|
|
{
|
|
Message: "data error message",
|
|
ValidationError: ServerValidationError{
|
|
Field: "field1",
|
|
Pattern: "pattern1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "No error",
|
|
responseBody: `{"Meta": {"rc": "ok", "msg": "success"}}`,
|
|
statusCode: 200,
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
a := assert.New(t)
|
|
handler := &DefaultResponseErrorHandler{}
|
|
|
|
// Create a new HTTP response recorder
|
|
recorder := httptest.NewRecorder()
|
|
recorder.WriteHeader(tt.statusCode)
|
|
recorder.Body = bytes.NewBufferString(tt.responseBody)
|
|
|
|
// Create a new HTTP request
|
|
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
|
recorder.Result().Request = req
|
|
|
|
// Call the HandleError function
|
|
err := handler.HandleError(recorder.Result())
|
|
|
|
if tt.expectedError == nil {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.Error(t, err)
|
|
a.EqualValues(tt.expectedError, err)
|
|
}
|
|
})
|
|
}
|
|
}
|