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 "net/http" 19 "reflect" 20 "testing" 21 "time" 22 23 "cloud.google.com/go/internal/testutil" 24 "github.com/google/go-cmp/cmp" 25 "google.golang.org/api/googleapi" 26 raw "google.golang.org/api/storage/v1" 27) 28 29func TestBucketAttrsToRawBucket(t *testing.T) { 30 t.Parallel() 31 attrs := &BucketAttrs{ 32 Name: "name", 33 ACL: []ACLRule{{Entity: "bob@example.com", Role: RoleOwner, Domain: "d", Email: "e"}}, 34 DefaultObjectACL: []ACLRule{{Entity: AllUsers, Role: RoleReader, EntityID: "eid", 35 ProjectTeam: &ProjectTeam{ProjectNumber: "17", Team: "t"}}}, 36 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0", 37 Location: "loc", 38 StorageClass: "class", 39 RetentionPolicy: &RetentionPolicy{ 40 RetentionPeriod: 3 * time.Second, 41 }, 42 BucketPolicyOnly: BucketPolicyOnly{Enabled: true}, 43 VersioningEnabled: false, 44 // should be ignored: 45 MetaGeneration: 39, 46 Created: time.Now(), 47 Labels: map[string]string{"label": "value"}, 48 CORS: []CORS{ 49 { 50 MaxAge: time.Hour, 51 Methods: []string{"GET", "POST"}, 52 Origins: []string{"*"}, 53 ResponseHeaders: []string{"FOO"}, 54 }, 55 }, 56 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"}, 57 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 58 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 59 Lifecycle: Lifecycle{ 60 Rules: []LifecycleRule{{ 61 Action: LifecycleAction{ 62 Type: SetStorageClassAction, 63 StorageClass: "NEARLINE", 64 }, 65 Condition: LifecycleCondition{ 66 AgeInDays: 10, 67 Liveness: Live, 68 CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC), 69 MatchesStorageClasses: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"}, 70 NumNewerVersions: 3, 71 }, 72 }, { 73 Action: LifecycleAction{ 74 Type: DeleteAction, 75 }, 76 Condition: LifecycleCondition{ 77 AgeInDays: 30, 78 Liveness: Live, 79 CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC), 80 MatchesStorageClasses: []string{"NEARLINE"}, 81 NumNewerVersions: 10, 82 }, 83 }, { 84 Action: LifecycleAction{ 85 Type: DeleteAction, 86 }, 87 Condition: LifecycleCondition{ 88 Liveness: Archived, 89 }, 90 }}, 91 }, 92 } 93 got := attrs.toRawBucket() 94 want := &raw.Bucket{ 95 Name: "name", 96 Acl: []*raw.BucketAccessControl{ 97 {Entity: "bob@example.com", Role: "OWNER"}, // other fields ignored on create/update 98 }, 99 DefaultObjectAcl: []*raw.ObjectAccessControl{ 100 {Entity: "allUsers", Role: "READER"}, // other fields ignored on create/update 101 }, 102 Location: "loc", 103 StorageClass: "class", 104 RetentionPolicy: &raw.BucketRetentionPolicy{ 105 RetentionPeriod: 3, 106 }, 107 IamConfiguration: &raw.BucketIamConfiguration{ 108 BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{ 109 Enabled: true, 110 }, 111 }, 112 Versioning: nil, // ignore VersioningEnabled if false 113 Labels: map[string]string{"label": "value"}, 114 Cors: []*raw.BucketCors{ 115 { 116 MaxAgeSeconds: 3600, 117 Method: []string{"GET", "POST"}, 118 Origin: []string{"*"}, 119 ResponseHeader: []string{"FOO"}, 120 }, 121 }, 122 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"}, 123 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 124 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 125 Lifecycle: &raw.BucketLifecycle{ 126 Rule: []*raw.BucketLifecycleRule{{ 127 Action: &raw.BucketLifecycleRuleAction{ 128 Type: SetStorageClassAction, 129 StorageClass: "NEARLINE", 130 }, 131 Condition: &raw.BucketLifecycleRuleCondition{ 132 Age: 10, 133 IsLive: googleapi.Bool(true), 134 CreatedBefore: "2017-01-02", 135 MatchesStorageClass: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"}, 136 NumNewerVersions: 3, 137 }, 138 }, { 139 Action: &raw.BucketLifecycleRuleAction{ 140 Type: DeleteAction, 141 }, 142 Condition: &raw.BucketLifecycleRuleCondition{ 143 Age: 30, 144 IsLive: googleapi.Bool(true), 145 CreatedBefore: "2017-01-02", 146 MatchesStorageClass: []string{"NEARLINE"}, 147 NumNewerVersions: 10, 148 }, 149 }, { 150 Action: &raw.BucketLifecycleRuleAction{ 151 Type: DeleteAction, 152 }, 153 Condition: &raw.BucketLifecycleRuleCondition{ 154 IsLive: googleapi.Bool(false), 155 }, 156 }}, 157 }, 158 } 159 if msg := testutil.Diff(got, want); msg != "" { 160 t.Error(msg) 161 } 162 163 attrs.VersioningEnabled = true 164 attrs.RequesterPays = true 165 got = attrs.toRawBucket() 166 want.Versioning = &raw.BucketVersioning{Enabled: true} 167 want.Billing = &raw.BucketBilling{RequesterPays: true} 168 if msg := testutil.Diff(got, want); msg != "" { 169 t.Error(msg) 170 } 171} 172 173func TestBucketAttrsToUpdateToRawBucket(t *testing.T) { 174 t.Parallel() 175 au := &BucketAttrsToUpdate{ 176 VersioningEnabled: false, 177 RequesterPays: false, 178 BucketPolicyOnly: &BucketPolicyOnly{Enabled: false}, 179 DefaultEventBasedHold: false, 180 RetentionPolicy: &RetentionPolicy{RetentionPeriod: time.Hour}, 181 Encryption: &BucketEncryption{DefaultKMSKeyName: "key2"}, 182 Lifecycle: &Lifecycle{ 183 Rules: []LifecycleRule{ 184 { 185 Action: LifecycleAction{Type: "Delete"}, 186 Condition: LifecycleCondition{AgeInDays: 30}, 187 }, 188 }, 189 }, 190 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 191 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 192 } 193 au.SetLabel("a", "foo") 194 au.DeleteLabel("b") 195 au.SetLabel("c", "") 196 got := au.toRawBucket() 197 want := &raw.Bucket{ 198 Versioning: &raw.BucketVersioning{ 199 Enabled: false, 200 ForceSendFields: []string{"Enabled"}, 201 }, 202 Labels: map[string]string{ 203 "a": "foo", 204 "c": "", 205 }, 206 Billing: &raw.BucketBilling{ 207 RequesterPays: false, 208 ForceSendFields: []string{"RequesterPays"}, 209 }, 210 DefaultEventBasedHold: false, 211 RetentionPolicy: &raw.BucketRetentionPolicy{RetentionPeriod: 3600}, 212 IamConfiguration: &raw.BucketIamConfiguration{ 213 BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{ 214 Enabled: false, 215 }, 216 }, 217 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key2"}, 218 NullFields: []string{"Labels.b"}, 219 Lifecycle: &raw.BucketLifecycle{ 220 Rule: []*raw.BucketLifecycleRule{ 221 { 222 Action: &raw.BucketLifecycleRuleAction{Type: "Delete"}, 223 Condition: &raw.BucketLifecycleRuleCondition{Age: 30}, 224 }, 225 }, 226 }, 227 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 228 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 229 ForceSendFields: []string{"DefaultEventBasedHold"}, 230 } 231 if msg := testutil.Diff(got, want); msg != "" { 232 t.Error(msg) 233 } 234 235 var au2 BucketAttrsToUpdate 236 au2.DeleteLabel("b") 237 got = au2.toRawBucket() 238 want = &raw.Bucket{ 239 Labels: map[string]string{}, 240 ForceSendFields: []string{"Labels"}, 241 NullFields: []string{"Labels.b"}, 242 } 243 if msg := testutil.Diff(got, want); msg != "" { 244 t.Error(msg) 245 } 246 247 // Test nulls. 248 au3 := &BucketAttrsToUpdate{ 249 RetentionPolicy: &RetentionPolicy{}, 250 Encryption: &BucketEncryption{}, 251 Logging: &BucketLogging{}, 252 Website: &BucketWebsite{}, 253 } 254 got = au3.toRawBucket() 255 want = &raw.Bucket{ 256 NullFields: []string{"RetentionPolicy", "Encryption", "Logging", "Website"}, 257 } 258 if msg := testutil.Diff(got, want); msg != "" { 259 t.Error(msg) 260 } 261} 262 263func TestCallBuilders(t *testing.T) { 264 rc, err := raw.New(&http.Client{}) 265 if err != nil { 266 t.Fatal(err) 267 } 268 c := &Client{raw: rc} 269 const metagen = 17 270 271 b := c.Bucket("name") 272 bm := b.If(BucketConditions{MetagenerationMatch: metagen}).UserProject("p") 273 274 equal := func(x, y interface{}) bool { 275 return testutil.Equal(x, y, 276 cmp.AllowUnexported( 277 raw.BucketsGetCall{}, 278 raw.BucketsDeleteCall{}, 279 raw.BucketsPatchCall{}, 280 ), 281 cmp.FilterPath(func(p cmp.Path) bool { 282 return p[len(p)-1].Type() == reflect.TypeOf(&raw.Service{}) 283 }, cmp.Ignore()), 284 ) 285 } 286 287 for i, test := range []struct { 288 callFunc func(*BucketHandle) (interface{}, error) 289 want interface { 290 Header() http.Header 291 } 292 metagenFunc func(interface{}) 293 }{ 294 { 295 func(b *BucketHandle) (interface{}, error) { return b.newGetCall() }, 296 rc.Buckets.Get("name").Projection("full"), 297 func(req interface{}) { req.(*raw.BucketsGetCall).IfMetagenerationMatch(metagen).UserProject("p") }, 298 }, 299 { 300 func(b *BucketHandle) (interface{}, error) { return b.newDeleteCall() }, 301 rc.Buckets.Delete("name"), 302 func(req interface{}) { req.(*raw.BucketsDeleteCall).IfMetagenerationMatch(metagen).UserProject("p") }, 303 }, 304 { 305 func(b *BucketHandle) (interface{}, error) { 306 return b.newPatchCall(&BucketAttrsToUpdate{ 307 VersioningEnabled: false, 308 RequesterPays: false, 309 }) 310 }, 311 rc.Buckets.Patch("name", &raw.Bucket{ 312 Versioning: &raw.BucketVersioning{ 313 Enabled: false, 314 ForceSendFields: []string{"Enabled"}, 315 }, 316 Billing: &raw.BucketBilling{ 317 RequesterPays: false, 318 ForceSendFields: []string{"RequesterPays"}, 319 }, 320 }).Projection("full"), 321 func(req interface{}) { req.(*raw.BucketsPatchCall).IfMetagenerationMatch(metagen).UserProject("p") }, 322 }, 323 } { 324 got, err := test.callFunc(b) 325 if err != nil { 326 t.Fatal(err) 327 } 328 setClientHeader(test.want.Header()) 329 if !equal(got, test.want) { 330 t.Errorf("#%d: got %#v, want %#v", i, got, test.want) 331 } 332 got, err = test.callFunc(bm) 333 if err != nil { 334 t.Fatal(err) 335 } 336 test.metagenFunc(test.want) 337 if !equal(got, test.want) { 338 t.Errorf("#%d:\ngot %#v\nwant %#v", i, got, test.want) 339 } 340 } 341 342 // Error. 343 bm = b.If(BucketConditions{MetagenerationMatch: 1, MetagenerationNotMatch: 2}) 344 if _, err := bm.newGetCall(); err == nil { 345 t.Errorf("got nil, want error") 346 } 347 if _, err := bm.newDeleteCall(); err == nil { 348 t.Errorf("got nil, want error") 349 } 350 if _, err := bm.newPatchCall(&BucketAttrsToUpdate{}); err == nil { 351 t.Errorf("got nil, want error") 352 } 353} 354 355func TestNewBucket(t *testing.T) { 356 labels := map[string]string{"a": "b"} 357 matchClasses := []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"} 358 aTime := time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC) 359 rb := &raw.Bucket{ 360 Name: "name", 361 Location: "loc", 362 DefaultEventBasedHold: true, 363 Metageneration: 3, 364 StorageClass: "sc", 365 TimeCreated: "2017-10-23T04:05:06Z", 366 Versioning: &raw.BucketVersioning{Enabled: true}, 367 Labels: labels, 368 Billing: &raw.BucketBilling{RequesterPays: true}, 369 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0", 370 Lifecycle: &raw.BucketLifecycle{ 371 Rule: []*raw.BucketLifecycleRule{{ 372 Action: &raw.BucketLifecycleRuleAction{ 373 Type: "SetStorageClass", 374 StorageClass: "NEARLINE", 375 }, 376 Condition: &raw.BucketLifecycleRuleCondition{ 377 Age: 10, 378 IsLive: googleapi.Bool(true), 379 CreatedBefore: "2017-01-02", 380 MatchesStorageClass: matchClasses, 381 NumNewerVersions: 3, 382 }, 383 }}, 384 }, 385 RetentionPolicy: &raw.BucketRetentionPolicy{ 386 RetentionPeriod: 3, 387 EffectiveTime: aTime.Format(time.RFC3339), 388 }, 389 IamConfiguration: &raw.BucketIamConfiguration{ 390 BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{ 391 Enabled: true, 392 LockedTime: aTime.Format(time.RFC3339), 393 }, 394 }, 395 Cors: []*raw.BucketCors{ 396 { 397 MaxAgeSeconds: 3600, 398 Method: []string{"GET", "POST"}, 399 Origin: []string{"*"}, 400 ResponseHeader: []string{"FOO"}, 401 }, 402 }, 403 Acl: []*raw.BucketAccessControl{ 404 {Bucket: "name", Role: "READER", Email: "joe@example.com", Entity: "allUsers"}, 405 }, 406 Encryption: &raw.BucketEncryption{DefaultKmsKeyName: "key"}, 407 Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 408 Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 409 } 410 want := &BucketAttrs{ 411 Name: "name", 412 Location: "loc", 413 DefaultEventBasedHold: true, 414 MetaGeneration: 3, 415 StorageClass: "sc", 416 Created: time.Date(2017, 10, 23, 4, 5, 6, 0, time.UTC), 417 VersioningEnabled: true, 418 Labels: labels, 419 Etag: "Zkyw9ACJZUvcYmlFaKGChzhmtnE/dt1zHSfweiWpwzdGsqXwuJZqiD0", 420 RequesterPays: true, 421 Lifecycle: Lifecycle{ 422 Rules: []LifecycleRule{ 423 { 424 Action: LifecycleAction{ 425 Type: SetStorageClassAction, 426 StorageClass: "NEARLINE", 427 }, 428 Condition: LifecycleCondition{ 429 AgeInDays: 10, 430 Liveness: Live, 431 CreatedBefore: time.Date(2017, 1, 2, 0, 0, 0, 0, time.UTC), 432 MatchesStorageClasses: matchClasses, 433 NumNewerVersions: 3, 434 }, 435 }, 436 }, 437 }, 438 RetentionPolicy: &RetentionPolicy{ 439 EffectiveTime: aTime, 440 RetentionPeriod: 3 * time.Second, 441 }, 442 BucketPolicyOnly: BucketPolicyOnly{Enabled: true, LockedTime: aTime}, 443 CORS: []CORS{ 444 { 445 MaxAge: time.Hour, 446 Methods: []string{"GET", "POST"}, 447 Origins: []string{"*"}, 448 ResponseHeaders: []string{"FOO"}, 449 }, 450 }, 451 Encryption: &BucketEncryption{DefaultKMSKeyName: "key"}, 452 Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, 453 Website: &BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, 454 ACL: []ACLRule{{Entity: "allUsers", Role: RoleReader, Email: "joe@example.com"}}, 455 DefaultObjectACL: nil, 456 } 457 got, err := newBucket(rb) 458 if err != nil { 459 t.Fatal(err) 460 } 461 if diff := testutil.Diff(got, want); diff != "" { 462 t.Errorf("got=-, want=+:\n%s", diff) 463 } 464} 465