1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package storage
16
17 import (
18 "context"
19 "fmt"
20 "net/http"
21 "net/url"
22 "strings"
23 "testing"
24 "time"
25
26 "cloud.google.com/go/internal/testutil"
27 "github.com/google/go-cmp/cmp/cmpopts"
28 "google.golang.org/api/googleapi"
29 "google.golang.org/api/iterator"
30 )
31
32 func TestHMACKeyHandle_GetParsing(t *testing.T) {
33 mt := &mockTransport{}
34 client := mockClient(t, mt)
35 projectID := "hmackey-project-id"
36 ctx := context.Background()
37
38 tests := []struct {
39 res string
40 want *HMACKey
41 wantErr string
42 }{
43 {
44 res: fmt.Sprintf(`
45 {
46 "kind": "storage#hmacKeyMetadata",
47 "projectId":%q,"state":"ACTIVE",
48 "timeCreated": "2019-07-06T11:21:58+00:00",
49 "updated": "2019-07-06T11:22:18+00:00"
50 }`, projectID),
51 want: &HMACKey{
52 State: Active,
53 ProjectID: projectID,
54 UpdatedTime: time.Date(2019, 07, 06, 11, 22, 18, 0, time.UTC),
55 CreatedTime: time.Date(2019, 07, 06, 11, 21, 58, 0, time.UTC),
56 },
57 },
58 {
59 res: fmt.Sprintf(`
60 {
61 "kind": "storage#hmacKeyMetadata",
62 "projectId":%q,"state":"ACTIVE",
63 "timeCreated": "2019-07-06T11:21:58+00:00",
64 "updated": "2019-07-06T11:22:18+00:00"
65 }`, projectID),
66 want: &HMACKey{
67 State: Active,
68 ProjectID: projectID,
69 UpdatedTime: time.Date(2019, 07, 06, 11, 22, 18, 0, time.UTC),
70 CreatedTime: time.Date(2019, 07, 06, 11, 21, 58, 0, time.UTC),
71 },
72 },
73 {
74 res: `{}`,
75
76 wantErr: `CreatedTime: parsing time "" as "2006-01-02T15:04:05Z07:00"`,
77 },
78 {
79 res: `{"timeCreated": "2019-07-foo"}`,
80
81 wantErr: `CreatedTime: parsing time "2019-07-foo" as "2006-01-02T15:04:05Z07:00"`,
82 },
83 {
84 res: `{
85 "kind": "storage#hmacKeyMetadata",
86 "state":"INACTIVE",
87 "timeCreated": "2019-07-06T11:21:58+00:00"
88 }`,
89
90 wantErr: `UpdatedTime: parsing time "" as "2006-01-02T15:04:05Z07:00"`,
91 },
92 }
93
94 for i, tt := range tests {
95 mt.addResult(&http.Response{
96 ProtoMajor: 1,
97 ProtoMinor: 1,
98 ContentLength: int64(len(tt.res)),
99 Status: "OK",
100 StatusCode: 200,
101 Body: bodyReader(tt.res),
102 }, nil)
103 hkh := client.HMACKeyHandle(projectID, "some-access-key-id")
104 got, err := hkh.Get(ctx)
105 if tt.wantErr != "" {
106 if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
107 t.Errorf("#%d: failed to match errors:\ngot: %q\nwant: %q", i, err, tt.wantErr)
108 }
109 if got != nil {
110 t.Errorf("#%d: unexpectedly got a non-nil result: %#v\n", i, got)
111 }
112 continue
113 }
114
115 if err != nil {
116 t.Errorf("#%d: got an unexpected error: %v", i, err)
117 continue
118 }
119
120 if diff := testutil.Diff(got, tt.want); diff != "" {
121 t.Errorf("#%d: got - want +\n\n%s", i, diff)
122 }
123 }
124 }
125
126 func TestHMACKeyHandle_Get_NotFound(t *testing.T) {
127 mt := &mockTransport{}
128 client := mockClient(t, mt)
129 ctx := context.Background()
130
131 mt.addResult(&http.Response{
132 ProtoMajor: 2,
133 ProtoMinor: 0,
134 Status: "OK",
135 StatusCode: http.StatusNotFound,
136 Body: bodyReader("Access ID not found in project"),
137 }, nil)
138
139 hkh := client.HMACKeyHandle("project-id", "some-access-key-id")
140 _, gotErr := hkh.Get(ctx)
141
142 wantErr := &googleapi.Error{
143 Body: "Access ID not found in project",
144 Code: http.StatusNotFound,
145 Message: "",
146 }
147 if diff := testutil.Diff(gotErr, wantErr, cmpopts.IgnoreUnexported(googleapi.Error{})); diff != "" {
148 t.Fatalf("Error mismatch, got - want +\n%s", diff)
149 }
150 }
151
152 func TestHMACKeyHandle_Delete(t *testing.T) {
153 mt := &mockTransport{}
154 client := mockClient(t, mt)
155 ctx := context.Background()
156
157 tests := []struct {
158 statusCode int
159 msg string
160 wantErr error
161 }{
162 {
163 statusCode: http.StatusBadRequest,
164 msg: "Cannot delete keys in 'ACTIVE' state",
165 wantErr: &googleapi.Error{
166 Code: http.StatusBadRequest, Message: "Cannot delete keys in 'ACTIVE' state",
167 Body: `{"error":{"message":"Cannot delete keys in 'ACTIVE' state"}}`,
168 },
169 },
170 {
171 statusCode: http.StatusNotFound,
172 msg: "random message",
173 wantErr: &googleapi.Error{
174 Code: http.StatusNotFound, Message: "random message",
175 Body: `{"error":{"message":"random message"}}`,
176 },
177 },
178 {
179 statusCode: http.StatusNotFound,
180 msg: "Access ID not found in project",
181 wantErr: &googleapi.Error{
182 Code: http.StatusNotFound, Message: "Access ID not found in project",
183 Body: `{"error":{"message":"Access ID not found in project"}}`,
184 },
185 },
186 }
187
188 for i, tt := range tests {
189 mt.addResult(&http.Response{
190 ProtoMajor: 2,
191 ProtoMinor: 0,
192 Status: tt.msg,
193 StatusCode: tt.statusCode,
194 Body: bodyReader(fmt.Sprintf(`{"error":{"message":%q}}`, tt.msg)),
195 }, nil)
196
197 hkh := client.HMACKeyHandle("project", "access-key-id")
198 err := hkh.Delete(ctx)
199
200 if diff := testutil.Diff(err, tt.wantErr, cmpopts.IgnoreUnexported(googleapi.Error{})); diff != "" {
201 t.Errorf("#%d: error mismatch got - want +\n%s", i, diff)
202 }
203 }
204 }
205
206 func TestHMACKeyHandle_Create(t *testing.T) {
207 mt := &mockTransport{}
208 client := mockClient(t, mt)
209 projectID := "hmackey-project-id"
210 serviceAccountEmail := "service-account-email-1"
211 ctx := context.Background()
212
213 tests := []struct {
214 res string
215 want *HMACKey
216 wantErr string
217 }{
218 {
219 res: `
220 {
221 "kind": "storage#hmackey",
222 "secret":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
223 "metadata": {
224 "projectId":"project-id","state":"ACTIVE",
225 "timeCreated": "2019-07-06T11:21:58+00:00",
226 "updated": "2019-07-06T11:22:18+00:00"
227 }
228 }`,
229 want: &HMACKey{
230 Secret: "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
231 State: Active,
232 ProjectID: "project-id",
233 UpdatedTime: time.Date(2019, 07, 06, 11, 22, 18, 0, time.UTC),
234 CreatedTime: time.Date(2019, 07, 06, 11, 21, 58, 0, time.UTC),
235 },
236 },
237 {
238 res: `{}`,
239 wantErr: "Metadata cannot be nil",
240 },
241 {
242 res: `{"metadata":{}}`,
243
244 wantErr: `CreatedTime: parsing time "" as "2006-01-02T15:04:05Z07:00"`,
245 },
246 {
247 res: `{"metadata":{"timeCreated": "2019-07-foo"}}`,
248
249 wantErr: `CreatedTime: parsing time "2019-07-foo" as "2006-01-02T15:04:05Z07:00"`,
250 },
251 {
252 res: `{
253 "kind": "storage#hmackey",
254 "secret":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
255 "metadata":{
256 "kind": "storage#hmacKeyMetadata",
257 "state":"ACTIVE",
258 "timeCreated": "2019-07-06T12:11:33+00:00",
259 "projectId": "project-id",
260 "updated": ""
261 }
262 }`,
263
264 want: &HMACKey{
265 Secret: "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
266 State: Active,
267 ProjectID: "project-id",
268 CreatedTime: time.Date(2019, 07, 06, 12, 11, 33, 0, time.UTC),
269 },
270 },
271 }
272
273 for i, tt := range tests {
274 mt.addResult(&http.Response{
275 ProtoMajor: 1,
276 ProtoMinor: 1,
277 ContentLength: int64(len(tt.res)),
278 Status: "OK",
279 StatusCode: 200,
280 Body: bodyReader(tt.res),
281 }, nil)
282 got, err := client.CreateHMACKey(ctx, projectID, serviceAccountEmail)
283 if tt.wantErr != "" {
284 if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
285 t.Errorf("#%d: failed to match errors:\ngot: %q\nwant: %q", i, err, tt.wantErr)
286 }
287 if got != nil {
288 t.Errorf("#%d: unexpectedly got a non-nil result: %#v\n", i, got)
289 }
290 continue
291 }
292
293 if err != nil {
294 t.Errorf("#%d: got an unexpected error: %v", i, err)
295 continue
296 }
297
298 if diff := testutil.Diff(got, tt.want); diff != "" {
299 t.Errorf("#%d: got - want +\n\n%s", i, diff)
300 }
301 }
302
303
304 mt.addResult(&http.Response{
305 ProtoMajor: 1,
306 ProtoMinor: 1,
307 Status: "OK",
308 StatusCode: 200,
309 Body: bodyReader("{}"),
310 }, nil)
311 hk, err := client.CreateHMACKey(ctx, projectID, "")
312 if err == nil {
313 t.Fatal("Unexpectedly succeeded in creating a key using a blank service account email")
314 }
315 if !strings.Contains(err.Error(), "non-blank service account email") {
316 t.Fatalf("Expected an error about a non-blank service account email: %v", err)
317 }
318 if hk != nil {
319 t.Fatalf("Unexpectedly got back a created HMACKey: %#v", hk)
320 }
321 }
322
323 func TestHMACKey_UpdateState(t *testing.T) {
324
325
326
327 mt := &mockTransport{}
328 client := mockClient(t, mt)
329 projectID := "hmackey-project-id"
330 ctx := context.Background()
331
332 hkh := client.HMACKeyHandle(projectID, "some-access-id")
333
334
335 invalidStates := []HMACState{"", Deleted, "active", "inactive", "foo_bar"}
336 for _, invalidState := range invalidStates {
337 t.Run("invalid-"+string(invalidState), func(t *testing.T) {
338 _, err := hkh.Update(ctx, HMACKeyAttrsToUpdate{
339 State: invalidState,
340 })
341 if err == nil {
342 t.Fatal("Unexpectedly succeeded")
343 }
344 invalidStateMsg := fmt.Sprintf(`storage: invalid state %q for update, must be either "ACTIVE" or "INACTIVE"`, invalidState)
345 if err.Error() != invalidStateMsg {
346 t.Fatalf("Mismatched error: got: %q\nwant: %q", err, invalidStateMsg)
347 }
348 })
349 }
350
351
352 validStates := []HMACState{Active, Inactive}
353 for _, validState := range validStates {
354 t.Run("valid-"+string(validState), func(t *testing.T) {
355 resBody := fmt.Sprintf(`{
356 "kind": "storage#hmacKeyMetadata",
357 "state":%q,
358 "timeCreated": "2019-07-11T12:11:33+00:00",
359 "projectId": "project-id",
360 "updated": "2019-07-11T12:13:33+00:00"
361 }`, validState)
362 mt.addResult(&http.Response{
363 ProtoMajor: 1,
364 ProtoMinor: 1,
365 ContentLength: int64(len(resBody)),
366 Status: "OK",
367 StatusCode: 200,
368 Body: bodyReader(resBody),
369 }, nil)
370
371 hu, err := hkh.Update(ctx, HMACKeyAttrsToUpdate{
372 State: validState,
373 })
374 if err != nil {
375 t.Fatalf("Unexpected failure: %v", err)
376 }
377 if hu.State != validState {
378 t.Fatalf("Unexpected updated state %q, expected %q", hu.State, validState)
379 }
380 })
381 }
382 }
383
384 func TestHMACKey_ListFull(t *testing.T) {
385 mt := &mockTransport{}
386 client := mockClient(t, mt)
387 projectID := "hmackey-project-id"
388 ctx := context.Background()
389
390 maxPages := 2
391 page := 0
392 mockResponse := func() {
393 defer func() {
394 page++
395 }()
396
397 var body string
398 if page >= maxPages {
399 body = `{"kind":"storage#hmacKeysMetadata","items":[]}`
400 } else {
401 offset := page * 2
402 body = fmt.Sprintf(`
403 {
404 "kind": "storage#hmacKeysMetadata",
405 "items": [{
406 "accessId": "accessid-%d",
407 "timeCreated": "2019-08-05T12:11:10+00:00",
408 "state": "ACTIVE"
409 }, {
410 "accessId": "accessid-%d",
411 "timeCreated": "2019-08-05T13:12:11+00:00",
412 "state": "INACTIVE"
413 }],
414 "nextPageToken": "pageToken"
415 }`, offset+1, offset+2)
416 }
417
418 mt.addResult(&http.Response{
419 ProtoMajor: 2,
420 ProtoMinor: 0,
421 Status: "OK",
422 StatusCode: 200,
423 Body: bodyReader(body),
424 }, nil)
425 }
426
427 iter := client.ListHMACKeys(ctx, projectID)
428
429 var gotKeys []*HMACKey
430 for {
431 mockResponse()
432 key, err := iter.Next()
433 if err == iterator.Done {
434 break
435 }
436 if err != nil {
437 t.Fatalf("Unexpected error: %v", err)
438 }
439 gotKeys = append(gotKeys, key)
440 }
441
442 wantKeys := []*HMACKey{
443 {
444 AccessID: "accessid-1",
445 CreatedTime: time.Date(2019, time.August, 5, 12, 11, 10, 0, time.UTC),
446 State: Active,
447 },
448 {
449 AccessID: "accessid-2",
450 CreatedTime: time.Date(2019, time.August, 5, 13, 12, 11, 0, time.UTC),
451 State: Inactive,
452 },
453 {
454 AccessID: "accessid-3",
455 CreatedTime: time.Date(2019, time.August, 5, 12, 11, 10, 0, time.UTC),
456 State: Active,
457 },
458 {
459 AccessID: "accessid-4",
460 CreatedTime: time.Date(2019, time.August, 5, 13, 12, 11, 0, time.UTC),
461 State: Inactive,
462 },
463 }
464
465 if diff := testutil.Diff(gotKeys, wantKeys); diff != "" {
466 t.Fatalf("Response mismatch: got - want +\n%s", diff)
467 }
468 }
469
470 func TestHMACKey_List_Options(t *testing.T) {
471 mt := &mockTransport{}
472 client := mockClient(t, mt)
473 projectID := "hmackey-project-id"
474
475
476
477 tests := []struct {
478 name string
479 opts []HMACKeyOption
480 wantQuery url.Values
481 }{
482 {
483 name: "defaults",
484 wantQuery: url.Values{
485 "alt": {"json"},
486 "prettyPrint": {"false"},
487 },
488 },
489 {
490 name: "show deleted keys",
491 opts: []HMACKeyOption{ShowDeletedHMACKeys()},
492 wantQuery: url.Values{"alt": {"json"}, "prettyPrint": {"false"}, "showDeletedKeys": {"true"}},
493 },
494 {
495 name: "for service account",
496 opts: []HMACKeyOption{ForHMACKeyServiceAccountEmail("foo@example.org")},
497 wantQuery: url.Values{
498 "alt": {"json"},
499 "prettyPrint": {"false"},
500 "serviceAccountEmail": {"foo@example.org"},
501 },
502 },
503 {
504 name: "for userProjectID",
505 opts: []HMACKeyOption{UserProjectForHMACKeys("project-x")},
506 wantQuery: url.Values{
507 "alt": {"json"},
508 "prettyPrint": {"false"},
509 "userProject": {"project-x"},
510 },
511 },
512 {
513 name: "all options",
514 opts: []HMACKeyOption{
515 ForHMACKeyServiceAccountEmail("foo@example.org"),
516 UserProjectForHMACKeys("project-x"),
517 ShowDeletedHMACKeys(),
518 },
519 wantQuery: url.Values{
520 "alt": {"json"},
521 "prettyPrint": {"false"},
522 "serviceAccountEmail": {"foo@example.org"},
523 "showDeletedKeys": {"true"},
524 "userProject": {"project-x"},
525 },
526 },
527 }
528
529 for _, tt := range tests {
530 t.Run(tt.name, func(t *testing.T) {
531 body := `{"kind":"storage#hmacKeysMetadata","items":[]}`
532 mt.addResult(&http.Response{
533 ProtoMajor: 2,
534 ProtoMinor: 0,
535 ContentLength: int64(len(body)),
536 Status: "OK",
537 StatusCode: 200,
538 Body: bodyReader(body),
539 }, nil)
540 ctx, cancel := context.WithCancel(context.Background())
541 iter := client.ListHMACKeys(ctx, projectID, tt.opts...)
542 _, _ = iter.Next()
543 cancel()
544
545 gotQuery := mt.gotReq.URL.Query()
546 if diff := testutil.Diff(gotQuery, tt.wantQuery); diff != "" {
547 t.Errorf("Query mismatch: got - want +\n%s", diff)
548 }
549 })
550 }
551 }
552
View as plain text