func TestHTTPMembersAPIAddSuccess(t *testing.T) { wantAction := &membersAPIActionAdd{ peerURLs: types.URLs([]url.URL{ {Scheme: "http", Host: "127.0.0.1:7002"}, }), } mAPI := &httpMembersAPI{ client: &actionAssertingHTTPClient{ t: t, act: wantAction, resp: http.Response{ StatusCode: http.StatusCreated, }, body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`), }, } wantResponseMember := &Member{ ID: "94088180e21eb87b", PeerURLs: []string{"http://127.0.0.1:7002"}, } m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002") if err != nil { t.Errorf("got non-nil err: %#v", err) } if !reflect.DeepEqual(wantResponseMember, m) { t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m) } }
func TestHTTPKeysAPIDeleteError(t *testing.T) { tests := []httpClient{ // generic HTTP client failure &staticHTTPClient{ err: errors.New("fail!"), }, // unusable status code &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusTeapot, }, }, // etcd Error response &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusInternalServerError, }, body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`), }, } for i, tt := range tests { kAPI := httpKeysAPI{client: tt} resp, err := kAPI.Delete(context.Background(), "/foo", nil) if err == nil { t.Errorf("#%d: received nil error", i) } if resp != nil { t.Errorf("#%d: received non-nil Response: %#v", i, resp) } } }
func TestHTTPClusterClientDoDeadlineExceedContext(t *testing.T) { fakeURL := url.URL{} tr := newFakeTransport() tr.finishCancel <- struct{}{} c := &httpClusterClient{ clientFactory: newHTTPClientFactory(tr, DefaultCheckRedirect, 0), endpoints: []url.URL{fakeURL}, } errc := make(chan error) go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() _, _, err := c.Do(ctx, &fakeAction{}) errc <- err }() select { case err := <-errc: if err != context.DeadlineExceeded { t.Errorf("err = %+v, want %+v", err, context.DeadlineExceeded) } case <-time.After(time.Second): t.Fatalf("unexpected timeout when waitting for request to deadline exceed") } }
func TestSimpleHTTPClientDoCancelContextResponseBodyClosed(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} // create an already-cancelled context ctx, cancel := context.WithCancel(context.Background()) cancel() body := &checkableReadCloser{ReadCloser: ioutil.NopCloser(strings.NewReader("foo"))} go func() { // wait that simpleHTTPClient knows the context is already timed out, // and calls CancelRequest testutil.WaitSchedule() // response is returned before cancel effects tr.respchan <- &http.Response{Body: body} }() _, _, err := c.Do(ctx, &fakeAction{}) if err == nil { t.Fatalf("expected non-nil error, got nil") } if !body.closed { t.Fatalf("expected closed body") } }
func TestHTTPClusterClientSyncFail(t *testing.T) { cf := newStaticHTTPClientFactory([]staticHTTPResponse{ {err: errors.New("fail!")}, }) hc := &httpClusterClient{ clientFactory: cf, rand: rand.New(rand.NewSource(0)), } err := hc.reset([]string{"http://127.0.0.1:2379"}) if err != nil { t.Fatalf("unexpected error during setup: %#v", err) } want := []string{"http://127.0.0.1:2379"} got := hc.Endpoints() if !reflect.DeepEqual(want, got) { t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got) } err = hc.Sync(context.Background()) if err == nil { t.Fatalf("got nil error during Sync") } got = hc.Endpoints() if !reflect.DeepEqual(want, got) { t.Fatalf("incorrect endpoints after failed Sync: want=%#v got=%#v", want, got) } }
func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} donechan := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) go func() { c.Do(ctx, &fakeAction{}) close(donechan) }() // This should call CancelRequest and begin the cancellation process cancel() select { case <-donechan: t.Fatalf("simpleHTTPClient.Do should not have exited yet") default: } tr.finishCancel <- struct{}{} select { case <-donechan: //expected behavior return case <-time.After(time.Second): t.Fatalf("simpleHTTPClient.Do did not exit within 1s") } }
func TestHTTPKeysAPIDeleteResponse(t *testing.T) { client := &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusOK, Header: http.Header{"X-Etcd-Index": []string{"22"}}, }, 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}}`), } wantResponse := &Response{ Action: "delete", Node: &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(22)}, PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)}, Index: uint64(22), } kAPI := &httpKeysAPI{client: client, prefix: "/pants"} resp, err := kAPI.Delete(context.Background(), "/foo/bar/baz", nil) if err != nil { t.Errorf("non-nil error: %#v", err) } if !reflect.DeepEqual(wantResponse, resp) { t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp) } }
func TestHTTPKeysAPIGetResponse(t *testing.T) { client := &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusOK, Header: http.Header{"X-Etcd-Index": []string{"42"}}, }, 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}]}}`), } wantResponse := &Response{ Action: "get", Node: &Node{ Key: "/pants/foo/bar", Nodes: []*Node{ {Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25}, }, CreatedIndex: uint64(19), ModifiedIndex: uint64(25), }, Index: uint64(42), } kAPI := &httpKeysAPI{client: client, prefix: "/pants"} resp, err := kAPI.Get(context.Background(), "/foo/bar", &GetOptions{Recursive: true}) if err != nil { t.Errorf("non-nil error: %#v", err) } if !reflect.DeepEqual(wantResponse, resp) { t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp) } }
func TestHTTPMembersAPIListError(t *testing.T) { tests := []httpClient{ // generic httpClient failure &staticHTTPClient{err: errors.New("fail!")}, // unrecognized HTTP status code &staticHTTPClient{ resp: http.Response{StatusCode: http.StatusTeapot}, }, // fail to unmarshal body on StatusOK &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusOK, }, body: []byte(`[{"id":"XX`), }, } for i, tt := range tests { mAPI := &httpMembersAPI{client: tt} ms, err := mAPI.List(context.Background()) if err == nil { t.Errorf("#%d: got nil err", i) } if ms != nil { t.Errorf("#%d: got non-nil Member slice", i) } } }
func TestHTTPMembersAPIListSuccess(t *testing.T) { wantAction := &membersAPIActionList{} mAPI := &httpMembersAPI{ client: &actionAssertingHTTPClient{ t: t, act: wantAction, resp: http.Response{ StatusCode: http.StatusOK, }, body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`), }, } wantResponseMembers := []Member{ { ID: "94088180e21eb87b", Name: "node2", PeerURLs: []string{"http://127.0.0.1:7002"}, ClientURLs: []string{"http://127.0.0.1:4002"}, }, } m, err := mAPI.List(context.Background()) if err != nil { t.Errorf("got non-nil err: %#v", err) } if !reflect.DeepEqual(wantResponseMembers, m) { t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m) } }
func TestHTTPKeysAPICreateInOrderAction(t *testing.T) { act := &createInOrderAction{ Dir: "/foo", Value: "bar", TTL: 0, } kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}} kAPI.CreateInOrder(context.Background(), "/foo", "bar", nil) }
func TestSimpleHTTPClientDoError(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} tr.errchan <- errors.New("fixture") _, _, err := c.Do(context.Background(), &fakeAction{}) if err == nil { t.Fatalf("expected non-nil error, got nil") } }
func TestSimpleHTTPClientDoCancelContext(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} tr.startCancel <- struct{}{} tr.finishCancel <- struct{}{} _, _, err := c.Do(context.Background(), &fakeAction{}) if err == nil { t.Fatalf("expected non-nil error, got nil") } }
func TestHTTPKeysAPIDeleteAction(t *testing.T) { tests := []struct { key string value string opts *DeleteOptions wantAction httpAction }{ // nil DeleteOptions { key: "/foo", opts: nil, wantAction: &deleteAction{ Key: "/foo", PrevValue: "", PrevIndex: 0, Recursive: false, }, }, // empty DeleteOptions { key: "/foo", opts: &DeleteOptions{}, wantAction: &deleteAction{ Key: "/foo", PrevValue: "", PrevIndex: 0, Recursive: false, }, }, // populated DeleteOptions { key: "/foo", opts: &DeleteOptions{ PrevValue: "baz", PrevIndex: 13, Recursive: true, }, wantAction: &deleteAction{ Key: "/foo", PrevValue: "baz", PrevIndex: 13, Recursive: true, }, }, } for i, tt := range tests { client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction} kAPI := httpKeysAPI{client: client} kAPI.Delete(context.Background(), tt.key, tt.opts) } }
func ExampleWithTimeout() { // Pass a context with a timeout to tell a blocking function that it // should abandon its work after the timeout elapses. ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) select { case <-time.After(200 * time.Millisecond): fmt.Println("overslept") case <-ctx.Done(): fmt.Println(ctx.Err()) // prints "context deadline exceeded" } // Output: // context deadline exceeded }
func TestHTTPKeysAPIUpdateAction(t *testing.T) { act := &setAction{ Key: "/foo", Value: "bar", PrevExist: PrevExist, PrevIndex: 0, PrevValue: "", TTL: 0, } kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}} kAPI.Update(context.Background(), "/foo", "bar") }
func TestHTTPWatcherNextWaitAction(t *testing.T) { initAction := waitAction{ Prefix: "/pants", Key: "/foo/bar", Recursive: true, WaitIndex: 19, } client := &actionAssertingHTTPClient{ t: t, act: &initAction, resp: http.Response{ StatusCode: http.StatusOK, Header: http.Header{"X-Etcd-Index": []string{"42"}}, }, 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}}`), } wantResponse := &Response{ Action: "update", Node: &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(21)}, PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)}, Index: uint64(42), } wantNextWait := waitAction{ Prefix: "/pants", Key: "/foo/bar", Recursive: true, WaitIndex: 22, } watcher := &httpWatcher{ client: client, nextWait: initAction, } resp, err := watcher.Next(context.Background()) if err != nil { t.Errorf("non-nil error: %#v", err) } if !reflect.DeepEqual(wantResponse, resp) { t.Errorf("received incorrect Response: want=%#v got=%#v", wantResponse, resp) } if !reflect.DeepEqual(wantNextWait, watcher.nextWait) { t.Errorf("nextWait incorrect: want=%#v got=%#v", wantNextWait, watcher.nextWait) } }
func TestHTTPKeysAPIGetAction(t *testing.T) { tests := []struct { key string opts *GetOptions wantAction httpAction }{ // nil GetOptions { key: "/foo", opts: nil, wantAction: &getAction{ Key: "/foo", Sorted: false, Recursive: false, }, }, // empty GetOptions { key: "/foo", opts: &GetOptions{}, wantAction: &getAction{ Key: "/foo", Sorted: false, Recursive: false, }, }, // populated GetOptions { key: "/foo", opts: &GetOptions{ Sort: true, Recursive: true, Quorum: true, }, wantAction: &getAction{ Key: "/foo", Sorted: true, Recursive: true, Quorum: true, }, }, } for i, tt := range tests { client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction} kAPI := httpKeysAPI{client: client} kAPI.Get(context.Background(), tt.key, tt.opts) } }
func TestHTTPWatcherNextFail(t *testing.T) { tests := []httpClient{ // generic HTTP client failure &staticHTTPClient{ err: errors.New("fail!"), }, // unusable status code &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusTeapot, }, }, // etcd Error response &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusNotFound, }, body: []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`), }, } for i, tt := range tests { act := waitAction{ Prefix: "/pants", Key: "/foo/bar", Recursive: true, WaitIndex: 19, } watcher := &httpWatcher{ client: tt, nextWait: act, } resp, err := watcher.Next(context.Background()) if err == nil { t.Errorf("#%d: expected non-nil error", i) } if resp != nil { t.Errorf("#%d: expected nil Response, got %#v", i, resp) } if !reflect.DeepEqual(act, watcher.nextWait) { t.Errorf("#%d: nextWait changed: want=%#v got=%#v", i, act, watcher.nextWait) } } }
func TestHTTPClusterClientSync(t *testing.T) { cf := newStaticHTTPClientFactory([]staticHTTPResponse{ { resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}}, 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"]}]}`), }, }) hc := &httpClusterClient{ clientFactory: cf, rand: rand.New(rand.NewSource(0)), } err := hc.reset([]string{"http://127.0.0.1:2379"}) if err != nil { t.Fatalf("unexpected error during setup: %#v", err) } want := []string{"http://127.0.0.1:2379"} got := hc.Endpoints() if !reflect.DeepEqual(want, got) { t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got) } err = hc.Sync(context.Background()) if err != nil { t.Fatalf("unexpected error during Sync: %#v", err) } want = []string{"http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002", "http://127.0.0.1:4003"} got = hc.Endpoints() sort.Sort(sort.StringSlice(got)) if !reflect.DeepEqual(want, got) { t.Fatalf("incorrect endpoints post-Sync: want=%#v got=%#v", want, got) } err = hc.reset([]string{"http://127.0.0.1:4009"}) if err != nil { t.Fatalf("unexpected error during reset: %#v", err) } want = []string{"http://127.0.0.1:4009"} got = hc.Endpoints() if !reflect.DeepEqual(want, got) { t.Fatalf("incorrect endpoints post-reset: want=%#v got=%#v", want, got) } }
func TestHTTPMembersAPIRemoveSuccess(t *testing.T) { wantAction := &membersAPIActionRemove{ memberID: "94088180e21eb87b", } mAPI := &httpMembersAPI{ client: &actionAssertingHTTPClient{ t: t, act: wantAction, resp: http.Response{ StatusCode: http.StatusNoContent, }, }, } if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil { t.Errorf("got non-nil err: %#v", err) } }
func (n *ng) reconnect() error { n.Close() var client etcd.Client cfg := n.getEtcdClientConfig() var err error if client, err = etcd.New(cfg); err != nil { return err } ctx, cancelFunc := context.WithCancel(context.Background()) n.context = ctx n.cancelFunc = cancelFunc n.client = client n.kapi = etcd.NewKeysAPI(n.client) n.requireQuorum = true if n.options.EtcdConsistency == "WEAK" { n.requireQuorum = false } return nil }
func TestHTTPClusterClientAutoSyncFail(t *testing.T) { cf := newStaticHTTPClientFactory([]staticHTTPResponse{ {err: errors.New("fail!")}, }) hc := &httpClusterClient{ clientFactory: cf, rand: rand.New(rand.NewSource(0)), } err := hc.reset([]string{"http://127.0.0.1:2379"}) if err != nil { t.Fatalf("unexpected error during setup: %#v", err) } err = hc.AutoSync(context.Background(), time.Hour) if err.Error() != ErrClusterUnavailable.Error() { t.Fatalf("incorrect error value: want=%v got=%v", ErrClusterUnavailable, err) } }
func TestSimpleHTTPClientDoHeaderTimeout(t *testing.T) { tr := newFakeTransport() tr.finishCancel <- struct{}{} c := &simpleHTTPClient{transport: tr, headerTimeout: time.Millisecond} errc := make(chan error) go func() { _, _, err := c.Do(context.Background(), &fakeAction{}) errc <- err }() select { case err := <-errc: if err == nil { t.Fatalf("expected non-nil error, got nil") } case <-time.After(time.Second): t.Fatalf("unexpected timeout when waitting for the test to finish") } }
func TestHTTPMembersAPIRemoveFail(t *testing.T) { tests := []httpClient{ // generic error &staticHTTPClient{ err: errors.New("fail!"), }, // unexpected HTTP status code &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusInternalServerError, }, }, } for i, tt := range tests { mAPI := &httpMembersAPI{client: tt} if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil { t.Errorf("#%d: got nil err", i) } } }
func TestSimpleHTTPClientDoCancelContextResponseBodyClosedWithBlockingBody(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} ctx, cancel := context.WithCancel(context.Background()) body := &checkableReadCloser{ReadCloser: &blockingBody{c: make(chan struct{})}} go func() { tr.respchan <- &http.Response{Body: body} time.Sleep(2 * time.Millisecond) // cancel after the body is received cancel() }() _, _, err := c.Do(ctx, &fakeAction{}) if err != context.Canceled { t.Fatalf("expected %+v, got %+v", context.Canceled, err) } if !body.closed { t.Fatalf("expected closed body") } }
// TestHTTPClusterClientSyncPinEndpoint tests that Sync() pins the endpoint when // it gets the exactly same member list as before. func TestHTTPClusterClientSyncPinEndpoint(t *testing.T) { cf := newStaticHTTPClientFactory([]staticHTTPResponse{ { resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}}, 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"]}]}`), }, { resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}}, 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"]}]}`), }, { resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}}, 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"]}]}`), }, }) hc := &httpClusterClient{ clientFactory: cf, rand: rand.New(rand.NewSource(0)), } err := hc.reset([]string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"}) if err != nil { t.Fatalf("unexpected error during setup: %#v", err) } pinnedEndpoint := hc.endpoints[hc.pinned] for i := 0; i < 3; i++ { err = hc.Sync(context.Background()) if err != nil { t.Fatalf("#%d: unexpected error during Sync: %#v", i, err) } if g := hc.endpoints[hc.pinned]; g != pinnedEndpoint { t.Errorf("#%d: pinned endpoint = %s, want %s", i, g, pinnedEndpoint) } } }
func TestHTTPClusterClientAutoSyncCancelContext(t *testing.T) { cf := newStaticHTTPClientFactory([]staticHTTPResponse{ { resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}}, 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"]}]}`), }, }) hc := &httpClusterClient{ clientFactory: cf, rand: rand.New(rand.NewSource(0)), } err := hc.reset([]string{"http://127.0.0.1:2379"}) if err != nil { t.Fatalf("unexpected error during setup: %#v", err) } ctx, cancel := context.WithCancel(context.Background()) cancel() err = hc.AutoSync(ctx, time.Hour) if err != context.Canceled { t.Fatalf("incorrect error value: want=%v got=%v", context.Canceled, err) } }
func TestSimpleHTTPClientDoSuccess(t *testing.T) { tr := newFakeTransport() c := &simpleHTTPClient{transport: tr} tr.respchan <- &http.Response{ StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(strings.NewReader("foo")), } resp, body, err := c.Do(context.Background(), &fakeAction{}) if err != nil { t.Fatalf("incorrect error value: want=nil got=%v", err) } wantCode := http.StatusTeapot if wantCode != resp.StatusCode { t.Fatalf("invalid response code: want=%d got=%d", wantCode, resp.StatusCode) } wantBody := []byte("foo") if !reflect.DeepEqual(wantBody, body) { t.Fatalf("invalid response body: want=%q got=%q", wantBody, body) } }
func TestRedirectFollowingHTTPClient(t *testing.T) { tests := []struct { checkRedirect CheckRedirectFunc client httpClient wantCode int wantErr error }{ // errors bubbled up { checkRedirect: func(int) error { return ErrTooManyRedirects }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { err: errors.New("fail!"), }, }, }, wantErr: errors.New("fail!"), }, // no need to follow redirect if none given { checkRedirect: func(int) error { return ErrTooManyRedirects }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTeapot, }, }, }, }, wantCode: http.StatusTeapot, }, // redirects if less than max { checkRedirect: func(via int) error { if via >= 2 { return ErrTooManyRedirects } return nil }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, { resp: http.Response{ StatusCode: http.StatusTeapot, }, }, }, }, wantCode: http.StatusTeapot, }, // succeed after reaching max redirects { checkRedirect: func(via int) error { if via >= 3 { return ErrTooManyRedirects } return nil }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, { resp: http.Response{ StatusCode: http.StatusTeapot, }, }, }, }, wantCode: http.StatusTeapot, }, // fail if too many redirects { checkRedirect: func(via int) error { if via >= 2 { return ErrTooManyRedirects } return nil }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, { resp: http.Response{ StatusCode: http.StatusTeapot, }, }, }, }, wantErr: ErrTooManyRedirects, }, // fail if Location header not set { checkRedirect: func(int) error { return ErrTooManyRedirects }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, }, }, }, }, wantErr: errors.New("Location header not set"), }, // fail if Location header is invalid { checkRedirect: func(int) error { return ErrTooManyRedirects }, client: &multiStaticHTTPClient{ responses: []staticHTTPResponse{ { resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{":"}}, }, }, }, }, wantErr: errors.New("Location header not valid URL: :"), }, // fail if redirects checked way too many times { checkRedirect: func(int) error { return nil }, client: &staticHTTPClient{ resp: http.Response{ StatusCode: http.StatusTemporaryRedirect, Header: http.Header{"Location": []string{"http://example.com"}}, }, }, wantErr: errTooManyRedirectChecks, }, } for i, tt := range tests { client := &redirectFollowingHTTPClient{client: tt.client, checkRedirect: tt.checkRedirect} resp, _, err := client.Do(context.Background(), nil) if !reflect.DeepEqual(tt.wantErr, err) { t.Errorf("#%d: got err=%v, want=%v", i, err, tt.wantErr) continue } if resp == nil { if tt.wantCode != 0 { t.Errorf("#%d: resp is nil, want=%d", i, tt.wantCode) } continue } if resp.StatusCode != tt.wantCode { t.Errorf("#%d: resp code=%d, want=%d", i, resp.StatusCode, tt.wantCode) continue } } }