Esempio n. 1
0
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{
				&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)
	}
}
Esempio n. 2
0
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)
		}
	}
}
Esempio n. 3
0
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 for CancelRequest to be called, informing us that simpleHTTPClient
		// knows the context is already timed out
		<-tr.startCancel

		tr.respchan <- &http.Response{Body: body}
		tr.finishCancel <- struct{}{}
	}()

	_, _, err := c.Do(ctx, &fakeAction{})
	if err == nil {
		t.Fatalf("expected non-nil error, got nil")
	}

	if !body.closed {
		t.Fatalf("expected closed body")
	}
}
Esempio n. 4
0
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)
	}
}
Esempio n. 5
0
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)
		}
	}
}
Esempio n. 6
0
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)
	}
}
Esempio n. 7
0
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")
	}
}
Esempio n. 8
0
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")
	}
}
Esempio n. 9
0
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)
	}
}
Esempio n. 10
0
func TestHTTPClusterClientSyncFail(t *testing.T) {
	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
		staticHTTPResponse{err: errors.New("fail!")},
	})

	hc := &httpClusterClient{clientFactory: cf}
	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)
	}
}
Esempio n. 11
0
func watch(kAPI etcd.KeysAPI, key string, stop chan struct{}) (res *etcd.Response) {
	for res == nil {
		select {
		case <-stop:
			log.Debugf("Gracefully closing etcd watch loop: key=%s", key)
			return
		default:
			opts := &etcd.WatcherOptions{
				AfterIndex: 0,
				Recursive:  true,
			}
			watcher := kAPI.Watcher(key, opts)
			log.Debugf("Creating etcd watcher: %s", key)

			var err error
			res, err = watcher.Next(context.Background())
			if err != nil {
				log.Errorf("etcd watcher %v returned error: %v", key, err)
			}
		}

		// Let's not slam the etcd server in the event that we know
		// an unexpected error occurred.
		time.Sleep(time.Second)
	}

	return
}
Esempio n. 12
0
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)
}
Esempio n. 13
0
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")
	}
}
Esempio n. 14
0
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")
	}
}
Esempio n. 15
0
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)
	}
}
Esempio n. 16
0
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
}
Esempio n. 17
0
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")
}
Esempio n. 18
0
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)
	}
}
Esempio n. 19
0
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)
	}
}
Esempio n. 20
0
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)
		}
	}
}
Esempio n. 21
0
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)
	}
}
Esempio n. 22
0
func TestHTTPClusterClientSync(t *testing.T) {
	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
		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}
	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:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"}
	got = hc.Endpoints()
	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)
	}
}
Esempio n. 23
0
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)
		}
	}
}
Esempio n. 24
0
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 == nil {
		t.Fatalf("expected non-nil error, got nil")
	}

	if !body.closed {
		t.Fatalf("expected closed body")
	}
}
Esempio n. 25
0
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)
	}
}
Esempio n. 26
0
func TestHTTPMembersAPIAddError(t *testing.T) {
	okPeer := "http://example.com:2379"
	tests := []struct {
		peerURL string
		client  httpClient

		// if wantErr == nil, assert that the returned error is non-nil
		// if wantErr != nil, assert that the returned error matches
		wantErr error
	}{
		// malformed peer URL
		{
			peerURL: ":",
		},

		// generic httpClient failure
		{
			peerURL: okPeer,
			client:  &staticHTTPClient{err: errors.New("fail!")},
		},

		// unrecognized HTTP status code
		{
			peerURL: okPeer,
			client: &staticHTTPClient{
				resp: http.Response{StatusCode: http.StatusTeapot},
			},
		},

		// unmarshal body into membersError on StatusConflict
		{
			peerURL: okPeer,
			client: &staticHTTPClient{
				resp: http.Response{
					StatusCode: http.StatusConflict,
				},
				body: []byte(`{"message":"fail!"}`),
			},
			wantErr: membersError{Message: "fail!"},
		},

		// fail to unmarshal body on StatusConflict
		{
			peerURL: okPeer,
			client: &staticHTTPClient{
				resp: http.Response{
					StatusCode: http.StatusConflict,
				},
				body: []byte(`{"`),
			},
		},

		// fail to unmarshal body on StatusCreated
		{
			peerURL: okPeer,
			client: &staticHTTPClient{
				resp: http.Response{
					StatusCode: http.StatusCreated,
				},
				body: []byte(`{"id":"XX`),
			},
		},
	}

	for i, tt := range tests {
		mAPI := &httpMembersAPI{client: tt.client}
		m, err := mAPI.Add(context.Background(), tt.peerURL)
		if err == nil {
			t.Errorf("#%d: got nil err", i)
		}
		if tt.wantErr != nil && !reflect.DeepEqual(tt.wantErr, err) {
			t.Errorf("#%d: incorrect error: want=%#v got=%#v", i, tt.wantErr, err)
		}
		if m != nil {
			t.Errorf("#%d: got non-nil Member", i)
		}
	}
}
Esempio n. 27
0
func TestHTTPKeysAPISetAction(t *testing.T) {
	tests := []struct {
		key        string
		value      string
		opts       *SetOptions
		wantAction httpAction
	}{
		// nil SetOptions
		{
			key:   "/foo",
			value: "bar",
			opts:  nil,
			wantAction: &setAction{
				Key:       "/foo",
				Value:     "bar",
				PrevValue: "",
				PrevIndex: 0,
				PrevExist: PrevIgnore,
				TTL:       0,
			},
		},
		// empty SetOptions
		{
			key:   "/foo",
			value: "bar",
			opts:  &SetOptions{},
			wantAction: &setAction{
				Key:       "/foo",
				Value:     "bar",
				PrevValue: "",
				PrevIndex: 0,
				PrevExist: PrevIgnore,
				TTL:       0,
			},
		},
		// populated SetOptions
		{
			key:   "/foo",
			value: "bar",
			opts: &SetOptions{
				PrevValue: "baz",
				PrevIndex: 13,
				PrevExist: PrevExist,
				TTL:       time.Minute,
				Dir:       true,
			},
			wantAction: &setAction{
				Key:       "/foo",
				Value:     "bar",
				PrevValue: "baz",
				PrevIndex: 13,
				PrevExist: PrevExist,
				TTL:       time.Minute,
				Dir:       true,
			},
		},
	}

	for i, tt := range tests {
		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
		kAPI := httpKeysAPI{client: client}
		kAPI.Set(context.Background(), tt.key, tt.value, tt.opts)
	}
}
Esempio n. 28
0
func TestHTTPClusterClientDo(t *testing.T) {
	fakeErr := errors.New("fake!")
	fakeURL := url.URL{}
	tests := []struct {
		client   *httpClusterClient
		wantCode int
		wantErr  error
	}{
		// first good response short-circuits Do
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
						staticHTTPResponse{err: fakeErr},
					},
				),
			},
			wantCode: http.StatusTeapot,
		},

		// fall through to good endpoint if err is arbitrary
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{err: fakeErr},
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
					},
				),
			},
			wantCode: http.StatusTeapot,
		},

		// context.DeadlineExceeded short-circuits Do
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{err: context.DeadlineExceeded},
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
					},
				),
			},
			wantErr: context.DeadlineExceeded,
		},

		// context.Canceled short-circuits Do
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{err: context.Canceled},
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
					},
				),
			},
			wantErr: context.Canceled,
		},

		// return err if there are no endpoints
		{
			client: &httpClusterClient{
				endpoints:     []url.URL{},
				clientFactory: newHTTPClientFactory(nil, nil),
			},
			wantErr: ErrNoEndpoints,
		},

		// return err if all endpoints return arbitrary errors
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{err: fakeErr},
						staticHTTPResponse{err: fakeErr},
					},
				),
			},
			wantErr: fakeErr,
		},

		// 500-level errors cause Do to fallthrough to next endpoint
		{
			client: &httpClusterClient{
				endpoints: []url.URL{fakeURL, fakeURL},
				clientFactory: newStaticHTTPClientFactory(
					[]staticHTTPResponse{
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusBadGateway}},
						staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
					},
				),
			},
			wantCode: http.StatusTeapot,
		},
	}

	for i, tt := range tests {
		resp, _, err := tt.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
		}
	}
}
Esempio n. 29
0
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{
					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{
					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{
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTemporaryRedirect,
							Header:     http.Header{"Location": []string{"http://example.com"}},
						},
					},
					staticHTTPResponse{
						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{
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTemporaryRedirect,
							Header:     http.Header{"Location": []string{"http://example.com"}},
						},
					},
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTemporaryRedirect,
							Header:     http.Header{"Location": []string{"http://example.com"}},
						},
					},
					staticHTTPResponse{
						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{
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTemporaryRedirect,
							Header:     http.Header{"Location": []string{"http://example.com"}},
						},
					},
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTemporaryRedirect,
							Header:     http.Header{"Location": []string{"http://example.com"}},
						},
					},
					staticHTTPResponse{
						resp: http.Response{
							StatusCode: http.StatusTeapot,
						},
					},
				},
			},
			wantErr: ErrTooManyRedirects,
		},

		// fail if Location header not set
		{
			checkRedirect: func(int) error { return ErrTooManyRedirects },
			client: &multiStaticHTTPClient{
				responses: []staticHTTPResponse{
					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{
					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
		}
	}
}
Esempio n. 30
0
func (r *EtcdRegistry) ctx() context.Context {
	ctx, _ := context.WithTimeout(context.Background(), r.reqTimeout)
	return ctx
}