package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" ) func TestReqTypeDetermination(t *testing.T) { bkt, obj, domain := "test-bucket", "test-object", "domain" for _, tc := range []struct { name string target string host string domains []string expectedType ReqType expectedBktName string expectedObjName string }{ { name: "bucket request, path-style", target: "/" + bkt, expectedType: bucketType, expectedBktName: bkt, }, { name: "bucket request with slash, path-style", target: "/" + bkt + "/", expectedType: bucketType, expectedBktName: bkt, }, { name: "object request, path-style", target: "/" + bkt + "/" + obj, expectedType: objectType, expectedBktName: bkt, expectedObjName: obj, }, { name: "object request with slash, path-style", target: "/" + bkt + "/" + obj + "/", expectedType: objectType, expectedBktName: bkt, expectedObjName: obj + "/", }, { name: "none type request", target: "/", expectedType: noneType, }, { name: "bucket request, virtual-hosted style", target: "/", host: bkt + "." + domain, domains: []string{"some-domain", domain}, expectedType: bucketType, expectedBktName: bkt, }, { name: "object request, virtual-hosted style", target: "/" + obj, host: bkt + "." + domain, domains: []string{"some-domain", domain}, expectedType: objectType, expectedBktName: bkt, expectedObjName: obj, }, } { t.Run(tc.name, func(t *testing.T) { r := httptest.NewRequest(http.MethodPut, tc.target, nil) r.Host = tc.host reqType, bktName, objName := getBucketObject(r, tc.domains) require.Equal(t, tc.expectedType, reqType) require.Equal(t, tc.expectedBktName, bktName) require.Equal(t, tc.expectedObjName, objName) }) } } func TestDetermineBucketOperation(t *testing.T) { const defaultValue = "value" for _, tc := range []struct { name string method string queryParam map[string]string expected string }{ { name: "OptionsBucketOperation", method: http.MethodOptions, expected: OptionsBucketOperation, }, { name: "HeadBucketOperation", method: http.MethodHead, expected: HeadBucketOperation, }, { name: "ListMultipartUploadsOperation", method: http.MethodGet, queryParam: map[string]string{UploadsQuery: defaultValue}, expected: ListMultipartUploadsOperation, }, { name: "GetBucketLocationOperation", method: http.MethodGet, queryParam: map[string]string{LocationQuery: defaultValue}, expected: GetBucketLocationOperation, }, { name: "GetBucketPolicyOperation", method: http.MethodGet, queryParam: map[string]string{PolicyQuery: defaultValue}, expected: GetBucketPolicyOperation, }, { name: "GetBucketLifecycleOperation", method: http.MethodGet, queryParam: map[string]string{LifecycleQuery: defaultValue}, expected: GetBucketLifecycleOperation, }, { name: "GetBucketEncryptionOperation", method: http.MethodGet, queryParam: map[string]string{EncryptionQuery: defaultValue}, expected: GetBucketEncryptionOperation, }, { name: "GetBucketCorsOperation", method: http.MethodGet, queryParam: map[string]string{CorsQuery: defaultValue}, expected: GetBucketCorsOperation, }, { name: "GetBucketACLOperation", method: http.MethodGet, queryParam: map[string]string{ACLQuery: defaultValue}, expected: GetBucketACLOperation, }, { name: "GetBucketWebsiteOperation", method: http.MethodGet, queryParam: map[string]string{WebsiteQuery: defaultValue}, expected: GetBucketWebsiteOperation, }, { name: "GetBucketAccelerateOperation", method: http.MethodGet, queryParam: map[string]string{AccelerateQuery: defaultValue}, expected: GetBucketAccelerateOperation, }, { name: "GetBucketRequestPaymentOperation", method: http.MethodGet, queryParam: map[string]string{RequestPaymentQuery: defaultValue}, expected: GetBucketRequestPaymentOperation, }, { name: "GetBucketLoggingOperation", method: http.MethodGet, queryParam: map[string]string{LoggingQuery: defaultValue}, expected: GetBucketLoggingOperation, }, { name: "GetBucketReplicationOperation", method: http.MethodGet, queryParam: map[string]string{ReplicationQuery: defaultValue}, expected: GetBucketReplicationOperation, }, { name: "GetBucketTaggingOperation", method: http.MethodGet, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: GetBucketTaggingOperation, }, { name: "GetBucketObjectLockConfigOperation", method: http.MethodGet, queryParam: map[string]string{ObjectLockQuery: defaultValue}, expected: GetBucketObjectLockConfigOperation, }, { name: "GetBucketVersioningOperation", method: http.MethodGet, queryParam: map[string]string{VersioningQuery: defaultValue}, expected: GetBucketVersioningOperation, }, { name: "GetBucketNotificationOperation", method: http.MethodGet, queryParam: map[string]string{NotificationQuery: defaultValue}, expected: GetBucketNotificationOperation, }, { name: "ListenBucketNotificationOperation", method: http.MethodGet, queryParam: map[string]string{EventsQuery: defaultValue}, expected: ListenBucketNotificationOperation, }, { name: "ListBucketObjectVersionsOperation", method: http.MethodGet, queryParam: map[string]string{VersionsQuery: defaultValue}, expected: ListBucketObjectVersionsOperation, }, { name: "ListObjectsV2MOperation", method: http.MethodGet, queryParam: map[string]string{ListTypeQuery: "2", MetadataQuery: "true"}, expected: ListObjectsV2MOperation, }, { name: "ListObjectsV2Operation", method: http.MethodGet, queryParam: map[string]string{ListTypeQuery: "2"}, expected: ListObjectsV2Operation, }, { name: "ListObjectsV1Operation", method: http.MethodGet, expected: ListObjectsV1Operation, }, { name: "PutBucketCorsOperation", method: http.MethodPut, queryParam: map[string]string{CorsQuery: defaultValue}, expected: PutBucketCorsOperation, }, { name: "PutBucketACLOperation", method: http.MethodPut, queryParam: map[string]string{ACLQuery: defaultValue}, expected: PutBucketACLOperation, }, { name: "PutBucketLifecycleOperation", method: http.MethodPut, queryParam: map[string]string{LifecycleQuery: defaultValue}, expected: PutBucketLifecycleOperation, }, { name: "PutBucketEncryptionOperation", method: http.MethodPut, queryParam: map[string]string{EncryptionQuery: defaultValue}, expected: PutBucketEncryptionOperation, }, { name: "PutBucketPolicyOperation", method: http.MethodPut, queryParam: map[string]string{PolicyQuery: defaultValue}, expected: PutBucketPolicyOperation, }, { name: "PutBucketObjectLockConfigOperation", method: http.MethodPut, queryParam: map[string]string{ObjectLockQuery: defaultValue}, expected: PutBucketObjectLockConfigOperation, }, { name: "PutBucketTaggingOperation", method: http.MethodPut, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: PutBucketTaggingOperation, }, { name: "PutBucketVersioningOperation", method: http.MethodPut, queryParam: map[string]string{VersioningQuery: defaultValue}, expected: PutBucketVersioningOperation, }, { name: "PutBucketNotificationOperation", method: http.MethodPut, queryParam: map[string]string{NotificationQuery: defaultValue}, expected: PutBucketNotificationOperation, }, { name: "CreateBucketOperation", method: http.MethodPut, expected: CreateBucketOperation, }, { name: "DeleteMultipleObjectsOperation", method: http.MethodPost, queryParam: map[string]string{DeleteQuery: defaultValue}, expected: DeleteMultipleObjectsOperation, }, { name: "PostObjectOperation", method: http.MethodPost, expected: PostObjectOperation, }, { name: "DeleteBucketCorsOperation", method: http.MethodDelete, queryParam: map[string]string{CorsQuery: defaultValue}, expected: DeleteBucketCorsOperation, }, { name: "DeleteBucketWebsiteOperation", method: http.MethodDelete, queryParam: map[string]string{WebsiteQuery: defaultValue}, expected: DeleteBucketWebsiteOperation, }, { name: "DeleteBucketTaggingOperation", method: http.MethodDelete, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: DeleteBucketTaggingOperation, }, { name: "DeleteBucketPolicyOperation", method: http.MethodDelete, queryParam: map[string]string{PolicyQuery: defaultValue}, expected: DeleteBucketPolicyOperation, }, { name: "DeleteBucketLifecycleOperation", method: http.MethodDelete, queryParam: map[string]string{LifecycleQuery: defaultValue}, expected: DeleteBucketLifecycleOperation, }, { name: "DeleteBucketEncryptionOperation", method: http.MethodDelete, queryParam: map[string]string{EncryptionQuery: defaultValue}, expected: DeleteBucketEncryptionOperation, }, { name: "DeleteBucketOperation", method: http.MethodDelete, expected: DeleteBucketOperation, }, { name: "UnmatchedBucketOperation", method: "invalid-method", expected: "UnmatchedBucketOperation", }, } { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.method, "/test", nil) if tc.queryParam != nil { addQueryParams(req, tc.queryParam) } actual := determineBucketOperation(req) require.Equal(t, tc.expected, actual) }) } } func TestDetermineObjectOperation(t *testing.T) { const ( amzCopySource = "X-Amz-Copy-Source" defaultValue = "value" ) for _, tc := range []struct { name string method string queryParam map[string]string headerKeys []string expected string }{ { name: "OptionsObjectOperation", method: http.MethodOptions, expected: OptionsObjectOperation, }, { name: "HeadObjectOperation", method: http.MethodHead, expected: HeadObjectOperation, }, { name: "ListPartsOperation", method: http.MethodGet, queryParam: map[string]string{UploadIDQuery: defaultValue}, expected: ListPartsOperation, }, { name: "GetObjectACLOperation", method: http.MethodGet, queryParam: map[string]string{ACLQuery: defaultValue}, expected: GetObjectACLOperation, }, { name: "GetObjectTaggingOperation", method: http.MethodGet, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: GetObjectTaggingOperation, }, { name: "GetObjectRetentionOperation", method: http.MethodGet, queryParam: map[string]string{RetentionQuery: defaultValue}, expected: GetObjectRetentionOperation, }, { name: "GetObjectLegalHoldOperation", method: http.MethodGet, queryParam: map[string]string{LegalQuery: defaultValue}, expected: GetObjectLegalHoldOperation, }, { name: "GetObjectAttributesOperation", method: http.MethodGet, queryParam: map[string]string{AttributesQuery: defaultValue}, expected: GetObjectAttributesOperation, }, { name: "GetObjectOperation", method: http.MethodGet, expected: GetObjectOperation, }, { name: "UploadPartCopyOperation", method: http.MethodPut, queryParam: map[string]string{PartNumberQuery: defaultValue, UploadIDQuery: defaultValue}, headerKeys: []string{amzCopySource}, expected: UploadPartCopyOperation, }, { name: "UploadPartOperation", method: http.MethodPut, queryParam: map[string]string{PartNumberQuery: defaultValue, UploadIDQuery: defaultValue}, expected: UploadPartOperation, }, { name: "PutObjectACLOperation", method: http.MethodPut, queryParam: map[string]string{ACLQuery: defaultValue}, expected: PutObjectACLOperation, }, { name: "PutObjectTaggingOperation", method: http.MethodPut, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: PutObjectTaggingOperation, }, { name: "CopyObjectOperation", method: http.MethodPut, headerKeys: []string{amzCopySource}, expected: CopyObjectOperation, }, { name: "PutObjectRetentionOperation", method: http.MethodPut, queryParam: map[string]string{RetentionQuery: defaultValue}, expected: PutObjectRetentionOperation, }, { name: "PutObjectLegalHoldOperation", method: http.MethodPut, queryParam: map[string]string{LegalHoldQuery: defaultValue}, expected: PutObjectLegalHoldOperation, }, { name: "PutObjectOperation", method: http.MethodPut, expected: PutObjectOperation, }, { name: "CompleteMultipartUploadOperation", method: http.MethodPost, queryParam: map[string]string{UploadIDQuery: defaultValue}, expected: CompleteMultipartUploadOperation, }, { name: "CreateMultipartUploadOperation", method: http.MethodPost, queryParam: map[string]string{UploadsQuery: defaultValue}, expected: CreateMultipartUploadOperation, }, { name: "SelectObjectContentOperation", method: http.MethodPost, expected: SelectObjectContentOperation, }, { name: "AbortMultipartUploadOperation", method: http.MethodDelete, queryParam: map[string]string{UploadIDQuery: defaultValue}, expected: AbortMultipartUploadOperation, }, { name: "DeleteObjectTaggingOperation", method: http.MethodDelete, queryParam: map[string]string{TaggingQuery: defaultValue}, expected: DeleteObjectTaggingOperation, }, { name: "DeleteObjectOperation", method: http.MethodDelete, expected: DeleteObjectOperation, }, { name: "UnmatchedObjectOperation", method: "invalid-method", expected: "UnmatchedObjectOperation", }, } { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.method, "/test", nil) if tc.queryParam != nil { addQueryParams(req, tc.queryParam) } if tc.headerKeys != nil { addHeaderParams(req, tc.headerKeys) } actual := determineObjectOperation(req) require.Equal(t, tc.expected, actual) }) } } func addQueryParams(req *http.Request, pairs map[string]string) { values := req.URL.Query() for key, val := range pairs { values.Add(key, val) } req.URL.RawQuery = values.Encode() } func addHeaderParams(req *http.Request, keys []string) { for _, key := range keys { req.Header.Set(key, "val") } } func TestDetermineGeneralOperation(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/test", nil) actual := determineGeneralOperation(req) require.Equal(t, ListBucketsOperation, actual) req = httptest.NewRequest(http.MethodPost, "/test", nil) actual = determineGeneralOperation(req) require.Equal(t, "UnmatchedOperation", actual) }