1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package client
16
17 import (
18 "context"
19 "encoding/json"
20 "errors"
21 "net/http"
22 "net/url"
23 "reflect"
24 "testing"
25
26 "go.etcd.io/etcd/client/pkg/v3/types"
27 )
28
29 func TestMembersAPIActionList(t *testing.T) {
30 ep := url.URL{Scheme: "http", Host: "example.com"}
31 act := &membersAPIActionList{}
32
33 wantURL := &url.URL{
34 Scheme: "http",
35 Host: "example.com",
36 Path: "/v2/members",
37 }
38
39 got := *act.HTTPRequest(ep)
40 err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
41 if err != nil {
42 t.Error(err.Error())
43 }
44 }
45
46 func TestMembersAPIActionAdd(t *testing.T) {
47 ep := url.URL{Scheme: "http", Host: "example.com"}
48 act := &membersAPIActionAdd{
49 peerURLs: types.URLs([]url.URL{
50 {Scheme: "https", Host: "127.0.0.1:8081"},
51 {Scheme: "http", Host: "127.0.0.1:8080"},
52 }),
53 }
54
55 wantURL := &url.URL{
56 Scheme: "http",
57 Host: "example.com",
58 Path: "/v2/members",
59 }
60 wantHeader := http.Header{
61 "Content-Type": []string{"application/json"},
62 }
63 wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
64
65 got := *act.HTTPRequest(ep)
66 err := assertRequest(got, "POST", wantURL, wantHeader, wantBody)
67 if err != nil {
68 t.Error(err.Error())
69 }
70 }
71
72 func TestMembersAPIActionUpdate(t *testing.T) {
73 ep := url.URL{Scheme: "http", Host: "example.com"}
74 act := &membersAPIActionUpdate{
75 memberID: "0xabcd",
76 peerURLs: types.URLs([]url.URL{
77 {Scheme: "https", Host: "127.0.0.1:8081"},
78 {Scheme: "http", Host: "127.0.0.1:8080"},
79 }),
80 }
81
82 wantURL := &url.URL{
83 Scheme: "http",
84 Host: "example.com",
85 Path: "/v2/members/0xabcd",
86 }
87 wantHeader := http.Header{
88 "Content-Type": []string{"application/json"},
89 }
90 wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
91
92 got := *act.HTTPRequest(ep)
93 err := assertRequest(got, "PUT", wantURL, wantHeader, wantBody)
94 if err != nil {
95 t.Error(err.Error())
96 }
97 }
98
99 func TestMembersAPIActionRemove(t *testing.T) {
100 ep := url.URL{Scheme: "http", Host: "example.com"}
101 act := &membersAPIActionRemove{memberID: "XXX"}
102
103 wantURL := &url.URL{
104 Scheme: "http",
105 Host: "example.com",
106 Path: "/v2/members/XXX",
107 }
108
109 got := *act.HTTPRequest(ep)
110 err := assertRequest(got, "DELETE", wantURL, http.Header{}, nil)
111 if err != nil {
112 t.Error(err.Error())
113 }
114 }
115
116 func TestMembersAPIActionLeader(t *testing.T) {
117 ep := url.URL{Scheme: "http", Host: "example.com"}
118 act := &membersAPIActionLeader{}
119
120 wantURL := &url.URL{
121 Scheme: "http",
122 Host: "example.com",
123 Path: "/v2/members/leader",
124 }
125
126 got := *act.HTTPRequest(ep)
127 err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
128 if err != nil {
129 t.Error(err.Error())
130 }
131 }
132
133 func TestAssertStatusCode(t *testing.T) {
134 if err := assertStatusCode(404, 400); err == nil {
135 t.Errorf("assertStatusCode failed to detect conflict in 400 vs 404")
136 }
137
138 if err := assertStatusCode(404, 400, 404); err != nil {
139 t.Errorf("assertStatusCode found conflict in (404,400) vs 400: %v", err)
140 }
141 }
142
143 func TestV2MembersURL(t *testing.T) {
144 got := v2MembersURL(url.URL{
145 Scheme: "http",
146 Host: "foo.example.com:4002",
147 Path: "/pants",
148 })
149 want := &url.URL{
150 Scheme: "http",
151 Host: "foo.example.com:4002",
152 Path: "/pants/v2/members",
153 }
154
155 if !reflect.DeepEqual(want, got) {
156 t.Fatalf("v2MembersURL got %#v, want %#v", got, want)
157 }
158 }
159
160 func TestMemberUnmarshal(t *testing.T) {
161 tests := []struct {
162 body []byte
163 wantMember Member
164 wantError bool
165 }{
166
167 {
168 body: []byte(`{"id": "c", "name": "dungarees"}`),
169 wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil},
170 },
171
172
173 {
174 body: []byte(`{"peerURLs": ["http://127.0.0.1:2379"], "clientURLs": ["http://127.0.0.1:2379"]}`),
175 wantMember: Member{
176 PeerURLs: []string{
177 "http://127.0.0.1:2379",
178 },
179 ClientURLs: []string{
180 "http://127.0.0.1:2379",
181 },
182 },
183 },
184
185
186 {
187 body: []byte(`{"peerURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
188 wantMember: Member{
189 PeerURLs: []string{
190 "http://127.0.0.1:2379",
191 "https://example.com",
192 },
193 ClientURLs: nil,
194 },
195 },
196
197
198 {
199 body: []byte(`{"clientURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
200 wantMember: Member{
201 PeerURLs: nil,
202 ClientURLs: []string{
203 "http://127.0.0.1:2379",
204 "https://example.com",
205 },
206 },
207 },
208
209
210 {
211 body: []byte(`{"peerU`),
212 wantError: true,
213 },
214 }
215
216 for i, tt := range tests {
217 got := Member{}
218 err := json.Unmarshal(tt.body, &got)
219 if tt.wantError != (err != nil) {
220 t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
221 continue
222 }
223
224 if !reflect.DeepEqual(tt.wantMember, got) {
225 t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
226 }
227 }
228 }
229
230 func TestMemberCollectionUnmarshalFail(t *testing.T) {
231 mc := &memberCollection{}
232 if err := mc.UnmarshalJSON([]byte(`{`)); err == nil {
233 t.Errorf("got nil error")
234 }
235 }
236
237 func TestMemberCollectionUnmarshal(t *testing.T) {
238 tests := []struct {
239 body []byte
240 want memberCollection
241 }{
242 {
243 body: []byte(`{}`),
244 want: memberCollection([]Member{}),
245 },
246 {
247 body: []byte(`{"members":[]}`),
248 want: memberCollection([]Member{}),
249 },
250 {
251 body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
252 want: memberCollection(
253 []Member{
254 {
255 ID: "2745e2525fce8fe",
256 Name: "node3",
257 PeerURLs: []string{
258 "http://127.0.0.1:7003",
259 },
260 ClientURLs: []string{
261 "http://127.0.0.1:4003",
262 },
263 },
264 {
265 ID: "42134f434382925",
266 Name: "node1",
267 PeerURLs: []string{
268 "http://127.0.0.1:2380",
269 "http://127.0.0.1:7001",
270 },
271 ClientURLs: []string{
272 "http://127.0.0.1:2379",
273 "http://127.0.0.1:4001",
274 },
275 },
276 {
277 ID: "94088180e21eb87b",
278 Name: "node2",
279 PeerURLs: []string{
280 "http://127.0.0.1:7002",
281 },
282 ClientURLs: []string{
283 "http://127.0.0.1:4002",
284 },
285 },
286 },
287 ),
288 },
289 }
290
291 for i, tt := range tests {
292 var got memberCollection
293 err := json.Unmarshal(tt.body, &got)
294 if err != nil {
295 t.Errorf("#%d: unexpected error: %v", i, err)
296 continue
297 }
298
299 if !reflect.DeepEqual(tt.want, got) {
300 t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got)
301 }
302 }
303 }
304
305 func TestMemberCreateRequestMarshal(t *testing.T) {
306 req := memberCreateOrUpdateRequest{
307 PeerURLs: types.URLs([]url.URL{
308 {Scheme: "http", Host: "127.0.0.1:8081"},
309 {Scheme: "https", Host: "127.0.0.1:8080"},
310 }),
311 }
312 want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`)
313
314 got, err := json.Marshal(&req)
315 if err != nil {
316 t.Fatalf("Marshal returned unexpected err=%v", err)
317 }
318
319 if !reflect.DeepEqual(want, got) {
320 t.Fatalf("Failed to marshal memberCreateRequest: want=%s, got=%s", want, got)
321 }
322 }
323
324 func TestHTTPMembersAPIAddSuccess(t *testing.T) {
325 wantAction := &membersAPIActionAdd{
326 peerURLs: types.URLs([]url.URL{
327 {Scheme: "http", Host: "127.0.0.1:7002"},
328 }),
329 }
330
331 mAPI := &httpMembersAPI{
332 client: &actionAssertingHTTPClient{
333 t: t,
334 act: wantAction,
335 resp: http.Response{
336 StatusCode: http.StatusCreated,
337 },
338 body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`),
339 },
340 }
341
342 wantResponseMember := &Member{
343 ID: "94088180e21eb87b",
344 PeerURLs: []string{"http://127.0.0.1:7002"},
345 }
346
347 m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002")
348 if err != nil {
349 t.Errorf("got non-nil err: %#v", err)
350 }
351 if !reflect.DeepEqual(wantResponseMember, m) {
352 t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m)
353 }
354 }
355
356 func TestHTTPMembersAPIAddError(t *testing.T) {
357 okPeer := "http://example.com:2379"
358 tests := []struct {
359 peerURL string
360 client httpClient
361
362
363
364 wantErr error
365 }{
366
367 {
368 peerURL: ":",
369 },
370
371
372 {
373 peerURL: okPeer,
374 client: &staticHTTPClient{err: errors.New("fail!")},
375 },
376
377
378 {
379 peerURL: okPeer,
380 client: &staticHTTPClient{
381 resp: http.Response{StatusCode: http.StatusTeapot},
382 },
383 },
384
385
386 {
387 peerURL: okPeer,
388 client: &staticHTTPClient{
389 resp: http.Response{
390 StatusCode: http.StatusConflict,
391 },
392 body: []byte(`{"message":"fail!"}`),
393 },
394 wantErr: membersError{Message: "fail!"},
395 },
396
397
398 {
399 peerURL: okPeer,
400 client: &staticHTTPClient{
401 resp: http.Response{
402 StatusCode: http.StatusConflict,
403 },
404 body: []byte(`{"`),
405 },
406 },
407
408
409 {
410 peerURL: okPeer,
411 client: &staticHTTPClient{
412 resp: http.Response{
413 StatusCode: http.StatusCreated,
414 },
415 body: []byte(`{"id":"XX`),
416 },
417 },
418 }
419
420 for i, tt := range tests {
421 mAPI := &httpMembersAPI{client: tt.client}
422 m, err := mAPI.Add(context.Background(), tt.peerURL)
423 if err == nil {
424 t.Errorf("#%d: got nil err", i)
425 }
426 if tt.wantErr != nil && !reflect.DeepEqual(tt.wantErr, err) {
427 t.Errorf("#%d: incorrect error: want=%#v got=%#v", i, tt.wantErr, err)
428 }
429 if m != nil {
430 t.Errorf("#%d: got non-nil Member", i)
431 }
432 }
433 }
434
435 func TestHTTPMembersAPIRemoveSuccess(t *testing.T) {
436 wantAction := &membersAPIActionRemove{
437 memberID: "94088180e21eb87b",
438 }
439
440 mAPI := &httpMembersAPI{
441 client: &actionAssertingHTTPClient{
442 t: t,
443 act: wantAction,
444 resp: http.Response{
445 StatusCode: http.StatusNoContent,
446 },
447 },
448 }
449
450 if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil {
451 t.Errorf("got non-nil err: %#v", err)
452 }
453 }
454
455 func TestHTTPMembersAPIRemoveFail(t *testing.T) {
456 tests := []httpClient{
457
458 &staticHTTPClient{
459 err: errors.New("fail!"),
460 },
461
462
463 &staticHTTPClient{
464 resp: http.Response{
465 StatusCode: http.StatusInternalServerError,
466 },
467 },
468 }
469
470 for i, tt := range tests {
471 mAPI := &httpMembersAPI{client: tt}
472 if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil {
473 t.Errorf("#%d: got nil err", i)
474 }
475 }
476 }
477
478 func TestHTTPMembersAPIListSuccess(t *testing.T) {
479 wantAction := &membersAPIActionList{}
480 mAPI := &httpMembersAPI{
481 client: &actionAssertingHTTPClient{
482 t: t,
483 act: wantAction,
484 resp: http.Response{
485 StatusCode: http.StatusOK,
486 },
487 body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`),
488 },
489 }
490
491 wantResponseMembers := []Member{
492 {
493 ID: "94088180e21eb87b",
494 Name: "node2",
495 PeerURLs: []string{"http://127.0.0.1:7002"},
496 ClientURLs: []string{"http://127.0.0.1:4002"},
497 },
498 }
499
500 m, err := mAPI.List(context.Background())
501 if err != nil {
502 t.Errorf("got non-nil err: %#v", err)
503 }
504 if !reflect.DeepEqual(wantResponseMembers, m) {
505 t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m)
506 }
507 }
508
509 func TestHTTPMembersAPIListError(t *testing.T) {
510 tests := []httpClient{
511
512 &staticHTTPClient{err: errors.New("fail!")},
513
514
515 &staticHTTPClient{
516 resp: http.Response{StatusCode: http.StatusTeapot},
517 },
518
519
520 &staticHTTPClient{
521 resp: http.Response{
522 StatusCode: http.StatusOK,
523 },
524 body: []byte(`[{"id":"XX`),
525 },
526 }
527
528 for i, tt := range tests {
529 mAPI := &httpMembersAPI{client: tt}
530 ms, err := mAPI.List(context.Background())
531 if err == nil {
532 t.Errorf("#%d: got nil err", i)
533 }
534 if ms != nil {
535 t.Errorf("#%d: got non-nil Member slice", i)
536 }
537 }
538 }
539
540 func TestHTTPMembersAPILeaderSuccess(t *testing.T) {
541 wantAction := &membersAPIActionLeader{}
542 mAPI := &httpMembersAPI{
543 client: &actionAssertingHTTPClient{
544 t: t,
545 act: wantAction,
546 resp: http.Response{
547 StatusCode: http.StatusOK,
548 },
549 body: []byte(`{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}`),
550 },
551 }
552
553 wantResponseMember := &Member{
554 ID: "94088180e21eb87b",
555 Name: "node2",
556 PeerURLs: []string{"http://127.0.0.1:7002"},
557 ClientURLs: []string{"http://127.0.0.1:4002"},
558 }
559
560 m, err := mAPI.Leader(context.Background())
561 if err != nil {
562 t.Errorf("err = %v, want %v", err, nil)
563 }
564 if !reflect.DeepEqual(wantResponseMember, m) {
565 t.Errorf("incorrect member: member = %v, want %v", wantResponseMember, m)
566 }
567 }
568
569 func TestHTTPMembersAPILeaderError(t *testing.T) {
570 tests := []httpClient{
571
572 &staticHTTPClient{err: errors.New("fail!")},
573
574
575 &staticHTTPClient{
576 resp: http.Response{StatusCode: http.StatusTeapot},
577 },
578
579
580 &staticHTTPClient{
581 resp: http.Response{
582 StatusCode: http.StatusOK,
583 },
584 body: []byte(`[{"id":"XX`),
585 },
586 }
587
588 for i, tt := range tests {
589 mAPI := &httpMembersAPI{client: tt}
590 m, err := mAPI.Leader(context.Background())
591 if err == nil {
592 t.Errorf("#%d: err = nil, want not nil", i)
593 }
594 if m != nil {
595 t.Errorf("member slice = %v, want nil", m)
596 }
597 }
598 }
599
View as plain text