2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2016-09-09 13:18:38 -04:00
package cmd
import (
"bytes"
"encoding/xml"
2021-11-24 13:01:07 -05:00
"fmt"
2022-09-19 14:05:16 -04:00
"io"
2016-09-09 13:18:38 -04:00
"net/http"
"net/http/httptest"
2016-11-16 12:46:09 -05:00
"strconv"
2016-09-09 13:18:38 -04:00
"testing"
2017-10-31 14:54:32 -04:00
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/auth"
2016-09-09 13:18:38 -04:00
)
2020-06-12 23:04:01 -04:00
// Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup.
2020-03-28 00:52:59 -04:00
func TestRemoveBucketHandler ( t * testing . T ) {
ExecObjectLayerAPITest ( t , testRemoveBucketHandler , [ ] string { "RemoveBucket" } )
}
func testRemoveBucketHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-04-13 15:00:11 -04:00
credentials auth . Credentials , t * testing . T ,
) {
2020-12-27 01:58:06 -05:00
_ , err := obj . PutObject ( GlobalContext , bucketName , "test-object" , mustGetPutObjReader ( t , bytes . NewReader ( [ ] byte { } ) , int64 ( 0 ) , "" , "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) , ObjectOptions { } )
2020-03-28 00:52:59 -04:00
// if object upload fails stop the test.
if err != nil {
t . Fatalf ( "Error uploading object: <ERROR> %v" , err )
}
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
rec := httptest . NewRecorder ( )
// construct HTTP request for DELETE bucket.
2020-07-20 15:52:49 -04:00
req , err := newTestSignedRequestV4 ( http . MethodDelete , getBucketLocationURL ( "" , bucketName ) , 0 , nil , credentials . AccessKey , credentials . SecretKey , nil )
2020-03-28 00:52:59 -04:00
if err != nil {
t . Fatalf ( "Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v" , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
switch rec . Code {
case http . StatusOK , http . StatusCreated , http . StatusAccepted , http . StatusNoContent :
t . Fatalf ( "Test %v: expected failure, but succeeded with %v" , instanceType , rec . Code )
}
// Verify response of the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest . NewRecorder ( )
// construct HTTP request for DELETE bucket.
2020-07-20 15:52:49 -04:00
reqV2 , err := newTestSignedRequestV2 ( http . MethodDelete , getBucketLocationURL ( "" , bucketName ) , 0 , nil , credentials . AccessKey , credentials . SecretKey , nil )
2020-03-28 00:52:59 -04:00
if err != nil {
t . Fatalf ( "Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v" , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( recV2 , reqV2 )
switch recV2 . Code {
case http . StatusOK , http . StatusCreated , http . StatusAccepted , http . StatusNoContent :
t . Fatalf ( "Test %v: expected failure, but succeeded with %v" , instanceType , recV2 . Code )
}
}
2020-06-12 23:04:01 -04:00
// Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup.
2016-09-09 13:18:38 -04:00
func TestGetBucketLocationHandler ( t * testing . T ) {
2016-10-09 12:21:37 -04:00
ExecObjectLayerAPITest ( t , testGetBucketLocationHandler , [ ] string { "GetBucketLocation" } )
2016-09-09 13:18:38 -04:00
}
2016-10-09 12:21:37 -04:00
func testGetBucketLocationHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
credentials auth . Credentials , t * testing . T ,
) {
2016-09-09 13:18:38 -04:00
// test cases with sample input and expected output.
testCases := [ ] struct {
bucketName string
accessKey string
secretKey string
// expected Response.
expectedRespStatus int
locationResponse [ ] byte
errorResponse APIErrorResponse
shouldPass bool
} {
2016-10-13 12:25:56 -04:00
// Test case - 1.
2016-09-09 13:18:38 -04:00
// Tests for authenticated request and proper response.
{
2016-10-13 12:25:56 -04:00
bucketName : bucketName ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusOK ,
locationResponse : [ ] byte ( ` < ? xml version = "1.0" encoding = "UTF-8" ? >
2016-09-09 13:18:38 -04:00
< LocationConstraint xmlns = "http://s3.amazonaws.com/doc/2006-03-01/" > < / LocationConstraint > ` ) ,
2016-10-13 12:25:56 -04:00
errorResponse : APIErrorResponse { } ,
shouldPass : true ,
2016-09-09 13:18:38 -04:00
} ,
2016-10-13 12:25:56 -04:00
// Test case - 2.
// Tests for signature mismatch error.
2016-09-09 13:18:38 -04:00
{
2016-10-13 12:25:56 -04:00
bucketName : bucketName ,
accessKey : "abcd" ,
secretKey : "abcd" ,
expectedRespStatus : http . StatusForbidden ,
locationResponse : [ ] byte ( "" ) ,
errorResponse : APIErrorResponse {
2019-08-06 15:08:58 -04:00
Resource : SlashSeparator + bucketName + SlashSeparator ,
2017-05-23 23:07:52 -04:00
Code : "InvalidAccessKeyId" ,
2020-05-21 12:09:18 -04:00
Message : "The Access Key Id you provided does not exist in our records." ,
2016-09-09 13:18:38 -04:00
} ,
2016-10-13 12:25:56 -04:00
shouldPass : false ,
2016-09-09 13:18:38 -04:00
} ,
}
for i , testCase := range testCases {
2016-10-13 12:25:56 -04:00
if i != 1 {
continue
}
2016-10-11 03:00:02 -04:00
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
2016-09-09 13:18:38 -04:00
rec := httptest . NewRecorder ( )
2016-09-13 22:00:01 -04:00
// construct HTTP request for Get bucket location.
2020-07-20 15:52:49 -04:00
req , err := newTestSignedRequestV4 ( http . MethodGet , getBucketLocationURL ( "" , testCase . bucketName ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-09-09 13:18:38 -04:00
if err != nil {
2016-09-13 22:00:01 -04:00
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v" , i + 1 , instanceType , err )
2016-09-09 13:18:38 -04:00
}
2016-10-13 12:25:56 -04:00
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
2016-09-09 13:18:38 -04:00
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
if rec . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , rec . Code )
}
if ! bytes . Equal ( testCase . locationResponse , rec . Body . Bytes ( ) ) && testCase . shouldPass {
2019-02-13 07:59:36 -05:00
t . Errorf ( "Test %d: %s: Expected the response to be `%s`, but instead found `%s`" , i + 1 , instanceType , string ( testCase . locationResponse ) , rec . Body . String ( ) )
2016-09-09 13:18:38 -04:00
}
errorResponse := APIErrorResponse { }
err = xml . Unmarshal ( rec . Body . Bytes ( ) , & errorResponse )
if err != nil && ! testCase . shouldPass {
2019-02-13 07:59:36 -05:00
t . Fatalf ( "Test %d: %s: Unable to marshal response body %s" , i + 1 , instanceType , rec . Body . String ( ) )
2016-09-09 13:18:38 -04:00
}
if errorResponse . Resource != testCase . errorResponse . Resource {
t . Errorf ( "Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Resource , errorResponse . Resource )
}
if errorResponse . Message != testCase . errorResponse . Message {
t . Errorf ( "Test %d: %s: Expected the error message to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Message , errorResponse . Message )
}
if errorResponse . Code != testCase . errorResponse . Code {
t . Errorf ( "Test %d: %s: Expected the error code to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Code , errorResponse . Code )
}
2016-10-13 12:25:56 -04:00
// Verify response of the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest . NewRecorder ( )
// construct HTTP request for PUT bucket policy endpoint.
2020-07-20 15:52:49 -04:00
reqV2 , err := newTestSignedRequestV2 ( http . MethodGet , getBucketLocationURL ( "" , testCase . bucketName ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-10-13 12:25:56 -04:00
if err != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v" , i + 1 , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( recV2 , reqV2 )
if recV2 . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , recV2 . Code )
}
errorResponse = APIErrorResponse { }
err = xml . Unmarshal ( recV2 . Body . Bytes ( ) , & errorResponse )
if err != nil && ! testCase . shouldPass {
2019-02-13 07:59:36 -05:00
t . Fatalf ( "Test %d: %s: Unable to marshal response body %s" , i + 1 , instanceType , recV2 . Body . String ( ) )
2016-10-13 12:25:56 -04:00
}
if errorResponse . Resource != testCase . errorResponse . Resource {
t . Errorf ( "Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Resource , errorResponse . Resource )
}
if errorResponse . Message != testCase . errorResponse . Message {
t . Errorf ( "Test %d: %s: Expected the error message to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Message , errorResponse . Message )
}
if errorResponse . Code != testCase . errorResponse . Code {
t . Errorf ( "Test %d: %s: Expected the error code to be `%s`, but instead found `%s`" , i + 1 , instanceType , testCase . errorResponse . Code , errorResponse . Code )
}
2016-09-09 13:18:38 -04:00
}
2016-10-09 12:21:37 -04:00
// Test for Anonymous/unsigned http request.
// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make any difference.
2020-07-20 15:52:49 -04:00
anonReq , err := newTestRequest ( http . MethodGet , getBucketLocationURL ( "" , bucketName ) , 0 , nil )
2016-10-09 12:21:37 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Fatalf ( "MinIO %s: Failed to create an anonymous request." , instanceType )
2016-10-09 12:21:37 -04:00
}
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
2018-04-24 18:53:30 -04:00
ExecObjectLayerAPIAnonTest ( t , obj , "TestGetBucketLocationHandler" , bucketName , "" , instanceType , apiRouter , anonReq , getAnonReadOnlyBucketPolicy ( bucketName ) )
2016-10-11 03:00:02 -04:00
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
nilBucket := "dummy-bucket"
2020-07-20 15:52:49 -04:00
nilReq , err := newTestRequest ( http . MethodGet , getBucketLocationURL ( "" , nilBucket ) , 0 , nil )
2016-10-11 03:00:02 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`." , instanceType )
2016-10-11 03:00:02 -04:00
}
// Executes the object layer set to `nil` test.
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest ( t , nilBucket , "" , instanceType , apiRouter , nilReq )
2016-09-09 13:18:38 -04:00
}
2020-06-12 23:04:01 -04:00
// Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup.
2016-09-09 13:18:38 -04:00
func TestHeadBucketHandler ( t * testing . T ) {
2016-10-09 12:21:37 -04:00
ExecObjectLayerAPITest ( t , testHeadBucketHandler , [ ] string { "HeadBucket" } )
2016-09-09 13:18:38 -04:00
}
2016-10-09 12:21:37 -04:00
func testHeadBucketHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
credentials auth . Credentials , t * testing . T ,
) {
2016-09-09 13:18:38 -04:00
// test cases with sample input and expected output.
testCases := [ ] struct {
bucketName string
accessKey string
secretKey string
// expected Response.
expectedRespStatus int
} {
2016-10-13 12:25:56 -04:00
// Test case - 1.
2016-09-09 13:18:38 -04:00
// Bucket exists.
{
bucketName : bucketName ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-09-09 13:18:38 -04:00
expectedRespStatus : http . StatusOK ,
} ,
2016-10-13 12:25:56 -04:00
// Test case - 2.
2016-09-09 13:18:38 -04:00
// Non-existent bucket name.
{
bucketName : "2333" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-09-09 13:18:38 -04:00
expectedRespStatus : http . StatusNotFound ,
} ,
2016-10-13 12:25:56 -04:00
// Test case - 3.
// Testing for signature mismatch error.
2024-01-18 02:03:17 -05:00
// setting invalid access and secret key.
2016-09-09 13:18:38 -04:00
{
bucketName : bucketName ,
2016-10-13 12:25:56 -04:00
accessKey : "abcd" ,
secretKey : "abcd" ,
2016-09-09 13:18:38 -04:00
expectedRespStatus : http . StatusForbidden ,
} ,
}
for i , testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest . NewRecorder ( )
2016-09-13 22:00:01 -04:00
// construct HTTP request for HEAD bucket.
2020-07-20 15:52:49 -04:00
req , err := newTestSignedRequestV4 ( http . MethodHead , getHEADBucketURL ( "" , testCase . bucketName ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-09-09 13:18:38 -04:00
if err != nil {
2016-09-13 22:00:01 -04:00
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v" , i + 1 , instanceType , err )
2016-09-09 13:18:38 -04:00
}
2016-10-13 12:25:56 -04:00
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
2016-09-09 13:18:38 -04:00
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
if rec . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , rec . Code )
}
2016-10-13 12:25:56 -04:00
// Verify response the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest . NewRecorder ( )
// construct HTTP request for PUT bucket policy endpoint.
2020-07-20 15:52:49 -04:00
reqV2 , err := newTestSignedRequestV2 ( http . MethodHead , getHEADBucketURL ( "" , testCase . bucketName ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-10-13 12:25:56 -04:00
if err != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v" , i + 1 , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( recV2 , reqV2 )
if recV2 . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , recV2 . Code )
}
2016-09-09 13:18:38 -04:00
}
2016-10-09 12:21:37 -04:00
// Test for Anonymous/unsigned http request.
2020-07-20 15:52:49 -04:00
anonReq , err := newTestRequest ( http . MethodHead , getHEADBucketURL ( "" , bucketName ) , 0 , nil )
2016-10-09 12:21:37 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Fatalf ( "MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v" ,
2016-10-09 12:21:37 -04:00
instanceType , bucketName , err )
}
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
2018-04-24 18:53:30 -04:00
ExecObjectLayerAPIAnonTest ( t , obj , "TestHeadBucketHandler" , bucketName , "" , instanceType , apiRouter , anonReq , getAnonReadOnlyBucketPolicy ( bucketName ) )
2016-10-11 03:00:02 -04:00
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
nilBucket := "dummy-bucket"
2020-07-20 15:52:49 -04:00
nilReq , err := newTestRequest ( http . MethodHead , getHEADBucketURL ( "" , nilBucket ) , 0 , nil )
2016-10-11 03:00:02 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`." , instanceType )
2016-10-11 03:00:02 -04:00
}
// execute the object layer set to `nil` test.
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest ( t , nilBucket , "" , instanceType , apiRouter , nilReq )
2016-09-09 13:18:38 -04:00
}
2016-09-13 22:00:01 -04:00
2020-06-12 23:04:01 -04:00
// Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup.
2016-09-13 22:00:01 -04:00
func TestListMultipartUploadsHandler ( t * testing . T ) {
2016-10-09 12:21:37 -04:00
ExecObjectLayerAPITest ( t , testListMultipartUploadsHandler , [ ] string { "ListMultipartUploads" } )
2016-09-13 22:00:01 -04:00
}
// testListMultipartUploadsHandler - Tests validate listing of multipart uploads.
2016-10-09 12:21:37 -04:00
func testListMultipartUploadsHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
credentials auth . Credentials , t * testing . T ,
) {
2016-09-13 22:00:01 -04:00
// Collection of non-exhaustive ListMultipartUploads test cases, valid errors
// and success responses.
testCases := [ ] struct {
// Inputs to ListMultipartUploads.
bucket string
prefix string
keyMarker string
uploadIDMarker string
delimiter string
maxUploads string
2016-10-13 12:25:56 -04:00
accessKey string
secretKey string
2016-09-13 22:00:01 -04:00
expectedRespStatus int
shouldPass bool
} {
2016-10-13 12:25:56 -04:00
// Test case - 1.
// Setting invalid bucket name.
{
bucket : ".test" ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "0" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2023-07-28 13:49:20 -04:00
expectedRespStatus : http . StatusBadRequest ,
2016-10-13 12:25:56 -04:00
shouldPass : false ,
} ,
// Test case - 2.
// Setting a non-existent bucket.
{
bucket : "volatile-bucket-1" ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "0" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusNotFound ,
shouldPass : false ,
} ,
// Test case -3.
2019-11-19 20:42:27 -05:00
// Delimiter unsupported, but response is empty.
2016-10-13 12:25:56 -04:00
{
bucket : bucketName ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "-" ,
maxUploads : "0" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2019-11-19 20:42:27 -05:00
expectedRespStatus : http . StatusOK ,
shouldPass : true ,
2016-10-13 12:25:56 -04:00
} ,
// Test case - 4.
// Setting Invalid prefix and marker combination.
{
bucket : bucketName ,
prefix : "asia" ,
keyMarker : "europe-object" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "0" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusNotImplemented ,
shouldPass : false ,
} ,
// Test case - 5.
// Invalid upload id and marker combination.
{
bucket : bucketName ,
prefix : "asia" ,
keyMarker : "asia/europe/" ,
uploadIDMarker : "abc" ,
delimiter : "" ,
maxUploads : "0" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusNotImplemented ,
shouldPass : false ,
} ,
// Test case - 6.
2024-01-18 02:03:17 -05:00
// Setting a negative value to max-uploads parameter, should result in http.StatusBadRequest.
2016-10-13 12:25:56 -04:00
{
bucket : bucketName ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "-1" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusBadRequest ,
shouldPass : false ,
} ,
// Test case - 7.
// Case with right set of parameters,
// should result in success 200OK.
{
bucket : bucketName ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
2019-08-06 15:08:58 -04:00
delimiter : SlashSeparator ,
2016-10-13 12:25:56 -04:00
maxUploads : "100" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusOK ,
shouldPass : true ,
} ,
// Test case - 8.
// Good case without delimiter.
{
bucket : bucketName ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "100" ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-10-13 12:25:56 -04:00
expectedRespStatus : http . StatusOK ,
shouldPass : true ,
} ,
// Test case - 9.
// Setting Invalid AccessKey and SecretKey to induce and verify Signature Mismatch error.
{
bucket : bucketName ,
prefix : "" ,
keyMarker : "" ,
uploadIDMarker : "" ,
delimiter : "" ,
maxUploads : "100" ,
accessKey : "abcd" ,
secretKey : "abcd" ,
expectedRespStatus : http . StatusForbidden ,
shouldPass : true ,
} ,
2016-09-13 22:00:01 -04:00
}
for i , testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest . NewRecorder ( )
// construct HTTP request for List multipart uploads endpoint.
u := getListMultipartUploadsURLWithParams ( "" , testCase . bucket , testCase . prefix , testCase . keyMarker , testCase . uploadIDMarker , testCase . delimiter , testCase . maxUploads )
2020-07-20 15:52:49 -04:00
req , gerr := newTestSignedRequestV4 ( http . MethodGet , u , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-09-13 22:00:01 -04:00
if gerr != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v" , i + 1 , instanceType , gerr )
}
2016-10-13 12:25:56 -04:00
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
2016-09-13 22:00:01 -04:00
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
if rec . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , rec . Code )
}
2016-10-13 12:25:56 -04:00
// Verify response the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest . NewRecorder ( )
// construct HTTP request for PUT bucket policy endpoint.
// verify response for V2 signed HTTP request.
2020-07-20 15:52:49 -04:00
reqV2 , err := newTestSignedRequestV2 ( http . MethodGet , u , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-10-13 12:25:56 -04:00
if err != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v" , i + 1 , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( recV2 , reqV2 )
if recV2 . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , recV2 . Code )
}
2016-09-13 22:00:01 -04:00
}
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest . NewRecorder ( )
// construct HTTP request for List multipart uploads endpoint.
u := getListMultipartUploadsURLWithParams ( "" , bucketName , "" , "" , "" , "" , "" )
2020-07-20 15:52:49 -04:00
req , err := newTestSignedRequestV4 ( http . MethodGet , u , 0 , nil , "" , "" , nil ) // Generate an anonymous request.
2016-09-13 22:00:01 -04:00
if err != nil {
t . Fatalf ( "Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v" , instanceType , err )
}
2016-10-13 12:25:56 -04:00
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
2016-09-13 22:00:01 -04:00
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
if rec . Code != http . StatusForbidden {
t . Errorf ( "Test %s: Expected the response status to be `http.StatusForbidden`, but instead found `%d`" , instanceType , rec . Code )
}
2016-10-09 12:21:37 -04:00
url := getListMultipartUploadsURLWithParams ( "" , testCases [ 6 ] . bucket , testCases [ 6 ] . prefix , testCases [ 6 ] . keyMarker ,
testCases [ 6 ] . uploadIDMarker , testCases [ 6 ] . delimiter , testCases [ 6 ] . maxUploads )
// Test for Anonymous/unsigned http request.
2020-07-20 15:52:49 -04:00
anonReq , err := newTestRequest ( http . MethodGet , url , 0 , nil )
2016-10-09 12:21:37 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Fatalf ( "MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v" ,
2016-10-09 12:21:37 -04:00
instanceType , bucketName , err )
}
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
2018-04-24 18:53:30 -04:00
ExecObjectLayerAPIAnonTest ( t , obj , "TestListMultipartUploadsHandler" , bucketName , "" , instanceType , apiRouter , anonReq , getAnonWriteOnlyBucketPolicy ( bucketName ) )
2016-10-09 12:21:37 -04:00
2016-10-11 03:00:02 -04:00
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
nilBucket := "dummy-bucket"
url = getListMultipartUploadsURLWithParams ( "" , nilBucket , "dummy-prefix" , testCases [ 6 ] . keyMarker ,
testCases [ 6 ] . uploadIDMarker , testCases [ 6 ] . delimiter , testCases [ 6 ] . maxUploads )
2020-07-20 15:52:49 -04:00
nilReq , err := newTestRequest ( http . MethodGet , url , 0 , nil )
2016-10-11 03:00:02 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`." , instanceType )
2016-10-11 03:00:02 -04:00
}
// execute the object layer set to `nil` test.
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest ( t , nilBucket , "" , instanceType , apiRouter , nilReq )
2016-09-13 22:00:01 -04:00
}
2016-09-15 02:53:42 -04:00
2020-06-12 23:04:01 -04:00
// Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup.
2016-09-15 02:53:42 -04:00
func TestListBucketsHandler ( t * testing . T ) {
2016-10-09 12:21:37 -04:00
ExecObjectLayerAPITest ( t , testListBucketsHandler , [ ] string { "ListBuckets" } )
2016-09-15 02:53:42 -04:00
}
// testListBucketsHandler - Tests validate listing of buckets.
2016-10-09 12:21:37 -04:00
func testListBucketsHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
credentials auth . Credentials , t * testing . T ,
) {
2016-09-15 02:53:42 -04:00
testCases := [ ] struct {
bucketName string
accessKey string
secretKey string
expectedRespStatus int
} {
2016-10-13 12:25:56 -04:00
// Test case - 1.
2016-09-15 02:53:42 -04:00
// Validate a good case request succeeds.
{
bucketName : bucketName ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-09-15 02:53:42 -04:00
expectedRespStatus : http . StatusOK ,
} ,
2016-10-13 12:25:56 -04:00
// Test case - 2.
// Test case with invalid accessKey to produce and validate Signature MisMatch error.
2016-09-15 02:53:42 -04:00
{
bucketName : bucketName ,
2016-10-13 12:25:56 -04:00
accessKey : "abcd" ,
secretKey : "abcd" ,
2016-09-15 02:53:42 -04:00
expectedRespStatus : http . StatusForbidden ,
} ,
}
for i , testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest . NewRecorder ( )
2020-07-20 15:52:49 -04:00
req , lerr := newTestSignedRequestV4 ( http . MethodGet , getListBucketURL ( "" ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-09-15 02:53:42 -04:00
if lerr != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v" , i + 1 , instanceType , lerr )
}
2016-10-13 12:25:56 -04:00
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
2016-09-15 02:53:42 -04:00
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( rec , req )
if rec . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , rec . Code )
}
2016-10-13 12:25:56 -04:00
// Verify response of the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest . NewRecorder ( )
// construct HTTP request for PUT bucket policy endpoint.
// verify response for V2 signed HTTP request.
2020-07-20 15:52:49 -04:00
reqV2 , err := newTestSignedRequestV2 ( http . MethodGet , getListBucketURL ( "" ) , 0 , nil , testCase . accessKey , testCase . secretKey , nil )
2016-10-13 12:25:56 -04:00
if err != nil {
t . Fatalf ( "Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v" , i + 1 , instanceType , err )
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter . ServeHTTP ( recV2 , reqV2 )
if recV2 . Code != testCase . expectedRespStatus {
t . Errorf ( "Test %d: %s: Expected the response status to be `%d`, but instead found `%d`" , i + 1 , instanceType , testCase . expectedRespStatus , recV2 . Code )
}
2016-09-15 02:53:42 -04:00
}
2016-10-09 12:21:37 -04:00
// Test for Anonymous/unsigned http request.
// ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make a difference.
2020-07-20 15:52:49 -04:00
anonReq , err := newTestRequest ( http . MethodGet , getListBucketURL ( "" ) , 0 , nil )
2016-10-09 12:21:37 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Fatalf ( "MinIO %s: Failed to create an anonymous request." , instanceType )
2016-10-09 12:21:37 -04:00
}
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
2018-04-24 18:53:30 -04:00
ExecObjectLayerAPIAnonTest ( t , obj , "ListBucketsHandler" , "" , "" , instanceType , apiRouter , anonReq , getAnonWriteOnlyBucketPolicy ( "*" ) )
2016-10-11 03:00:02 -04:00
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
2020-07-20 15:52:49 -04:00
nilReq , err := newTestRequest ( http . MethodGet , getListBucketURL ( "" ) , 0 , nil )
2016-10-11 03:00:02 -04:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`." , instanceType )
2016-10-11 03:00:02 -04:00
}
// execute the object layer set to `nil` test.
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest ( t , "" , "" , instanceType , apiRouter , nilReq )
2016-09-15 02:53:42 -04:00
}
2016-11-16 12:46:09 -05:00
2020-06-12 23:04:01 -04:00
// Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup.
2016-11-16 12:46:09 -05:00
func TestAPIDeleteMultipleObjectsHandler ( t * testing . T ) {
2021-11-24 13:01:07 -05:00
ExecObjectLayerAPITest ( t , testAPIDeleteMultipleObjectsHandler , [ ] string { "DeleteMultipleObjects" , "PutBucketPolicy" } )
2016-11-16 12:46:09 -05:00
}
func testAPIDeleteMultipleObjectsHandler ( obj ObjectLayer , instanceType , bucketName string , apiRouter http . Handler ,
2022-01-02 12:15:06 -05:00
credentials auth . Credentials , t * testing . T ,
) {
2016-11-16 12:46:09 -05:00
var err error
sha256sum := ""
var objectNames [ ] string
for i := 0 ; i < 10 ; i ++ {
2023-05-02 19:09:33 -04:00
contentBytes := [ ] byte ( "hello" )
2016-11-16 12:46:09 -05:00
objectName := "test-object-" + strconv . Itoa ( i )
2023-01-20 10:46:06 -05:00
if i == 0 {
objectName += "/"
2023-05-02 19:09:33 -04:00
contentBytes = [ ] byte { }
2023-01-20 10:46:06 -05:00
}
2016-11-16 12:46:09 -05:00
// uploading the object.
2020-12-27 01:58:06 -05:00
_ , err = obj . PutObject ( GlobalContext , bucketName , objectName , mustGetPutObjReader ( t , bytes . NewReader ( contentBytes ) , int64 ( len ( contentBytes ) ) , "" , sha256sum ) , ObjectOptions { } )
2016-11-16 12:46:09 -05:00
// if object upload fails stop the test.
if err != nil {
t . Fatalf ( "Put Object %d: Error uploading object: <ERROR> %v" , i , err )
}
// object used for the test.
objectNames = append ( objectNames , objectName )
}
2023-05-02 19:09:33 -04:00
contentBytes := [ ] byte ( "hello" )
2021-11-24 13:01:07 -05:00
for _ , name := range [ ] string { "private/object" , "public/object" } {
// Uploading the object with retention enabled
_ , err = obj . PutObject ( GlobalContext , bucketName , name , mustGetPutObjReader ( t , bytes . NewReader ( contentBytes ) , int64 ( len ( contentBytes ) ) , "" , sha256sum ) , ObjectOptions { } )
// if object upload fails stop the test.
if err != nil {
t . Fatalf ( "Put Object %s: Error uploading object: <ERROR> %v" , name , err )
}
}
// The following block will create a bucket policy with delete object to 'public/*'. This is
// to test a mixed response of a successful & failure while deleting objects in a single request
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 ) )
rec := httptest . NewRecorder ( )
req , err := newTestSignedRequestV4 ( http . MethodPut , getPutPolicyURL ( "" , bucketName ) , int64 ( len ( policyBytes ) ) , bytes . NewReader ( policyBytes ) ,
credentials . AccessKey , credentials . SecretKey , nil )
if err != nil {
t . Fatalf ( "Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v" , err )
}
apiRouter . ServeHTTP ( rec , req )
if rec . Code != http . StatusNoContent {
t . Errorf ( "Expected the response status to be `%d`, but instead found `%d`" , 200 , rec . Code )
}
2020-06-12 23:04:01 -04:00
getObjectToDeleteList := func ( objectNames [ ] string ) ( objectList [ ] ObjectToDelete ) {
2016-11-16 12:46:09 -05:00
for _ , objectName := range objectNames {
2020-06-12 23:04:01 -04:00
objectList = append ( objectList , ObjectToDelete {
2022-01-03 04:28:52 -05:00
ObjectV : ObjectV {
ObjectName : objectName ,
} ,
2020-06-12 23:04:01 -04:00
} )
2016-11-16 12:46:09 -05:00
}
2020-06-12 23:04:01 -04:00
return objectList
2016-11-16 12:46:09 -05:00
}
2020-06-12 23:04:01 -04:00
getDeleteErrorList := func ( objects [ ] ObjectToDelete ) ( deleteErrorList [ ] DeleteError ) {
2017-08-15 15:49:31 -04:00
for _ , obj := range objects {
deleteErrorList = append ( deleteErrorList , DeleteError {
2019-02-12 04:25:52 -05:00
Code : errorCodes [ ErrAccessDenied ] . Code ,
Message : errorCodes [ ErrAccessDenied ] . Description ,
2017-08-15 15:49:31 -04:00
Key : obj . ObjectName ,
} )
}
return deleteErrorList
}
2016-11-16 12:46:09 -05:00
2022-01-03 04:28:52 -05:00
objects := [ ] ObjectToDelete { }
objects = append ( objects , ObjectToDelete {
ObjectV : ObjectV {
ObjectName : "private/object" ,
} ,
} )
objects = append ( objects , ObjectToDelete {
ObjectV : ObjectV {
ObjectName : "public/object" ,
} ,
} )
2016-11-16 12:46:09 -05:00
requestList := [ ] DeleteObjectsRequest {
2020-06-12 23:04:01 -04:00
{ Quiet : false , Objects : getObjectToDeleteList ( objectNames [ : 5 ] ) } ,
{ Quiet : true , Objects : getObjectToDeleteList ( objectNames [ 5 : ] ) } ,
2022-01-03 04:28:52 -05:00
{ Quiet : false , Objects : objects } ,
2016-11-16 12:46:09 -05:00
}
// generate multi objects delete response.
successRequest0 := encodeResponse ( requestList [ 0 ] )
2020-06-12 23:04:01 -04:00
deletedObjects := make ( [ ] DeletedObject , len ( requestList [ 0 ] . Objects ) )
for i := range requestList [ 0 ] . Objects {
2023-05-04 17:43:52 -04:00
var vid string
if isDirObject ( requestList [ 0 ] . Objects [ i ] . ObjectName ) {
2023-06-23 16:26:00 -04:00
vid = ""
2023-05-04 17:43:52 -04:00
}
2020-06-12 23:04:01 -04:00
deletedObjects [ i ] = DeletedObject {
ObjectName : requestList [ 0 ] . Objects [ i ] . ObjectName ,
2023-05-04 17:43:52 -04:00
VersionID : vid ,
2020-06-12 23:04:01 -04:00
}
}
successResponse0 := generateMultiDeleteResponse ( requestList [ 0 ] . Quiet , deletedObjects , nil )
2016-11-16 12:46:09 -05:00
encodedSuccessResponse0 := encodeResponse ( successResponse0 )
successRequest1 := encodeResponse ( requestList [ 1 ] )
2020-06-12 23:04:01 -04:00
deletedObjects = make ( [ ] DeletedObject , len ( requestList [ 1 ] . Objects ) )
2023-05-04 17:43:52 -04:00
for i := range requestList [ 1 ] . Objects {
var vid string
if isDirObject ( requestList [ 0 ] . Objects [ i ] . ObjectName ) {
2023-06-23 16:26:00 -04:00
vid = ""
2023-05-04 17:43:52 -04:00
}
2020-06-12 23:04:01 -04:00
deletedObjects [ i ] = DeletedObject {
ObjectName : requestList [ 1 ] . Objects [ i ] . ObjectName ,
2023-05-04 17:43:52 -04:00
VersionID : vid ,
2020-06-12 23:04:01 -04:00
}
}
successResponse1 := generateMultiDeleteResponse ( requestList [ 1 ] . Quiet , deletedObjects , nil )
2016-11-16 12:46:09 -05:00
encodedSuccessResponse1 := encodeResponse ( successResponse1 )
// generate multi objects delete response for errors.
// errorRequest := encodeResponse(requestList[1])
2020-06-12 23:04:01 -04:00
errorResponse := generateMultiDeleteResponse ( requestList [ 1 ] . Quiet , deletedObjects , nil )
2016-11-16 12:46:09 -05:00
encodedErrorResponse := encodeResponse ( errorResponse )
2017-08-15 15:49:31 -04:00
anonRequest := encodeResponse ( requestList [ 0 ] )
anonResponse := generateMultiDeleteResponse ( requestList [ 0 ] . Quiet , nil , getDeleteErrorList ( requestList [ 0 ] . Objects ) )
encodedAnonResponse := encodeResponse ( anonResponse )
2021-11-24 13:01:07 -05:00
anonRequestWithPartialPublicAccess := encodeResponse ( requestList [ 2 ] )
anonResponseWithPartialPublicAccess := generateMultiDeleteResponse ( requestList [ 2 ] . Quiet ,
[ ] DeletedObject {
{ ObjectName : "public/object" } ,
} ,
[ ] DeleteError {
{
Code : errorCodes [ ErrAccessDenied ] . Code ,
Message : errorCodes [ ErrAccessDenied ] . Description ,
Key : "private/object" ,
} ,
} )
encodedAnonResponseWithPartialPublicAccess := encodeResponse ( anonResponseWithPartialPublicAccess )
2016-11-16 12:46:09 -05:00
testCases := [ ] struct {
bucket string
objects [ ] byte
accessKey string
secretKey string
expectedContent [ ] byte
expectedRespStatus int
} {
2023-01-20 10:46:06 -05:00
// Test case - 0.
2016-11-16 12:46:09 -05:00
// Delete objects with invalid access key.
2023-01-20 10:46:06 -05:00
0 : {
2016-11-16 12:46:09 -05:00
bucket : bucketName ,
objects : successRequest0 ,
accessKey : "Invalid-AccessID" ,
2016-12-26 13:21:23 -05:00
secretKey : credentials . SecretKey ,
2016-11-16 12:46:09 -05:00
expectedContent : nil ,
expectedRespStatus : http . StatusForbidden ,
} ,
2023-01-20 10:46:06 -05:00
// Test case - 1.
2016-11-16 12:46:09 -05:00
// Delete valid objects with quiet flag off.
2023-01-20 10:46:06 -05:00
1 : {
bucket : bucketName ,
objects : successRequest0 ,
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
expectedContent : encodedSuccessResponse0 ,
expectedRespStatus : http . StatusOK ,
} ,
// Test case - 2.
// Delete deleted objects with quiet flag off.
2 : {
2016-11-16 12:46:09 -05:00
bucket : bucketName ,
objects : successRequest0 ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-11-16 12:46:09 -05:00
expectedContent : encodedSuccessResponse0 ,
expectedRespStatus : http . StatusOK ,
} ,
// Test case - 3.
// Delete valid objects with quiet flag on.
2023-01-20 10:46:06 -05:00
3 : {
2016-11-16 12:46:09 -05:00
bucket : bucketName ,
objects : successRequest1 ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-11-16 12:46:09 -05:00
expectedContent : encodedSuccessResponse1 ,
expectedRespStatus : http . StatusOK ,
} ,
// Test case - 4.
// Delete previously deleted objects.
2023-01-20 10:46:06 -05:00
4 : {
2016-11-16 12:46:09 -05:00
bucket : bucketName ,
objects : successRequest1 ,
2016-12-26 13:21:23 -05:00
accessKey : credentials . AccessKey ,
secretKey : credentials . SecretKey ,
2016-11-16 12:46:09 -05:00
expectedContent : encodedErrorResponse ,
expectedRespStatus : http . StatusOK ,
} ,
2017-08-15 15:49:31 -04:00
// Test case - 5.
// Anonymous user access denied response
2019-04-09 14:39:42 -04:00
// Currently anonymous users cannot delete multiple objects in MinIO server
2023-01-20 10:46:06 -05:00
5 : {
2017-08-15 15:49:31 -04:00
bucket : bucketName ,
objects : anonRequest ,
accessKey : "" ,
secretKey : "" ,
expectedContent : encodedAnonResponse ,
expectedRespStatus : http . StatusOK ,
} ,
2021-11-24 13:01:07 -05:00
// Test case - 6.
// Anonymous user has access to some public folder, issue removing with
// another private object as well
2023-01-20 10:46:06 -05:00
6 : {
2021-11-24 13:01:07 -05:00
bucket : bucketName ,
objects : anonRequestWithPartialPublicAccess ,
accessKey : "" ,
secretKey : "" ,
expectedContent : encodedAnonResponseWithPartialPublicAccess ,
expectedRespStatus : http . StatusOK ,
} ,
2016-11-16 12:46:09 -05:00
}
for i , testCase := range testCases {
var req * http . Request
var actualContent [ ] byte
2017-08-15 15:49:31 -04:00
// Generate a signed or anonymous request based on the testCase
if testCase . accessKey != "" {
2020-07-20 15:52:49 -04:00
req , err = newTestSignedRequestV4 ( http . MethodPost , getDeleteMultipleObjectsURL ( "" , bucketName ) ,
2018-09-20 22:22:09 -04:00
int64 ( len ( testCase . objects ) ) , bytes . NewReader ( testCase . objects ) , testCase . accessKey , testCase . secretKey , nil )
2017-08-15 15:49:31 -04:00
} else {
2020-07-20 15:52:49 -04:00
req , err = newTestRequest ( http . MethodPost , getDeleteMultipleObjectsURL ( "" , bucketName ) ,
2017-08-15 15:49:31 -04:00
int64 ( len ( testCase . objects ) ) , bytes . NewReader ( testCase . objects ) )
}
2016-11-16 12:46:09 -05:00
if err != nil {
t . Fatalf ( "Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v" , err )
}
rec := httptest . NewRecorder ( )
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to executes the registered handler.
apiRouter . ServeHTTP ( rec , req )
// Assert the response code with the expected status.
if rec . Code != testCase . expectedRespStatus {
2023-01-20 10:46:06 -05:00
t . Errorf ( "Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`" , i , instanceType , testCase . expectedRespStatus , rec . Code )
2016-11-16 12:46:09 -05:00
}
// read the response body.
2022-09-19 14:05:16 -04:00
actualContent , err = io . ReadAll ( rec . Body )
2016-11-16 12:46:09 -05:00
if err != nil {
2023-01-20 10:46:06 -05:00
t . Fatalf ( "Test %d : MinIO %s: Failed parsing response body: <ERROR> %v" , i , instanceType , err )
2016-11-16 12:46:09 -05:00
}
// Verify whether the bucket obtained object is same as the one created.
if testCase . expectedContent != nil && ! bytes . Equal ( testCase . expectedContent , actualContent ) {
2020-10-28 12:18:35 -04:00
t . Log ( string ( testCase . expectedContent ) , string ( actualContent ) )
2023-01-20 10:46:06 -05:00
t . Errorf ( "Test %d : MinIO %s: Object content differs from expected value." , i , instanceType )
2016-11-16 12:46:09 -05:00
}
}
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
// since the `objectLayer==nil` check is performed before any other checks inside the handlers.
// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
// Indicating that all parts are uploaded and initiating completeMultipartUpload.
nilBucket := "dummy-bucket"
nilObject := ""
2020-07-20 15:52:49 -04:00
nilReq , err := newTestSignedRequestV4 ( http . MethodPost , getDeleteMultipleObjectsURL ( "" , nilBucket ) , 0 , nil , "" , "" , nil )
2016-11-16 12:46:09 -05:00
if err != nil {
2019-04-09 14:39:42 -04:00
t . Errorf ( "MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`." , instanceType )
2016-11-16 12:46:09 -05:00
}
// execute the object layer set to `nil` test.
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest ( t , nilBucket , nilObject , instanceType , apiRouter , nilReq )
}