1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package client
16
17 import (
18 "context"
19 "errors"
20 "fmt"
21 "io/ioutil"
22 "net/http"
23 "net/url"
24 "reflect"
25 "testing"
26 "time"
27 )
28
29 func TestV2KeysURLHelper(t *testing.T) {
30 tests := []struct {
31 endpoint url.URL
32 prefix string
33 key string
34 want url.URL
35 }{
36
37 {
38 endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
39 prefix: "",
40 key: "",
41 want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
42 },
43
44
45 {
46 endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
47 prefix: "",
48 key: "/foo/bar",
49 want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
50 },
51
52
53 {
54 endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
55 prefix: "",
56 key: "/foo/bar",
57 want: url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
58 },
59
60
61 {
62 endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
63 prefix: "",
64 key: "",
65 want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
66 },
67
68
69 {
70 endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
71 prefix: "",
72 key: "",
73 want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
74 },
75
76 {
77 endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
78 prefix: "/bar",
79 key: "/baz",
80 want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"},
81 },
82
83 {
84 endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
85 prefix: "/bar",
86 key: "",
87 want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar"},
88 },
89
90 {
91 endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
92 prefix: "/bar",
93 key: "/baz/",
94 want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz/"},
95 },
96 }
97
98 for i, tt := range tests {
99 got := v2KeysURL(tt.endpoint, tt.prefix, tt.key)
100 if tt.want != *got {
101 t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
102 }
103 }
104 }
105
106 func TestGetAction(t *testing.T) {
107 ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
108 baseWantURL := &url.URL{
109 Scheme: "http",
110 Host: "example.com",
111 Path: "/v2/keys/foo/bar",
112 }
113 wantHeader := http.Header{}
114
115 tests := []struct {
116 recursive bool
117 sorted bool
118 quorum bool
119 wantQuery string
120 }{
121 {
122 recursive: false,
123 sorted: false,
124 quorum: false,
125 wantQuery: "quorum=false&recursive=false&sorted=false",
126 },
127 {
128 recursive: true,
129 sorted: false,
130 quorum: false,
131 wantQuery: "quorum=false&recursive=true&sorted=false",
132 },
133 {
134 recursive: false,
135 sorted: true,
136 quorum: false,
137 wantQuery: "quorum=false&recursive=false&sorted=true",
138 },
139 {
140 recursive: true,
141 sorted: true,
142 quorum: false,
143 wantQuery: "quorum=false&recursive=true&sorted=true",
144 },
145 {
146 recursive: false,
147 sorted: false,
148 quorum: true,
149 wantQuery: "quorum=true&recursive=false&sorted=false",
150 },
151 }
152
153 for i, tt := range tests {
154 f := getAction{
155 Key: "/foo/bar",
156 Recursive: tt.recursive,
157 Sorted: tt.sorted,
158 Quorum: tt.quorum,
159 }
160 got := *f.HTTPRequest(ep)
161
162 wantURL := baseWantURL
163 wantURL.RawQuery = tt.wantQuery
164
165 err := assertRequest(got, "GET", wantURL, wantHeader, nil)
166 if err != nil {
167 t.Errorf("#%d: %v", i, err)
168 }
169 }
170 }
171
172 func TestWaitAction(t *testing.T) {
173 ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
174 baseWantURL := &url.URL{
175 Scheme: "http",
176 Host: "example.com",
177 Path: "/v2/keys/foo/bar",
178 }
179 wantHeader := http.Header{}
180
181 tests := []struct {
182 waitIndex uint64
183 recursive bool
184 wantQuery string
185 }{
186 {
187 recursive: false,
188 waitIndex: uint64(0),
189 wantQuery: "recursive=false&wait=true&waitIndex=0",
190 },
191 {
192 recursive: false,
193 waitIndex: uint64(12),
194 wantQuery: "recursive=false&wait=true&waitIndex=12",
195 },
196 {
197 recursive: true,
198 waitIndex: uint64(12),
199 wantQuery: "recursive=true&wait=true&waitIndex=12",
200 },
201 }
202
203 for i, tt := range tests {
204 f := waitAction{
205 Key: "/foo/bar",
206 WaitIndex: tt.waitIndex,
207 Recursive: tt.recursive,
208 }
209 got := *f.HTTPRequest(ep)
210
211 wantURL := baseWantURL
212 wantURL.RawQuery = tt.wantQuery
213
214 err := assertRequest(got, "GET", wantURL, wantHeader, nil)
215 if err != nil {
216 t.Errorf("#%d: unexpected error: %#v", i, err)
217 }
218 }
219 }
220
221 func TestSetAction(t *testing.T) {
222 wantHeader := http.Header(map[string][]string{
223 "Content-Type": {"application/x-www-form-urlencoded"},
224 })
225
226 tests := []struct {
227 act setAction
228 wantURL string
229 wantBody string
230 }{
231
232 {
233 act: setAction{
234 Prefix: defaultV2KeysPrefix,
235 Key: "foo",
236 },
237 wantURL: "http://example.com/v2/keys/foo",
238 wantBody: "value=",
239 },
240
241
242 {
243 act: setAction{
244 Prefix: "/pfx",
245 Key: "foo",
246 },
247 wantURL: "http://example.com/pfx/foo",
248 wantBody: "value=",
249 },
250
251
252 {
253 act: setAction{
254 Key: "foo",
255 },
256 wantURL: "http://example.com/foo",
257 wantBody: "value=",
258 },
259
260
261 {
262 act: setAction{
263 Prefix: defaultV2KeysPrefix,
264 Key: "foo/bar/baz",
265 },
266 wantURL: "http://example.com/v2/keys/foo/bar/baz",
267 wantBody: "value=",
268 },
269
270
271 {
272 act: setAction{
273 Prefix: "/foo/",
274 Key: "/bar",
275 },
276 wantURL: "http://example.com/foo/bar",
277 wantBody: "value=",
278 },
279
280
281 {
282 act: setAction{
283 Key: "/foo/",
284 },
285 wantURL: "http://example.com/foo/",
286 wantBody: "value=",
287 },
288
289
290 {
291 act: setAction{
292 Key: "foo",
293 Value: "baz",
294 },
295 wantURL: "http://example.com/foo",
296 wantBody: "value=baz",
297 },
298
299
300 {
301 act: setAction{
302 Key: "foo",
303 PrevExist: PrevIgnore,
304 },
305 wantURL: "http://example.com/foo",
306 wantBody: "value=",
307 },
308
309
310 {
311 act: setAction{
312 Key: "foo",
313 PrevExist: PrevExist,
314 },
315 wantURL: "http://example.com/foo?prevExist=true",
316 wantBody: "value=",
317 },
318
319
320 {
321 act: setAction{
322 Key: "foo",
323 PrevExist: PrevNoExist,
324 },
325 wantURL: "http://example.com/foo?prevExist=false",
326 wantBody: "value=",
327 },
328
329
330 {
331 act: setAction{
332 Key: "foo",
333 PrevValue: "bar baz",
334 },
335 wantURL: "http://example.com/foo?prevValue=bar+baz",
336 wantBody: "value=",
337 },
338
339
340 {
341 act: setAction{
342 Key: "foo",
343 PrevIndex: uint64(12),
344 },
345 wantURL: "http://example.com/foo?prevIndex=12",
346 wantBody: "value=",
347 },
348
349
350 {
351 act: setAction{
352 Key: "foo",
353 TTL: 3 * time.Minute,
354 },
355 wantURL: "http://example.com/foo",
356 wantBody: "ttl=180&value=",
357 },
358
359
360 {
361 act: setAction{
362 Key: "foo",
363 TTL: 3 * time.Minute,
364 Refresh: true,
365 },
366 wantURL: "http://example.com/foo",
367 wantBody: "refresh=true&ttl=180&value=",
368 },
369
370
371 {
372 act: setAction{
373 Key: "foo",
374 Dir: true,
375 },
376 wantURL: "http://example.com/foo?dir=true",
377 wantBody: "",
378 },
379
380 {
381 act: setAction{
382 Key: "foo",
383 Value: "bar",
384 Dir: true,
385 },
386 wantURL: "http://example.com/foo?dir=true",
387 wantBody: "",
388 },
389
390 {
391 act: setAction{
392 Key: "foo",
393 PrevExist: PrevExist,
394 Dir: true,
395 },
396 wantURL: "http://example.com/foo?dir=true&prevExist=true",
397 wantBody: "",
398 },
399
400 {
401 act: setAction{
402 Key: "foo",
403 PrevValue: "bar",
404 Dir: true,
405 },
406 wantURL: "http://example.com/foo?dir=true",
407 wantBody: "",
408 },
409
410 {
411 act: setAction{
412 Key: "foo",
413 NoValueOnSuccess: true,
414 },
415 wantURL: "http://example.com/foo?noValueOnSuccess=true",
416 wantBody: "value=",
417 },
418 }
419
420 for i, tt := range tests {
421 u, err := url.Parse(tt.wantURL)
422 if err != nil {
423 t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
424 }
425
426 got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
427 if err := assertRequest(*got, "PUT", u, wantHeader, []byte(tt.wantBody)); err != nil {
428 t.Errorf("#%d: %v", i, err)
429 }
430 }
431 }
432
433 func TestCreateInOrderAction(t *testing.T) {
434 wantHeader := http.Header(map[string][]string{
435 "Content-Type": {"application/x-www-form-urlencoded"},
436 })
437
438 tests := []struct {
439 act createInOrderAction
440 wantURL string
441 wantBody string
442 }{
443
444 {
445 act: createInOrderAction{
446 Prefix: defaultV2KeysPrefix,
447 Dir: "foo",
448 },
449 wantURL: "http://example.com/v2/keys/foo",
450 wantBody: "value=",
451 },
452
453
454 {
455 act: createInOrderAction{
456 Prefix: "/pfx",
457 Dir: "foo",
458 },
459 wantURL: "http://example.com/pfx/foo",
460 wantBody: "value=",
461 },
462
463
464 {
465 act: createInOrderAction{
466 Dir: "foo",
467 },
468 wantURL: "http://example.com/foo",
469 wantBody: "value=",
470 },
471
472
473 {
474 act: createInOrderAction{
475 Prefix: defaultV2KeysPrefix,
476 Dir: "foo/bar/baz",
477 },
478 wantURL: "http://example.com/v2/keys/foo/bar/baz",
479 wantBody: "value=",
480 },
481
482
483 {
484 act: createInOrderAction{
485 Prefix: "/foo/",
486 Dir: "/bar",
487 },
488 wantURL: "http://example.com/foo/bar",
489 wantBody: "value=",
490 },
491
492
493 {
494 act: createInOrderAction{
495 Dir: "/foo/",
496 },
497 wantURL: "http://example.com/foo/",
498 wantBody: "value=",
499 },
500
501
502 {
503 act: createInOrderAction{
504 Dir: "foo",
505 Value: "baz",
506 },
507 wantURL: "http://example.com/foo",
508 wantBody: "value=baz",
509 },
510
511 {
512 act: createInOrderAction{
513 Dir: "foo",
514 TTL: 3 * time.Minute,
515 },
516 wantURL: "http://example.com/foo",
517 wantBody: "ttl=180&value=",
518 },
519 }
520
521 for i, tt := range tests {
522 u, err := url.Parse(tt.wantURL)
523 if err != nil {
524 t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
525 }
526
527 got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
528 if err := assertRequest(*got, "POST", u, wantHeader, []byte(tt.wantBody)); err != nil {
529 t.Errorf("#%d: %v", i, err)
530 }
531 }
532 }
533
534 func TestDeleteAction(t *testing.T) {
535 wantHeader := http.Header(map[string][]string{
536 "Content-Type": {"application/x-www-form-urlencoded"},
537 })
538
539 tests := []struct {
540 act deleteAction
541 wantURL string
542 }{
543
544 {
545 act: deleteAction{
546 Prefix: defaultV2KeysPrefix,
547 Key: "foo",
548 },
549 wantURL: "http://example.com/v2/keys/foo",
550 },
551
552
553 {
554 act: deleteAction{
555 Prefix: "/pfx",
556 Key: "foo",
557 },
558 wantURL: "http://example.com/pfx/foo",
559 },
560
561
562 {
563 act: deleteAction{
564 Key: "foo",
565 },
566 wantURL: "http://example.com/foo",
567 },
568
569
570 {
571 act: deleteAction{
572 Prefix: defaultV2KeysPrefix,
573 Key: "foo/bar/baz",
574 },
575 wantURL: "http://example.com/v2/keys/foo/bar/baz",
576 },
577
578
579 {
580 act: deleteAction{
581 Prefix: "/foo/",
582 Key: "/bar",
583 },
584 wantURL: "http://example.com/foo/bar",
585 },
586
587
588 {
589 act: deleteAction{
590 Key: "/foo/",
591 },
592 wantURL: "http://example.com/foo/",
593 },
594
595
596 {
597 act: deleteAction{
598 Key: "foo",
599 Recursive: true,
600 },
601 wantURL: "http://example.com/foo?recursive=true",
602 },
603
604
605 {
606 act: deleteAction{
607 Key: "foo",
608 PrevValue: "bar baz",
609 },
610 wantURL: "http://example.com/foo?prevValue=bar+baz",
611 },
612
613
614 {
615 act: deleteAction{
616 Key: "foo",
617 PrevIndex: uint64(12),
618 },
619 wantURL: "http://example.com/foo?prevIndex=12",
620 },
621 }
622
623 for i, tt := range tests {
624 u, err := url.Parse(tt.wantURL)
625 if err != nil {
626 t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
627 }
628
629 got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
630 if err := assertRequest(*got, "DELETE", u, wantHeader, nil); err != nil {
631 t.Errorf("#%d: %v", i, err)
632 }
633 }
634 }
635
636 func assertRequest(got http.Request, wantMethod string, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
637 if wantMethod != got.Method {
638 return fmt.Errorf("want.Method=%#v got.Method=%#v", wantMethod, got.Method)
639 }
640
641 if !reflect.DeepEqual(wantURL, got.URL) {
642 return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
643 }
644
645 if !reflect.DeepEqual(wantHeader, got.Header) {
646 return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
647 }
648
649 if got.Body == nil {
650 if wantBody != nil {
651 return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
652 }
653 } else {
654 if wantBody == nil {
655 return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
656 }
657 gotBytes, err := ioutil.ReadAll(got.Body)
658 if err != nil {
659 return err
660 }
661
662 if !reflect.DeepEqual(wantBody, gotBytes) {
663 return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
664 }
665 }
666
667 return nil
668 }
669
670 func TestUnmarshalSuccessfulResponse(t *testing.T) {
671 var expiration time.Time
672 expiration.UnmarshalText([]byte("2015-04-07T04:40:23.044979686Z"))
673
674 tests := []struct {
675 indexHdr string
676 clusterIDHdr string
677 body string
678 wantRes *Response
679 wantErr bool
680 }{
681
682 {
683 indexHdr: "1",
684 body: `{"action":"delete"}`,
685 wantRes: &Response{Action: "delete", Index: 1},
686 wantErr: false,
687 },
688
689
690 {
691 indexHdr: "15",
692 body: `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
693 wantRes: &Response{
694 Action: "delete",
695 Index: 15,
696 Node: nil,
697 PrevNode: &Node{
698 Key: "/foo",
699 Value: "bar",
700 ModifiedIndex: 12,
701 CreatedIndex: 10,
702 },
703 },
704 wantErr: false,
705 },
706
707
708 {
709 indexHdr: "15",
710 body: `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10, "ttl": 10, "expiration": "2015-04-07T04:40:23.044979686Z"}}`,
711 wantRes: &Response{
712 Action: "get",
713 Index: 15,
714 Node: &Node{
715 Key: "/foo",
716 Value: "bar",
717 ModifiedIndex: 12,
718 CreatedIndex: 10,
719 TTL: 10,
720 Expiration: &expiration,
721 },
722 PrevNode: nil,
723 },
724 wantErr: false,
725 },
726
727
728 {
729 indexHdr: "15",
730 clusterIDHdr: "abcdef",
731 body: `{"action":"get", "node": {"key": "/foo", "dir": true, "modifiedIndex": 12, "createdIndex": 10}}`,
732 wantRes: &Response{
733 Action: "get",
734 Index: 15,
735 Node: &Node{
736 Key: "/foo",
737 Dir: true,
738 ModifiedIndex: 12,
739 CreatedIndex: 10,
740 },
741 PrevNode: nil,
742 ClusterID: "abcdef",
743 },
744 wantErr: false,
745 },
746
747
748 {
749 indexHdr: "15",
750 body: `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
751 wantRes: &Response{
752 Action: "update",
753 Index: 15,
754 PrevNode: &Node{
755 Key: "/foo",
756 Value: "baz",
757 ModifiedIndex: 10,
758 CreatedIndex: 10,
759 },
760 Node: &Node{
761 Key: "/foo",
762 Value: "bar",
763 ModifiedIndex: 12,
764 CreatedIndex: 10,
765 },
766 },
767 wantErr: false,
768 },
769
770
771 {
772 indexHdr: "",
773 body: `garbage`,
774 wantRes: nil,
775 wantErr: true,
776 },
777
778
779 {
780 indexHdr: "poo",
781 body: `{}`,
782 wantRes: nil,
783 wantErr: true,
784 },
785 }
786
787 for i, tt := range tests {
788 h := make(http.Header)
789 h.Add("X-Etcd-Index", tt.indexHdr)
790 res, err := unmarshalSuccessfulKeysResponse(h, []byte(tt.body))
791 if tt.wantErr != (err != nil) {
792 t.Errorf("#%d: wantErr=%t, err=%v", i, tt.wantErr, err)
793 }
794
795 if (res == nil) != (tt.wantRes == nil) {
796 t.Errorf("#%d: received res=%#v, but expected res=%#v", i, res, tt.wantRes)
797 continue
798 } else if tt.wantRes == nil {
799
800 continue
801 }
802
803 if res.Action != tt.wantRes.Action {
804 t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.wantRes.Action)
805 }
806 if res.Index != tt.wantRes.Index {
807 t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.wantRes.Index)
808 }
809 if !reflect.DeepEqual(res.Node, tt.wantRes.Node) {
810 t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.wantRes.Node)
811 }
812 }
813 }
814
815 func TestUnmarshalFailedKeysResponse(t *testing.T) {
816 body := []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`)
817
818 wantErr := Error{
819 Code: 100,
820 Message: "Key not found",
821 Cause: "/foo",
822 Index: uint64(18),
823 }
824
825 gotErr := unmarshalFailedKeysResponse(body)
826 if !reflect.DeepEqual(wantErr, gotErr) {
827 t.Errorf("unexpected error: want=%#v got=%#v", wantErr, gotErr)
828 }
829 }
830
831 func TestUnmarshalFailedKeysResponseBadJSON(t *testing.T) {
832 err := unmarshalFailedKeysResponse([]byte(`{"er`))
833 if err == nil {
834 t.Errorf("got nil error")
835 } else if _, ok := err.(Error); ok {
836 t.Errorf("error is of incorrect type *Error: %#v", err)
837 }
838 }
839
840 func TestHTTPWatcherNextWaitAction(t *testing.T) {
841 initAction := waitAction{
842 Prefix: "/pants",
843 Key: "/foo/bar",
844 Recursive: true,
845 WaitIndex: 19,
846 }
847
848 client := &actionAssertingHTTPClient{
849 t: t,
850 act: &initAction,
851 resp: http.Response{
852 StatusCode: http.StatusOK,
853 Header: http.Header{"X-Etcd-Index": []string{"42"}},
854 },
855 body: []byte(`{"action":"update","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":19},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
856 }
857
858 wantResponse := &Response{
859 Action: "update",
860 Node: &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(21)},
861 PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
862 Index: uint64(42),
863 }
864
865 wantNextWait := waitAction{
866 Prefix: "/pants",
867 Key: "/foo/bar",
868 Recursive: true,
869 WaitIndex: 22,
870 }
871
872 watcher := &httpWatcher{
873 client: client,
874 nextWait: initAction,
875 }
876
877 resp, err := watcher.Next(context.Background())
878 if err != nil {
879 t.Errorf("non-nil error: %#v", err)
880 }
881
882 if !reflect.DeepEqual(wantResponse, resp) {
883 t.Errorf("received incorrect Response: want=%#v got=%#v", wantResponse, resp)
884 }
885
886 if !reflect.DeepEqual(wantNextWait, watcher.nextWait) {
887 t.Errorf("nextWait incorrect: want=%#v got=%#v", wantNextWait, watcher.nextWait)
888 }
889 }
890
891 func TestHTTPWatcherNextFail(t *testing.T) {
892 tests := []httpClient{
893
894 &staticHTTPClient{
895 err: errors.New("fail!"),
896 },
897
898
899 &staticHTTPClient{
900 resp: http.Response{
901 StatusCode: http.StatusTeapot,
902 },
903 },
904
905
906 &staticHTTPClient{
907 resp: http.Response{
908 StatusCode: http.StatusNotFound,
909 },
910 body: []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`),
911 },
912 }
913
914 for i, tt := range tests {
915 act := waitAction{
916 Prefix: "/pants",
917 Key: "/foo/bar",
918 Recursive: true,
919 WaitIndex: 19,
920 }
921
922 watcher := &httpWatcher{
923 client: tt,
924 nextWait: act,
925 }
926
927 resp, err := watcher.Next(context.Background())
928 if err == nil {
929 t.Errorf("#%d: expected non-nil error", i)
930 }
931 if resp != nil {
932 t.Errorf("#%d: expected nil Response, got %#v", i, resp)
933 }
934 if !reflect.DeepEqual(act, watcher.nextWait) {
935 t.Errorf("#%d: nextWait changed: want=%#v got=%#v", i, act, watcher.nextWait)
936 }
937 }
938 }
939
940 func TestHTTPKeysAPIWatcherAction(t *testing.T) {
941 tests := []struct {
942 key string
943 opts *WatcherOptions
944 want waitAction
945 }{
946 {
947 key: "/foo",
948 opts: nil,
949 want: waitAction{
950 Key: "/foo",
951 Recursive: false,
952 WaitIndex: 0,
953 },
954 },
955
956 {
957 key: "/foo",
958 opts: &WatcherOptions{
959 Recursive: false,
960 AfterIndex: 0,
961 },
962 want: waitAction{
963 Key: "/foo",
964 Recursive: false,
965 WaitIndex: 0,
966 },
967 },
968
969 {
970 key: "/foo",
971 opts: &WatcherOptions{
972 Recursive: true,
973 AfterIndex: 0,
974 },
975 want: waitAction{
976 Key: "/foo",
977 Recursive: true,
978 WaitIndex: 0,
979 },
980 },
981
982 {
983 key: "/foo",
984 opts: &WatcherOptions{
985 Recursive: false,
986 AfterIndex: 19,
987 },
988 want: waitAction{
989 Key: "/foo",
990 Recursive: false,
991 WaitIndex: 20,
992 },
993 },
994 }
995
996 for i, tt := range tests {
997 testError := errors.New("fail!")
998 kAPI := &httpKeysAPI{
999 client: &staticHTTPClient{err: testError},
1000 }
1001
1002 want := &httpWatcher{
1003 client: &staticHTTPClient{err: testError},
1004 nextWait: tt.want,
1005 }
1006
1007 got := kAPI.Watcher(tt.key, tt.opts)
1008 if !reflect.DeepEqual(want, got) {
1009 t.Errorf("#%d: incorrect watcher: want=%#v got=%#v", i, want, got)
1010 }
1011 }
1012 }
1013
1014 func TestHTTPKeysAPISetAction(t *testing.T) {
1015 tests := []struct {
1016 key string
1017 value string
1018 opts *SetOptions
1019 wantAction httpAction
1020 }{
1021
1022 {
1023 key: "/foo",
1024 value: "bar",
1025 opts: nil,
1026 wantAction: &setAction{
1027 Key: "/foo",
1028 Value: "bar",
1029 PrevValue: "",
1030 PrevIndex: 0,
1031 PrevExist: PrevIgnore,
1032 TTL: 0,
1033 },
1034 },
1035
1036 {
1037 key: "/foo",
1038 value: "bar",
1039 opts: &SetOptions{},
1040 wantAction: &setAction{
1041 Key: "/foo",
1042 Value: "bar",
1043 PrevValue: "",
1044 PrevIndex: 0,
1045 PrevExist: PrevIgnore,
1046 TTL: 0,
1047 },
1048 },
1049
1050 {
1051 key: "/foo",
1052 value: "bar",
1053 opts: &SetOptions{
1054 PrevValue: "baz",
1055 PrevIndex: 13,
1056 PrevExist: PrevExist,
1057 TTL: time.Minute,
1058 Dir: true,
1059 },
1060 wantAction: &setAction{
1061 Key: "/foo",
1062 Value: "bar",
1063 PrevValue: "baz",
1064 PrevIndex: 13,
1065 PrevExist: PrevExist,
1066 TTL: time.Minute,
1067 Dir: true,
1068 },
1069 },
1070 }
1071
1072 for i, tt := range tests {
1073 client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
1074 kAPI := httpKeysAPI{client: client}
1075 kAPI.Set(context.Background(), tt.key, tt.value, tt.opts)
1076 }
1077 }
1078
1079 func TestHTTPKeysAPISetError(t *testing.T) {
1080 tests := []httpClient{
1081
1082 &staticHTTPClient{
1083 err: errors.New("fail!"),
1084 },
1085
1086
1087 &staticHTTPClient{
1088 resp: http.Response{
1089 StatusCode: http.StatusTeapot,
1090 },
1091 },
1092
1093
1094 &staticHTTPClient{
1095 resp: http.Response{
1096 StatusCode: http.StatusInternalServerError,
1097 },
1098 body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
1099 },
1100 }
1101
1102 for i, tt := range tests {
1103 kAPI := httpKeysAPI{client: tt}
1104 resp, err := kAPI.Set(context.Background(), "/foo", "bar", nil)
1105 if err == nil {
1106 t.Errorf("#%d: received nil error", i)
1107 }
1108 if resp != nil {
1109 t.Errorf("#%d: received non-nil Response: %#v", i, resp)
1110 }
1111 }
1112 }
1113
1114 func TestHTTPKeysAPISetResponse(t *testing.T) {
1115 client := &staticHTTPClient{
1116 resp: http.Response{
1117 StatusCode: http.StatusOK,
1118 Header: http.Header{"X-Etcd-Index": []string{"21"}},
1119 },
1120 body: []byte(`{"action":"set","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":21},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
1121 }
1122
1123 wantResponse := &Response{
1124 Action: "set",
1125 Node: &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(21), ModifiedIndex: uint64(21)},
1126 PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
1127 Index: uint64(21),
1128 }
1129
1130 kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
1131 resp, err := kAPI.Set(context.Background(), "/foo/bar/baz", "snarf", nil)
1132 if err != nil {
1133 t.Errorf("non-nil error: %#v", err)
1134 }
1135 if !reflect.DeepEqual(wantResponse, resp) {
1136 t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
1137 }
1138 }
1139
1140 func TestHTTPKeysAPIGetAction(t *testing.T) {
1141 tests := []struct {
1142 key string
1143 opts *GetOptions
1144 wantAction httpAction
1145 }{
1146
1147 {
1148 key: "/foo",
1149 opts: nil,
1150 wantAction: &getAction{
1151 Key: "/foo",
1152 Sorted: false,
1153 Recursive: false,
1154 },
1155 },
1156
1157 {
1158 key: "/foo",
1159 opts: &GetOptions{},
1160 wantAction: &getAction{
1161 Key: "/foo",
1162 Sorted: false,
1163 Recursive: false,
1164 },
1165 },
1166
1167 {
1168 key: "/foo",
1169 opts: &GetOptions{
1170 Sort: true,
1171 Recursive: true,
1172 Quorum: true,
1173 },
1174 wantAction: &getAction{
1175 Key: "/foo",
1176 Sorted: true,
1177 Recursive: true,
1178 Quorum: true,
1179 },
1180 },
1181 }
1182
1183 for i, tt := range tests {
1184 client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
1185 kAPI := httpKeysAPI{client: client}
1186 kAPI.Get(context.Background(), tt.key, tt.opts)
1187 }
1188 }
1189
1190 func TestHTTPKeysAPIGetError(t *testing.T) {
1191 tests := []httpClient{
1192
1193 &staticHTTPClient{
1194 err: errors.New("fail!"),
1195 },
1196
1197
1198 &staticHTTPClient{
1199 resp: http.Response{
1200 StatusCode: http.StatusTeapot,
1201 },
1202 },
1203
1204
1205 &staticHTTPClient{
1206 resp: http.Response{
1207 StatusCode: http.StatusInternalServerError,
1208 },
1209 body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
1210 },
1211 }
1212
1213 for i, tt := range tests {
1214 kAPI := httpKeysAPI{client: tt}
1215 resp, err := kAPI.Get(context.Background(), "/foo", nil)
1216 if err == nil {
1217 t.Errorf("#%d: received nil error", i)
1218 }
1219 if resp != nil {
1220 t.Errorf("#%d: received non-nil Response: %#v", i, resp)
1221 }
1222 }
1223 }
1224
1225 func TestHTTPKeysAPIGetResponse(t *testing.T) {
1226 client := &staticHTTPClient{
1227 resp: http.Response{
1228 StatusCode: http.StatusOK,
1229 Header: http.Header{"X-Etcd-Index": []string{"42"}},
1230 },
1231 body: []byte(`{"action":"get","node":{"key":"/pants/foo/bar","modifiedIndex":25,"createdIndex":19,"nodes":[{"key":"/pants/foo/bar/baz","value":"snarf","createdIndex":21,"modifiedIndex":25}]}}`),
1232 }
1233
1234 wantResponse := &Response{
1235 Action: "get",
1236 Node: &Node{
1237 Key: "/pants/foo/bar",
1238 Nodes: []*Node{
1239 {Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25},
1240 },
1241 CreatedIndex: uint64(19),
1242 ModifiedIndex: uint64(25),
1243 },
1244 Index: uint64(42),
1245 }
1246
1247 kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
1248 resp, err := kAPI.Get(context.Background(), "/foo/bar", &GetOptions{Recursive: true})
1249 if err != nil {
1250 t.Errorf("non-nil error: %#v", err)
1251 }
1252 if !reflect.DeepEqual(wantResponse, resp) {
1253 t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
1254 }
1255 }
1256
1257 func TestHTTPKeysAPIDeleteAction(t *testing.T) {
1258 tests := []struct {
1259 key string
1260 opts *DeleteOptions
1261 wantAction httpAction
1262 }{
1263
1264 {
1265 key: "/foo",
1266 opts: nil,
1267 wantAction: &deleteAction{
1268 Key: "/foo",
1269 PrevValue: "",
1270 PrevIndex: 0,
1271 Recursive: false,
1272 },
1273 },
1274
1275 {
1276 key: "/foo",
1277 opts: &DeleteOptions{},
1278 wantAction: &deleteAction{
1279 Key: "/foo",
1280 PrevValue: "",
1281 PrevIndex: 0,
1282 Recursive: false,
1283 },
1284 },
1285
1286 {
1287 key: "/foo",
1288 opts: &DeleteOptions{
1289 PrevValue: "baz",
1290 PrevIndex: 13,
1291 Recursive: true,
1292 },
1293 wantAction: &deleteAction{
1294 Key: "/foo",
1295 PrevValue: "baz",
1296 PrevIndex: 13,
1297 Recursive: true,
1298 },
1299 },
1300 }
1301
1302 for i, tt := range tests {
1303 client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
1304 kAPI := httpKeysAPI{client: client}
1305 kAPI.Delete(context.Background(), tt.key, tt.opts)
1306 }
1307 }
1308
1309 func TestHTTPKeysAPIDeleteError(t *testing.T) {
1310 tests := []httpClient{
1311
1312 &staticHTTPClient{
1313 err: errors.New("fail!"),
1314 },
1315
1316
1317 &staticHTTPClient{
1318 resp: http.Response{
1319 StatusCode: http.StatusTeapot,
1320 },
1321 },
1322
1323
1324 &staticHTTPClient{
1325 resp: http.Response{
1326 StatusCode: http.StatusInternalServerError,
1327 },
1328 body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
1329 },
1330 }
1331
1332 for i, tt := range tests {
1333 kAPI := httpKeysAPI{client: tt}
1334 resp, err := kAPI.Delete(context.Background(), "/foo", nil)
1335 if err == nil {
1336 t.Errorf("#%d: received nil error", i)
1337 }
1338 if resp != nil {
1339 t.Errorf("#%d: received non-nil Response: %#v", i, resp)
1340 }
1341 }
1342 }
1343
1344 func TestHTTPKeysAPIDeleteResponse(t *testing.T) {
1345 client := &staticHTTPClient{
1346 resp: http.Response{
1347 StatusCode: http.StatusOK,
1348 Header: http.Header{"X-Etcd-Index": []string{"22"}},
1349 },
1350 body: []byte(`{"action":"delete","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":22,"createdIndex":19},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
1351 }
1352
1353 wantResponse := &Response{
1354 Action: "delete",
1355 Node: &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(22)},
1356 PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
1357 Index: uint64(22),
1358 }
1359
1360 kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
1361 resp, err := kAPI.Delete(context.Background(), "/foo/bar/baz", nil)
1362 if err != nil {
1363 t.Errorf("non-nil error: %#v", err)
1364 }
1365 if !reflect.DeepEqual(wantResponse, resp) {
1366 t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
1367 }
1368 }
1369
1370 func TestHTTPKeysAPICreateAction(t *testing.T) {
1371 act := &setAction{
1372 Key: "/foo",
1373 Value: "bar",
1374 PrevExist: PrevNoExist,
1375 PrevIndex: 0,
1376 PrevValue: "",
1377 TTL: 0,
1378 }
1379
1380 kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
1381 kAPI.Create(context.Background(), "/foo", "bar")
1382 }
1383
1384 func TestHTTPKeysAPICreateInOrderAction(t *testing.T) {
1385 act := &createInOrderAction{
1386 Dir: "/foo",
1387 Value: "bar",
1388 TTL: 0,
1389 }
1390 kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
1391 kAPI.CreateInOrder(context.Background(), "/foo", "bar", nil)
1392 }
1393
1394 func TestHTTPKeysAPIUpdateAction(t *testing.T) {
1395 act := &setAction{
1396 Key: "/foo",
1397 Value: "bar",
1398 PrevExist: PrevExist,
1399 PrevIndex: 0,
1400 PrevValue: "",
1401 TTL: 0,
1402 }
1403
1404 kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
1405 kAPI.Update(context.Background(), "/foo", "bar")
1406 }
1407
1408 func TestNodeTTLDuration(t *testing.T) {
1409 tests := []struct {
1410 node *Node
1411 want time.Duration
1412 }{
1413 {
1414 node: &Node{TTL: 0},
1415 want: 0,
1416 },
1417 {
1418 node: &Node{TTL: 97},
1419 want: 97 * time.Second,
1420 },
1421 }
1422
1423 for i, tt := range tests {
1424 got := tt.node.TTLDuration()
1425 if tt.want != got {
1426 t.Errorf("#%d: incorrect duration: want=%v got=%v", i, tt.want, got)
1427 }
1428 }
1429 }
1430
View as plain text