keys_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. "testing"
  22. )
  23. func TestV2KeysURLHelper(t *testing.T) {
  24. tests := []struct {
  25. endpoint url.URL
  26. key string
  27. want url.URL
  28. }{
  29. // key is empty, no problem
  30. {
  31. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  32. key: "",
  33. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  34. },
  35. // key is joined to path
  36. {
  37. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  38. key: "/foo/bar",
  39. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
  40. },
  41. // key is joined to path when path is empty
  42. {
  43. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
  44. key: "/foo/bar",
  45. want: url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
  46. },
  47. // Host field carries through with port
  48. {
  49. endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
  50. key: "",
  51. want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
  52. },
  53. // Scheme carries through
  54. {
  55. endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
  56. key: "",
  57. want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
  58. },
  59. }
  60. for i, tt := range tests {
  61. got := v2KeysURL(tt.endpoint, tt.key)
  62. if tt.want != *got {
  63. t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
  64. }
  65. }
  66. }
  67. func TestGetAction(t *testing.T) {
  68. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  69. wantURL := &url.URL{
  70. Scheme: "http",
  71. Host: "example.com",
  72. Path: "/v2/keys/foo/bar",
  73. }
  74. wantHeader := http.Header{}
  75. tests := []struct {
  76. recursive bool
  77. wantQuery string
  78. }{
  79. {
  80. recursive: false,
  81. wantQuery: "recursive=false",
  82. },
  83. {
  84. recursive: true,
  85. wantQuery: "recursive=true",
  86. },
  87. }
  88. for i, tt := range tests {
  89. f := getAction{
  90. Key: "/foo/bar",
  91. Recursive: tt.recursive,
  92. }
  93. got := *f.httpRequest(ep)
  94. wantURL := wantURL
  95. wantURL.RawQuery = tt.wantQuery
  96. err := assertResponse(got, wantURL, wantHeader, nil)
  97. if err != nil {
  98. t.Errorf("#%d: %v", i, err)
  99. }
  100. }
  101. }
  102. func TestWaitAction(t *testing.T) {
  103. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  104. wantURL := &url.URL{
  105. Scheme: "http",
  106. Host: "example.com",
  107. Path: "/v2/keys/foo/bar",
  108. }
  109. wantHeader := http.Header{}
  110. tests := []struct {
  111. waitIndex uint64
  112. recursive bool
  113. wantQuery string
  114. }{
  115. {
  116. recursive: false,
  117. waitIndex: uint64(0),
  118. wantQuery: "recursive=false&wait=true&waitIndex=0",
  119. },
  120. {
  121. recursive: false,
  122. waitIndex: uint64(12),
  123. wantQuery: "recursive=false&wait=true&waitIndex=12",
  124. },
  125. {
  126. recursive: true,
  127. waitIndex: uint64(12),
  128. wantQuery: "recursive=true&wait=true&waitIndex=12",
  129. },
  130. }
  131. for i, tt := range tests {
  132. f := waitAction{
  133. Key: "/foo/bar",
  134. WaitIndex: tt.waitIndex,
  135. Recursive: tt.recursive,
  136. }
  137. got := *f.httpRequest(ep)
  138. wantURL := wantURL
  139. wantURL.RawQuery = tt.wantQuery
  140. err := assertResponse(got, wantURL, wantHeader, nil)
  141. if err != nil {
  142. t.Errorf("#%d: %v", i, err)
  143. }
  144. }
  145. }
  146. func TestCreateAction(t *testing.T) {
  147. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  148. wantURL := &url.URL{
  149. Scheme: "http",
  150. Host: "example.com",
  151. Path: "/v2/keys/foo/bar",
  152. RawQuery: "prevExist=false",
  153. }
  154. wantHeader := http.Header(map[string][]string{
  155. "Content-Type": []string{"application/x-www-form-urlencoded"},
  156. })
  157. ttl12 := uint64(12)
  158. tests := []struct {
  159. value string
  160. ttl *uint64
  161. wantBody string
  162. }{
  163. {
  164. value: "baz",
  165. wantBody: "value=baz",
  166. },
  167. {
  168. value: "baz",
  169. ttl: &ttl12,
  170. wantBody: "ttl=12&value=baz",
  171. },
  172. }
  173. for i, tt := range tests {
  174. f := createAction{
  175. Key: "/foo/bar",
  176. Value: tt.value,
  177. TTL: tt.ttl,
  178. }
  179. got := *f.httpRequest(ep)
  180. err := assertResponse(got, wantURL, wantHeader, []byte(tt.wantBody))
  181. if err != nil {
  182. t.Errorf("#%d: %v", i, err)
  183. }
  184. }
  185. }
  186. func assertResponse(got http.Request, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  187. if !reflect.DeepEqual(wantURL, got.URL) {
  188. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  189. }
  190. if !reflect.DeepEqual(wantHeader, got.Header) {
  191. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  192. }
  193. if got.Body == nil {
  194. if wantBody != nil {
  195. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  196. }
  197. } else {
  198. if wantBody == nil {
  199. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  200. } else {
  201. gotBytes, err := ioutil.ReadAll(got.Body)
  202. if err != nil {
  203. return err
  204. }
  205. if !reflect.DeepEqual(wantBody, gotBytes) {
  206. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, gotBytes)
  207. }
  208. }
  209. }
  210. return nil
  211. }
  212. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  213. tests := []struct {
  214. body string
  215. res *Response
  216. expectError bool
  217. }{
  218. // Neither PrevNode or Node
  219. {
  220. `{"action":"delete"}`,
  221. &Response{Action: "delete"},
  222. false,
  223. },
  224. // PrevNode
  225. {
  226. `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  227. &Response{Action: "delete", PrevNode: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  228. false,
  229. },
  230. // Node
  231. {
  232. `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  233. &Response{Action: "get", Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  234. false,
  235. },
  236. // PrevNode and Node
  237. {
  238. `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  239. &Response{Action: "update", PrevNode: &Node{Key: "/foo", Value: "baz", ModifiedIndex: 10, CreatedIndex: 10}, Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  240. false,
  241. },
  242. // Garbage in body
  243. {
  244. `garbage`,
  245. nil,
  246. true,
  247. },
  248. }
  249. for i, tt := range tests {
  250. res, err := unmarshalSuccessfulResponse([]byte(tt.body))
  251. if tt.expectError != (err != nil) {
  252. t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err)
  253. }
  254. if (res == nil) != (tt.res == nil) {
  255. t.Errorf("#%d: received res==%v, but expected res==%v", i, res, tt.res)
  256. continue
  257. } else if tt.res == nil {
  258. // expected and succesfully got nil response
  259. continue
  260. }
  261. if res.Action != tt.res.Action {
  262. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.res.Action)
  263. }
  264. if !reflect.DeepEqual(res.Node, tt.res.Node) {
  265. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.res.Node)
  266. }
  267. }
  268. }
  269. func TestUnmarshalErrorResponse(t *testing.T) {
  270. unrecognized := errors.New("test fixture")
  271. tests := []struct {
  272. code int
  273. want error
  274. }{
  275. {http.StatusBadRequest, unrecognized},
  276. {http.StatusUnauthorized, unrecognized},
  277. {http.StatusPaymentRequired, unrecognized},
  278. {http.StatusForbidden, unrecognized},
  279. {http.StatusNotFound, ErrKeyNoExist},
  280. {http.StatusMethodNotAllowed, unrecognized},
  281. {http.StatusNotAcceptable, unrecognized},
  282. {http.StatusProxyAuthRequired, unrecognized},
  283. {http.StatusRequestTimeout, unrecognized},
  284. {http.StatusConflict, unrecognized},
  285. {http.StatusGone, unrecognized},
  286. {http.StatusLengthRequired, unrecognized},
  287. {http.StatusPreconditionFailed, ErrKeyExists},
  288. {http.StatusRequestEntityTooLarge, unrecognized},
  289. {http.StatusRequestURITooLong, unrecognized},
  290. {http.StatusUnsupportedMediaType, unrecognized},
  291. {http.StatusRequestedRangeNotSatisfiable, unrecognized},
  292. {http.StatusExpectationFailed, unrecognized},
  293. {http.StatusTeapot, unrecognized},
  294. {http.StatusInternalServerError, ErrNoLeader},
  295. {http.StatusNotImplemented, unrecognized},
  296. {http.StatusBadGateway, unrecognized},
  297. {http.StatusServiceUnavailable, unrecognized},
  298. {http.StatusGatewayTimeout, unrecognized},
  299. {http.StatusHTTPVersionNotSupported, unrecognized},
  300. }
  301. for i, tt := range tests {
  302. want := tt.want
  303. if reflect.DeepEqual(unrecognized, want) {
  304. want = fmt.Errorf("unrecognized HTTP status code %d", tt.code)
  305. }
  306. got := unmarshalErrorResponse(tt.code)
  307. if !reflect.DeepEqual(want, got) {
  308. t.Errorf("#%d: want=%v, got=%v", i, want, got)
  309. }
  310. }
  311. }