http_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /*
  2. Copyright 2014 CoreOS, Inc.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package client
  14. import (
  15. "errors"
  16. "fmt"
  17. "io/ioutil"
  18. "net/http"
  19. "net/url"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "time"
  24. "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
  25. )
  26. func TestV2URLHelper(t *testing.T) {
  27. tests := []struct {
  28. endpoint url.URL
  29. key string
  30. want url.URL
  31. }{
  32. // key is empty, no problem
  33. {
  34. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
  35. key: "",
  36. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  37. },
  38. // key is joined to path
  39. {
  40. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
  41. key: "/foo/bar",
  42. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
  43. },
  44. // Host field carries through with port
  45. {
  46. endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: ""},
  47. key: "",
  48. want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
  49. },
  50. // Scheme carries through
  51. {
  52. endpoint: url.URL{Scheme: "https", Host: "example.com", Path: ""},
  53. key: "",
  54. want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
  55. },
  56. // Path on endpoint is not ignored
  57. {
  58. endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/prefix"},
  59. key: "/foo",
  60. want: url.URL{Scheme: "https", Host: "example.com", Path: "/prefix/v2/keys/foo"},
  61. },
  62. }
  63. for i, tt := range tests {
  64. got := v2KeysURL(tt.endpoint, tt.key)
  65. if tt.want != *got {
  66. t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
  67. }
  68. }
  69. }
  70. func TestGetAction(t *testing.T) {
  71. ep := url.URL{Scheme: "http", Host: "example.com"}
  72. wantURL := &url.URL{
  73. Scheme: "http",
  74. Host: "example.com",
  75. Path: "/v2/keys/foo/bar",
  76. }
  77. wantHeader := http.Header{}
  78. tests := []struct {
  79. recursive bool
  80. wantQuery string
  81. }{
  82. {
  83. recursive: false,
  84. wantQuery: "recursive=false",
  85. },
  86. {
  87. recursive: true,
  88. wantQuery: "recursive=true",
  89. },
  90. }
  91. for i, tt := range tests {
  92. f := getAction{
  93. Key: "/foo/bar",
  94. Recursive: tt.recursive,
  95. }
  96. got := *f.httpRequest(ep)
  97. wantURL := wantURL
  98. wantURL.RawQuery = tt.wantQuery
  99. err := assertResponse(got, wantURL, wantHeader, nil)
  100. if err != nil {
  101. t.Errorf("#%d: %v", i, err)
  102. }
  103. }
  104. }
  105. func TestWaitAction(t *testing.T) {
  106. ep := url.URL{Scheme: "http", Host: "example.com"}
  107. wantURL := &url.URL{
  108. Scheme: "http",
  109. Host: "example.com",
  110. Path: "/v2/keys/foo/bar",
  111. }
  112. wantHeader := http.Header{}
  113. tests := []struct {
  114. waitIndex uint64
  115. recursive bool
  116. wantQuery string
  117. }{
  118. {
  119. recursive: false,
  120. waitIndex: uint64(0),
  121. wantQuery: "recursive=false&wait=true&waitIndex=0",
  122. },
  123. {
  124. recursive: false,
  125. waitIndex: uint64(12),
  126. wantQuery: "recursive=false&wait=true&waitIndex=12",
  127. },
  128. {
  129. recursive: true,
  130. waitIndex: uint64(12),
  131. wantQuery: "recursive=true&wait=true&waitIndex=12",
  132. },
  133. }
  134. for i, tt := range tests {
  135. f := waitAction{
  136. Key: "/foo/bar",
  137. WaitIndex: tt.waitIndex,
  138. Recursive: tt.recursive,
  139. }
  140. got := *f.httpRequest(ep)
  141. wantURL := wantURL
  142. wantURL.RawQuery = tt.wantQuery
  143. err := assertResponse(got, wantURL, wantHeader, nil)
  144. if err != nil {
  145. t.Errorf("#%d: %v", i, err)
  146. }
  147. }
  148. }
  149. func TestCreateAction(t *testing.T) {
  150. ep := url.URL{Scheme: "http", Host: "example.com"}
  151. wantURL := &url.URL{
  152. Scheme: "http",
  153. Host: "example.com",
  154. Path: "/v2/keys/foo/bar",
  155. RawQuery: "prevExist=false",
  156. }
  157. wantHeader := http.Header(map[string][]string{
  158. "Content-Type": []string{"application/x-www-form-urlencoded"},
  159. })
  160. ttl12 := uint64(12)
  161. tests := []struct {
  162. value string
  163. ttl *uint64
  164. wantBody string
  165. }{
  166. {
  167. value: "baz",
  168. wantBody: "value=baz",
  169. },
  170. {
  171. value: "baz",
  172. ttl: &ttl12,
  173. wantBody: "ttl=12&value=baz",
  174. },
  175. }
  176. for i, tt := range tests {
  177. f := createAction{
  178. Key: "/foo/bar",
  179. Value: tt.value,
  180. TTL: tt.ttl,
  181. }
  182. got := *f.httpRequest(ep)
  183. err := assertResponse(got, wantURL, wantHeader, []byte(tt.wantBody))
  184. if err != nil {
  185. t.Errorf("#%d: %v", i, err)
  186. }
  187. }
  188. }
  189. func assertResponse(got http.Request, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  190. if !reflect.DeepEqual(wantURL, got.URL) {
  191. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  192. }
  193. if !reflect.DeepEqual(wantHeader, got.Header) {
  194. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  195. }
  196. if got.Body == nil {
  197. if wantBody != nil {
  198. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  199. }
  200. } else {
  201. if wantBody == nil {
  202. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  203. } else {
  204. gotBytes, err := ioutil.ReadAll(got.Body)
  205. if err != nil {
  206. return err
  207. }
  208. if !reflect.DeepEqual(wantBody, gotBytes) {
  209. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, gotBytes)
  210. }
  211. }
  212. }
  213. return nil
  214. }
  215. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  216. tests := []struct {
  217. body string
  218. res *Response
  219. expectError bool
  220. }{
  221. // Neither PrevNode or Node
  222. {
  223. `{"action":"delete"}`,
  224. &Response{Action: "delete"},
  225. false,
  226. },
  227. // PrevNode
  228. {
  229. `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  230. &Response{Action: "delete", PrevNode: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  231. false,
  232. },
  233. // Node
  234. {
  235. `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  236. &Response{Action: "get", Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  237. false,
  238. },
  239. // PrevNode and Node
  240. {
  241. `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  242. &Response{Action: "update", PrevNode: &Node{Key: "/foo", Value: "baz", ModifiedIndex: 10, CreatedIndex: 10}, Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  243. false,
  244. },
  245. // Garbage in body
  246. {
  247. `garbage`,
  248. nil,
  249. true,
  250. },
  251. }
  252. for i, tt := range tests {
  253. res, err := unmarshalSuccessfulResponse([]byte(tt.body))
  254. if tt.expectError != (err != nil) {
  255. t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err)
  256. }
  257. if (res == nil) != (tt.res == nil) {
  258. t.Errorf("#%d: received res==%v, but expected res==%v", i, res, tt.res)
  259. continue
  260. } else if tt.res == nil {
  261. // expected and succesfully got nil response
  262. continue
  263. }
  264. if res.Action != tt.res.Action {
  265. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.res.Action)
  266. }
  267. if !reflect.DeepEqual(res.Node, tt.res.Node) {
  268. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.res.Node)
  269. }
  270. }
  271. }
  272. func TestUnmarshalErrorResponse(t *testing.T) {
  273. unrecognized := errors.New("test fixture")
  274. tests := []struct {
  275. code int
  276. want error
  277. }{
  278. {http.StatusBadRequest, unrecognized},
  279. {http.StatusUnauthorized, unrecognized},
  280. {http.StatusPaymentRequired, unrecognized},
  281. {http.StatusForbidden, unrecognized},
  282. {http.StatusNotFound, ErrKeyNoExist},
  283. {http.StatusMethodNotAllowed, unrecognized},
  284. {http.StatusNotAcceptable, unrecognized},
  285. {http.StatusProxyAuthRequired, unrecognized},
  286. {http.StatusRequestTimeout, unrecognized},
  287. {http.StatusConflict, unrecognized},
  288. {http.StatusGone, unrecognized},
  289. {http.StatusLengthRequired, unrecognized},
  290. {http.StatusPreconditionFailed, ErrKeyExists},
  291. {http.StatusRequestEntityTooLarge, unrecognized},
  292. {http.StatusRequestURITooLong, unrecognized},
  293. {http.StatusUnsupportedMediaType, unrecognized},
  294. {http.StatusRequestedRangeNotSatisfiable, unrecognized},
  295. {http.StatusExpectationFailed, unrecognized},
  296. {http.StatusTeapot, unrecognized},
  297. {http.StatusInternalServerError, ErrNoLeader},
  298. {http.StatusNotImplemented, unrecognized},
  299. {http.StatusBadGateway, unrecognized},
  300. {http.StatusServiceUnavailable, unrecognized},
  301. {http.StatusGatewayTimeout, unrecognized},
  302. {http.StatusHTTPVersionNotSupported, unrecognized},
  303. }
  304. for i, tt := range tests {
  305. want := tt.want
  306. if reflect.DeepEqual(unrecognized, want) {
  307. want = fmt.Errorf("unrecognized HTTP status code %d", tt.code)
  308. }
  309. got := unmarshalErrorResponse(tt.code)
  310. if !reflect.DeepEqual(want, got) {
  311. t.Errorf("#%d: want=%v, got=%v", i, want, got)
  312. }
  313. }
  314. }
  315. type fakeTransport struct {
  316. respchan chan *http.Response
  317. errchan chan error
  318. startCancel chan struct{}
  319. finishCancel chan struct{}
  320. }
  321. func newFakeTransport() *fakeTransport {
  322. return &fakeTransport{
  323. respchan: make(chan *http.Response, 1),
  324. errchan: make(chan error, 1),
  325. startCancel: make(chan struct{}, 1),
  326. finishCancel: make(chan struct{}, 1),
  327. }
  328. }
  329. func (t *fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
  330. select {
  331. case resp := <-t.respchan:
  332. return resp, nil
  333. case err := <-t.errchan:
  334. return nil, err
  335. case <-t.startCancel:
  336. // wait on finishCancel to simulate taking some amount of
  337. // time while calling CancelRequest
  338. <-t.finishCancel
  339. return nil, errors.New("cancelled")
  340. }
  341. }
  342. func (t *fakeTransport) CancelRequest(*http.Request) {
  343. t.startCancel <- struct{}{}
  344. }
  345. type fakeAction struct{}
  346. func (a *fakeAction) httpRequest(url.URL) *http.Request {
  347. return &http.Request{}
  348. }
  349. func TestHTTPClientDoSuccess(t *testing.T) {
  350. tr := newFakeTransport()
  351. c := &httpClient{transport: tr}
  352. tr.respchan <- &http.Response{
  353. StatusCode: http.StatusTeapot,
  354. Body: ioutil.NopCloser(strings.NewReader("foo")),
  355. }
  356. resp, body, err := c.do(context.Background(), &fakeAction{})
  357. if err != nil {
  358. t.Fatalf("incorrect error value: want=nil got=%v", err)
  359. }
  360. wantCode := http.StatusTeapot
  361. if wantCode != resp.StatusCode {
  362. t.Fatalf("invalid response code: want=%d got=%d", wantCode, resp.StatusCode)
  363. }
  364. wantBody := []byte("foo")
  365. if !reflect.DeepEqual(wantBody, body) {
  366. t.Fatalf("invalid response body: want=%q got=%q", wantBody, body)
  367. }
  368. }
  369. func TestHTTPClientDoError(t *testing.T) {
  370. tr := newFakeTransport()
  371. c := &httpClient{transport: tr}
  372. tr.errchan <- errors.New("fixture")
  373. _, _, err := c.do(context.Background(), &fakeAction{})
  374. if err == nil {
  375. t.Fatalf("expected non-nil error, got nil")
  376. }
  377. }
  378. func TestHTTPClientDoCancelContext(t *testing.T) {
  379. tr := newFakeTransport()
  380. c := &httpClient{transport: tr}
  381. tr.startCancel <- struct{}{}
  382. tr.finishCancel <- struct{}{}
  383. _, _, err := c.do(context.Background(), &fakeAction{})
  384. if err == nil {
  385. t.Fatalf("expected non-nil error, got nil")
  386. }
  387. }
  388. func TestHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
  389. tr := newFakeTransport()
  390. c := &httpClient{transport: tr}
  391. donechan := make(chan struct{})
  392. ctx, cancel := context.WithCancel(context.Background())
  393. go func() {
  394. c.do(ctx, &fakeAction{})
  395. close(donechan)
  396. }()
  397. // This should call CancelRequest and begin the cancellation process
  398. cancel()
  399. select {
  400. case <-donechan:
  401. t.Fatalf("httpClient.do should not have exited yet")
  402. default:
  403. }
  404. tr.finishCancel <- struct{}{}
  405. select {
  406. case <-donechan:
  407. //expected behavior
  408. return
  409. case <-time.After(time.Second):
  410. t.Fatalf("httpClient.do did not exit within 1s")
  411. }
  412. }