From 91a0ade90829827aa6435af1aeb817ac96ecabe2 Mon Sep 17 00:00:00 2001 From: Bala FA Date: Mon, 7 Nov 2016 16:02:27 -0800 Subject: [PATCH] tests: add unit test for HeadObjectHandler (#3197) Fixes #3068 --- cmd/object-handlers_test.go | 146 ++++++++++++++++++++++++++++++++++++ cmd/test-utils_test.go | 37 ++++----- 2 files changed, 166 insertions(+), 17 deletions(-) diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index eceb95ea0..793c774b5 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -46,6 +46,152 @@ const ( MissingUploadID ) +// Wrapper for calling HeadObject API handler tests for both XL multiple disks and FS single drive setup. +func TestAPIHeadObjectHandler(t *testing.T) { + ExecObjectLayerAPITest(t, testAPIHeadObjectHandler, []string{"HeadObject"}) +} + +func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, + credentials credential, t *testing.T) { + objectName := "test-object" + // set of byte data for PutObject. + // object has to be created before running tests for HeadObject. + // this is required even to assert the HeadObject data, + // since dataInserted === dataFetched back is a primary criteria for any object storage this assertion is critical. + bytesData := []struct { + byteData []byte + }{ + {generateBytesData(6 * 1024 * 1024)}, + } + // set of inputs for uploading the objects before tests for downloading is done. + putObjectInputs := []struct { + bucketName string + objectName string + contentLength int64 + textData []byte + metaData map[string]string + }{ + {bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)}, + } + sha256sum := "" + // iterate through the above set of inputs and upload the object. + for i, input := range putObjectInputs { + // uploading the object. + _, err := obj.PutObject(input.bucketName, input.objectName, input.contentLength, bytes.NewBuffer(input.textData), input.metaData, sha256sum) + // if object upload fails stop the test. + if err != nil { + t.Fatalf("Put Object case %d: Error uploading object: %v", i+1, err) + } + } + + // test cases with inputs and expected result for HeadObject. + testCases := []struct { + bucketName string + objectName string + accessKey string + secretKey string + // expected output. + expectedRespStatus int // expected response status body. + }{ + // Test case - 1. + // Fetching stat info of object and validating it. + { + bucketName: bucketName, + objectName: objectName, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusOK, + }, + // Test case - 2. + // Case with non-existent object name. + { + bucketName: bucketName, + objectName: "abcd", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusNotFound, + }, + // Test case - 3. + // Test case to induce a signature mismatch. + // Using invalid accessID. + { + bucketName: bucketName, + objectName: objectName, + accessKey: "Invalid-AccessID", + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusForbidden, + }, + } + + // Iterating over the cases, fetching the object validating the response. + 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 Get Object end point. + req, err := newTestSignedRequestV4("HEAD", getHeadObjectURL("", testCase.bucketName, testCase.objectName), + 0, nil, testCase.accessKey, testCase.secretKey) + if err != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: %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,`func (api objectAPIHandlers) GetObjectHandler` handles the request. + apiRouter.ServeHTTP(rec, req) + + // Assert the response code with the expected status. + if rec.Code != testCase.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, 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 Head Object endpoint. + reqV2, err := newTestSignedRequestV2("HEAD", getHeadObjectURL("", testCase.bucketName, testCase.objectName), + 0, nil, testCase.accessKey, testCase.secretKey) + + if err != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: %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) + } + } + + // Test for Anonymous/unsigned http request. + anonReq, err := newTestRequest("HEAD", getHeadObjectURL("", bucketName, objectName), 0, nil) + + if err != nil { + t.Fatalf("Minio %s: Failed to create an anonymous request for %s/%s: %v", + instanceType, bucketName, objectName, err) + } + + // 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. + ExecObjectLayerAPIAnonTest(t, "TestAPIHeadObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getReadOnlyObjectStatement) + + // 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" + nilObject := "dummy-object" + nilReq, err := newTestSignedRequestV4("HEAD", getGetObjectURL("", nilBucket, nilObject), + 0, nil, "", "") + + if err != nil { + t.Errorf("Minio %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) + } + // execute the object layer set to `nil` test. + // `ExecObjectLayerAPINilTest` manages the operation. + ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) +} + // Wrapper for calling GetObject API handler tests for both XL multiple disks and FS single drive setup. func TestAPIGetObjectHandler(t *testing.T) { defer DetectTestLeak(t)() diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 7cd910999..cd0092c76 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -1948,56 +1948,59 @@ func registerBucketLevelFunc(bucket *router.Router, api objectAPIHandlers, apiFu case "PostPolicy": // Register PostPolicy handler. bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler) - // Register GetObject handler. + case "HeadObject": + // Register HeadObject handler. + bucket.Methods("Head").Path("/{object:.+}").HandlerFunc(api.HeadObjectHandler) case "GetObject": + // Register GetObject handler. bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler) - // Register PutObject handler. case "PutObject": + // Register PutObject handler. bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler) - // Register Delete Object handler. case "DeleteObject": + // Register Delete Object handler. bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler) - // Register Copy Object handler. case "CopyObject": + // Register Copy Object handler. bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectHandler) - // Register PutBucket Policy handler. case "PutBucketPolicy": + // Register PutBucket Policy handler. bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "") - // Register Delete bucket HTTP policy handler. case "DeleteBucketPolicy": + // Register Delete bucket HTTP policy handler. bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "") - // Register Get Bucket policy HTTP Handler. case "GetBucketPolicy": + // Register Get Bucket policy HTTP Handler. bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "") - // Register GetBucketLocation handler. case "GetBucketLocation": + // Register GetBucketLocation handler. bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "") - // Register HeadBucket handler. case "HeadBucket": + // Register HeadBucket handler. bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler) - // Register New Multipart upload handler. case "NewMultipart": + // Register New Multipart upload handler. bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "") - // Register PutObjectPart handler. case "PutObjectPart": + // Register PutObjectPart handler. bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") - // Register ListObjectParts handler. case "ListObjectParts": + // Register ListObjectParts handler. bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}") - // Register ListMultipartUploads handler. case "ListMultipartUploads": + // Register ListMultipartUploads handler. bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "") - // Register Complete Multipart Upload handler. case "CompleteMultipart": + // Register Complete Multipart Upload handler. bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}") - // Register GetBucketNotification Handler. case "GetBucketNotification": + // Register GetBucketNotification Handler. bucket.Methods("GET").HandlerFunc(api.GetBucketNotificationHandler).Queries("notification", "") - // Register PutBucketNotification Handler. case "PutBucketNotification": + // Register PutBucketNotification Handler. bucket.Methods("PUT").HandlerFunc(api.PutBucketNotificationHandler).Queries("notification", "") - // Register ListenBucketNotification Handler. case "ListenBucketNotification": + // Register ListenBucketNotification Handler. bucket.Methods("GET").HandlerFunc(api.ListenBucketNotificationHandler).Queries("events", "{events:.*}") } }