http_test.go 11 KB

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