github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bucket-handlers_test.go (about)

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