client_test.go 18 KB

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