storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-handlers_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/xml"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"strconv"
    26  	"testing"
    27  
    28  	"storj.io/minio/pkg/auth"
    29  )
    30  
    31  // Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup.
    32  func TestRemoveBucketHandler(t *testing.T) {
    33  	ExecObjectLayerAPITest(t, testRemoveBucketHandler, []string{"RemoveBucket"})
    34  }
    35  
    36  func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
    37  	credentials auth.Credentials, t *testing.T) {
    38  	_, err := obj.PutObject(GlobalContext, bucketName, "test-object", mustGetPutObjReader(t, bytes.NewReader([]byte{}), int64(0), "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ObjectOptions{})
    39  	// if object upload fails stop the test.
    40  	if err != nil {
    41  		t.Fatalf("Error uploading object: <ERROR> %v", err)
    42  	}
    43  
    44  	// initialize httptest Recorder, this records any mutations to response writer inside the handler.
    45  	rec := httptest.NewRecorder()
    46  	// construct HTTP request for DELETE bucket.
    47  	req, err := newTestSignedRequestV4(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
    48  	if err != nil {
    49  		t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err)
    50  	}
    51  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
    52  	// Call the ServeHTTP to execute the handler.
    53  	apiRouter.ServeHTTP(rec, req)
    54  	switch rec.Code {
    55  	case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent:
    56  		t.Fatalf("Test %v: expected failure, but succeeded with %v", instanceType, rec.Code)
    57  	}
    58  
    59  	// Verify response of the V2 signed HTTP request.
    60  	// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
    61  	recV2 := httptest.NewRecorder()
    62  	// construct HTTP request for DELETE bucket.
    63  	reqV2, err := newTestSignedRequestV2(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
    64  	if err != nil {
    65  		t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err)
    66  	}
    67  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
    68  	// Call the ServeHTTP to execute the handler.
    69  	apiRouter.ServeHTTP(recV2, reqV2)
    70  	switch recV2.Code {
    71  	case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent:
    72  		t.Fatalf("Test %v: expected failure, but succeeded with %v", instanceType, recV2.Code)
    73  	}
    74  }
    75  
    76  // Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup.
    77  func TestGetBucketLocationHandler(t *testing.T) {
    78  	ExecObjectLayerAPITest(t, testGetBucketLocationHandler, []string{"GetBucketLocation"})
    79  }
    80  
    81  func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
    82  	credentials auth.Credentials, t *testing.T) {
    83  
    84  	// test cases with sample input and expected output.
    85  	testCases := []struct {
    86  		bucketName string
    87  		accessKey  string
    88  		secretKey  string
    89  		// expected Response.
    90  		expectedRespStatus int
    91  		locationResponse   []byte
    92  		errorResponse      APIErrorResponse
    93  		shouldPass         bool
    94  	}{
    95  		// Test case - 1.
    96  		// Tests for authenticated request and proper response.
    97  		{
    98  			bucketName:         bucketName,
    99  			accessKey:          credentials.AccessKey,
   100  			secretKey:          credentials.SecretKey,
   101  			expectedRespStatus: http.StatusOK,
   102  			locationResponse: []byte(`<?xml version="1.0" encoding="UTF-8"?>
   103  <LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></LocationConstraint>`),
   104  			errorResponse: APIErrorResponse{},
   105  			shouldPass:    true,
   106  		},
   107  		// Test case - 2.
   108  		// Tests for signature mismatch error.
   109  		{
   110  			bucketName:         bucketName,
   111  			accessKey:          "abcd",
   112  			secretKey:          "abcd",
   113  			expectedRespStatus: http.StatusForbidden,
   114  			locationResponse:   []byte(""),
   115  			errorResponse: APIErrorResponse{
   116  				Resource: SlashSeparator + bucketName + SlashSeparator,
   117  				Code:     "InvalidAccessKeyId",
   118  				Message:  "The Access Key Id you provided does not exist in our records.",
   119  			},
   120  			shouldPass: false,
   121  		},
   122  	}
   123  
   124  	for i, testCase := range testCases {
   125  		if i != 1 {
   126  			continue
   127  		}
   128  		// initialize httptest Recorder, this records any mutations to response writer inside the handler.
   129  		rec := httptest.NewRecorder()
   130  		// construct HTTP request for Get bucket location.
   131  		req, err := newTestSignedRequestV4(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   132  		if err != nil {
   133  			t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
   134  		}
   135  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   136  		// Call the ServeHTTP to execute the handler.
   137  		apiRouter.ServeHTTP(rec, req)
   138  		if rec.Code != testCase.expectedRespStatus {
   139  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   140  		}
   141  		if !bytes.Equal(testCase.locationResponse, rec.Body.Bytes()) && testCase.shouldPass {
   142  			t.Errorf("Test %d: %s: Expected the response to be `%s`, but instead found `%s`", i+1, instanceType, string(testCase.locationResponse), rec.Body.String())
   143  		}
   144  		errorResponse := APIErrorResponse{}
   145  		err = xml.Unmarshal(rec.Body.Bytes(), &errorResponse)
   146  		if err != nil && !testCase.shouldPass {
   147  			t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, rec.Body.String())
   148  		}
   149  		if errorResponse.Resource != testCase.errorResponse.Resource {
   150  			t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource)
   151  		}
   152  		if errorResponse.Message != testCase.errorResponse.Message {
   153  			t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message)
   154  		}
   155  		if errorResponse.Code != testCase.errorResponse.Code {
   156  			t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
   157  		}
   158  
   159  		// Verify response of the V2 signed HTTP request.
   160  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   161  		recV2 := httptest.NewRecorder()
   162  		// construct HTTP request for PUT bucket policy endpoint.
   163  		reqV2, err := newTestSignedRequestV2(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   164  
   165  		if err != nil {
   166  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
   167  		}
   168  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   169  		// Call the ServeHTTP to execute the handler.
   170  		apiRouter.ServeHTTP(recV2, reqV2)
   171  		if recV2.Code != testCase.expectedRespStatus {
   172  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   173  		}
   174  
   175  		errorResponse = APIErrorResponse{}
   176  		err = xml.Unmarshal(recV2.Body.Bytes(), &errorResponse)
   177  		if err != nil && !testCase.shouldPass {
   178  			t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, recV2.Body.String())
   179  		}
   180  		if errorResponse.Resource != testCase.errorResponse.Resource {
   181  			t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource)
   182  		}
   183  		if errorResponse.Message != testCase.errorResponse.Message {
   184  			t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message)
   185  		}
   186  		if errorResponse.Code != testCase.errorResponse.Code {
   187  			t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
   188  		}
   189  
   190  	}
   191  
   192  	// Test for Anonymous/unsigned http request.
   193  	// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make any difference.
   194  	anonReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", bucketName), 0, nil)
   195  	if err != nil {
   196  		t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType)
   197  	}
   198  
   199  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   200  	// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
   201  	// unsigned request goes through and its validated again.
   202  	ExecObjectLayerAPIAnonTest(t, obj, "TestGetBucketLocationHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName))
   203  
   204  	// HTTP request for testing when `objectLayer` is set to `nil`.
   205  	// There is no need to use an existing bucket and valid input for creating the request
   206  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   207  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   208  
   209  	nilBucket := "dummy-bucket"
   210  	nilReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", nilBucket), 0, nil)
   211  
   212  	if err != nil {
   213  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   214  	}
   215  	// Executes the object layer set to `nil` test.
   216  	// `ExecObjectLayerAPINilTest` manages the operation.
   217  	ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq)
   218  }
   219  
   220  // Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup.
   221  func TestHeadBucketHandler(t *testing.T) {
   222  	ExecObjectLayerAPITest(t, testHeadBucketHandler, []string{"HeadBucket"})
   223  }
   224  
   225  func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   226  	credentials auth.Credentials, t *testing.T) {
   227  
   228  	// test cases with sample input and expected output.
   229  	testCases := []struct {
   230  		bucketName string
   231  		accessKey  string
   232  		secretKey  string
   233  		// expected Response.
   234  		expectedRespStatus int
   235  	}{
   236  		// Test case - 1.
   237  		// Bucket exists.
   238  		{
   239  			bucketName:         bucketName,
   240  			accessKey:          credentials.AccessKey,
   241  			secretKey:          credentials.SecretKey,
   242  			expectedRespStatus: http.StatusOK,
   243  		},
   244  		// Test case - 2.
   245  		// Non-existent bucket name.
   246  		{
   247  			bucketName:         "2333",
   248  			accessKey:          credentials.AccessKey,
   249  			secretKey:          credentials.SecretKey,
   250  			expectedRespStatus: http.StatusNotFound,
   251  		},
   252  		// Test case - 3.
   253  		// Testing for signature mismatch error.
   254  		// setting invalid acess and secret key.
   255  		{
   256  			bucketName:         bucketName,
   257  			accessKey:          "abcd",
   258  			secretKey:          "abcd",
   259  			expectedRespStatus: http.StatusForbidden,
   260  		},
   261  	}
   262  
   263  	for i, testCase := range testCases {
   264  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   265  		rec := httptest.NewRecorder()
   266  		// construct HTTP request for HEAD bucket.
   267  		req, err := newTestSignedRequestV4(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   268  		if err != nil {
   269  			t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
   270  		}
   271  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   272  		// Call the ServeHTTP to execute the handler.
   273  		apiRouter.ServeHTTP(rec, req)
   274  		if rec.Code != testCase.expectedRespStatus {
   275  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   276  		}
   277  
   278  		// Verify response the V2 signed HTTP request.
   279  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   280  		recV2 := httptest.NewRecorder()
   281  		// construct HTTP request for PUT bucket policy endpoint.
   282  		reqV2, err := newTestSignedRequestV2(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   283  
   284  		if err != nil {
   285  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
   286  		}
   287  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   288  		// Call the ServeHTTP to execute the handler.
   289  		apiRouter.ServeHTTP(recV2, reqV2)
   290  		if recV2.Code != testCase.expectedRespStatus {
   291  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   292  		}
   293  
   294  	}
   295  
   296  	// Test for Anonymous/unsigned http request.
   297  	anonReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", bucketName), 0, nil)
   298  
   299  	if err != nil {
   300  		t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
   301  			instanceType, bucketName, err)
   302  	}
   303  
   304  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   305  	// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
   306  	// unsigned request goes through and its validated again.
   307  	ExecObjectLayerAPIAnonTest(t, obj, "TestHeadBucketHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName))
   308  
   309  	// HTTP request for testing when `objectLayer` is set to `nil`.
   310  	// There is no need to use an existing bucket and valid input for creating the request
   311  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   312  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   313  
   314  	nilBucket := "dummy-bucket"
   315  	nilReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", nilBucket), 0, nil)
   316  
   317  	if err != nil {
   318  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   319  	}
   320  	// execute the object layer set to `nil` test.
   321  	// `ExecObjectLayerAPINilTest` manages the operation.
   322  	ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq)
   323  }
   324  
   325  // Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup.
   326  func TestListMultipartUploadsHandler(t *testing.T) {
   327  	ExecObjectLayerAPITest(t, testListMultipartUploadsHandler, []string{"ListMultipartUploads"})
   328  }
   329  
   330  // testListMultipartUploadsHandler - Tests validate listing of multipart uploads.
   331  func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   332  	credentials auth.Credentials, t *testing.T) {
   333  
   334  	// Collection of non-exhaustive ListMultipartUploads test cases, valid errors
   335  	// and success responses.
   336  	testCases := []struct {
   337  		// Inputs to ListMultipartUploads.
   338  		bucket             string
   339  		prefix             string
   340  		keyMarker          string
   341  		uploadIDMarker     string
   342  		delimiter          string
   343  		maxUploads         string
   344  		accessKey          string
   345  		secretKey          string
   346  		expectedRespStatus int
   347  		shouldPass         bool
   348  	}{
   349  		// Test case - 1.
   350  		// Setting invalid bucket name.
   351  		{
   352  			bucket:             ".test",
   353  			prefix:             "",
   354  			keyMarker:          "",
   355  			uploadIDMarker:     "",
   356  			delimiter:          "",
   357  			maxUploads:         "0",
   358  			accessKey:          credentials.AccessKey,
   359  			secretKey:          credentials.SecretKey,
   360  			expectedRespStatus: http.StatusNotFound,
   361  			shouldPass:         false,
   362  		},
   363  		// Test case - 2.
   364  		// Setting a non-existent bucket.
   365  		{
   366  			bucket:             "volatile-bucket-1",
   367  			prefix:             "",
   368  			keyMarker:          "",
   369  			uploadIDMarker:     "",
   370  			delimiter:          "",
   371  			maxUploads:         "0",
   372  			accessKey:          credentials.AccessKey,
   373  			secretKey:          credentials.SecretKey,
   374  			expectedRespStatus: http.StatusNotFound,
   375  			shouldPass:         false,
   376  		},
   377  		// Test case -3.
   378  		// Delimiter unsupported, but response is empty.
   379  		{
   380  			bucket:             bucketName,
   381  			prefix:             "",
   382  			keyMarker:          "",
   383  			uploadIDMarker:     "",
   384  			delimiter:          "-",
   385  			maxUploads:         "0",
   386  			accessKey:          credentials.AccessKey,
   387  			secretKey:          credentials.SecretKey,
   388  			expectedRespStatus: http.StatusOK,
   389  			shouldPass:         true,
   390  		},
   391  		// Test case - 4.
   392  		// Setting Invalid prefix and marker combination.
   393  		{
   394  			bucket:             bucketName,
   395  			prefix:             "asia",
   396  			keyMarker:          "europe-object",
   397  			uploadIDMarker:     "",
   398  			delimiter:          "",
   399  			maxUploads:         "0",
   400  			accessKey:          credentials.AccessKey,
   401  			secretKey:          credentials.SecretKey,
   402  			expectedRespStatus: http.StatusNotImplemented,
   403  			shouldPass:         false,
   404  		},
   405  		// Test case - 5.
   406  		// Invalid upload id and marker combination.
   407  		{
   408  			bucket:             bucketName,
   409  			prefix:             "asia",
   410  			keyMarker:          "asia/europe/",
   411  			uploadIDMarker:     "abc",
   412  			delimiter:          "",
   413  			maxUploads:         "0",
   414  			accessKey:          credentials.AccessKey,
   415  			secretKey:          credentials.SecretKey,
   416  			expectedRespStatus: http.StatusNotImplemented,
   417  			shouldPass:         false,
   418  		},
   419  		// Test case - 6.
   420  		// Setting a negative value to max-uploads paramater, should result in http.StatusBadRequest.
   421  		{
   422  			bucket:             bucketName,
   423  			prefix:             "",
   424  			keyMarker:          "",
   425  			uploadIDMarker:     "",
   426  			delimiter:          "",
   427  			maxUploads:         "-1",
   428  			accessKey:          credentials.AccessKey,
   429  			secretKey:          credentials.SecretKey,
   430  			expectedRespStatus: http.StatusBadRequest,
   431  			shouldPass:         false,
   432  		},
   433  		// Test case - 7.
   434  		// Case with right set of parameters,
   435  		// should result in success 200OK.
   436  		{
   437  			bucket:             bucketName,
   438  			prefix:             "",
   439  			keyMarker:          "",
   440  			uploadIDMarker:     "",
   441  			delimiter:          SlashSeparator,
   442  			maxUploads:         "100",
   443  			accessKey:          credentials.AccessKey,
   444  			secretKey:          credentials.SecretKey,
   445  			expectedRespStatus: http.StatusOK,
   446  			shouldPass:         true,
   447  		},
   448  		// Test case - 8.
   449  		// Good case without delimiter.
   450  		{
   451  			bucket:             bucketName,
   452  			prefix:             "",
   453  			keyMarker:          "",
   454  			uploadIDMarker:     "",
   455  			delimiter:          "",
   456  			maxUploads:         "100",
   457  			accessKey:          credentials.AccessKey,
   458  			secretKey:          credentials.SecretKey,
   459  			expectedRespStatus: http.StatusOK,
   460  			shouldPass:         true,
   461  		},
   462  		// Test case - 9.
   463  		// Setting Invalid AccessKey and SecretKey to induce and verify Signature Mismatch error.
   464  		{
   465  			bucket:             bucketName,
   466  			prefix:             "",
   467  			keyMarker:          "",
   468  			uploadIDMarker:     "",
   469  			delimiter:          "",
   470  			maxUploads:         "100",
   471  			accessKey:          "abcd",
   472  			secretKey:          "abcd",
   473  			expectedRespStatus: http.StatusForbidden,
   474  			shouldPass:         true,
   475  		},
   476  	}
   477  
   478  	for i, testCase := range testCases {
   479  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   480  		rec := httptest.NewRecorder()
   481  
   482  		// construct HTTP request for List multipart uploads endpoint.
   483  		u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
   484  		req, gerr := newTestSignedRequestV4(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
   485  		if gerr != nil {
   486  			t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
   487  		}
   488  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   489  		// Call the ServeHTTP to execute the handler.
   490  		apiRouter.ServeHTTP(rec, req)
   491  		if rec.Code != testCase.expectedRespStatus {
   492  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   493  		}
   494  
   495  		// Verify response the V2 signed HTTP request.
   496  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   497  		recV2 := httptest.NewRecorder()
   498  		// construct HTTP request for PUT bucket policy endpoint.
   499  
   500  		// verify response for V2 signed HTTP request.
   501  		reqV2, err := newTestSignedRequestV2(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil)
   502  		if err != nil {
   503  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
   504  		}
   505  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   506  		// Call the ServeHTTP to execute the handler.
   507  		apiRouter.ServeHTTP(recV2, reqV2)
   508  		if recV2.Code != testCase.expectedRespStatus {
   509  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   510  		}
   511  	}
   512  
   513  	// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   514  	rec := httptest.NewRecorder()
   515  
   516  	// construct HTTP request for List multipart uploads endpoint.
   517  	u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "")
   518  	req, err := newTestSignedRequestV4(http.MethodGet, u, 0, nil, "", "", nil) // Generate an anonymous request.
   519  	if err != nil {
   520  		t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
   521  	}
   522  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   523  	// Call the ServeHTTP to execute the handler.
   524  	apiRouter.ServeHTTP(rec, req)
   525  	if rec.Code != http.StatusForbidden {
   526  		t.Errorf("Test %s: Expected the response status to be `http.StatusForbidden`, but instead found `%d`", instanceType, rec.Code)
   527  	}
   528  
   529  	url := getListMultipartUploadsURLWithParams("", testCases[6].bucket, testCases[6].prefix, testCases[6].keyMarker,
   530  		testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads)
   531  	// Test for Anonymous/unsigned http request.
   532  	anonReq, err := newTestRequest(http.MethodGet, url, 0, nil)
   533  	if err != nil {
   534  		t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v",
   535  			instanceType, bucketName, err)
   536  	}
   537  
   538  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   539  	// sets the bucket policy using the policy statement generated from `getWriteOnlyBucketStatement` so that the
   540  	// unsigned request goes through and its validated again.
   541  	ExecObjectLayerAPIAnonTest(t, obj, "TestListMultipartUploadsHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy(bucketName))
   542  
   543  	// HTTP request for testing when `objectLayer` is set to `nil`.
   544  	// There is no need to use an existing bucket and valid input for creating the request
   545  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   546  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   547  
   548  	nilBucket := "dummy-bucket"
   549  	url = getListMultipartUploadsURLWithParams("", nilBucket, "dummy-prefix", testCases[6].keyMarker,
   550  		testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads)
   551  
   552  	nilReq, err := newTestRequest(http.MethodGet, url, 0, nil)
   553  
   554  	if err != nil {
   555  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   556  	}
   557  	// execute the object layer set to `nil` test.
   558  	// `ExecObjectLayerAPINilTest` manages the operation.
   559  	ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq)
   560  }
   561  
   562  // Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup.
   563  func TestListBucketsHandler(t *testing.T) {
   564  	ExecObjectLayerAPITest(t, testListBucketsHandler, []string{"ListBuckets"})
   565  }
   566  
   567  // testListBucketsHandler - Tests validate listing of buckets.
   568  func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   569  	credentials auth.Credentials, t *testing.T) {
   570  
   571  	testCases := []struct {
   572  		bucketName         string
   573  		accessKey          string
   574  		secretKey          string
   575  		expectedRespStatus int
   576  	}{
   577  		// Test case - 1.
   578  		// Validate a good case request succeeds.
   579  		{
   580  			bucketName:         bucketName,
   581  			accessKey:          credentials.AccessKey,
   582  			secretKey:          credentials.SecretKey,
   583  			expectedRespStatus: http.StatusOK,
   584  		},
   585  		// Test case - 2.
   586  		// Test case with invalid accessKey to produce and validate Signature MisMatch error.
   587  		{
   588  			bucketName:         bucketName,
   589  			accessKey:          "abcd",
   590  			secretKey:          "abcd",
   591  			expectedRespStatus: http.StatusForbidden,
   592  		},
   593  	}
   594  
   595  	for i, testCase := range testCases {
   596  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   597  		rec := httptest.NewRecorder()
   598  		req, lerr := newTestSignedRequestV4(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   599  		if lerr != nil {
   600  			t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
   601  		}
   602  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   603  		// Call the ServeHTTP to execute the handler.
   604  		apiRouter.ServeHTTP(rec, req)
   605  		if rec.Code != testCase.expectedRespStatus {
   606  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   607  		}
   608  
   609  		// Verify response of the V2 signed HTTP request.
   610  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   611  		recV2 := httptest.NewRecorder()
   612  		// construct HTTP request for PUT bucket policy endpoint.
   613  
   614  		// verify response for V2 signed HTTP request.
   615  		reqV2, err := newTestSignedRequestV2(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil)
   616  
   617  		if err != nil {
   618  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
   619  		}
   620  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   621  		// Call the ServeHTTP to execute the handler.
   622  		apiRouter.ServeHTTP(recV2, reqV2)
   623  		if recV2.Code != testCase.expectedRespStatus {
   624  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   625  		}
   626  	}
   627  
   628  	// Test for Anonymous/unsigned http request.
   629  	// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make a difference.
   630  	anonReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil)
   631  
   632  	if err != nil {
   633  		t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType)
   634  	}
   635  
   636  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   637  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
   638  	// unsigned request goes through and its validated again.
   639  	ExecObjectLayerAPIAnonTest(t, obj, "ListBucketsHandler", "", "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy("*"))
   640  
   641  	// HTTP request for testing when `objectLayer` is set to `nil`.
   642  	// There is no need to use an existing bucket and valid input for creating the request
   643  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   644  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   645  
   646  	nilReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil)
   647  
   648  	if err != nil {
   649  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   650  	}
   651  	// execute the object layer set to `nil` test.
   652  	// `ExecObjectLayerAPINilTest` manages the operation.
   653  	ExecObjectLayerAPINilTest(t, "", "", instanceType, apiRouter, nilReq)
   654  }
   655  
   656  // Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup.
   657  func TestAPIDeleteMultipleObjectsHandler(t *testing.T) {
   658  	ExecObjectLayerAPITest(t, testAPIDeleteMultipleObjectsHandler, []string{"DeleteMultipleObjects"})
   659  }
   660  
   661  func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   662  	credentials auth.Credentials, t *testing.T) {
   663  
   664  	var err error
   665  
   666  	contentBytes := []byte("hello")
   667  	sha256sum := ""
   668  	var objectNames []string
   669  	for i := 0; i < 10; i++ {
   670  		objectName := "test-object-" + strconv.Itoa(i)
   671  		// uploading the object.
   672  		_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
   673  		// if object upload fails stop the test.
   674  		if err != nil {
   675  			t.Fatalf("Put Object %d:  Error uploading object: <ERROR> %v", i, err)
   676  		}
   677  
   678  		// object used for the test.
   679  		objectNames = append(objectNames, objectName)
   680  	}
   681  
   682  	getObjectToDeleteList := func(objectNames []string) (objectList []ObjectToDelete) {
   683  		for _, objectName := range objectNames {
   684  			objectList = append(objectList, ObjectToDelete{
   685  				ObjectName: objectName,
   686  			})
   687  		}
   688  
   689  		return objectList
   690  	}
   691  
   692  	getDeleteErrorList := func(objects []ObjectToDelete) (deleteErrorList []DeleteError) {
   693  		for _, obj := range objects {
   694  			deleteErrorList = append(deleteErrorList, DeleteError{
   695  				Code:    errorCodes[ErrAccessDenied].Code,
   696  				Message: errorCodes[ErrAccessDenied].Description,
   697  				Key:     obj.ObjectName,
   698  			})
   699  		}
   700  
   701  		return deleteErrorList
   702  	}
   703  
   704  	requestList := []DeleteObjectsRequest{
   705  		{Quiet: false, Objects: getObjectToDeleteList(objectNames[:5])},
   706  		{Quiet: true, Objects: getObjectToDeleteList(objectNames[5:])},
   707  	}
   708  
   709  	// generate multi objects delete response.
   710  	successRequest0 := EncodeResponse(requestList[0])
   711  
   712  	deletedObjects := make([]DeletedObject, len(requestList[0].Objects))
   713  	for i := range requestList[0].Objects {
   714  		deletedObjects[i] = DeletedObject{
   715  			ObjectName: requestList[0].Objects[i].ObjectName,
   716  		}
   717  	}
   718  
   719  	successResponse0 := generateMultiDeleteResponse(requestList[0].Quiet, deletedObjects, nil)
   720  	encodedSuccessResponse0 := EncodeResponse(successResponse0)
   721  
   722  	successRequest1 := EncodeResponse(requestList[1])
   723  
   724  	deletedObjects = make([]DeletedObject, len(requestList[1].Objects))
   725  	for i := range requestList[0].Objects {
   726  		deletedObjects[i] = DeletedObject{
   727  			ObjectName: requestList[1].Objects[i].ObjectName,
   728  		}
   729  	}
   730  
   731  	successResponse1 := generateMultiDeleteResponse(requestList[1].Quiet, deletedObjects, nil)
   732  	encodedSuccessResponse1 := EncodeResponse(successResponse1)
   733  
   734  	// generate multi objects delete response for errors.
   735  	// errorRequest := EncodeResponse(requestList[1])
   736  	errorResponse := generateMultiDeleteResponse(requestList[1].Quiet, deletedObjects, nil)
   737  	encodedErrorResponse := EncodeResponse(errorResponse)
   738  
   739  	anonRequest := EncodeResponse(requestList[0])
   740  	anonResponse := generateMultiDeleteResponse(requestList[0].Quiet, nil, getDeleteErrorList(requestList[0].Objects))
   741  	encodedAnonResponse := EncodeResponse(anonResponse)
   742  
   743  	testCases := []struct {
   744  		bucket             string
   745  		objects            []byte
   746  		accessKey          string
   747  		secretKey          string
   748  		expectedContent    []byte
   749  		expectedRespStatus int
   750  	}{
   751  		// Test case - 1.
   752  		// Delete objects with invalid access key.
   753  		{
   754  			bucket:             bucketName,
   755  			objects:            successRequest0,
   756  			accessKey:          "Invalid-AccessID",
   757  			secretKey:          credentials.SecretKey,
   758  			expectedContent:    nil,
   759  			expectedRespStatus: http.StatusForbidden,
   760  		},
   761  		// Test case - 2.
   762  		// Delete valid objects with quiet flag off.
   763  		{
   764  			bucket:             bucketName,
   765  			objects:            successRequest0,
   766  			accessKey:          credentials.AccessKey,
   767  			secretKey:          credentials.SecretKey,
   768  			expectedContent:    encodedSuccessResponse0,
   769  			expectedRespStatus: http.StatusOK,
   770  		},
   771  		// Test case - 3.
   772  		// Delete valid objects with quiet flag on.
   773  		{
   774  			bucket:             bucketName,
   775  			objects:            successRequest1,
   776  			accessKey:          credentials.AccessKey,
   777  			secretKey:          credentials.SecretKey,
   778  			expectedContent:    encodedSuccessResponse1,
   779  			expectedRespStatus: http.StatusOK,
   780  		},
   781  		// Test case - 4.
   782  		// Delete previously deleted objects.
   783  		{
   784  			bucket:             bucketName,
   785  			objects:            successRequest1,
   786  			accessKey:          credentials.AccessKey,
   787  			secretKey:          credentials.SecretKey,
   788  			expectedContent:    encodedErrorResponse,
   789  			expectedRespStatus: http.StatusOK,
   790  		},
   791  		// Test case - 5.
   792  		// Anonymous user access denied response
   793  		// Currently anonymous users cannot delete multiple objects in MinIO server
   794  		{
   795  			bucket:             bucketName,
   796  			objects:            anonRequest,
   797  			accessKey:          "",
   798  			secretKey:          "",
   799  			expectedContent:    encodedAnonResponse,
   800  			expectedRespStatus: http.StatusOK,
   801  		},
   802  	}
   803  
   804  	for i, testCase := range testCases {
   805  		var req *http.Request
   806  		var actualContent []byte
   807  
   808  		// Generate a signed or anonymous request based on the testCase
   809  		if testCase.accessKey != "" {
   810  			req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
   811  				int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil)
   812  		} else {
   813  			req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName),
   814  				int64(len(testCase.objects)), bytes.NewReader(testCase.objects))
   815  		}
   816  
   817  		if err != nil {
   818  			t.Fatalf("Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v", err)
   819  		}
   820  
   821  		rec := httptest.NewRecorder()
   822  
   823  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   824  		// Call the ServeHTTP to executes the registered handler.
   825  		apiRouter.ServeHTTP(rec, req)
   826  		// Assert the response code with the expected status.
   827  		if rec.Code != testCase.expectedRespStatus {
   828  			t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
   829  		}
   830  
   831  		// read the response body.
   832  		actualContent, err = ioutil.ReadAll(rec.Body)
   833  		if err != nil {
   834  			t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
   835  		}
   836  
   837  		// Verify whether the bucket obtained object is same as the one created.
   838  		if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
   839  			t.Log(string(testCase.expectedContent), string(actualContent))
   840  			t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType)
   841  		}
   842  	}
   843  
   844  	// HTTP request to test the case of `objectLayer` being set to `nil`.
   845  	// There is no need to use an existing bucket or valid input for creating the request,
   846  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   847  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   848  	// Indicating that all parts are uploaded and initiating completeMultipartUpload.
   849  	nilBucket := "dummy-bucket"
   850  	nilObject := ""
   851  
   852  	nilReq, err := newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", nilBucket), 0, nil, "", "", nil)
   853  	if err != nil {
   854  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   855  	}
   856  	// execute the object layer set to `nil` test.
   857  	// `ExecObjectLayerAPINilTest` manages the operation.
   858  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
   859  }