github.com/minio/minio-go/v6@v6.0.57/api-error-response_test.go (about) 1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2017 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "bytes" 22 "encoding/xml" 23 "fmt" 24 "io/ioutil" 25 "net/http" 26 "reflect" 27 "strconv" 28 "testing" 29 ) 30 31 // Tests validate the Error generator function for http response with error. 32 func TestHttpRespToErrorResponse(t *testing.T) { 33 // 'genAPIErrorResponse' generates ErrorResponse for given APIError. 34 // provides a encodable populated response values. 35 genAPIErrorResponse := func(err APIError, bucketName string) ErrorResponse { 36 return ErrorResponse{ 37 Code: err.Code, 38 Message: err.Description, 39 BucketName: bucketName, 40 } 41 } 42 43 // Encodes the response headers into XML format. 44 encodeErr := func(response ErrorResponse) []byte { 45 buf := &bytes.Buffer{} 46 buf.WriteString(xml.Header) 47 encoder := xml.NewEncoder(buf) 48 err := encoder.Encode(response) 49 if err != nil { 50 t.Fatalf("error encoding response: %v", err) 51 } 52 return buf.Bytes() 53 } 54 55 // `createAPIErrorResponse` Mocks XML error response from the server. 56 createAPIErrorResponse := func(APIErr APIError, bucketName string) *http.Response { 57 // generate error response. 58 // response body contains the XML error message. 59 resp := &http.Response{} 60 errorResponse := genAPIErrorResponse(APIErr, bucketName) 61 encodedErrorResponse := encodeErr(errorResponse) 62 // write Header. 63 resp.StatusCode = APIErr.HTTPStatusCode 64 resp.Body = ioutil.NopCloser(bytes.NewBuffer(encodedErrorResponse)) 65 66 return resp 67 } 68 69 // 'genErrResponse' contructs error response based http Status Code 70 genErrResponse := func(resp *http.Response, code, message, bucketName, objectName string) ErrorResponse { 71 errResp := ErrorResponse{ 72 StatusCode: resp.StatusCode, 73 Code: code, 74 Message: message, 75 BucketName: bucketName, 76 Key: objectName, 77 RequestID: resp.Header.Get("x-amz-request-id"), 78 HostID: resp.Header.Get("x-amz-id-2"), 79 Region: resp.Header.Get("x-amz-bucket-region"), 80 } 81 return errResp 82 } 83 84 // Generate invalid argument error. 85 genInvalidError := func(message string) error { 86 errResp := ErrorResponse{ 87 StatusCode: http.StatusBadRequest, 88 Code: "InvalidArgument", 89 Message: message, 90 RequestID: "minio", 91 } 92 return errResp 93 } 94 95 // Set common http response headers. 96 setCommonHeaders := func(resp *http.Response) *http.Response { 97 // set headers. 98 resp.Header = make(http.Header) 99 resp.Header.Set("x-amz-request-id", "xyz") 100 resp.Header.Set("x-amz-id-2", "abc") 101 resp.Header.Set("x-amz-bucket-region", "us-east-1") 102 return resp 103 } 104 105 // Generate http response with empty body. 106 // Set the StatusCode to the argument supplied. 107 // Sets common headers. 108 genEmptyBodyResponse := func(statusCode int) *http.Response { 109 resp := &http.Response{ 110 StatusCode: statusCode, 111 Body: ioutil.NopCloser(bytes.NewReader(nil)), 112 } 113 setCommonHeaders(resp) 114 return resp 115 } 116 117 // Decode XML error message from the http response body. 118 decodeXMLError := func(resp *http.Response) error { 119 errResp := ErrorResponse{ 120 StatusCode: resp.StatusCode, 121 } 122 err := xmlDecoder(resp.Body, &errResp) 123 if err != nil { 124 t.Fatalf("XML decoding of response body failed: %v", err) 125 } 126 return errResp 127 } 128 129 // List of APIErrors used to generate/mock server side XML error response. 130 APIErrors := []APIError{ 131 { 132 Code: "NoSuchBucketPolicy", 133 Description: "The specified bucket does not have a bucket policy.", 134 HTTPStatusCode: http.StatusNotFound, 135 }, 136 } 137 138 // List of expected response. 139 // Used for asserting the actual response. 140 expectedErrResponse := []error{ 141 genInvalidError("Response is empty. " + "Please report this issue at https://github.com/minio/minio-go/issues."), 142 decodeXMLError(createAPIErrorResponse(APIErrors[0], "minio-bucket")), 143 genErrResponse(setCommonHeaders(&http.Response{StatusCode: http.StatusNotFound}), "NoSuchBucket", "The specified bucket does not exist.", "minio-bucket", ""), 144 genErrResponse(setCommonHeaders(&http.Response{StatusCode: http.StatusNotFound}), "NoSuchKey", "The specified key does not exist.", "minio-bucket", "Asia/"), 145 genErrResponse(setCommonHeaders(&http.Response{StatusCode: http.StatusForbidden}), "AccessDenied", "Access Denied.", "minio-bucket", ""), 146 genErrResponse(setCommonHeaders(&http.Response{StatusCode: http.StatusConflict}), "Conflict", "Bucket not empty.", "minio-bucket", ""), 147 genErrResponse(setCommonHeaders(&http.Response{StatusCode: http.StatusBadRequest}), "Bad Request", "Bad Request", "minio-bucket", ""), 148 } 149 150 // List of http response to be used as input. 151 inputResponses := []*http.Response{ 152 nil, 153 createAPIErrorResponse(APIErrors[0], "minio-bucket"), 154 genEmptyBodyResponse(http.StatusNotFound), 155 genEmptyBodyResponse(http.StatusNotFound), 156 genEmptyBodyResponse(http.StatusForbidden), 157 genEmptyBodyResponse(http.StatusConflict), 158 genEmptyBodyResponse(http.StatusBadRequest), 159 } 160 161 testCases := []struct { 162 bucketName string 163 objectName string 164 inputHTTPResp *http.Response 165 // expected results. 166 expectedResult error 167 // flag indicating whether tests should pass. 168 169 }{ 170 {"minio-bucket", "", inputResponses[0], expectedErrResponse[0]}, 171 {"minio-bucket", "", inputResponses[1], expectedErrResponse[1]}, 172 {"minio-bucket", "", inputResponses[2], expectedErrResponse[2]}, 173 {"minio-bucket", "Asia/", inputResponses[3], expectedErrResponse[3]}, 174 {"minio-bucket", "", inputResponses[4], expectedErrResponse[4]}, 175 {"minio-bucket", "", inputResponses[5], expectedErrResponse[5]}, 176 } 177 178 for i, testCase := range testCases { 179 actualResult := httpRespToErrorResponse(testCase.inputHTTPResp, testCase.bucketName, testCase.objectName) 180 if !reflect.DeepEqual(testCase.expectedResult, actualResult) { 181 t.Errorf("Test %d: Expected result to be '%#v', but instead got '%#v'", i+1, testCase.expectedResult, actualResult) 182 } 183 } 184 } 185 186 // Test validates 'ErrEntityTooLarge' error response. 187 func TestErrEntityTooLarge(t *testing.T) { 188 msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", 1000000, 99999) 189 expectedResult := ErrorResponse{ 190 StatusCode: http.StatusBadRequest, 191 Code: "EntityTooLarge", 192 Message: msg, 193 BucketName: "minio-bucket", 194 Key: "Asia/", 195 } 196 actualResult := ErrEntityTooLarge(1000000, 99999, "minio-bucket", "Asia/") 197 if !reflect.DeepEqual(expectedResult, actualResult) { 198 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 199 } 200 } 201 202 // Test validates 'ErrEntityTooSmall' error response. 203 func TestErrEntityTooSmall(t *testing.T) { 204 msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", -1) 205 expectedResult := ErrorResponse{ 206 StatusCode: http.StatusBadRequest, 207 Code: "EntityTooSmall", 208 Message: msg, 209 BucketName: "minio-bucket", 210 Key: "Asia/", 211 } 212 actualResult := ErrEntityTooSmall(-1, "minio-bucket", "Asia/") 213 if !reflect.DeepEqual(expectedResult, actualResult) { 214 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 215 } 216 } 217 218 // Test validates 'ErrUnexpectedEOF' error response. 219 func TestErrUnexpectedEOF(t *testing.T) { 220 msg := fmt.Sprintf("Data read ‘%s’ is not equal to the size ‘%s’ of the input Reader.", 221 strconv.FormatInt(100, 10), strconv.FormatInt(101, 10)) 222 expectedResult := ErrorResponse{ 223 StatusCode: http.StatusBadRequest, 224 Code: "UnexpectedEOF", 225 Message: msg, 226 BucketName: "minio-bucket", 227 Key: "Asia/", 228 } 229 actualResult := ErrUnexpectedEOF(100, 101, "minio-bucket", "Asia/") 230 if !reflect.DeepEqual(expectedResult, actualResult) { 231 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 232 } 233 } 234 235 // Test validates 'ErrInvalidBucketName' error response. 236 func TestErrInvalidBucketName(t *testing.T) { 237 expectedResult := ErrorResponse{ 238 StatusCode: http.StatusBadRequest, 239 Code: "InvalidBucketName", 240 Message: "Invalid Bucket name", 241 RequestID: "minio", 242 } 243 actualResult := ErrInvalidBucketName("Invalid Bucket name") 244 if !reflect.DeepEqual(expectedResult, actualResult) { 245 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 246 } 247 } 248 249 // Test validates 'ErrInvalidObjectName' error response. 250 func TestErrInvalidObjectName(t *testing.T) { 251 expectedResult := ErrorResponse{ 252 StatusCode: http.StatusNotFound, 253 Code: "NoSuchKey", 254 Message: "Invalid Object Key", 255 RequestID: "minio", 256 } 257 actualResult := ErrInvalidObjectName("Invalid Object Key") 258 if !reflect.DeepEqual(expectedResult, actualResult) { 259 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 260 } 261 } 262 263 // Test validates 'ErrInvalidArgument' response. 264 func TestErrInvalidArgument(t *testing.T) { 265 expectedResult := ErrorResponse{ 266 StatusCode: http.StatusBadRequest, 267 Code: "InvalidArgument", 268 Message: "Invalid Argument", 269 RequestID: "minio", 270 } 271 actualResult := ErrInvalidArgument("Invalid Argument") 272 if !reflect.DeepEqual(expectedResult, actualResult) { 273 t.Errorf("Expected result to be '%#v', but instead got '%#v'", expectedResult, actualResult) 274 } 275 } 276 277 // Tests if the Message field is missing. 278 func TestErrWithoutMessage(t *testing.T) { 279 errResp := ErrorResponse{ 280 Code: "AccessDenied", 281 RequestID: "minio", 282 } 283 if errResp.Error() != "Access Denied." { 284 t.Errorf("Expected \"Access Denied.\", got %s", errResp) 285 } 286 errResp = ErrorResponse{ 287 Code: "InvalidArgument", 288 RequestID: "minio", 289 } 290 if errResp.Error() != "Error response code InvalidArgument." { 291 t.Errorf("Expected \"Error response code InvalidArgument.\", got %s", errResp) 292 } 293 } 294 295 // Tests if ErrorResponse is comparable since it is compared 296 // inside golang http code (https://github.com/golang/go/issues/29768) 297 func TestErrorResponseComparable(t *testing.T) { 298 var e1 interface{} = ErrorResponse{} 299 var e2 interface{} = ErrorResponse{} 300 if e1 != e2 { 301 t.Fatalf("ErrorResponse should be comparable") 302 } 303 }