1// Copyright 2017 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
15package storage
16
17import (
18	"context"
19	"net/http"
20	"reflect"
21	"testing"
22	"time"
23
24	"cloud.google.com/go/internal/testutil"
25	"github.com/google/go-cmp/cmp"
26	"google.golang.org/api/googleapi"
27	raw "google.golang.org/api/storage/v1"
28)
29
30func TestBucketAttrsToRawBucket(t *testing.T) {
31	t.Parallel()
32	attrs := &BucketAttrs{
33		Name: "name",
34		ACL:  []ACLRule{{Entity: "bob@example.com", Role: RoleOwner, Domain: "d", Email: "e"}},
35		DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader, EntityID: "eid",
36			ProjectTeam: &ProjectTeam{ProjectNumber: "17", Team: "t"}}},
37		Etag:         "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
38		Location:     "loc",
39		StorageClass: "class",
40		RetentionPolicy: &RetentionPolicy{
41			RetentionPeriod: 3 * time.Second,
42		},
43		BucketPolicyOnly:         BucketPolicyOnly{Enabled: true},
44		UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true},
45		PublicAccessPrevention:   PublicAccessPreventionEnforced,
46		VersioningEnabled:        false,
47		// should be ignored:
48		MetaGeneration: 39,
49		Created:        time.Now(),
50		Labels:         map[string]string{"label": "value"},
51		CORS: []CORS{
52			{
53				MaxAge:          time.Hour,
54				Methods:         []string{"GET", "POST"},
55				Origins:         []string{"*"},
56				ResponseHeaders: []string{"FOO"},
57			},
58		},
59		Encryption: &BucketEncryption{DefaultKMSKeyName: "key"},
60		Logging:    &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
61		Website:    &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
62		Lifecycle: Lifecycle{
63			Rules: []LifecycleRule{{
64				Action: LifecycleAction{
65					Type:         SetStorageClassAction,
66					StorageClass: "NEARLINE",
67				},
68				Condition: LifecycleCondition{
69					AgeInDays:             10,
70					Liveness:              Live,
71					CreatedBefore:         time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
72					MatchesStorageClasses: []string{"STANDARD"},
73					NumNewerVersions:      3,
74				},
75			}, {
76				Action: LifecycleAction{
77					Type:         SetStorageClassAction,
78					StorageClass: "ARCHIVE",
79				},
80				Condition: LifecycleCondition{
81					CustomTimeBefore:      time.Date(2020, 1, 2, 3, 0, 0, 0, time.UTC),
82					DaysSinceCustomTime:   100,
83					Liveness:              Live,
84					MatchesStorageClasses: []string{"STANDARD"},
85				},
86			}, {
87				Action: LifecycleAction{
88					Type: DeleteAction,
89				},
90				Condition: LifecycleCondition{
91					DaysSinceNoncurrentTime: 30,
92					Liveness:                Live,
93					NoncurrentTimeBefore:    time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
94					MatchesStorageClasses:   []string{"NEARLINE"},
95					NumNewerVersions:        10,
96				},
97			}, {
98				Action: LifecycleAction{
99					Type: DeleteAction,
100				},
101				Condition: LifecycleCondition{
102					Liveness: Archived,
103				},
104			}},
105		},
106	}
107	got := attrs.toRawBucket()
108	want := &raw.Bucket{
109		Name: "name",
110		Acl: []*raw.BucketAccessControl{
111			{Entity: "bob@example.com", Role: "OWNER"}, // other fields ignored on create/update
112		},
113		DefaultObjectAcl: []*raw.ObjectAccessControl{
114			{Entity: "allUsers", Role: "READER"}, // other fields ignored on create/update
115		},
116		Location:     "loc",
117		StorageClass: "class",
118		RetentionPolicy: &raw.BucketRetentionPolicy{
119			RetentionPeriod: 3,
120		},
121		IamConfiguration: &raw.BucketIamConfiguration{
122			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
123				Enabled: true,
124			},
125			PublicAccessPrevention: "enforced",
126		},
127		Versioning: nil, // ignore VersioningEnabled if false
128		Labels:     map[string]string{"label": "value"},
129		Cors: []*raw.BucketCors{
130			{
131				MaxAgeSeconds:  3600,
132				Method:         []string{"GET", "POST"},
133				Origin:         []string{"*"},
134				ResponseHeader: []string{"FOO"},
135			},
136		},
137		Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"},
138		Logging:    &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
139		Website:    &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
140		Lifecycle: &raw.BucketLifecycle{
141			Rule: []*raw.BucketLifecycleRule{{
142				Action: &raw.BucketLifecycleRuleAction{
143					Type:         SetStorageClassAction,
144					StorageClass: "NEARLINE",
145				},
146				Condition: &raw.BucketLifecycleRuleCondition{
147					Age:                 10,
148					IsLive:              googleapi.Bool(true),
149					CreatedBefore:       "2017-01-02",
150					MatchesStorageClass: []string{"STANDARD"},
151					NumNewerVersions:    3,
152				},
153			},
154				{
155					Action: &raw.BucketLifecycleRuleAction{
156						StorageClass: "ARCHIVE",
157						Type:         SetStorageClassAction,
158					},
159					Condition: &raw.BucketLifecycleRuleCondition{
160						IsLive:              googleapi.Bool(true),
161						CustomTimeBefore:    "2020-01-02",
162						DaysSinceCustomTime: 100,
163						MatchesStorageClass: []string{"STANDARD"},
164					},
165				},
166				{
167					Action: &raw.BucketLifecycleRuleAction{
168						Type: DeleteAction,
169					},
170					Condition: &raw.BucketLifecycleRuleCondition{
171						DaysSinceNoncurrentTime: 30,
172						IsLive:                  googleapi.Bool(true),
173						NoncurrentTimeBefore:    "2017-01-02",
174						MatchesStorageClass:     []string{"NEARLINE"},
175						NumNewerVersions:        10,
176					},
177				}, {
178					Action: &raw.BucketLifecycleRuleAction{
179						Type: DeleteAction,
180					},
181					Condition: &raw.BucketLifecycleRuleCondition{
182						IsLive: googleapi.Bool(false),
183					},
184				}},
185		},
186	}
187	if msg := testutil.Diff(got, want); msg != "" {
188		t.Error(msg)
189	}
190
191	attrs.VersioningEnabled = true
192	attrs.RequesterPays = true
193	got = attrs.toRawBucket()
194	want.Versioning = &raw.BucketVersioning{Enabled: true}
195	want.Billing = &raw.BucketBilling{RequesterPays: true}
196	if msg := testutil.Diff(got, want); msg != "" {
197		t.Error(msg)
198	}
199
200	// Test that setting either of BucketPolicyOnly or UniformBucketLevelAccess
201	// will enable UniformBucketLevelAccess.
202	// Set UBLA.Enabled = true --> UBLA should be set to enabled in the proto.
203	attrs.BucketPolicyOnly = BucketPolicyOnly{}
204	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
205	got = attrs.toRawBucket()
206	want.IamConfiguration = &raw.BucketIamConfiguration{
207		UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
208			Enabled: true,
209		},
210		PublicAccessPrevention: "enforced",
211	}
212	if msg := testutil.Diff(got, want); msg != "" {
213		t.Errorf(msg)
214	}
215
216	// Set BucketPolicyOnly.Enabled = true --> UBLA should be set to enabled in
217	// the proto.
218	attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
219	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
220	got = attrs.toRawBucket()
221	want.IamConfiguration = &raw.BucketIamConfiguration{
222		UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
223			Enabled: true,
224		},
225		PublicAccessPrevention: "enforced",
226	}
227	if msg := testutil.Diff(got, want); msg != "" {
228		t.Errorf(msg)
229	}
230
231	// Set both BucketPolicyOnly.Enabled = true and
232	// UniformBucketLevelAccess.Enabled=true --> UBLA should be set to enabled
233	// in the proto.
234	attrs.BucketPolicyOnly = BucketPolicyOnly{Enabled: true}
235	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
236	got = attrs.toRawBucket()
237	want.IamConfiguration = &raw.BucketIamConfiguration{
238		UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
239			Enabled: true,
240		},
241		PublicAccessPrevention: "enforced",
242	}
243	if msg := testutil.Diff(got, want); msg != "" {
244		t.Errorf(msg)
245	}
246
247	// Set UBLA.Enabled=false and BucketPolicyOnly.Enabled=false --> UBLA
248	// should be disabled in the proto.
249	attrs.BucketPolicyOnly = BucketPolicyOnly{}
250	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{}
251	got = attrs.toRawBucket()
252	want.IamConfiguration = &raw.BucketIamConfiguration{
253		PublicAccessPrevention: "enforced",
254	}
255	if msg := testutil.Diff(got, want); msg != "" {
256		t.Errorf(msg)
257	}
258
259	// Test that setting PublicAccessPrevention to "unspecified" leads to the
260	// inherited setting being propagated in the proto.
261	attrs.PublicAccessPrevention = PublicAccessPreventionUnspecified
262	got = attrs.toRawBucket()
263	want.IamConfiguration = &raw.BucketIamConfiguration{
264		PublicAccessPrevention: "inherited",
265	}
266	if msg := testutil.Diff(got, want); msg != "" {
267		t.Errorf(msg)
268	}
269
270	// Test that setting PublicAccessPrevention to "inherited" leads to the
271	// setting being propagated in the proto.
272	attrs.PublicAccessPrevention = PublicAccessPreventionInherited
273	got = attrs.toRawBucket()
274	want.IamConfiguration = &raw.BucketIamConfiguration{
275		PublicAccessPrevention: "inherited",
276	}
277	if msg := testutil.Diff(got, want); msg != "" {
278		t.Errorf(msg)
279	}
280
281	// Re-enable UBLA and confirm that it does not affect the PAP setting.
282	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: true}
283	got = attrs.toRawBucket()
284	want.IamConfiguration = &raw.BucketIamConfiguration{
285		UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
286			Enabled: true,
287		},
288		PublicAccessPrevention: "inherited",
289	}
290	if msg := testutil.Diff(got, want); msg != "" {
291		t.Errorf(msg)
292	}
293
294	// Disable UBLA and reset PAP to default. Confirm that the IAM config is set
295	// to nil in the proto.
296	attrs.UniformBucketLevelAccess = UniformBucketLevelAccess{Enabled: false}
297	attrs.PublicAccessPrevention = PublicAccessPreventionUnknown
298	got = attrs.toRawBucket()
299	want.IamConfiguration = nil
300	if msg := testutil.Diff(got, want); msg != "" {
301		t.Errorf(msg)
302	}
303}
304
305func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
306	t.Parallel()
307	au := &BucketAttrsToUpdate{
308		VersioningEnabled:        false,
309		RequesterPays:            false,
310		BucketPolicyOnly:         &BucketPolicyOnly{Enabled: false},
311		UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
312		DefaultEventBasedHold:    false,
313		RetentionPolicy:          &RetentionPolicy{RetentionPeriod: time.Hour},
314		Encryption:               &BucketEncryption{DefaultKMSKeyName: "key2"},
315		Lifecycle: &Lifecycle{
316			Rules: []LifecycleRule{
317				{
318					Action:    LifecycleAction{Type: "Delete"},
319					Condition: LifecycleCondition{AgeInDays: 30},
320				},
321			},
322		},
323		Logging:      &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
324		Website:      &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
325		StorageClass: "NEARLINE",
326	}
327	au.SetLabel("a", "foo")
328	au.DeleteLabel("b")
329	au.SetLabel("c", "")
330	got := au.toRawBucket()
331	want := &raw.Bucket{
332		Versioning: &raw.BucketVersioning{
333			Enabled:         false,
334			ForceSendFields: []string{"Enabled"},
335		},
336		Labels: map[string]string{
337			"a": "foo",
338			"c": "",
339		},
340		Billing: &raw.BucketBilling{
341			RequesterPays:   false,
342			ForceSendFields: []string{"RequesterPays"},
343		},
344		DefaultEventBasedHold: false,
345		RetentionPolicy:       &raw.BucketRetentionPolicy{RetentionPeriod: 3600},
346		IamConfiguration: &raw.BucketIamConfiguration{
347			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
348				Enabled:         false,
349				ForceSendFields: []string{"Enabled"},
350			},
351		},
352		Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key2"},
353		NullFields: []string{"Labels.b"},
354		Lifecycle: &raw.BucketLifecycle{
355			Rule: []*raw.BucketLifecycleRule{
356				{
357					Action:    &raw.BucketLifecycleRuleAction{Type: "Delete"},
358					Condition: &raw.BucketLifecycleRuleCondition{Age: 30},
359				},
360			},
361		},
362		Logging:         &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
363		Website:         &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
364		StorageClass:    "NEARLINE",
365		ForceSendFields: []string{"DefaultEventBasedHold", "Lifecycle"},
366	}
367	if msg := testutil.Diff(got, want); msg != "" {
368		t.Error(msg)
369	}
370
371	var au2 BucketAttrsToUpdate
372	au2.DeleteLabel("b")
373	got = au2.toRawBucket()
374	want = &raw.Bucket{
375		Labels:          map[string]string{},
376		ForceSendFields: []string{"Labels"},
377		NullFields:      []string{"Labels.b"},
378	}
379	if msg := testutil.Diff(got, want); msg != "" {
380		t.Error(msg)
381	}
382
383	// Test nulls.
384	au3 := &BucketAttrsToUpdate{
385		RetentionPolicy: &RetentionPolicy{},
386		Encryption:      &BucketEncryption{},
387		Logging:         &BucketLogging{},
388		Website:         &BucketWebsite{},
389	}
390	got = au3.toRawBucket()
391	want = &raw.Bucket{
392		NullFields: []string{"RetentionPolicy", "Encryption", "Logging", "Website"},
393	}
394	if msg := testutil.Diff(got, want); msg != "" {
395		t.Error(msg)
396	}
397
398	// Test that setting either of BucketPolicyOnly or UniformBucketLevelAccess
399	// will enable UniformBucketLevelAccess.
400	// Set UBLA.Enabled = true --> UBLA should be set to enabled in the proto.
401	au4 := &BucketAttrsToUpdate{
402		UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: true},
403	}
404	got = au4.toRawBucket()
405	want = &raw.Bucket{
406		IamConfiguration: &raw.BucketIamConfiguration{
407			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
408				Enabled:         true,
409				ForceSendFields: []string{"Enabled"},
410			},
411		},
412	}
413	if msg := testutil.Diff(got, want); msg != "" {
414		t.Errorf(msg)
415	}
416
417	// Set BucketPolicyOnly.Enabled = true --> UBLA should be set to enabled in
418	// the proto.
419	au5 := &BucketAttrsToUpdate{
420		BucketPolicyOnly: &BucketPolicyOnly{Enabled: true},
421	}
422	got = au5.toRawBucket()
423	want = &raw.Bucket{
424		IamConfiguration: &raw.BucketIamConfiguration{
425			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
426				Enabled:         true,
427				ForceSendFields: []string{"Enabled"},
428			},
429		},
430	}
431	if msg := testutil.Diff(got, want); msg != "" {
432		t.Errorf(msg)
433	}
434
435	// Set both BucketPolicyOnly.Enabled = true and
436	// UniformBucketLevelAccess.Enabled=true --> UBLA should be set to enabled
437	// in the proto.
438	au6 := &BucketAttrsToUpdate{
439		BucketPolicyOnly:         &BucketPolicyOnly{Enabled: true},
440		UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: true},
441	}
442	got = au6.toRawBucket()
443	want = &raw.Bucket{
444		IamConfiguration: &raw.BucketIamConfiguration{
445			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
446				Enabled:         true,
447				ForceSendFields: []string{"Enabled"},
448			},
449		},
450	}
451	if msg := testutil.Diff(got, want); msg != "" {
452		t.Errorf(msg)
453	}
454
455	// Set UBLA.Enabled=false and BucketPolicyOnly.Enabled=false --> UBLA
456	// should be disabled in the proto.
457	au7 := &BucketAttrsToUpdate{
458		BucketPolicyOnly:         &BucketPolicyOnly{Enabled: false},
459		UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
460	}
461	got = au7.toRawBucket()
462	want = &raw.Bucket{
463		IamConfiguration: &raw.BucketIamConfiguration{
464			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
465				Enabled:         false,
466				ForceSendFields: []string{"Enabled"},
467			},
468		},
469	}
470	if msg := testutil.Diff(got, want); msg != "" {
471		t.Errorf(msg)
472	}
473
474	// UBLA.Enabled will have precedence above BucketPolicyOnly.Enabled if both
475	// are set with different values.
476	au8 := &BucketAttrsToUpdate{
477		BucketPolicyOnly:         &BucketPolicyOnly{Enabled: true},
478		UniformBucketLevelAccess: &UniformBucketLevelAccess{Enabled: false},
479	}
480	got = au8.toRawBucket()
481	want = &raw.Bucket{
482		IamConfiguration: &raw.BucketIamConfiguration{
483			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
484				Enabled:         false,
485				ForceSendFields: []string{"Enabled"},
486			},
487		},
488	}
489	if msg := testutil.Diff(got, want); msg != "" {
490		t.Errorf(msg)
491	}
492
493	// Set an empty Lifecycle and verify that it will be sent.
494	au9 := &BucketAttrsToUpdate{
495		Lifecycle: &Lifecycle{},
496	}
497	got = au9.toRawBucket()
498	want = &raw.Bucket{
499		Lifecycle: &raw.BucketLifecycle{
500			ForceSendFields: []string{"Rule"},
501		},
502		ForceSendFields: []string{"Lifecycle"},
503	}
504	if msg := testutil.Diff(got, want); msg != "" {
505		t.Errorf(msg)
506	}
507}
508
509func TestCallBuilders(t *testing.T) {
510	rc, err := raw.NewService(context.Background())
511	if err != nil {
512		t.Fatal(err)
513	}
514	c := &Client{raw: rc}
515	const metagen = 17
516
517	b := c.Bucket("name")
518	bm := b.If(BucketConditions{MetagenerationMatch: metagen}).UserProject("p")
519
520	equal := func(x, y interface{}) bool {
521		return testutil.Equal(x, y,
522			cmp.AllowUnexported(
523				raw.BucketsGetCall{},
524				raw.BucketsDeleteCall{},
525				raw.BucketsPatchCall{},
526			),
527			cmp.FilterPath(func(p cmp.Path) bool {
528				return p[len(p)-1].Type() == reflect.TypeOf(&raw.Service{})
529			}, cmp.Ignore()),
530		)
531	}
532
533	for i, test := range []struct {
534		callFunc func(*BucketHandle) (interface{}, error)
535		want     interface {
536			Header() http.Header
537		}
538		metagenFunc func(interface{})
539	}{
540		{
541			func(b *BucketHandle) (interface{}, error) { return b.newGetCall() },
542			rc.Buckets.Get("name").Projection("full"),
543			func(req interface{}) { req.(*raw.BucketsGetCall).IfMetagenerationMatch(metagen).UserProject("p") },
544		},
545		{
546			func(b *BucketHandle) (interface{}, error) { return b.newDeleteCall() },
547			rc.Buckets.Delete("name"),
548			func(req interface{}) { req.(*raw.BucketsDeleteCall).IfMetagenerationMatch(metagen).UserProject("p") },
549		},
550		{
551			func(b *BucketHandle) (interface{}, error) {
552				return b.newPatchCall(&BucketAttrsToUpdate{
553					VersioningEnabled: false,
554					RequesterPays:     false,
555				})
556			},
557			rc.Buckets.Patch("name", &raw.Bucket{
558				Versioning: &raw.BucketVersioning{
559					Enabled:         false,
560					ForceSendFields: []string{"Enabled"},
561				},
562				Billing: &raw.BucketBilling{
563					RequesterPays:   false,
564					ForceSendFields: []string{"RequesterPays"},
565				},
566			}).Projection("full"),
567			func(req interface{}) { req.(*raw.BucketsPatchCall).IfMetagenerationMatch(metagen).UserProject("p") },
568		},
569	} {
570		got, err := test.callFunc(b)
571		if err != nil {
572			t.Fatal(err)
573		}
574		setClientHeader(test.want.Header())
575		if !equal(got, test.want) {
576			t.Errorf("#%d: got %#v, want %#v", i, got, test.want)
577		}
578		got, err = test.callFunc(bm)
579		if err != nil {
580			t.Fatal(err)
581		}
582		test.metagenFunc(test.want)
583		if !equal(got, test.want) {
584			t.Errorf("#%d:\ngot  %#v\nwant %#v", i, got, test.want)
585		}
586	}
587
588	// Error.
589	bm = b.If(BucketConditions{MetagenerationMatch: 1, MetagenerationNotMatch: 2})
590	if _, err := bm.newGetCall(); err == nil {
591		t.Errorf("got nil, want error")
592	}
593	if _, err := bm.newDeleteCall(); err == nil {
594		t.Errorf("got nil, want error")
595	}
596	if _, err := bm.newPatchCall(&BucketAttrsToUpdate{}); err == nil {
597		t.Errorf("got nil, want error")
598	}
599}
600
601func TestNewBucket(t *testing.T) {
602	labels := map[string]string{"a": "b"}
603	matchClasses := []string{"STANDARD"}
604	aTime := time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC)
605	rb := &raw.Bucket{
606		Name:                  "name",
607		Location:              "loc",
608		DefaultEventBasedHold: true,
609		Metageneration:        3,
610		StorageClass:          "sc",
611		TimeCreated:           "2017-10-23T04:05:06Z",
612		Versioning:            &raw.BucketVersioning{Enabled: true},
613		Labels:                labels,
614		Billing:               &raw.BucketBilling{RequesterPays: true},
615		Etag:                  "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
616		Lifecycle: &raw.BucketLifecycle{
617			Rule: []*raw.BucketLifecycleRule{{
618				Action: &raw.BucketLifecycleRuleAction{
619					Type:         "SetStorageClass",
620					StorageClass: "NEARLINE",
621				},
622				Condition: &raw.BucketLifecycleRuleCondition{
623					Age:                 10,
624					IsLive:              googleapi.Bool(true),
625					CreatedBefore:       "2017-01-02",
626					MatchesStorageClass: matchClasses,
627					NumNewerVersions:    3,
628				},
629			}},
630		},
631		RetentionPolicy: &raw.BucketRetentionPolicy{
632			RetentionPeriod: 3,
633			EffectiveTime:   aTime.Format(time.RFC3339),
634		},
635		IamConfiguration: &raw.BucketIamConfiguration{
636			BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
637				Enabled:    true,
638				LockedTime: aTime.Format(time.RFC3339),
639			},
640			UniformBucketLevelAccess: &raw.BucketIamConfigurationUniformBucketLevelAccess{
641				Enabled:    true,
642				LockedTime: aTime.Format(time.RFC3339),
643			},
644		},
645		Cors: []*raw.BucketCors{
646			{
647				MaxAgeSeconds:  3600,
648				Method:         []string{"GET", "POST"},
649				Origin:         []string{"*"},
650				ResponseHeader: []string{"FOO"},
651			},
652		},
653		Acl: []*raw.BucketAccessControl{
654			{Bucket: "name", Role: "READER", Email: "joe@example.com", Entity: "allUsers"},
655		},
656		LocationType:  "dual-region",
657		Encryption:    &raw.BucketEncryption{DefaultKmsKeyName: "key"},
658		Logging:       &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
659		Website:       &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
660		ProjectNumber: 123231313,
661	}
662	want := &BucketAttrs{
663		Name:                  "name",
664		Location:              "loc",
665		DefaultEventBasedHold: true,
666		MetaGeneration:        3,
667		StorageClass:          "sc",
668		Created:               time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC),
669		VersioningEnabled:     true,
670		Labels:                labels,
671		Etag:                  "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0",
672		RequesterPays:         true,
673		Lifecycle: Lifecycle{
674			Rules: []LifecycleRule{
675				{
676					Action: LifecycleAction{
677						Type:         SetStorageClassAction,
678						StorageClass: "NEARLINE",
679					},
680					Condition: LifecycleCondition{
681						AgeInDays:             10,
682						Liveness:              Live,
683						CreatedBefore:         time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC),
684						MatchesStorageClasses: matchClasses,
685						NumNewerVersions:      3,
686					},
687				},
688			},
689		},
690		RetentionPolicy: &RetentionPolicy{
691			EffectiveTime:   aTime,
692			RetentionPeriod: 3 * time.Second,
693		},
694		BucketPolicyOnly:         BucketPolicyOnly{Enabled: true, LockedTime: aTime},
695		UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true, LockedTime: aTime},
696		CORS: []CORS{
697			{
698				MaxAge:          time.Hour,
699				Methods:         []string{"GET", "POST"},
700				Origins:         []string{"*"},
701				ResponseHeaders: []string{"FOO"},
702			},
703		},
704		Encryption:       &BucketEncryption{DefaultKMSKeyName: "key"},
705		Logging:          &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"},
706		Website:          &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"},
707		ACL:              []ACLRule{{Entity: "allUsers", Role: RoleReader, Email: "joe@example.com"}},
708		DefaultObjectACL: nil,
709		LocationType:     "dual-region",
710		ProjectNumber:    123231313,
711	}
712	got, err := newBucket(rb)
713	if err != nil {
714		t.Fatal(err)
715	}
716	if diff := testutil.Diff(got, want); diff != "" {
717		t.Errorf("got=-, want=+:\n%s", diff)
718	}
719}
720