// Copyright 2014 Martini Authors // Copyright 2014 The Macaron Authors // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package binding import ( "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var errorTestCases = []errorTestCase{ { description: "No errors", errors: Errors{}, expected: errorTestResult{ statusCode: http.StatusOK, }, }, { description: "Deserialization error", errors: Errors{ { Classification: ERR_DESERIALIZATION, Message: "Some parser error here", }, }, expected: errorTestResult{ statusCode: http.StatusBadRequest, contentType: jsonContentType, body: `[{"classification":"DeserializationError","message":"Some parser error here"}]`, }, }, { description: "Content-Type error", errors: Errors{ { Classification: ERR_CONTENT_TYPE, Message: "Empty Content-Type", }, }, expected: errorTestResult{ statusCode: http.StatusUnsupportedMediaType, contentType: jsonContentType, body: `[{"classification":"ContentTypeError","message":"Empty Content-Type"}]`, }, }, { description: "Requirement error", errors: Errors{ { FieldNames: []string{"some_field"}, Classification: ERR_REQUIRED, Message: "Required", }, }, expected: errorTestResult{ statusCode: http.StatusUnprocessableEntity, contentType: jsonContentType, body: `[{"fieldNames":["some_field"],"classification":"RequiredError","message":"Required"}]`, }, }, { description: "Bad header error", errors: Errors{ { Classification: "HeaderError", Message: "The X-Something header must be specified", }, }, expected: errorTestResult{ statusCode: http.StatusUnprocessableEntity, contentType: jsonContentType, body: `[{"classification":"HeaderError","message":"The X-Something header must be specified"}]`, }, }, { description: "Custom field error", errors: Errors{ { FieldNames: []string{"month", "year"}, Classification: "DateError", Message: "The month and year must be in the future", }, }, expected: errorTestResult{ statusCode: http.StatusUnprocessableEntity, contentType: jsonContentType, body: `[{"fieldNames":["month","year"],"classification":"DateError","message":"The month and year must be in the future"}]`, }, }, { description: "Multiple errors", errors: Errors{ { FieldNames: []string{"foo"}, Classification: ERR_REQUIRED, Message: "Required", }, { FieldNames: []string{"foo"}, Classification: "LengthError", Message: "The length of the 'foo' field is too short", }, }, expected: errorTestResult{ statusCode: http.StatusUnprocessableEntity, contentType: jsonContentType, body: `[{"fieldNames":["foo"],"classification":"RequiredError","message":"Required"},{"fieldNames":["foo"],"classification":"LengthError","message":"The length of the 'foo' field is too short"}]`, }, }, } func Test_ErrorHandler(t *testing.T) { for _, testCase := range errorTestCases { performErrorTest(t, testCase) } } func performErrorTest(t *testing.T, testCase errorTestCase) { resp := httptest.NewRecorder() errorHandler(testCase.errors, resp) assert.Equal(t, testCase.expected.statusCode, resp.Code) assert.Equal(t, testCase.expected.contentType, resp.Header().Get("Content-Type")) actualBody, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Equal(t, testCase.expected.body, string(actualBody)) } type ( errorTestCase struct { description string errors Errors expected errorTestResult } errorTestResult struct { statusCode int contentType string body string } )