diff --git a/storage/integration_test.go b/storage/integration_test.go index 295ef71fcec..cb56880e083 100644 --- a/storage/integration_test.go +++ b/storage/integration_test.go @@ -61,6 +61,7 @@ import ( "google.golang.org/api/transport" iampb "google.golang.org/genproto/googleapis/iam/v1" "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type skipTransportTestKey string @@ -3669,109 +3670,111 @@ func TestIntegration_PredefinedACLs(t *testing.T) { } func TestIntegration_ServiceAccount(t *testing.T) { - ctx := context.Background() - client := testConfig(ctx, t) - defer client.Close() - - s, err := client.ServiceAccount(ctx, testutil.ProjID()) - if err != nil { - t.Fatal(err) - } - want := "@gs-project-accounts.iam.gserviceaccount.com" - if !strings.Contains(s, want) { - t.Fatalf("got %v, want to contain %v", s, want) - } + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, _, _ string, client *Client) { + s, err := client.ServiceAccount(ctx, testutil.ProjID()) + if err != nil { + t.Fatal(err) + } + want := "@gs-project-accounts.iam.gserviceaccount.com" + if !strings.Contains(s, want) { + t.Fatalf("got %v, want to contain %v", s, want) + } + }) } func TestIntegration_ReaderAttrs(t *testing.T) { - ctx := context.Background() - client := testConfig(ctx, t) - defer client.Close() - bkt := client.Bucket(bucketName) + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) { + bkt := client.Bucket(bucket) - const defaultType = "text/plain" - obj := "some-object" - c := randomContents() - if err := writeObject(ctx, bkt.Object(obj), defaultType, c); err != nil { - t.Errorf("Write for %v failed with %v", obj, err) - } - oh := bkt.Object(obj) + const defaultType = "text/plain" + o := bkt.Object("reader-attrs-obj") + c := randomContents() + if err := writeObject(ctx, o, defaultType, c); err != nil { + t.Errorf("Write for %v failed with %v", o.ObjectName(), err) + } + defer func() { + if err := o.Delete(ctx); err != nil { + log.Printf("failed to delete test object: %v", err) + } + }() - rc, err := oh.NewReader(ctx) - if err != nil { - t.Fatal(err) - } + rc, err := o.NewReader(ctx) + if err != nil { + t.Fatal(err) + } - attrs, err := oh.Attrs(ctx) - if err != nil { - t.Fatal(err) - } + attrs, err := o.Attrs(ctx) + if err != nil { + t.Fatal(err) + } - got := rc.Attrs - want := ReaderObjectAttrs{ - Size: attrs.Size, - ContentType: attrs.ContentType, - ContentEncoding: attrs.ContentEncoding, - CacheControl: attrs.CacheControl, - LastModified: got.LastModified, // ignored, tested separately - Generation: attrs.Generation, - Metageneration: attrs.Metageneration, - } - if got != want { - t.Fatalf("got %v, wanted %v", got, want) - } + got := rc.Attrs + want := ReaderObjectAttrs{ + Size: attrs.Size, + ContentType: attrs.ContentType, + ContentEncoding: attrs.ContentEncoding, + CacheControl: attrs.CacheControl, + LastModified: got.LastModified, // ignored, tested separately + Generation: attrs.Generation, + Metageneration: attrs.Metageneration, + } + if got != want { + t.Fatalf("got %v, wanted %v", got, want) + } - if got.LastModified.IsZero() { - t.Fatal("LastModified is 0, should be >0") - } + if got.LastModified.IsZero() { + t.Fatal("LastModified is 0, should be >0") + } + }) } // Test that context cancellation correctly stops a download before completion. func TestIntegration_ReaderCancel(t *testing.T) { - ctx := context.Background() - client := testConfig(ctx, t) - defer client.Close() + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) { + bkt := client.Bucket(bucket) + obj := bkt.Object("reader-cancel-obj") - bkt := client.Bucket(bucketName) + minObjectSize := 2500000 // 2.5 Mb - // Upload a 1MB object. - obj := bkt.Object("reader-cancel-obj") - w := obj.NewWriter(ctx) - c := randomContents() - for i := 0; i < 62500; i++ { - if _, err := w.Write(c); err != nil { - t.Fatalf("writer.Write: %v", err) + w := obj.NewWriter(ctx) + c := randomContents() + for written := 0; written < minObjectSize; { + n, err := w.Write(c) + if err != nil { + t.Fatalf("w.Write: %v", err) + } + written += n } - } - w.Close() - - // Create a reader (which makes a GET request to GCS and opens the body to - // read the object) and then cancel the context before reading. - readerCtx, cancel := context.WithCancel(ctx) - r, err := obj.NewReader(readerCtx) - if err != nil { - t.Fatalf("obj.NewReader: %v", err) - } - defer r.Close() + if err := w.Close(); err != nil { + t.Fatalf("writer close: %v", err) + } + defer func() { + if err := obj.Delete(ctx); err != nil { + log.Printf("failed to delete test object: %v", err) + } + }() - cancel() - - // Read the object 1KB a time. We cannot guarantee that Reads will return a - // context canceled error immediately, but they should always do so before we - // reach EOF. - var readErr error - for i := 0; i < 1000; i++ { - buf := make([]byte, 1000) - _, readErr = r.Read(buf) - if readErr != nil { - if errors.Is(readErr, context.Canceled) { - return + // Create a reader (which makes a GET request to GCS and opens the body to + // read the object) and then cancel the context before reading. + readerCtx, cancel := context.WithCancel(ctx) + r, err := obj.NewReader(readerCtx) + if err != nil { + t.Fatalf("obj.NewReader: %v", err) + } + defer func() { + if err := r.Close(); err != nil { + log.Printf("r.Close(): %v", err) } - break + }() + + cancel() + + _, err = io.Copy(io.Discard, r) + if err == nil || !errors.Is(err, context.Canceled) && !(status.Code(err) == codes.Canceled) { + t.Fatalf("r.Read: got error %v, want context.Canceled", err) } - } - t.Fatalf("Reader.Read: got %v, want context.Canceled", readErr) + }) } // Ensures that a file stored with a: @@ -4026,34 +4029,77 @@ func TestIntegration_PostPolicyV4(t *testing.T) { // Verify that custom scopes passed in by the user are applied correctly. func TestIntegration_Scopes(t *testing.T) { - // A default client should be able to write objects since it has scope of - // FullControl - ctx := context.Background() - clientFullControl := testConfig(ctx, t) - defer clientFullControl.Close() + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) { + bkt := client.Bucket(bucket) + obj := bkt.Object("test-scopes") + contents := []byte("This object should not be written.\n") - bkt := clientFullControl.Bucket(bucketName) - obj := "FakeObj1" - contents := []byte("This object should be written successfully\n") - if err := writeObject(ctx, bkt.Object(obj), "text/plain", contents); err != nil { - t.Fatalf("writing: %v", err) - } + // A client with ReadOnly scope should be able to read bucket successfully. + if _, err := bkt.Attrs(ctx); err != nil { + t.Errorf("client with ScopeReadOnly was not able to read attrs: %v", err) + } - // A client with ReadOnly scope should not be able to write successfully. - clientReadOnly, err := NewClient(ctx, option.WithScopes(ScopeReadOnly)) - defer clientReadOnly.Close() - if err != nil { - t.Fatalf("error creating client: %v", err) - } + // Should not be able to write successfully. + if err := writeObject(ctx, obj, "text/plain", contents); err == nil { + if err := obj.Delete(ctx); err != nil { + t.Logf("obj.Delete: %v", err) + } + t.Error("client with ScopeReadOnly was able to write an object unexpectedly.") + } + + // Should not be able to change permissions. + if _, err := obj.Update(ctx, ObjectAttrsToUpdate{ACL: []ACLRule{{Entity: "domain-google.com", Role: RoleReader}}}); err == nil { + t.Error("client with ScopeReadWrite was able to change unexpectedly.") + } + }, option.WithScopes(ScopeReadOnly)) - bkt = clientReadOnly.Bucket(bucketName) - obj = "FakeObj2" - contents = []byte("This object should not be written.\n") + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) { + bkt := client.Bucket(bucket) + obj := bkt.Object("test-scopes") + contents := []byte("This object should be written.\n") - if err := writeObject(ctx, bkt.Object(obj), "text/plain", contents); err == nil { - t.Fatal("client with ScopeReadOnly was able to write an object unexpectedly.") - } + // A client with ReadWrite scope should be able to read bucket successfully. + if _, err := bkt.Attrs(ctx); err != nil { + t.Errorf("client with ScopeReadOnly was not able to read attrs: %v", err) + } + + // Should be able to write to an object. + if err := writeObject(ctx, obj, "text/plain", contents); err != nil { + t.Errorf("client with ScopeReadWrite was not able to write: %v", err) + } + defer func() { + if err := obj.Delete(ctx); err != nil { + t.Logf("obj.Delete: %v", err) + } + }() + + // Should not be able to change permissions. + if _, err := obj.Update(ctx, ObjectAttrsToUpdate{ACL: []ACLRule{{Entity: "domain-google.com", Role: RoleReader}}}); err == nil { + t.Error("client with ScopeReadWrite was able to change permissions unexpectedly") + } + }, option.WithScopes(ScopeReadWrite)) + + multiTransportTest(context.Background(), t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) { + bkt := client.Bucket(bucket) + obj := bkt.Object("test-scopes") + contents := []byte("This object should be written.\n") + + // A client without any scopes should not be able to perform ops. + if _, err := bkt.Attrs(ctx); err == nil { + t.Errorf("client with no scopes was able to read attrs unexpectedly") + } + if err := writeObject(ctx, obj, "text/plain", contents); err == nil { + if err := obj.Delete(ctx); err != nil { + t.Logf("obj.Delete: %v", err) + } + t.Error("client with no scopes was able to write an object unexpectedly.") + } + + if _, err := obj.Update(ctx, ObjectAttrsToUpdate{ACL: []ACLRule{{Entity: "domain-google.com", Role: RoleReader}}}); err == nil { + t.Error("client with no scopes was able to change permissions unexpectedly") + } + }, option.WithScopes("")) } func TestIntegration_SignedURL_Bucket(t *testing.T) {