client_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. // Copyright 2015 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package client
  15. import (
  16. "errors"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "net/url"
  21. "reflect"
  22. "strings"
  23. "testing"
  24. "time"
  25. "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
  26. "github.com/coreos/etcd/pkg/testutil"
  27. )
  28. type actionAssertingHTTPClient struct {
  29. t *testing.T
  30. num int
  31. act httpAction
  32. resp http.Response
  33. body []byte
  34. err error
  35. }
  36. func (a *actionAssertingHTTPClient) Do(_ context.Context, act httpAction) (*http.Response, []byte, error) {
  37. if !reflect.DeepEqual(a.act, act) {
  38. a.t.Errorf("#%d: unexpected httpAction: want=%#v got=%#v", a.num, a.act, act)
  39. }
  40. return &a.resp, a.body, a.err
  41. }
  42. type staticHTTPClient struct {
  43. resp http.Response
  44. body []byte
  45. err error
  46. }
  47. func (s *staticHTTPClient) Do(context.Context, httpAction) (*http.Response, []byte, error) {
  48. return &s.resp, s.body, s.err
  49. }
  50. type staticHTTPAction struct {
  51. request http.Request
  52. }
  53. func (s *staticHTTPAction) HTTPRequest(url.URL) *http.Request {
  54. return &s.request
  55. }
  56. type staticHTTPResponse struct {
  57. resp http.Response
  58. body []byte
  59. err error
  60. }
  61. type multiStaticHTTPClient struct {
  62. responses []staticHTTPResponse
  63. cur int
  64. }
  65. func (s *multiStaticHTTPClient) Do(context.Context, httpAction) (*http.Response, []byte, error) {
  66. r := s.responses[s.cur]
  67. s.cur++
  68. return &r.resp, r.body, r.err
  69. }
  70. func newStaticHTTPClientFactory(responses []staticHTTPResponse) httpClientFactory {
  71. var cur int
  72. return func(url.URL) httpClient {
  73. r := responses[cur]
  74. cur++
  75. return &staticHTTPClient{resp: r.resp, body: r.body, err: r.err}
  76. }
  77. }
  78. type fakeTransport struct {
  79. respchan chan *http.Response
  80. errchan chan error
  81. startCancel chan struct{}
  82. finishCancel chan struct{}
  83. }
  84. func newFakeTransport() *fakeTransport {
  85. return &fakeTransport{
  86. respchan: make(chan *http.Response, 1),
  87. errchan: make(chan error, 1),
  88. startCancel: make(chan struct{}, 1),
  89. finishCancel: make(chan struct{}, 1),
  90. }
  91. }
  92. func (t *fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
  93. select {
  94. case resp := <-t.respchan:
  95. return resp, nil
  96. case err := <-t.errchan:
  97. return nil, err
  98. case <-t.startCancel:
  99. select {
  100. // this simulates that the request is finished before cancel effects
  101. case resp := <-t.respchan:
  102. return resp, nil
  103. // wait on finishCancel to simulate taking some amount of
  104. // time while calling CancelRequest
  105. case <-t.finishCancel:
  106. return nil, errors.New("cancelled")
  107. }
  108. }
  109. }
  110. func (t *fakeTransport) CancelRequest(*http.Request) {
  111. t.startCancel <- struct{}{}
  112. }
  113. type fakeAction struct{}
  114. func (a *fakeAction) HTTPRequest(url.URL) *http.Request {
  115. return &http.Request{}
  116. }
  117. func TestSimpleHTTPClientDoSuccess(t *testing.T) {
  118. tr := newFakeTransport()
  119. c := &simpleHTTPClient{transport: tr}
  120. tr.respchan <- &http.Response{
  121. StatusCode: http.StatusTeapot,
  122. Body: ioutil.NopCloser(strings.NewReader("foo")),
  123. }
  124. resp, body, err := c.Do(context.Background(), &fakeAction{})
  125. if err != nil {
  126. t.Fatalf("incorrect error value: want=nil got=%v", err)
  127. }
  128. wantCode := http.StatusTeapot
  129. if wantCode != resp.StatusCode {
  130. t.Fatalf("invalid response code: want=%d got=%d", wantCode, resp.StatusCode)
  131. }
  132. wantBody := []byte("foo")
  133. if !reflect.DeepEqual(wantBody, body) {
  134. t.Fatalf("invalid response body: want=%q got=%q", wantBody, body)
  135. }
  136. }
  137. func TestSimpleHTTPClientDoError(t *testing.T) {
  138. tr := newFakeTransport()
  139. c := &simpleHTTPClient{transport: tr}
  140. tr.errchan <- errors.New("fixture")
  141. _, _, err := c.Do(context.Background(), &fakeAction{})
  142. if err == nil {
  143. t.Fatalf("expected non-nil error, got nil")
  144. }
  145. }
  146. func TestSimpleHTTPClientDoCancelContext(t *testing.T) {
  147. tr := newFakeTransport()
  148. c := &simpleHTTPClient{transport: tr}
  149. tr.startCancel <- struct{}{}
  150. tr.finishCancel <- struct{}{}
  151. _, _, err := c.Do(context.Background(), &fakeAction{})
  152. if err == nil {
  153. t.Fatalf("expected non-nil error, got nil")
  154. }
  155. }
  156. type checkableReadCloser struct {
  157. io.ReadCloser
  158. closed bool
  159. }
  160. func (c *checkableReadCloser) Close() error {
  161. if !c.closed {
  162. c.closed = true
  163. return c.ReadCloser.Close()
  164. }
  165. return nil
  166. }
  167. func TestSimpleHTTPClientDoCancelContextResponseBodyClosed(t *testing.T) {
  168. tr := newFakeTransport()
  169. c := &simpleHTTPClient{transport: tr}
  170. // create an already-cancelled context
  171. ctx, cancel := context.WithCancel(context.Background())
  172. cancel()
  173. body := &checkableReadCloser{ReadCloser: ioutil.NopCloser(strings.NewReader("foo"))}
  174. go func() {
  175. // wait that simpleHTTPClient knows the context is already timed out,
  176. // and calls CancelRequest
  177. testutil.WaitSchedule()
  178. // response is returned before cancel effects
  179. tr.respchan <- &http.Response{Body: body}
  180. }()
  181. _, _, err := c.Do(ctx, &fakeAction{})
  182. if err == nil {
  183. t.Fatalf("expected non-nil error, got nil")
  184. }
  185. if !body.closed {
  186. t.Fatalf("expected closed body")
  187. }
  188. }
  189. type blockingBody struct {
  190. c chan struct{}
  191. }
  192. func (bb *blockingBody) Read(p []byte) (n int, err error) {
  193. <-bb.c
  194. return 0, errors.New("closed")
  195. }
  196. func (bb *blockingBody) Close() error {
  197. close(bb.c)
  198. return nil
  199. }
  200. func TestSimpleHTTPClientDoCancelContextResponseBodyClosedWithBlockingBody(t *testing.T) {
  201. tr := newFakeTransport()
  202. c := &simpleHTTPClient{transport: tr}
  203. ctx, cancel := context.WithCancel(context.Background())
  204. body := &checkableReadCloser{ReadCloser: &blockingBody{c: make(chan struct{})}}
  205. go func() {
  206. tr.respchan <- &http.Response{Body: body}
  207. time.Sleep(2 * time.Millisecond)
  208. // cancel after the body is received
  209. cancel()
  210. }()
  211. _, _, err := c.Do(ctx, &fakeAction{})
  212. if err == nil {
  213. t.Fatalf("expected non-nil error, got nil")
  214. }
  215. if !body.closed {
  216. t.Fatalf("expected closed body")
  217. }
  218. }
  219. func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
  220. tr := newFakeTransport()
  221. c := &simpleHTTPClient{transport: tr}
  222. donechan := make(chan struct{})
  223. ctx, cancel := context.WithCancel(context.Background())
  224. go func() {
  225. c.Do(ctx, &fakeAction{})
  226. close(donechan)
  227. }()
  228. // This should call CancelRequest and begin the cancellation process
  229. cancel()
  230. select {
  231. case <-donechan:
  232. t.Fatalf("simpleHTTPClient.Do should not have exited yet")
  233. default:
  234. }
  235. tr.finishCancel <- struct{}{}
  236. select {
  237. case <-donechan:
  238. //expected behavior
  239. return
  240. case <-time.After(time.Second):
  241. t.Fatalf("simpleHTTPClient.Do did not exit within 1s")
  242. }
  243. }
  244. func TestHTTPClusterClientDo(t *testing.T) {
  245. fakeErr := errors.New("fake!")
  246. fakeURL := url.URL{}
  247. tests := []struct {
  248. client *httpClusterClient
  249. wantCode int
  250. wantErr error
  251. }{
  252. // first good response short-circuits Do
  253. {
  254. client: &httpClusterClient{
  255. endpoints: []url.URL{fakeURL, fakeURL},
  256. clientFactory: newStaticHTTPClientFactory(
  257. []staticHTTPResponse{
  258. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
  259. staticHTTPResponse{err: fakeErr},
  260. },
  261. ),
  262. },
  263. wantCode: http.StatusTeapot,
  264. },
  265. // fall through to good endpoint if err is arbitrary
  266. {
  267. client: &httpClusterClient{
  268. endpoints: []url.URL{fakeURL, fakeURL},
  269. clientFactory: newStaticHTTPClientFactory(
  270. []staticHTTPResponse{
  271. staticHTTPResponse{err: fakeErr},
  272. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
  273. },
  274. ),
  275. },
  276. wantCode: http.StatusTeapot,
  277. },
  278. // context.DeadlineExceeded short-circuits Do
  279. {
  280. client: &httpClusterClient{
  281. endpoints: []url.URL{fakeURL, fakeURL},
  282. clientFactory: newStaticHTTPClientFactory(
  283. []staticHTTPResponse{
  284. staticHTTPResponse{err: context.DeadlineExceeded},
  285. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
  286. },
  287. ),
  288. },
  289. wantErr: &ClusterError{Errors: []error{context.DeadlineExceeded}},
  290. },
  291. // context.Canceled short-circuits Do
  292. {
  293. client: &httpClusterClient{
  294. endpoints: []url.URL{fakeURL, fakeURL},
  295. clientFactory: newStaticHTTPClientFactory(
  296. []staticHTTPResponse{
  297. staticHTTPResponse{err: context.Canceled},
  298. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
  299. },
  300. ),
  301. },
  302. wantErr: &ClusterError{Errors: []error{context.Canceled}},
  303. },
  304. // return err if there are no endpoints
  305. {
  306. client: &httpClusterClient{
  307. endpoints: []url.URL{},
  308. clientFactory: newHTTPClientFactory(nil, nil),
  309. },
  310. wantErr: ErrNoEndpoints,
  311. },
  312. // return err if all endpoints return arbitrary errors
  313. {
  314. client: &httpClusterClient{
  315. endpoints: []url.URL{fakeURL, fakeURL},
  316. clientFactory: newStaticHTTPClientFactory(
  317. []staticHTTPResponse{
  318. staticHTTPResponse{err: fakeErr},
  319. staticHTTPResponse{err: fakeErr},
  320. },
  321. ),
  322. },
  323. wantErr: &ClusterError{Errors: []error{fakeErr, fakeErr}},
  324. },
  325. // 500-level errors cause Do to fallthrough to next endpoint
  326. {
  327. client: &httpClusterClient{
  328. endpoints: []url.URL{fakeURL, fakeURL},
  329. clientFactory: newStaticHTTPClientFactory(
  330. []staticHTTPResponse{
  331. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusBadGateway}},
  332. staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
  333. },
  334. ),
  335. },
  336. wantCode: http.StatusTeapot,
  337. },
  338. }
  339. for i, tt := range tests {
  340. resp, _, err := tt.client.Do(context.Background(), nil)
  341. if !reflect.DeepEqual(tt.wantErr, err) {
  342. t.Errorf("#%d: got err=%v, want=%v", i, err, tt.wantErr)
  343. continue
  344. }
  345. if resp == nil {
  346. if tt.wantCode != 0 {
  347. t.Errorf("#%d: resp is nil, want=%d", i, tt.wantCode)
  348. }
  349. continue
  350. }
  351. if resp.StatusCode != tt.wantCode {
  352. t.Errorf("#%d: resp code=%d, want=%d", i, resp.StatusCode, tt.wantCode)
  353. continue
  354. }
  355. }
  356. }
  357. func TestRedirectedHTTPAction(t *testing.T) {
  358. act := &redirectedHTTPAction{
  359. action: &staticHTTPAction{
  360. request: http.Request{
  361. Method: "DELETE",
  362. URL: &url.URL{
  363. Scheme: "https",
  364. Host: "foo.example.com",
  365. Path: "/ping",
  366. },
  367. },
  368. },
  369. location: url.URL{
  370. Scheme: "https",
  371. Host: "bar.example.com",
  372. Path: "/pong",
  373. },
  374. }
  375. want := &http.Request{
  376. Method: "DELETE",
  377. URL: &url.URL{
  378. Scheme: "https",
  379. Host: "bar.example.com",
  380. Path: "/pong",
  381. },
  382. }
  383. got := act.HTTPRequest(url.URL{Scheme: "http", Host: "baz.example.com", Path: "/pang"})
  384. if !reflect.DeepEqual(want, got) {
  385. t.Fatalf("HTTPRequest is %#v, want %#v", want, got)
  386. }
  387. }
  388. func TestRedirectFollowingHTTPClient(t *testing.T) {
  389. tests := []struct {
  390. checkRedirect CheckRedirectFunc
  391. client httpClient
  392. wantCode int
  393. wantErr error
  394. }{
  395. // errors bubbled up
  396. {
  397. checkRedirect: func(int) error { return ErrTooManyRedirects },
  398. client: &multiStaticHTTPClient{
  399. responses: []staticHTTPResponse{
  400. staticHTTPResponse{
  401. err: errors.New("fail!"),
  402. },
  403. },
  404. },
  405. wantErr: errors.New("fail!"),
  406. },
  407. // no need to follow redirect if none given
  408. {
  409. checkRedirect: func(int) error { return ErrTooManyRedirects },
  410. client: &multiStaticHTTPClient{
  411. responses: []staticHTTPResponse{
  412. staticHTTPResponse{
  413. resp: http.Response{
  414. StatusCode: http.StatusTeapot,
  415. },
  416. },
  417. },
  418. },
  419. wantCode: http.StatusTeapot,
  420. },
  421. // redirects if less than max
  422. {
  423. checkRedirect: func(via int) error {
  424. if via >= 2 {
  425. return ErrTooManyRedirects
  426. }
  427. return nil
  428. },
  429. client: &multiStaticHTTPClient{
  430. responses: []staticHTTPResponse{
  431. staticHTTPResponse{
  432. resp: http.Response{
  433. StatusCode: http.StatusTemporaryRedirect,
  434. Header: http.Header{"Location": []string{"http://example.com"}},
  435. },
  436. },
  437. staticHTTPResponse{
  438. resp: http.Response{
  439. StatusCode: http.StatusTeapot,
  440. },
  441. },
  442. },
  443. },
  444. wantCode: http.StatusTeapot,
  445. },
  446. // succeed after reaching max redirects
  447. {
  448. checkRedirect: func(via int) error {
  449. if via >= 3 {
  450. return ErrTooManyRedirects
  451. }
  452. return nil
  453. },
  454. client: &multiStaticHTTPClient{
  455. responses: []staticHTTPResponse{
  456. staticHTTPResponse{
  457. resp: http.Response{
  458. StatusCode: http.StatusTemporaryRedirect,
  459. Header: http.Header{"Location": []string{"http://example.com"}},
  460. },
  461. },
  462. staticHTTPResponse{
  463. resp: http.Response{
  464. StatusCode: http.StatusTemporaryRedirect,
  465. Header: http.Header{"Location": []string{"http://example.com"}},
  466. },
  467. },
  468. staticHTTPResponse{
  469. resp: http.Response{
  470. StatusCode: http.StatusTeapot,
  471. },
  472. },
  473. },
  474. },
  475. wantCode: http.StatusTeapot,
  476. },
  477. // fail if too many redirects
  478. {
  479. checkRedirect: func(via int) error {
  480. if via >= 2 {
  481. return ErrTooManyRedirects
  482. }
  483. return nil
  484. },
  485. client: &multiStaticHTTPClient{
  486. responses: []staticHTTPResponse{
  487. staticHTTPResponse{
  488. resp: http.Response{
  489. StatusCode: http.StatusTemporaryRedirect,
  490. Header: http.Header{"Location": []string{"http://example.com"}},
  491. },
  492. },
  493. staticHTTPResponse{
  494. resp: http.Response{
  495. StatusCode: http.StatusTemporaryRedirect,
  496. Header: http.Header{"Location": []string{"http://example.com"}},
  497. },
  498. },
  499. staticHTTPResponse{
  500. resp: http.Response{
  501. StatusCode: http.StatusTeapot,
  502. },
  503. },
  504. },
  505. },
  506. wantErr: ErrTooManyRedirects,
  507. },
  508. // fail if Location header not set
  509. {
  510. checkRedirect: func(int) error { return ErrTooManyRedirects },
  511. client: &multiStaticHTTPClient{
  512. responses: []staticHTTPResponse{
  513. staticHTTPResponse{
  514. resp: http.Response{
  515. StatusCode: http.StatusTemporaryRedirect,
  516. },
  517. },
  518. },
  519. },
  520. wantErr: errors.New("Location header not set"),
  521. },
  522. // fail if Location header is invalid
  523. {
  524. checkRedirect: func(int) error { return ErrTooManyRedirects },
  525. client: &multiStaticHTTPClient{
  526. responses: []staticHTTPResponse{
  527. staticHTTPResponse{
  528. resp: http.Response{
  529. StatusCode: http.StatusTemporaryRedirect,
  530. Header: http.Header{"Location": []string{":"}},
  531. },
  532. },
  533. },
  534. },
  535. wantErr: errors.New("Location header not valid URL: :"),
  536. },
  537. // fail if redirects checked way too many times
  538. {
  539. checkRedirect: func(int) error { return nil },
  540. client: &staticHTTPClient{
  541. resp: http.Response{
  542. StatusCode: http.StatusTemporaryRedirect,
  543. Header: http.Header{"Location": []string{"http://example.com"}},
  544. },
  545. },
  546. wantErr: errTooManyRedirectChecks,
  547. },
  548. }
  549. for i, tt := range tests {
  550. client := &redirectFollowingHTTPClient{client: tt.client, checkRedirect: tt.checkRedirect}
  551. resp, _, err := client.Do(context.Background(), nil)
  552. if !reflect.DeepEqual(tt.wantErr, err) {
  553. t.Errorf("#%d: got err=%v, want=%v", i, err, tt.wantErr)
  554. continue
  555. }
  556. if resp == nil {
  557. if tt.wantCode != 0 {
  558. t.Errorf("#%d: resp is nil, want=%d", i, tt.wantCode)
  559. }
  560. continue
  561. }
  562. if resp.StatusCode != tt.wantCode {
  563. t.Errorf("#%d: resp code=%d, want=%d", i, resp.StatusCode, tt.wantCode)
  564. continue
  565. }
  566. }
  567. }
  568. func TestDefaultCheckRedirect(t *testing.T) {
  569. tests := []struct {
  570. num int
  571. err error
  572. }{
  573. {0, nil},
  574. {5, nil},
  575. {10, nil},
  576. {11, ErrTooManyRedirects},
  577. {29, ErrTooManyRedirects},
  578. }
  579. for i, tt := range tests {
  580. err := DefaultCheckRedirect(tt.num)
  581. if !reflect.DeepEqual(tt.err, err) {
  582. t.Errorf("#%d: want=%#v got=%#v", i, tt.err, err)
  583. }
  584. }
  585. }
  586. func TestHTTPClusterClientSync(t *testing.T) {
  587. cf := newStaticHTTPClientFactory([]staticHTTPResponse{
  588. staticHTTPResponse{
  589. resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
  590. 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"]}]}`),
  591. },
  592. })
  593. hc := &httpClusterClient{clientFactory: cf}
  594. err := hc.reset([]string{"http://127.0.0.1:2379"})
  595. if err != nil {
  596. t.Fatalf("unexpected error during setup: %#v", err)
  597. }
  598. want := []string{"http://127.0.0.1:2379"}
  599. got := hc.Endpoints()
  600. if !reflect.DeepEqual(want, got) {
  601. t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got)
  602. }
  603. err = hc.Sync(context.Background())
  604. if err != nil {
  605. t.Fatalf("unexpected error during Sync: %#v", err)
  606. }
  607. 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"}
  608. got = hc.Endpoints()
  609. if !reflect.DeepEqual(want, got) {
  610. t.Fatalf("incorrect endpoints post-Sync: want=%#v got=%#v", want, got)
  611. }
  612. err = hc.reset([]string{"http://127.0.0.1:4009"})
  613. if err != nil {
  614. t.Fatalf("unexpected error during reset: %#v", err)
  615. }
  616. want = []string{"http://127.0.0.1:4009"}
  617. got = hc.Endpoints()
  618. if !reflect.DeepEqual(want, got) {
  619. t.Fatalf("incorrect endpoints post-reset: want=%#v got=%#v", want, got)
  620. }
  621. }
  622. func TestHTTPClusterClientSyncFail(t *testing.T) {
  623. cf := newStaticHTTPClientFactory([]staticHTTPResponse{
  624. staticHTTPResponse{err: errors.New("fail!")},
  625. })
  626. hc := &httpClusterClient{clientFactory: cf}
  627. err := hc.reset([]string{"http://127.0.0.1:2379"})
  628. if err != nil {
  629. t.Fatalf("unexpected error during setup: %#v", err)
  630. }
  631. want := []string{"http://127.0.0.1:2379"}
  632. got := hc.Endpoints()
  633. if !reflect.DeepEqual(want, got) {
  634. t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got)
  635. }
  636. err = hc.Sync(context.Background())
  637. if err == nil {
  638. t.Fatalf("got nil error during Sync")
  639. }
  640. got = hc.Endpoints()
  641. if !reflect.DeepEqual(want, got) {
  642. t.Fatalf("incorrect endpoints after failed Sync: want=%#v got=%#v", want, got)
  643. }
  644. }
  645. func TestHTTPClusterClientResetFail(t *testing.T) {
  646. tests := [][]string{
  647. // need at least one endpoint
  648. []string{},
  649. // urls must be valid
  650. []string{":"},
  651. }
  652. for i, tt := range tests {
  653. hc := &httpClusterClient{}
  654. err := hc.reset(tt)
  655. if err == nil {
  656. t.Errorf("#%d: expected non-nil error", i)
  657. }
  658. }
  659. }