cloud.google.com/go/storage@v1.40.0/client_test.go (about) 1 // Copyright 2022 Google LLC 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 // http://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 storage 16 17 import ( 18 "context" 19 "fmt" 20 "log" 21 "os" 22 "strconv" 23 "strings" 24 "testing" 25 "time" 26 27 "cloud.google.com/go/iam/apiv1/iampb" 28 "github.com/google/go-cmp/cmp" 29 "google.golang.org/api/iterator" 30 ) 31 32 var emulatorClients map[string]storageClient 33 var veneerClient *Client 34 35 func TestCreateBucketEmulated(t *testing.T) { 36 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 37 want := &BucketAttrs{ 38 Name: bucket, 39 Logging: &BucketLogging{ 40 LogBucket: bucket, 41 }, 42 } 43 got, err := client.CreateBucket(context.Background(), project, want.Name, want, nil) 44 if err != nil { 45 t.Fatal(err) 46 } 47 want.Location = "US" 48 if diff := cmp.Diff(got.Name, want.Name); diff != "" { 49 t.Errorf("Name got(-),want(+):\n%s", diff) 50 } 51 if diff := cmp.Diff(got.Location, want.Location); diff != "" { 52 t.Errorf("Location got(-),want(+):\n%s", diff) 53 } 54 if diff := cmp.Diff(got.Logging.LogBucket, want.Logging.LogBucket); diff != "" { 55 t.Errorf("LogBucket got(-),want(+):\n%s", diff) 56 } 57 }) 58 } 59 60 func TestDeleteBucketEmulated(t *testing.T) { 61 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 62 b := &BucketAttrs{ 63 Name: bucket, 64 } 65 // Create the bucket that will be deleted. 66 _, err := client.CreateBucket(context.Background(), project, b.Name, b, nil) 67 if err != nil { 68 t.Fatalf("client.CreateBucket: %v", err) 69 } 70 // Delete the bucket that was just created. 71 err = client.DeleteBucket(context.Background(), b.Name, nil) 72 if err != nil { 73 t.Fatalf("client.DeleteBucket: %v", err) 74 } 75 }) 76 } 77 78 func TestGetBucketEmulated(t *testing.T) { 79 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 80 want := &BucketAttrs{ 81 Name: bucket, 82 } 83 // Create the bucket that will be retrieved. 84 _, err := client.CreateBucket(context.Background(), project, want.Name, want, nil) 85 if err != nil { 86 t.Fatalf("client.CreateBucket: %v", err) 87 } 88 got, err := client.GetBucket(context.Background(), want.Name, &BucketConditions{MetagenerationMatch: 1}) 89 if err != nil { 90 t.Fatal(err) 91 } 92 if diff := cmp.Diff(got.Name, want.Name); diff != "" { 93 t.Errorf("got(-),want(+):\n%s", diff) 94 } 95 }) 96 } 97 98 func TestUpdateBucketEmulated(t *testing.T) { 99 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 100 bkt := &BucketAttrs{ 101 Name: bucket, 102 } 103 // Create the bucket that will be updated. 104 _, err := client.CreateBucket(context.Background(), project, bkt.Name, bkt, nil) 105 if err != nil { 106 t.Fatalf("client.CreateBucket: %v", err) 107 } 108 109 ua := &BucketAttrsToUpdate{ 110 VersioningEnabled: false, 111 RequesterPays: false, 112 DefaultEventBasedHold: false, 113 Encryption: &BucketEncryption{DefaultKMSKeyName: "key2"}, 114 Lifecycle: &Lifecycle{ 115 Rules: []LifecycleRule{ 116 { 117 Action: LifecycleAction{Type: "Delete"}, 118 Condition: LifecycleCondition{AgeInDays: 30}, 119 }, 120 }, 121 }, 122 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 123 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 124 StorageClass: "NEARLINE", 125 RPO: RPOAsyncTurbo, 126 } 127 want := &BucketAttrs{ 128 Name: bucket, 129 VersioningEnabled: false, 130 RequesterPays: false, 131 DefaultEventBasedHold: false, 132 Encryption: &BucketEncryption{DefaultKMSKeyName: "key2"}, 133 Lifecycle: Lifecycle{ 134 Rules: []LifecycleRule{ 135 { 136 Action: LifecycleAction{Type: "Delete"}, 137 Condition: LifecycleCondition{AgeInDays: 30}, 138 }, 139 }, 140 }, 141 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 142 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 143 StorageClass: "NEARLINE", 144 RPO: RPOAsyncTurbo, 145 } 146 147 got, err := client.UpdateBucket(context.Background(), bucket, ua, &BucketConditions{MetagenerationMatch: 1}) 148 if err != nil { 149 t.Fatal(err) 150 } 151 if diff := cmp.Diff(got.Name, want.Name); diff != "" { 152 t.Errorf("Name: got(-),want(+):\n%s", diff) 153 } 154 if diff := cmp.Diff(got.VersioningEnabled, want.VersioningEnabled); diff != "" { 155 t.Errorf("VersioningEnabled: got(-),want(+):\n%s", diff) 156 } 157 if diff := cmp.Diff(got.RequesterPays, want.RequesterPays); diff != "" { 158 t.Errorf("RequesterPays: got(-),want(+):\n%s", diff) 159 } 160 if diff := cmp.Diff(got.DefaultEventBasedHold, want.DefaultEventBasedHold); diff != "" { 161 t.Errorf("DefaultEventBasedHold: got(-),want(+):\n%s", diff) 162 } 163 if diff := cmp.Diff(got.Encryption, want.Encryption); diff != "" { 164 t.Errorf("Encryption: got(-),want(+):\n%s", diff) 165 } 166 if diff := cmp.Diff(got.Lifecycle, want.Lifecycle); diff != "" { 167 t.Errorf("Lifecycle: got(-),want(+):\n%s", diff) 168 } 169 if diff := cmp.Diff(got.Logging, want.Logging); diff != "" { 170 t.Errorf("Logging: got(-),want(+):\n%s", diff) 171 } 172 if diff := cmp.Diff(got.Website, want.Website); diff != "" { 173 t.Errorf("Website: got(-),want(+):\n%s", diff) 174 } 175 if diff := cmp.Diff(got.RPO, want.RPO); diff != "" { 176 t.Errorf("RPO: got(-),want(+):\n%s", diff) 177 } 178 if diff := cmp.Diff(got.StorageClass, want.StorageClass); diff != "" { 179 t.Errorf("StorageClass: got(-),want(+):\n%s", diff) 180 } 181 }) 182 } 183 184 func TestGetServiceAccountEmulated(t *testing.T) { 185 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 186 _, err := client.GetServiceAccount(context.Background(), project) 187 if err != nil { 188 t.Fatalf("client.GetServiceAccount: %v", err) 189 } 190 }) 191 } 192 193 func TestGetSetTestIamPolicyEmulated(t *testing.T) { 194 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 195 battrs, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 196 Name: bucket, 197 }, nil) 198 if err != nil { 199 t.Fatalf("client.CreateBucket: %v", err) 200 } 201 got, err := client.GetIamPolicy(context.Background(), battrs.Name, 0) 202 if err != nil { 203 t.Fatalf("client.GetIamPolicy: %v", err) 204 } 205 err = client.SetIamPolicy(context.Background(), battrs.Name, &iampb.Policy{ 206 Etag: got.GetEtag(), 207 Bindings: []*iampb.Binding{{Role: "roles/viewer", Members: []string{"allUsers"}}}, 208 }) 209 if err != nil { 210 t.Fatalf("client.SetIamPolicy: %v", err) 211 } 212 want := []string{"storage.foo", "storage.bar"} 213 perms, err := client.TestIamPermissions(context.Background(), battrs.Name, want) 214 if err != nil { 215 t.Fatalf("client.TestIamPermissions: %v", err) 216 } 217 if diff := cmp.Diff(perms, want); diff != "" { 218 t.Errorf("got(-),want(+):\n%s", diff) 219 } 220 }) 221 } 222 223 func TestDeleteObjectEmulated(t *testing.T) { 224 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 225 // Populate test object that will be deleted. 226 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 227 Name: bucket, 228 }, nil) 229 if err != nil { 230 t.Fatalf("client.CreateBucket: %v", err) 231 } 232 want := ObjectAttrs{ 233 Bucket: bucket, 234 Name: fmt.Sprintf("testObject-%d", time.Now().Nanosecond()), 235 } 236 w := veneerClient.Bucket(bucket).Object(want.Name).NewWriter(context.Background()) 237 if _, err := w.Write(randomBytesToWrite); err != nil { 238 t.Fatalf("failed to populate test object: %v", err) 239 } 240 if err := w.Close(); err != nil { 241 t.Fatalf("closing object: %v", err) 242 } 243 err = client.DeleteObject(context.Background(), bucket, want.Name, defaultGen, nil) 244 if err != nil { 245 t.Fatalf("client.DeleteBucket: %v", err) 246 } 247 }) 248 } 249 250 func TestGetObjectEmulated(t *testing.T) { 251 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 252 // Populate test object. 253 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 254 Name: bucket, 255 }, nil) 256 if err != nil { 257 t.Fatalf("client.CreateBucket: %v", err) 258 } 259 want := ObjectAttrs{ 260 Bucket: bucket, 261 Name: fmt.Sprintf("testObject-%d", time.Now().Nanosecond()), 262 } 263 w := veneerClient.Bucket(bucket).Object(want.Name).NewWriter(context.Background()) 264 if _, err := w.Write(randomBytesToWrite); err != nil { 265 t.Fatalf("failed to populate test object: %v", err) 266 } 267 if err := w.Close(); err != nil { 268 t.Fatalf("closing object: %v", err) 269 } 270 got, err := client.GetObject(context.Background(), bucket, want.Name, defaultGen, nil, nil) 271 if err != nil { 272 t.Fatal(err) 273 } 274 if diff := cmp.Diff(got.Name, want.Name); diff != "" { 275 t.Errorf("got(-),want(+):\n%s", diff) 276 } 277 }) 278 } 279 280 func TestRewriteObjectEmulated(t *testing.T) { 281 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 282 // Populate test object. 283 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 284 Name: bucket, 285 }, nil) 286 if err != nil { 287 t.Fatalf("client.CreateBucket: %v", err) 288 } 289 src := ObjectAttrs{ 290 Bucket: bucket, 291 Name: fmt.Sprintf("testObject-%d", time.Now().Nanosecond()), 292 } 293 w := veneerClient.Bucket(bucket).Object(src.Name).NewWriter(context.Background()) 294 if _, err := w.Write(randomBytesToWrite); err != nil { 295 t.Fatalf("failed to populate test object: %v", err) 296 } 297 if err := w.Close(); err != nil { 298 t.Fatalf("closing object: %v", err) 299 } 300 req := &rewriteObjectRequest{ 301 dstObject: destinationObject{ 302 bucket: bucket, 303 name: fmt.Sprintf("copy-of-%s", src.Name), 304 attrs: &ObjectAttrs{}, 305 }, 306 srcObject: sourceObject{ 307 bucket: bucket, 308 name: src.Name, 309 gen: defaultGen, 310 }, 311 } 312 got, err := client.RewriteObject(context.Background(), req) 313 if err != nil { 314 t.Fatal(err) 315 } 316 if !got.done { 317 t.Fatal("didn't finish writing!") 318 } 319 if want := int64(len(randomBytesToWrite)); got.written != want { 320 t.Errorf("Bytes written: got %d, want %d", got.written, want) 321 } 322 }) 323 } 324 325 func TestUpdateObjectEmulated(t *testing.T) { 326 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 327 // Populate test object. 328 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 329 Name: bucket, 330 }, nil) 331 if err != nil { 332 t.Fatalf("client.CreateBucket: %v", err) 333 } 334 ct := time.Date(2022, 5, 25, 12, 12, 12, 0, time.UTC) 335 o := ObjectAttrs{ 336 Bucket: bucket, 337 Name: fmt.Sprintf("testObject-%d", time.Now().Nanosecond()), 338 CustomTime: ct, 339 } 340 w := veneerClient.Bucket(bucket).Object(o.Name).NewWriter(context.Background()) 341 if _, err := w.Write(randomBytesToWrite); err != nil { 342 t.Fatalf("failed to populate test object: %v", err) 343 } 344 if err := w.Close(); err != nil { 345 t.Fatalf("closing object: %v", err) 346 } 347 want := &ObjectAttrsToUpdate{ 348 EventBasedHold: false, 349 TemporaryHold: false, 350 ContentType: "text/html", 351 ContentLanguage: "en", 352 ContentEncoding: "gzip", 353 ContentDisposition: "", 354 CacheControl: "", 355 CustomTime: ct.Add(10 * time.Hour), 356 } 357 358 params := &updateObjectParams{bucket: bucket, object: o.Name, uattrs: want, gen: defaultGen, conds: &Conditions{MetagenerationMatch: 1}} 359 got, err := client.UpdateObject(context.Background(), params) 360 if err != nil { 361 t.Fatalf("client.UpdateObject: %v", err) 362 } 363 if diff := cmp.Diff(got.Name, o.Name); diff != "" { 364 t.Errorf("Name: got(-),want(+):\n%s", diff) 365 } 366 if diff := cmp.Diff(got.EventBasedHold, want.EventBasedHold); diff != "" { 367 t.Errorf("EventBasedHold: got(-),want(+):\n%s", diff) 368 } 369 if diff := cmp.Diff(got.TemporaryHold, want.TemporaryHold); diff != "" { 370 t.Errorf("TemporaryHold: got(-),want(+):\n%s", diff) 371 } 372 if diff := cmp.Diff(got.ContentType, want.ContentType); diff != "" { 373 t.Errorf("ContentType: got(-),want(+):\n%s", diff) 374 } 375 if diff := cmp.Diff(got.ContentLanguage, want.ContentLanguage); diff != "" { 376 t.Errorf("ContentLanguage: got(-),want(+):\n%s", diff) 377 } 378 if diff := cmp.Diff(got.ContentEncoding, want.ContentEncoding); diff != "" { 379 t.Errorf("ContentEncoding: got(-),want(+):\n%s", diff) 380 } 381 if diff := cmp.Diff(got.ContentDisposition, want.ContentDisposition); diff != "" { 382 t.Errorf("ContentDisposition: got(-),want(+):\n%s", diff) 383 } 384 if diff := cmp.Diff(got.CacheControl, want.CacheControl); diff != "" { 385 t.Errorf("CacheControl: got(-),want(+):\n%s", diff) 386 } 387 if diff := cmp.Diff(got.CustomTime, want.CustomTime); diff != "" { 388 t.Errorf("CustomTime: got(-),want(+):\n%s", diff) 389 } 390 }) 391 } 392 393 func TestListObjectsEmulated(t *testing.T) { 394 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 395 // Populate test data. 396 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 397 Name: bucket, 398 }, nil) 399 if err != nil { 400 t.Fatalf("client.CreateBucket: %v", err) 401 } 402 prefix := time.Now().Nanosecond() 403 want := []*ObjectAttrs{ 404 { 405 Bucket: bucket, 406 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 407 }, 408 { 409 Bucket: bucket, 410 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 411 }, 412 { 413 Bucket: bucket, 414 Name: fmt.Sprintf("object-%d", time.Now().Nanosecond()), 415 }, 416 } 417 for _, obj := range want { 418 w := veneerClient.Bucket(bucket).Object(obj.Name).NewWriter(context.Background()) 419 if _, err := w.Write(randomBytesToWrite); err != nil { 420 t.Fatalf("failed to populate test data: %v", err) 421 } 422 if err := w.Close(); err != nil { 423 t.Fatalf("closing object: %v", err) 424 } 425 } 426 427 // Simple list, no query. 428 it := client.ListObjects(context.Background(), bucket, nil) 429 var o *ObjectAttrs 430 var got int 431 for i := 0; err == nil && i <= len(want); i++ { 432 o, err = it.Next() 433 if err != nil { 434 break 435 } 436 got++ 437 if diff := cmp.Diff(o.Name, want[i].Name); diff != "" { 438 t.Errorf("got(-),want(+):\n%s", diff) 439 } 440 } 441 if err != iterator.Done { 442 t.Fatalf("expected %q but got %q", iterator.Done, err) 443 } 444 expected := len(want) 445 if got != expected { 446 t.Errorf("expected to get %d objects, but got %d", expected, got) 447 } 448 }) 449 } 450 451 func TestListObjectsWithPrefixEmulated(t *testing.T) { 452 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 453 // Populate test data. 454 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 455 Name: bucket, 456 }, nil) 457 if err != nil { 458 t.Fatalf("client.CreateBucket: %v", err) 459 } 460 prefix := time.Now().Nanosecond() 461 want := []*ObjectAttrs{ 462 { 463 Bucket: bucket, 464 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 465 }, 466 { 467 Bucket: bucket, 468 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 469 }, 470 { 471 Bucket: bucket, 472 Name: fmt.Sprintf("object-%d", time.Now().Nanosecond()), 473 }, 474 } 475 for _, obj := range want { 476 w := veneerClient.Bucket(bucket).Object(obj.Name).NewWriter(context.Background()) 477 if _, err := w.Write(randomBytesToWrite); err != nil { 478 t.Fatalf("failed to populate test data: %v", err) 479 } 480 if err := w.Close(); err != nil { 481 t.Fatalf("closing object: %v", err) 482 } 483 } 484 485 // Query with Prefix. 486 it := client.ListObjects(context.Background(), bucket, &Query{Prefix: strconv.Itoa(prefix)}) 487 var o *ObjectAttrs 488 var got int 489 want = want[:2] 490 for i := 0; i <= len(want); i++ { 491 o, err = it.Next() 492 if err != nil { 493 break 494 } 495 got++ 496 if diff := cmp.Diff(o.Name, want[i].Name); diff != "" { 497 t.Errorf("got(-),want(+):\n%s", diff) 498 } 499 } 500 if err != iterator.Done { 501 t.Fatalf("expected %q but got %q", iterator.Done, err) 502 } 503 expected := len(want) 504 if got != expected { 505 t.Errorf("expected to get %d objects, but got %d", expected, got) 506 } 507 }) 508 } 509 510 func TestListBucketsEmulated(t *testing.T) { 511 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 512 prefix := time.Now().Nanosecond() 513 want := []*BucketAttrs{ 514 {Name: fmt.Sprintf("%d-%s-%d", prefix, bucket, time.Now().Nanosecond())}, 515 {Name: fmt.Sprintf("%d-%s-%d", prefix, bucket, time.Now().Nanosecond())}, 516 {Name: fmt.Sprintf("%s-%d", bucket, time.Now().Nanosecond())}, 517 } 518 // Create the buckets that will be listed. 519 for _, b := range want { 520 _, err := client.CreateBucket(context.Background(), project, b.Name, b, nil) 521 if err != nil { 522 t.Fatalf("client.CreateBucket: %v", err) 523 } 524 } 525 526 it := client.ListBuckets(context.Background(), project) 527 it.Prefix = strconv.Itoa(prefix) 528 // Drop the non-prefixed bucket from the expected results. 529 want = want[:2] 530 var err error 531 var b *BucketAttrs 532 var got int 533 for i := 0; err == nil && i <= len(want); i++ { 534 b, err = it.Next() 535 if err != nil { 536 break 537 } 538 got++ 539 if diff := cmp.Diff(b.Name, want[i].Name); diff != "" { 540 t.Errorf("got(-),want(+):\n%s", diff) 541 } 542 } 543 if err != iterator.Done { 544 t.Fatalf("expected %q but got %q", iterator.Done, err) 545 } 546 expected := len(want) 547 if got != expected { 548 t.Errorf("expected to get %d buckets, but got %d", expected, got) 549 } 550 }) 551 } 552 553 func TestListBucketACLsEmulated(t *testing.T) { 554 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 555 ctx := context.Background() 556 attrs := &BucketAttrs{ 557 Name: bucket, 558 PredefinedACL: "publicRead", 559 } 560 // Create the bucket that will be retrieved. 561 if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil { 562 t.Fatalf("client.CreateBucket: %v", err) 563 } 564 565 acls, err := client.ListBucketACLs(ctx, bucket) 566 if err != nil { 567 t.Fatalf("client.ListBucketACLs: %v", err) 568 } 569 if want, got := len(acls), 2; want != got { 570 t.Errorf("ListBucketACLs: got %v, want %v items", acls, want) 571 } 572 }) 573 } 574 575 func TestUpdateBucketACLEmulated(t *testing.T) { 576 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 577 ctx := context.Background() 578 attrs := &BucketAttrs{ 579 Name: bucket, 580 PredefinedACL: "authenticatedRead", 581 } 582 // Create the bucket that will be retrieved. 583 if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil { 584 t.Fatalf("client.CreateBucket: %v", err) 585 } 586 var listAcls []ACLRule 587 var err error 588 // Assert bucket has two BucketACL entities, including project owner and predefinedACL. 589 if listAcls, err = client.ListBucketACLs(ctx, bucket); err != nil { 590 t.Fatalf("client.ListBucketACLs: %v", err) 591 } 592 if got, want := len(listAcls), 2; got != want { 593 t.Errorf("ListBucketACLs: got %v, want %v items", listAcls, want) 594 } 595 entity := AllUsers 596 role := RoleReader 597 err = client.UpdateBucketACL(ctx, bucket, entity, role) 598 if err != nil { 599 t.Fatalf("client.UpdateBucketACL: %v", err) 600 } 601 // Assert bucket now has three BucketACL entities, including existing ACLs. 602 if listAcls, err = client.ListBucketACLs(ctx, bucket); err != nil { 603 t.Fatalf("client.ListBucketACLs: %v", err) 604 } 605 if got, want := len(listAcls), 3; got != want { 606 t.Errorf("ListBucketACLs: got %v, want %v items", listAcls, want) 607 } 608 }) 609 } 610 611 func TestDeleteBucketACLEmulated(t *testing.T) { 612 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 613 ctx := context.Background() 614 attrs := &BucketAttrs{ 615 Name: bucket, 616 PredefinedACL: "publicRead", 617 } 618 // Create the bucket that will be retrieved. 619 if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil { 620 t.Fatalf("client.CreateBucket: %v", err) 621 } 622 // Assert bucket has two BucketACL entities, including project owner and predefinedACL. 623 acls, err := client.ListBucketACLs(ctx, bucket) 624 if err != nil { 625 t.Fatalf("client.ListBucketACLs: %v", err) 626 } 627 if got, want := len(acls), 2; got != want { 628 t.Errorf("ListBucketACLs: got %v, want %v items", acls, want) 629 } 630 // Delete one BucketACL with AllUsers entity. 631 if err := client.DeleteBucketACL(ctx, bucket, AllUsers); err != nil { 632 t.Fatalf("client.DeleteBucketACL: %v", err) 633 } 634 // Assert bucket has one BucketACL. 635 acls, err = client.ListBucketACLs(ctx, bucket) 636 if err != nil { 637 t.Fatalf("client.ListBucketACLs: %v", err) 638 } 639 if got, want := len(acls), 1; got != want { 640 t.Errorf("ListBucketACLs: got %v, want %v items", acls, want) 641 } 642 }) 643 } 644 645 func TestDefaultObjectACLCRUDEmulated(t *testing.T) { 646 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 647 ctx := context.Background() 648 attrs := &BucketAttrs{ 649 Name: bucket, 650 PredefinedDefaultObjectACL: "publicRead", 651 } 652 // Create the bucket that will be retrieved. 653 if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil { 654 t.Fatalf("client.CreateBucket: %v", err) 655 } 656 // Assert bucket has 2 DefaultObjectACL entities, including project owner and PredefinedDefaultObjectACL. 657 acls, err := client.ListDefaultObjectACLs(ctx, bucket) 658 if err != nil { 659 t.Fatalf("client.ListDefaultObjectACLs: %v", err) 660 } 661 if got, want := len(acls), 2; got != want { 662 t.Errorf("ListDefaultObjectACLs: got %v, want %v items", acls, want) 663 } 664 entity := AllAuthenticatedUsers 665 role := RoleOwner 666 err = client.UpdateDefaultObjectACL(ctx, bucket, entity, role) 667 if err != nil { 668 t.Fatalf("UpdateDefaultObjectCL: %v", err) 669 } 670 // Assert there are now 3 DefaultObjectACL entities, including existing DefaultObjectACLs. 671 acls, err = client.ListDefaultObjectACLs(ctx, bucket) 672 if err != nil { 673 t.Fatalf("client.ListDefaultObjectACLs: %v", err) 674 } 675 if got, want := len(acls), 3; got != want { 676 t.Errorf("ListDefaultObjectACLs: %v got %v, want %v items", len(acls), acls, want) 677 } 678 // Delete 1 DefaultObjectACL with AllUsers entity. 679 if err := client.DeleteDefaultObjectACL(ctx, bucket, AllUsers); err != nil { 680 t.Fatalf("client.DeleteDefaultObjectACL: %v", err) 681 } 682 // Assert bucket has 2 DefaultObjectACL entities. 683 acls, err = client.ListDefaultObjectACLs(ctx, bucket) 684 if err != nil { 685 t.Fatalf("client.ListDefaultObjectACLs: %v", err) 686 } 687 if got, want := len(acls), 2; got != want { 688 t.Errorf("ListDefaultObjectACLs: %v got %v, want %v items", len(acls), acls, want) 689 } 690 }) 691 } 692 693 func TestObjectACLCRUDEmulated(t *testing.T) { 694 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 695 // Populate test object. 696 ctx := context.Background() 697 _, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{ 698 Name: bucket, 699 }, nil) 700 if err != nil { 701 t.Fatalf("CreateBucket: %v", err) 702 } 703 o := ObjectAttrs{ 704 Bucket: bucket, 705 Name: fmt.Sprintf("testObject-%d", time.Now().Nanosecond()), 706 } 707 w := veneerClient.Bucket(bucket).Object(o.Name).NewWriter(context.Background()) 708 if _, err := w.Write(randomBytesToWrite); err != nil { 709 t.Fatalf("failed to populate test object: %v", err) 710 } 711 if err := w.Close(); err != nil { 712 t.Fatalf("closing object: %v", err) 713 } 714 var listAcls []ACLRule 715 // Assert there are 4 ObjectACL entities, including object owner and project owners/editors/viewers. 716 if listAcls, err = client.ListObjectACLs(ctx, bucket, o.Name); err != nil { 717 t.Fatalf("ListObjectACLs: %v", err) 718 } 719 if got, want := len(listAcls), 4; got != want { 720 t.Errorf("ListObjectACLs: got %v, want %v items", listAcls, want) 721 } 722 entity := AllUsers 723 role := RoleReader 724 err = client.UpdateObjectACL(ctx, bucket, o.Name, entity, role) 725 if err != nil { 726 t.Fatalf("UpdateObjectCL: %v", err) 727 } 728 // Assert there are now 5 ObjectACL entities, including existing ACLs. 729 if listAcls, err = client.ListObjectACLs(ctx, bucket, o.Name); err != nil { 730 t.Fatalf("ListObjectACLs: %v", err) 731 } 732 if got, want := len(listAcls), 5; got != want { 733 t.Errorf("ListObjectACLs: got %v, want %v items", listAcls, want) 734 } 735 if err = client.DeleteObjectACL(ctx, bucket, o.Name, AllUsers); err != nil { 736 t.Fatalf("client.DeleteObjectACL: %v", err) 737 } 738 // Assert there are now 4 ObjectACL entities after deletion. 739 if listAcls, err = client.ListObjectACLs(ctx, bucket, o.Name); err != nil { 740 t.Fatalf("ListObjectACLs: %v", err) 741 } 742 if got, want := len(listAcls), 4; got != want { 743 t.Errorf("ListObjectACLs: got %v, want %v items", listAcls, want) 744 } 745 }) 746 } 747 748 func TestOpenReaderEmulated(t *testing.T) { 749 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 750 // Populate test data. 751 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 752 Name: bucket, 753 }, nil) 754 if err != nil { 755 t.Fatalf("client.CreateBucket: %v", err) 756 } 757 prefix := time.Now().Nanosecond() 758 want := &ObjectAttrs{ 759 Bucket: bucket, 760 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 761 } 762 w := veneerClient.Bucket(bucket).Object(want.Name).NewWriter(context.Background()) 763 if _, err := w.Write(randomBytesToWrite); err != nil { 764 t.Fatalf("failed to populate test data: %v", err) 765 } 766 if err := w.Close(); err != nil { 767 t.Fatalf("closing object: %v", err) 768 } 769 770 params := &newRangeReaderParams{ 771 bucket: bucket, 772 object: want.Name, 773 gen: defaultGen, 774 offset: 0, 775 length: -1, 776 } 777 r, err := client.NewRangeReader(context.Background(), params) 778 if err != nil { 779 t.Fatalf("opening reading: %v", err) 780 } 781 wantLen := len(randomBytesToWrite) 782 got := make([]byte, wantLen) 783 n, err := r.Read(got) 784 if n != wantLen { 785 t.Fatalf("expected to read %d bytes, but got %d", wantLen, n) 786 } 787 if diff := cmp.Diff(got, randomBytesToWrite); diff != "" { 788 t.Fatalf("Read: got(-),want(+):\n%s", diff) 789 } 790 }) 791 } 792 793 func TestOpenWriterEmulated(t *testing.T) { 794 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 795 if strings.Contains(project, "grpc") { 796 t.Skip("Implementation in testbench pending: https://github.com/googleapis/storage-testbench/issues/568") 797 } 798 799 // Populate test data. 800 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 801 Name: bucket, 802 }, nil) 803 if err != nil { 804 t.Fatalf("client.CreateBucket: %v", err) 805 } 806 prefix := time.Now().Nanosecond() 807 want := &ObjectAttrs{ 808 Bucket: bucket, 809 Name: fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond()), 810 Generation: defaultGen, 811 } 812 813 var gotAttrs *ObjectAttrs 814 params := &openWriterParams{ 815 attrs: want, 816 bucket: bucket, 817 ctx: context.Background(), 818 donec: make(chan struct{}), 819 setError: func(_ error) {}, // no-op 820 progress: func(_ int64) {}, // no-op 821 setObj: func(o *ObjectAttrs) { gotAttrs = o }, 822 } 823 pw, err := client.OpenWriter(params) 824 if err != nil { 825 t.Fatalf("failed to open writer: %v", err) 826 } 827 if _, err := pw.Write(randomBytesToWrite); err != nil { 828 t.Fatalf("failed to populate test data: %v", err) 829 } 830 if err := pw.Close(); err != nil { 831 t.Fatalf("closing object: %v", err) 832 } 833 select { 834 case <-params.donec: 835 } 836 if gotAttrs == nil { 837 t.Fatalf("Writer finished, but resulting object wasn't set") 838 } 839 if diff := cmp.Diff(gotAttrs.Name, want.Name); diff != "" { 840 t.Fatalf("Resulting object name: got(-),want(+):\n%s", diff) 841 } 842 843 r, err := veneerClient.Bucket(bucket).Object(want.Name).NewReader(context.Background()) 844 if err != nil { 845 t.Fatalf("opening reading: %v", err) 846 } 847 wantLen := len(randomBytesToWrite) 848 got := make([]byte, wantLen) 849 n, err := r.Read(got) 850 if n != wantLen { 851 t.Fatalf("expected to read %d bytes, but got %d", wantLen, n) 852 } 853 if diff := cmp.Diff(got, randomBytesToWrite); diff != "" { 854 t.Fatalf("checking written content: got(-),want(+):\n%s", diff) 855 } 856 }) 857 } 858 859 func TestListNotificationsEmulated(t *testing.T) { 860 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 861 // Populate test object. 862 ctx := context.Background() 863 _, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{ 864 Name: bucket, 865 }, nil) 866 if err != nil { 867 t.Fatalf("client.CreateBucket: %v", err) 868 } 869 _, err = client.CreateNotification(ctx, bucket, &Notification{ 870 TopicProjectID: project, 871 TopicID: "go-storage-notification-test", 872 PayloadFormat: "JSON_API_V1", 873 }) 874 if err != nil { 875 t.Fatalf("client.CreateNotification: %v", err) 876 } 877 n, err := client.ListNotifications(ctx, bucket) 878 if err != nil { 879 t.Fatalf("client.ListNotifications: %v", err) 880 } 881 if want, got := 1, len(n); want != got { 882 t.Errorf("ListNotifications: got %v, want %v items", n, want) 883 } 884 }) 885 } 886 887 func TestCreateNotificationEmulated(t *testing.T) { 888 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 889 // Populate test object. 890 ctx := context.Background() 891 _, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{ 892 Name: bucket, 893 }, nil) 894 if err != nil { 895 t.Fatalf("client.CreateBucket: %v", err) 896 } 897 898 want := &Notification{ 899 TopicProjectID: project, 900 TopicID: "go-storage-notification-test", 901 PayloadFormat: "JSON_API_V1", 902 } 903 got, err := client.CreateNotification(ctx, bucket, want) 904 if err != nil { 905 t.Fatalf("client.CreateNotification: %v", err) 906 } 907 if diff := cmp.Diff(got.TopicID, want.TopicID); diff != "" { 908 t.Errorf("CreateNotification topic: got(-),want(+):\n%s", diff) 909 } 910 }) 911 } 912 913 func TestDeleteNotificationEmulated(t *testing.T) { 914 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 915 // Populate test object. 916 ctx := context.Background() 917 _, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{ 918 Name: bucket, 919 }, nil) 920 if err != nil { 921 t.Fatalf("client.CreateBucket: %v", err) 922 } 923 var n *Notification 924 n, err = client.CreateNotification(ctx, bucket, &Notification{ 925 TopicProjectID: project, 926 TopicID: "go-storage-notification-test", 927 PayloadFormat: "JSON_API_V1", 928 }) 929 if err != nil { 930 t.Fatalf("client.CreateNotification: %v", err) 931 } 932 err = client.DeleteNotification(ctx, bucket, n.ID) 933 if err != nil { 934 t.Fatalf("client.DeleteNotification: %v", err) 935 } 936 }) 937 } 938 939 func initEmulatorClients() func() error { 940 noopCloser := func() error { return nil } 941 if !isEmulatorEnvironmentSet() { 942 return noopCloser 943 } 944 ctx := context.Background() 945 946 grpcClient, err := newGRPCStorageClient(ctx) 947 if err != nil { 948 log.Fatalf("Error setting up gRPC client for emulator tests: %v", err) 949 return noopCloser 950 } 951 httpClient, err := newHTTPStorageClient(ctx) 952 if err != nil { 953 log.Fatalf("Error setting up HTTP client for emulator tests: %v", err) 954 return noopCloser 955 } 956 957 emulatorClients = map[string]storageClient{ 958 "http": httpClient, 959 "grpc": grpcClient, 960 } 961 962 veneerClient, err = NewClient(ctx) 963 if err != nil { 964 log.Fatalf("Error setting up Veneer client for emulator tests: %v", err) 965 return noopCloser 966 } 967 968 return func() error { 969 gerr := grpcClient.Close() 970 herr := httpClient.Close() 971 verr := veneerClient.Close() 972 973 if gerr != nil { 974 return gerr 975 } else if herr != nil { 976 return herr 977 } 978 return verr 979 } 980 } 981 982 func TestLockBucketRetentionPolicyEmulated(t *testing.T) { 983 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 984 b := &BucketAttrs{ 985 Name: bucket, 986 RetentionPolicy: &RetentionPolicy{ 987 RetentionPeriod: time.Minute, 988 }, 989 } 990 // Create the bucket that will be locked. 991 _, err := client.CreateBucket(context.Background(), project, b.Name, b, nil) 992 if err != nil { 993 t.Fatalf("client.CreateBucket: %v", err) 994 } 995 // Lock the bucket's retention policy. 996 err = client.LockBucketRetentionPolicy(context.Background(), b.Name, &BucketConditions{MetagenerationMatch: 1}) 997 if err != nil { 998 t.Fatalf("client.LockBucketRetentionPolicy: %v", err) 999 } 1000 got, err := client.GetBucket(context.Background(), bucket, nil) 1001 if err != nil { 1002 t.Fatalf("client.GetBucket: %v", err) 1003 } 1004 if !got.RetentionPolicy.IsLocked { 1005 t.Error("Expected bucket retention policy to be locked, but was not.") 1006 } 1007 }) 1008 } 1009 1010 func TestComposeEmulated(t *testing.T) { 1011 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 1012 ctx := context.Background() 1013 1014 // Populate test data. 1015 _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{ 1016 Name: bucket, 1017 }, nil) 1018 if err != nil { 1019 t.Fatalf("client.CreateBucket: %v", err) 1020 } 1021 prefix := time.Now().Nanosecond() 1022 srcNames := []string{ 1023 fmt.Sprintf("%d-object1", prefix), 1024 fmt.Sprintf("%d-object2", prefix), 1025 } 1026 1027 for _, n := range srcNames { 1028 w := veneerClient.Bucket(bucket).Object(n).NewWriter(ctx) 1029 if _, err := w.Write(randomBytesToWrite); err != nil { 1030 t.Fatalf("failed to populate test data: %v", err) 1031 } 1032 if err := w.Close(); err != nil { 1033 t.Fatalf("closing object: %v", err) 1034 } 1035 } 1036 1037 dstName := fmt.Sprintf("%d-object3", prefix) 1038 req := composeObjectRequest{ 1039 dstBucket: bucket, 1040 dstObject: destinationObject{ 1041 name: dstName, 1042 attrs: &ObjectAttrs{StorageClass: "COLDLINE"}, 1043 }, 1044 srcs: []sourceObject{ 1045 {name: srcNames[0]}, 1046 {name: srcNames[1]}, 1047 }, 1048 } 1049 attrs, err := client.ComposeObject(ctx, &req) 1050 if err != nil { 1051 t.Fatalf("client.ComposeObject(): %v", err) 1052 } 1053 if got := attrs.Name; got != dstName { 1054 t.Errorf("attrs.Name: got %v, want %v", got, dstName) 1055 } 1056 // Check that the destination object size is equal to the sum of its 1057 // sources. 1058 if got, want := attrs.Size, 2*len(randomBytesToWrite); got != int64(want) { 1059 t.Errorf("attrs.Size: got %v, want %v", got, want) 1060 } 1061 // Check that destination attrs set via object attrs are preserved. 1062 if got, want := attrs.StorageClass, "COLDLINE"; got != want { 1063 t.Errorf("attrs.StorageClass: got %v, want %v", got, want) 1064 } 1065 }) 1066 } 1067 1068 func TestHMACKeyCRUDEmulated(t *testing.T) { 1069 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 1070 ctx := context.Background() 1071 serviceAccountEmail := "test@test-project.iam.gserviceaccount.com" 1072 want, err := client.CreateHMACKey(ctx, project, serviceAccountEmail) 1073 if err != nil { 1074 t.Fatalf("CreateHMACKey: %v", err) 1075 } 1076 if want == nil { 1077 t.Fatal("CreateHMACKey: Unexpectedly got back a nil HMAC key") 1078 } 1079 if want.State != Active { 1080 t.Fatalf("CreateHMACKey: Unexpected state %q, expected %q", want.State, Active) 1081 } 1082 got, err := client.GetHMACKey(ctx, project, want.AccessID) 1083 if err != nil { 1084 t.Fatalf("GetHMACKey: %v", err) 1085 } 1086 if diff := cmp.Diff(got.ID, want.ID); diff != "" { 1087 t.Errorf("GetHMACKey ID:got(-),want(+):\n%s", diff) 1088 } 1089 if diff := cmp.Diff(got.UpdatedTime, want.UpdatedTime); diff != "" { 1090 t.Errorf("GetHMACKey UpdatedTime: got(-),want(+):\n%s", diff) 1091 } 1092 attr := &HMACKeyAttrsToUpdate{ 1093 State: Inactive, 1094 } 1095 got, err = client.UpdateHMACKey(ctx, project, serviceAccountEmail, want.AccessID, attr) 1096 if err != nil { 1097 t.Fatalf("UpdateHMACKey: %v", err) 1098 } 1099 if got.State != attr.State { 1100 t.Errorf("UpdateHMACKey State: got %v, want %v", got.State, attr.State) 1101 } 1102 showDeletedKeys := false 1103 it := client.ListHMACKeys(ctx, project, serviceAccountEmail, showDeletedKeys) 1104 var count int 1105 var e error 1106 for ; ; count++ { 1107 _, e = it.Next() 1108 if e != nil { 1109 break 1110 } 1111 } 1112 if e != iterator.Done { 1113 t.Fatalf("ListHMACKeys: expected %q but got %q", iterator.Done, err) 1114 } 1115 if expected := 1; count != expected { 1116 t.Errorf("ListHMACKeys: expected to get %d hmacKeys, but got %d", expected, count) 1117 } 1118 err = client.DeleteHMACKey(ctx, project, want.AccessID) 1119 if err != nil { 1120 t.Fatalf("DeleteHMACKey: %v", err) 1121 } 1122 got, err = client.GetHMACKey(ctx, project, want.AccessID) 1123 if err == nil { 1124 t.Fatalf("GetHMACKey unexcepted error: wanted 404") 1125 } 1126 }) 1127 } 1128 1129 func TestBucketConditionsEmulated(t *testing.T) { 1130 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 1131 ctx := context.Background() 1132 cases := []struct { 1133 name string 1134 call func(bucket string, metaGen int64) error 1135 }{ 1136 { 1137 name: "get", 1138 call: func(bucket string, metaGen int64) error { 1139 _, err := client.GetBucket(ctx, bucket, &BucketConditions{MetagenerationMatch: metaGen}) 1140 return err 1141 }, 1142 }, 1143 { 1144 name: "update", 1145 call: func(bucket string, metaGen int64) error { 1146 _, err := client.UpdateBucket(ctx, bucket, &BucketAttrsToUpdate{StorageClass: "ARCHIVE"}, &BucketConditions{MetagenerationMatch: metaGen}) 1147 return err 1148 }, 1149 }, 1150 { 1151 name: "delete", 1152 call: func(bucket string, metaGen int64) error { 1153 return client.DeleteBucket(ctx, bucket, &BucketConditions{MetagenerationMatch: metaGen}) 1154 }, 1155 }, 1156 { 1157 name: "lockRetentionPolicy", 1158 call: func(bucket string, metaGen int64) error { 1159 return client.LockBucketRetentionPolicy(ctx, bucket, &BucketConditions{MetagenerationMatch: metaGen}) 1160 }, 1161 }, 1162 } 1163 for _, c := range cases { 1164 t.Run(c.name, func(r *testing.T) { 1165 bucket, metaGen, err := createBucket(ctx, project) 1166 if err != nil { 1167 r.Fatalf("creating bucket: %v", err) 1168 } 1169 if err := c.call(bucket, metaGen); err != nil { 1170 r.Errorf("error: %v", err) 1171 } 1172 }) 1173 } 1174 }) 1175 } 1176 1177 func TestObjectConditionsEmulated(t *testing.T) { 1178 transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) { 1179 ctx := context.Background() 1180 1181 // Create test bucket 1182 if _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{Name: bucket}, nil); err != nil { 1183 t.Fatalf("client.CreateBucket: %v", err) 1184 } 1185 1186 cases := []struct { 1187 name string 1188 call func() error 1189 }{ 1190 { 1191 name: "update generation", 1192 call: func() error { 1193 objName, gen, _, err := createObject(ctx, bucket) 1194 if err != nil { 1195 return fmt.Errorf("creating object: %w", err) 1196 } 1197 uattrs := &ObjectAttrsToUpdate{CustomTime: time.Now()} 1198 _, err = client.UpdateObject(ctx, &updateObjectParams{bucket: bucket, object: objName, uattrs: uattrs, gen: gen}) 1199 return err 1200 }, 1201 }, 1202 { 1203 name: "update ifMetagenerationMatch", 1204 call: func() error { 1205 objName, gen, metaGen, err := createObject(ctx, bucket) 1206 if err != nil { 1207 return fmt.Errorf("creating object: %w", err) 1208 } 1209 uattrs := &ObjectAttrsToUpdate{CustomTime: time.Now()} 1210 conds := &Conditions{ 1211 GenerationMatch: gen, 1212 MetagenerationMatch: metaGen, 1213 } 1214 _, err = client.UpdateObject(ctx, &updateObjectParams{bucket: bucket, object: objName, uattrs: uattrs, gen: gen, conds: conds}) 1215 return err 1216 }, 1217 }, 1218 { 1219 name: "write ifGenerationMatch", 1220 call: func() error { 1221 var err error 1222 _, err = client.OpenWriter(&openWriterParams{ 1223 ctx: ctx, 1224 chunkSize: 256 * 1024, 1225 chunkRetryDeadline: 0, 1226 bucket: bucket, 1227 attrs: &ObjectAttrs{}, 1228 conds: &Conditions{DoesNotExist: true}, 1229 encryptionKey: nil, 1230 sendCRC32C: false, 1231 donec: nil, 1232 setError: func(e error) { 1233 if e != nil { 1234 err = e 1235 } 1236 }, 1237 progress: nil, 1238 setObj: nil, 1239 }) 1240 return err 1241 }, 1242 }, 1243 { 1244 name: "rewrite ifMetagenerationMatch", 1245 call: func() error { 1246 objName, gen, metaGen, err := createObject(ctx, bucket) 1247 if err != nil { 1248 return fmt.Errorf("creating object: %w", err) 1249 } 1250 _, err = client.RewriteObject(ctx, &rewriteObjectRequest{ 1251 srcObject: sourceObject{ 1252 name: objName, 1253 bucket: bucket, 1254 gen: gen, 1255 conds: &Conditions{ 1256 GenerationMatch: gen, 1257 MetagenerationMatch: metaGen, 1258 }, 1259 }, 1260 dstObject: destinationObject{ 1261 name: fmt.Sprintf("%d-object", time.Now().Nanosecond()), 1262 bucket: bucket, 1263 conds: &Conditions{ 1264 DoesNotExist: true, 1265 }, 1266 attrs: &ObjectAttrs{}, 1267 }, 1268 }) 1269 return err 1270 }, 1271 }, 1272 { 1273 name: "compose ifGenerationMatch", 1274 call: func() error { 1275 obj1, obj1Gen, _, err := createObject(ctx, bucket) 1276 if err != nil { 1277 return fmt.Errorf("creating object: %w", err) 1278 } 1279 obj2, obj2Gen, _, err := createObject(ctx, bucket) 1280 if err != nil { 1281 return fmt.Errorf("creating object: %w", err) 1282 } 1283 _, err = client.ComposeObject(ctx, &composeObjectRequest{ 1284 dstBucket: bucket, 1285 dstObject: destinationObject{ 1286 name: fmt.Sprintf("%d-object", time.Now().Nanosecond()), 1287 bucket: bucket, 1288 conds: &Conditions{DoesNotExist: true}, 1289 attrs: &ObjectAttrs{}, 1290 }, 1291 srcs: []sourceObject{ 1292 { 1293 name: obj1, 1294 bucket: bucket, 1295 gen: obj1Gen, 1296 conds: &Conditions{ 1297 GenerationMatch: obj1Gen, 1298 }, 1299 }, 1300 { 1301 name: obj2, 1302 bucket: bucket, 1303 conds: &Conditions{ 1304 GenerationMatch: obj2Gen, 1305 }, 1306 }, 1307 }, 1308 }) 1309 return err 1310 }, 1311 }, 1312 { 1313 name: "delete ifGenerationMatch", 1314 call: func() error { 1315 objName, gen, _, err := createObject(ctx, bucket) 1316 if err != nil { 1317 return fmt.Errorf("creating object: %w", err) 1318 } 1319 err = client.DeleteObject(ctx, bucket, objName, gen, &Conditions{GenerationMatch: gen}) 1320 return err 1321 }, 1322 }, 1323 { 1324 name: "get ifMetagenerationMatch", 1325 call: func() error { 1326 objName, gen, metaGen, err := createObject(ctx, bucket) 1327 if err != nil { 1328 return fmt.Errorf("creating object: %w", err) 1329 } 1330 _, err = client.GetObject(ctx, bucket, objName, gen, nil, &Conditions{GenerationMatch: gen, MetagenerationMatch: metaGen}) 1331 return err 1332 }, 1333 }, 1334 } 1335 for _, c := range cases { 1336 t.Run(c.name, func(r *testing.T) { 1337 if err := c.call(); err != nil { 1338 r.Errorf("error: %v", err) 1339 } 1340 }) 1341 } 1342 }) 1343 } 1344 1345 // createObject creates an object in the emulator and returns its name, generation, and 1346 // metageneration. 1347 func createObject(ctx context.Context, bucket string) (string, int64, int64, error) { 1348 prefix := time.Now().Nanosecond() 1349 objName := fmt.Sprintf("%d-object", prefix) 1350 1351 w := veneerClient.Bucket(bucket).Object(objName).NewWriter(ctx) 1352 if _, err := w.Write(randomBytesToWrite); err != nil { 1353 return "", 0, 0, fmt.Errorf("failed to populate test data: %w", err) 1354 } 1355 if err := w.Close(); err != nil { 1356 return "", 0, 0, fmt.Errorf("closing object: %w", err) 1357 } 1358 attrs, err := veneerClient.Bucket(bucket).Object(objName).Attrs(ctx) 1359 if err != nil { 1360 return "", 0, 0, fmt.Errorf("get object: %w", err) 1361 } 1362 return objName, attrs.Generation, attrs.Metageneration, nil 1363 } 1364 1365 // createBucket creates a new bucket in the emulator and returns its name and 1366 // metageneration. 1367 func createBucket(ctx context.Context, projectID string) (string, int64, error) { 1368 prefix := time.Now().Nanosecond() 1369 bucket := fmt.Sprintf("%d-bucket", prefix) 1370 1371 if err := veneerClient.Bucket(bucket).Create(ctx, projectID, nil); err != nil { 1372 return "", 0, fmt.Errorf("Bucket.Create: %w", err) 1373 } 1374 attrs, err := veneerClient.Bucket(bucket).Attrs(ctx) 1375 if err != nil { 1376 return "", 0, fmt.Errorf("Bucket.Attrs: %w", err) 1377 } 1378 return bucket, attrs.MetaGeneration, nil 1379 } 1380 1381 // transportClienttest executes the given function with a sub-test, a project name 1382 // based on the transport, a unique bucket name also based on the transport, and 1383 // the transport-specific client to run the test with. It also checks the environment 1384 // to ensure it is suitable for emulator-based tests, or skips. 1385 func transportClientTest(t *testing.T, test func(*testing.T, string, string, storageClient)) { 1386 checkEmulatorEnvironment(t) 1387 1388 for transport, client := range emulatorClients { 1389 t.Run(transport, func(t *testing.T) { 1390 project := fmt.Sprintf("%s-project", transport) 1391 bucket := fmt.Sprintf("%s-bucket-%d", transport, time.Now().Nanosecond()) 1392 test(t, project, bucket, client) 1393 }) 1394 } 1395 } 1396 1397 // checkEmulatorEnvironment skips the test if the emulator environment variables 1398 // are not set. 1399 func checkEmulatorEnvironment(t *testing.T) { 1400 if !isEmulatorEnvironmentSet() { 1401 t.Skip("Emulator tests skipped without emulator environment variables set") 1402 } 1403 } 1404 1405 // isEmulatorEnvironmentSet checks if the emulator environment variables are set. 1406 func isEmulatorEnvironmentSet() bool { 1407 return os.Getenv("STORAGE_EMULATOR_HOST_GRPC") != "" && os.Getenv("STORAGE_EMULATOR_HOST") != "" 1408 }