storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-lifecycle-handlers_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "encoding/xml" 22 "net/http" 23 "net/http/httptest" 24 "testing" 25 26 "storj.io/minio/pkg/auth" 27 ) 28 29 // Test S3 Bucket lifecycle APIs with wrong credentials 30 func TestBucketLifecycleWrongCredentials(t *testing.T) { 31 ExecObjectLayerAPITest(t, testBucketLifecycleHandlersWrongCredentials, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}) 32 } 33 34 // Test for authentication 35 func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 36 credentials auth.Credentials, t *testing.T) { 37 // test cases with sample input and expected output. 38 testCases := []struct { 39 method string 40 bucketName string 41 accessKey string 42 secretKey string 43 // Sent body 44 body []byte 45 // Expected response 46 expectedRespStatus int 47 lifecycleResponse []byte 48 errorResponse APIErrorResponse 49 shouldPass bool 50 }{ 51 // GET empty credentials 52 { 53 method: http.MethodGet, bucketName: bucketName, 54 accessKey: "", 55 secretKey: "", 56 expectedRespStatus: http.StatusForbidden, 57 lifecycleResponse: []byte(""), 58 errorResponse: APIErrorResponse{ 59 Resource: SlashSeparator + bucketName + SlashSeparator, 60 Code: "AccessDenied", 61 Message: "Access Denied.", 62 }, 63 shouldPass: false, 64 }, 65 // GET wrong credentials 66 { 67 method: http.MethodGet, bucketName: bucketName, 68 accessKey: "abcd", 69 secretKey: "abcd", 70 expectedRespStatus: http.StatusForbidden, 71 lifecycleResponse: []byte(""), 72 errorResponse: APIErrorResponse{ 73 Resource: SlashSeparator + bucketName + SlashSeparator, 74 Code: "InvalidAccessKeyId", 75 Message: "The Access Key Id you provided does not exist in our records.", 76 }, 77 shouldPass: false, 78 }, 79 // PUT empty credentials 80 { 81 method: http.MethodPut, 82 bucketName: bucketName, 83 accessKey: "", 84 secretKey: "", 85 expectedRespStatus: http.StatusForbidden, 86 lifecycleResponse: []byte(""), 87 errorResponse: APIErrorResponse{ 88 Resource: SlashSeparator + bucketName + SlashSeparator, 89 Code: "AccessDenied", 90 Message: "Access Denied.", 91 }, 92 shouldPass: false, 93 }, 94 // PUT wrong credentials 95 { 96 method: http.MethodPut, 97 bucketName: bucketName, 98 accessKey: "abcd", 99 secretKey: "abcd", 100 expectedRespStatus: http.StatusForbidden, 101 lifecycleResponse: []byte(""), 102 errorResponse: APIErrorResponse{ 103 Resource: SlashSeparator + bucketName + SlashSeparator, 104 Code: "InvalidAccessKeyId", 105 Message: "The Access Key Id you provided does not exist in our records.", 106 }, 107 shouldPass: false, 108 }, 109 // DELETE empty credentials 110 { 111 method: http.MethodDelete, 112 bucketName: bucketName, 113 accessKey: "", 114 secretKey: "", 115 expectedRespStatus: http.StatusForbidden, 116 lifecycleResponse: []byte(""), 117 errorResponse: APIErrorResponse{ 118 Resource: SlashSeparator + bucketName + SlashSeparator, 119 Code: "AccessDenied", 120 Message: "Access Denied.", 121 }, 122 shouldPass: false, 123 }, 124 // DELETE wrong credentials 125 { 126 method: http.MethodDelete, 127 bucketName: bucketName, 128 accessKey: "abcd", 129 secretKey: "abcd", 130 expectedRespStatus: http.StatusForbidden, 131 lifecycleResponse: []byte(""), 132 errorResponse: APIErrorResponse{ 133 Resource: SlashSeparator + bucketName + SlashSeparator, 134 Code: "InvalidAccessKeyId", 135 Message: "The Access Key Id you provided does not exist in our records.", 136 }, 137 shouldPass: false, 138 }, 139 } 140 141 testBucketLifecycle(obj, instanceType, bucketName, apiRouter, t, testCases) 142 } 143 144 // Test S3 Bucket lifecycle APIs 145 func TestBucketLifecycle(t *testing.T) { 146 ExecObjectLayerAPITest(t, testBucketLifecycleHandlers, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}) 147 } 148 149 // Simple tests of bucket lifecycle: PUT, GET, DELETE. 150 // Tests are related and the order is important. 151 func testBucketLifecycleHandlers(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 152 creds auth.Credentials, t *testing.T) { 153 154 // test cases with sample input and expected output. 155 testCases := []struct { 156 method string 157 bucketName string 158 accessKey string 159 secretKey string 160 // Sent body 161 body []byte 162 // Expected response 163 expectedRespStatus int 164 lifecycleResponse []byte 165 errorResponse APIErrorResponse 166 shouldPass bool 167 }{ 168 // Test case - 1. 169 // Filter contains more than (Prefix,Tag,And) rule 170 { 171 method: http.MethodPut, 172 bucketName: bucketName, 173 accessKey: creds.AccessKey, 174 secretKey: creds.SecretKey, 175 body: []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix><Tag><Key>Key1</Key><Value>Value1</Value></Tag></Filter><Status>Enabled</Status><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`), 176 expectedRespStatus: http.StatusBadRequest, 177 lifecycleResponse: []byte(``), 178 errorResponse: APIErrorResponse{ 179 Resource: SlashSeparator + bucketName + SlashSeparator, 180 Code: "InvalidRequest", 181 Message: "Filter must have exactly one of Prefix, Tag, or And specified", 182 }, 183 184 shouldPass: false, 185 }, 186 // Date contains wrong format 187 { 188 method: http.MethodPut, 189 bucketName: bucketName, 190 accessKey: creds.AccessKey, 191 secretKey: creds.SecretKey, 192 body: []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix><Tag><Key>Key1</Key><Value>Value1</Value></Tag></Filter><Status>Enabled</Status><Expiration><Date>365</Date></Expiration></Rule></LifecycleConfiguration>`), 193 expectedRespStatus: http.StatusBadRequest, 194 lifecycleResponse: []byte(``), 195 errorResponse: APIErrorResponse{ 196 Resource: SlashSeparator + bucketName + SlashSeparator, 197 Code: "InvalidRequest", 198 Message: "Date must be provided in ISO 8601 format", 199 }, 200 201 shouldPass: false, 202 }, 203 { 204 method: http.MethodPut, 205 bucketName: bucketName, 206 accessKey: creds.AccessKey, 207 secretKey: creds.SecretKey, 208 body: []byte(`<?xml version="1.0" encoding="UTF-8"?><LifecycleConfiguration><Rule><ID>id</ID><Filter><Prefix>logs/</Prefix></Filter><Status>Enabled</Status><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`), 209 expectedRespStatus: http.StatusOK, 210 lifecycleResponse: []byte(``), 211 errorResponse: APIErrorResponse{}, 212 shouldPass: true, 213 }, 214 { 215 method: http.MethodGet, 216 accessKey: creds.AccessKey, 217 secretKey: creds.SecretKey, 218 bucketName: bucketName, 219 body: []byte(``), 220 expectedRespStatus: http.StatusOK, 221 lifecycleResponse: []byte(`<LifecycleConfiguration><Rule><ID>id</ID><Status>Enabled</Status><Filter><Prefix>logs/</Prefix></Filter><Expiration><Days>365</Days></Expiration></Rule></LifecycleConfiguration>`), 222 errorResponse: APIErrorResponse{}, 223 shouldPass: true, 224 }, 225 { 226 method: http.MethodDelete, 227 accessKey: creds.AccessKey, 228 secretKey: creds.SecretKey, 229 bucketName: bucketName, 230 body: []byte(``), 231 expectedRespStatus: http.StatusNoContent, 232 lifecycleResponse: []byte(``), 233 errorResponse: APIErrorResponse{}, 234 shouldPass: true, 235 }, 236 { 237 method: http.MethodGet, 238 accessKey: creds.AccessKey, 239 secretKey: creds.SecretKey, 240 bucketName: bucketName, 241 body: []byte(``), 242 expectedRespStatus: http.StatusNotFound, 243 lifecycleResponse: []byte(``), 244 errorResponse: APIErrorResponse{ 245 Resource: SlashSeparator + bucketName + SlashSeparator, 246 Code: "NoSuchLifecycleConfiguration", 247 Message: "The lifecycle configuration does not exist", 248 }, 249 shouldPass: false, 250 }, 251 } 252 253 testBucketLifecycle(obj, instanceType, bucketName, apiRouter, t, testCases) 254 } 255 256 // testBucketLifecycle is a generic testing of lifecycle requests 257 func testBucketLifecycle(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 258 t *testing.T, testCases []struct { 259 method string 260 bucketName string 261 accessKey string 262 secretKey string 263 body []byte 264 expectedRespStatus int 265 lifecycleResponse []byte 266 errorResponse APIErrorResponse 267 shouldPass bool 268 }) { 269 270 for i, testCase := range testCases { 271 // initialize httptest Recorder, this records any mutations to response writer inside the handler. 272 rec := httptest.NewRecorder() 273 // construct HTTP request 274 req, err := newTestSignedRequestV4(testCase.method, getBucketLifecycleURL("", testCase.bucketName), 275 int64(len(testCase.body)), bytes.NewReader(testCase.body), testCase.accessKey, testCase.secretKey, nil) 276 if err != nil { 277 t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err) 278 } 279 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 280 // Call the ServeHTTP to execute the handler. 281 apiRouter.ServeHTTP(rec, req) 282 if rec.Code != testCase.expectedRespStatus { 283 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 284 } 285 if testCase.shouldPass && !bytes.Equal(testCase.lifecycleResponse, rec.Body.Bytes()) { 286 t.Errorf("Test %d: %s: Expected the response to be `%s`, but instead found `%s`", i+1, instanceType, string(testCase.lifecycleResponse), rec.Body.String()) 287 } 288 errorResponse := APIErrorResponse{} 289 err = xml.Unmarshal(rec.Body.Bytes(), &errorResponse) 290 if err != nil && !testCase.shouldPass { 291 t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, rec.Body.String()) 292 } 293 if errorResponse.Resource != testCase.errorResponse.Resource { 294 t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource) 295 } 296 if errorResponse.Message != testCase.errorResponse.Message { 297 t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message) 298 } 299 if errorResponse.Code != testCase.errorResponse.Code { 300 t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code) 301 } 302 } 303 }