github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/blob/s3blob/s3blob_test.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package s3blob 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/http" 22 "testing" 23 24 s3managerv2 "github.com/aws/aws-sdk-go-v2/feature/s3/manager" 25 s3v2 "github.com/aws/aws-sdk-go-v2/service/s3" 26 typesv2 "github.com/aws/aws-sdk-go-v2/service/s3/types" 27 "github.com/aws/aws-sdk-go/aws" 28 "github.com/aws/aws-sdk-go/aws/awserr" 29 "github.com/aws/aws-sdk-go/aws/client" 30 "github.com/aws/aws-sdk-go/aws/session" 31 "github.com/aws/aws-sdk-go/service/s3" 32 "github.com/aws/aws-sdk-go/service/s3/s3manager" 33 "github.com/aws/smithy-go" 34 "gocloud.dev/blob" 35 "gocloud.dev/blob/driver" 36 "gocloud.dev/blob/drivertest" 37 "gocloud.dev/internal/testing/setup" 38 ) 39 40 // These constants record the region & bucket used for the last --record. 41 // If you want to use --record mode, 42 // 1. Create a bucket in your AWS project from the S3 management console. 43 // 44 // https://s3.console.aws.amazon.com/s3/home. 45 // 46 // 2. Update this constant to your bucket name. 47 // TODO(issue #300): Use Terraform to provision a bucket, and get the bucket 48 // 49 // name from the Terraform output instead (saving a copy of it for replay). 50 const ( 51 bucketName = "go-cloud-testing" 52 region = "us-west-1" 53 ) 54 55 type harness struct { 56 useV2 bool 57 session *session.Session 58 clientV2 *s3v2.Client 59 opts *Options 60 rt http.RoundTripper 61 closer func() 62 } 63 64 func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 65 sess, rt, done, _ := setup.NewAWSSession(ctx, t, region) 66 return &harness{useV2: false, session: sess, opts: nil, rt: rt, closer: done}, nil 67 } 68 69 func newHarnessUsingLegacyList(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 70 sess, rt, done, _ := setup.NewAWSSession(ctx, t, region) 71 return &harness{useV2: false, session: sess, opts: &Options{UseLegacyList: true}, rt: rt, closer: done}, nil 72 } 73 74 func newHarnessV2(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 75 cfg, rt, done, _ := setup.NewAWSv2Config(ctx, t, region) 76 return &harness{useV2: true, clientV2: s3v2.NewFromConfig(cfg), opts: nil, rt: rt, closer: done}, nil 77 } 78 79 func newHarnessUsingLegacyListV2(ctx context.Context, t *testing.T) (drivertest.Harness, error) { 80 cfg, rt, done, _ := setup.NewAWSv2Config(ctx, t, region) 81 return &harness{useV2: true, clientV2: s3v2.NewFromConfig(cfg), opts: &Options{UseLegacyList: true}, rt: rt, closer: done}, nil 82 } 83 84 func (h *harness) HTTPClient() *http.Client { 85 return &http.Client{Transport: h.rt} 86 } 87 88 func (h *harness) MakeDriver(ctx context.Context) (driver.Bucket, error) { 89 return openBucket(ctx, h.useV2, h.session, h.clientV2, bucketName, h.opts) 90 } 91 92 func (h *harness) MakeDriverForNonexistentBucket(ctx context.Context) (driver.Bucket, error) { 93 return openBucket(ctx, h.useV2, h.session, h.clientV2, "go-cdk-bucket-does-not-exist", h.opts) 94 } 95 96 func (h *harness) Close() { 97 h.closer() 98 } 99 100 func TestConformance(t *testing.T) { 101 drivertest.RunConformanceTests(t, newHarness, []drivertest.AsTest{verifyContentLanguage{useV2: false, usingLegacyList: false}}) 102 } 103 104 func TestConformanceUsingLegacyList(t *testing.T) { 105 drivertest.RunConformanceTests(t, newHarnessUsingLegacyList, []drivertest.AsTest{verifyContentLanguage{useV2: false, usingLegacyList: true}}) 106 } 107 108 func TestConformanceV2(t *testing.T) { 109 drivertest.RunConformanceTests(t, newHarnessV2, []drivertest.AsTest{verifyContentLanguage{useV2: true, usingLegacyList: false}}) 110 } 111 112 func TestConformanceUsingLegacyListV2(t *testing.T) { 113 drivertest.RunConformanceTests(t, newHarnessUsingLegacyListV2, []drivertest.AsTest{verifyContentLanguage{useV2: true, usingLegacyList: true}}) 114 } 115 116 func BenchmarkS3blob(b *testing.B) { 117 sess, err := session.NewSession(&aws.Config{ 118 Region: aws.String(region), 119 }) 120 if err != nil { 121 b.Fatal(err) 122 } 123 bkt, err := OpenBucket(context.Background(), sess, bucketName, nil) 124 if err != nil { 125 b.Fatal(err) 126 } 127 drivertest.RunBenchmarks(b, bkt) 128 } 129 130 const language = "nl" 131 132 // verifyContentLanguage uses As to access the underlying GCS types and 133 // read/write the ContentLanguage field. 134 type verifyContentLanguage struct { 135 useV2 bool 136 usingLegacyList bool 137 } 138 139 func (verifyContentLanguage) Name() string { 140 return "verify ContentLanguage can be written and read through As" 141 } 142 143 func (v verifyContentLanguage) BucketCheck(b *blob.Bucket) error { 144 if v.useV2 { 145 var client *s3v2.Client 146 if !b.As(&client) { 147 return errors.New("Bucket.As failed") 148 } 149 return nil 150 } 151 var client *s3.S3 152 if !b.As(&client) { 153 return errors.New("Bucket.As failed") 154 } 155 return nil 156 } 157 158 func (v verifyContentLanguage) ErrorCheck(b *blob.Bucket, err error) error { 159 if v.useV2 { 160 var e smithy.APIError 161 if !b.ErrorAs(err, &e) { 162 return errors.New("blob.ErrorAs failed") 163 } 164 } else { 165 var e awserr.Error 166 if !b.ErrorAs(err, &e) { 167 return errors.New("blob.ErrorAs failed") 168 } 169 } 170 return nil 171 } 172 173 func (v verifyContentLanguage) BeforeRead(as func(interface{}) bool) error { 174 if v.useV2 { 175 var req *s3v2.GetObjectInput 176 if !as(&req) { 177 return errors.New("BeforeRead As failed") 178 } 179 return nil 180 } 181 var req *s3.GetObjectInput 182 if !as(&req) { 183 return errors.New("BeforeRead As failed") 184 } 185 return nil 186 } 187 188 func (v verifyContentLanguage) BeforeWrite(as func(interface{}) bool) error { 189 if v.useV2 { 190 var req *s3v2.PutObjectInput 191 if !as(&req) { 192 return errors.New("Writer.As failed for PutObjectInput") 193 } 194 req.ContentLanguage = aws.String(language) 195 var u *s3managerv2.Uploader 196 if !as(&u) { 197 return errors.New("Writer.As failed for Uploader") 198 } 199 return nil 200 } 201 var req *s3manager.UploadInput 202 if !as(&req) { 203 return errors.New("Writer.As failed for UploadInput") 204 } 205 req.ContentLanguage = aws.String(language) 206 var u *s3manager.Uploader 207 if !as(&u) { 208 return errors.New("Writer.As failed for Uploader") 209 } 210 return nil 211 } 212 213 func (v verifyContentLanguage) BeforeCopy(as func(interface{}) bool) error { 214 if v.useV2 { 215 var in *s3v2.CopyObjectInput 216 if !as(&in) { 217 return errors.New("BeforeCopy.As failed") 218 } 219 return nil 220 } 221 var in *s3.CopyObjectInput 222 if !as(&in) { 223 return errors.New("BeforeCopy.As failed") 224 } 225 return nil 226 } 227 228 func (v verifyContentLanguage) BeforeList(as func(interface{}) bool) error { 229 if v.useV2 { 230 if v.usingLegacyList { 231 var req *s3v2.ListObjectsInput 232 if !as(&req) { 233 return errors.New("List.As failed") 234 } 235 } else { 236 var req *s3v2.ListObjectsV2Input 237 if !as(&req) { 238 return errors.New("List.As failed") 239 } 240 } 241 return nil 242 } 243 if v.usingLegacyList { 244 var req *s3.ListObjectsInput 245 if !as(&req) { 246 return errors.New("List.As failed") 247 } 248 } else { 249 var req *s3.ListObjectsV2Input 250 if !as(&req) { 251 return errors.New("List.As failed") 252 } 253 } 254 return nil 255 } 256 257 func (v verifyContentLanguage) BeforeSign(as func(interface{}) bool) error { 258 if v.useV2 { 259 var ( 260 get *s3v2.GetObjectInput 261 put *s3v2.PutObjectInput 262 del *s3v2.DeleteObjectInput 263 ) 264 if as(&get) || as(&put) || as(&del) { 265 return nil 266 } 267 return errors.New("BeforeSign.As failed") 268 } 269 var ( 270 get *s3.GetObjectInput 271 put *s3.PutObjectInput 272 del *s3.DeleteObjectInput 273 ) 274 if as(&get) || as(&put) || as(&del) { 275 return nil 276 } 277 return errors.New("BeforeSign.As failed") 278 } 279 280 func (v verifyContentLanguage) AttributesCheck(attrs *blob.Attributes) error { 281 if v.useV2 { 282 var hoo s3v2.HeadObjectOutput 283 if !attrs.As(&hoo) { 284 return errors.New("Attributes.As returned false") 285 } 286 if got := *hoo.ContentLanguage; got != language { 287 return fmt.Errorf("got %q want %q", got, language) 288 } 289 return nil 290 } 291 var hoo s3.HeadObjectOutput 292 if !attrs.As(&hoo) { 293 return errors.New("Attributes.As returned false") 294 } 295 if got := *hoo.ContentLanguage; got != language { 296 return fmt.Errorf("got %q want %q", got, language) 297 } 298 return nil 299 } 300 301 func (v verifyContentLanguage) ReaderCheck(r *blob.Reader) error { 302 if v.useV2 { 303 var goo s3v2.GetObjectOutput 304 if !r.As(&goo) { 305 return errors.New("Reader.As returned false") 306 } 307 if got := *goo.ContentLanguage; got != language { 308 return fmt.Errorf("got %q want %q", got, language) 309 } 310 return nil 311 } 312 var goo s3.GetObjectOutput 313 if !r.As(&goo) { 314 return errors.New("Reader.As returned false") 315 } 316 if got := *goo.ContentLanguage; got != language { 317 return fmt.Errorf("got %q want %q", got, language) 318 } 319 return nil 320 } 321 322 func (v verifyContentLanguage) ListObjectCheck(o *blob.ListObject) error { 323 if v.useV2 { 324 if o.IsDir { 325 var commonPrefix typesv2.CommonPrefix 326 if !o.As(&commonPrefix) { 327 return errors.New("ListObject.As for directory returned false") 328 } 329 return nil 330 } 331 var obj typesv2.Object 332 if !o.As(&obj) { 333 return errors.New("ListObject.As for object returned false") 334 } 335 if obj.Key == nil || o.Key != *obj.Key { 336 return errors.New("ListObject.As for object returned a different item") 337 } 338 return nil 339 } 340 if o.IsDir { 341 var commonPrefix s3.CommonPrefix 342 if !o.As(&commonPrefix) { 343 return errors.New("ListObject.As for directory returned false") 344 } 345 return nil 346 } 347 var obj s3.Object 348 if !o.As(&obj) { 349 return errors.New("ListObject.As for object returned false") 350 } 351 if obj.Key == nil || o.Key != *obj.Key { 352 return errors.New("ListObject.As for object returned a different item") 353 } 354 return nil 355 } 356 357 func TestOpenBucket(t *testing.T) { 358 tests := []struct { 359 description string 360 useV2 bool 361 bucketName string 362 nilClient bool 363 want string 364 wantErr bool 365 }{ 366 { 367 description: "empty bucket name results in error", 368 wantErr: true, 369 }, 370 { 371 description: "empty bucket name results in error V2", 372 useV2: true, 373 wantErr: true, 374 }, 375 { 376 description: "nil client results in error", 377 bucketName: "foo", 378 nilClient: true, 379 wantErr: true, 380 }, 381 { 382 description: "nil client results in error V2", 383 bucketName: "foo", 384 useV2: true, 385 nilClient: true, 386 wantErr: true, 387 }, 388 { 389 description: "success", 390 bucketName: "foo", 391 want: "foo", 392 }, 393 { 394 description: "success V2", 395 bucketName: "foo", 396 useV2: true, 397 want: "foo", 398 }, 399 } 400 401 ctx := context.Background() 402 for _, test := range tests { 403 t.Run(test.description, func(t *testing.T) { 404 var sess client.ConfigProvider 405 var clientV2 *s3v2.Client 406 if !test.nilClient { 407 if test.useV2 { 408 cfg, _, done, _ := setup.NewAWSv2Config(ctx, t, region) 409 defer done() 410 clientV2 = s3v2.NewFromConfig(cfg) 411 } else { 412 s, _, done, _ := setup.NewAWSSession(ctx, t, region) 413 defer done() 414 sess = s 415 } 416 } 417 418 // Create driver impl. 419 drv, err := openBucket(ctx, test.useV2, sess, clientV2, test.bucketName, nil) 420 if (err != nil) != test.wantErr { 421 t.Errorf("got err %v want error %v", err, test.wantErr) 422 } 423 if err == nil && drv != nil && drv.name != test.want { 424 t.Errorf("got %q want %q", drv.name, test.want) 425 } 426 427 // Create portable type. 428 var b *blob.Bucket 429 if test.useV2 { 430 b, err = OpenBucketV2(ctx, clientV2, test.bucketName, nil) 431 } else { 432 b, err = OpenBucket(ctx, sess, test.bucketName, nil) 433 } 434 if b != nil { 435 defer b.Close() 436 } 437 if (err != nil) != test.wantErr { 438 t.Errorf("got err %v want error %v", err, test.wantErr) 439 } 440 }) 441 } 442 } 443 444 func TestOpenBucketFromURL(t *testing.T) { 445 tests := []struct { 446 URL string 447 WantErr bool 448 }{ 449 // OK. 450 {"s3://mybucket", false}, 451 // OK, setting region. 452 {"s3://mybucket?region=us-west1", false}, 453 // OK, setting profile. 454 {"s3://mybucket?profile=main", false}, 455 // OK, setting both profile and region. 456 {"s3://mybucket?profile=main®ion=us-west-1", false}, 457 // OK, use V2. 458 {"s3://mybucket?awssdk=2", false}, 459 // Invalid parameter together with a valid one. 460 {"s3://mybucket?profile=main¶m=value", true}, 461 // Invalid parameter. 462 {"s3://mybucket?param=value", true}, 463 } 464 465 ctx := context.Background() 466 for _, test := range tests { 467 b, err := blob.OpenBucket(ctx, test.URL) 468 if b != nil { 469 defer b.Close() 470 } 471 if (err != nil) != test.WantErr { 472 t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr) 473 } 474 } 475 }