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  }