keys_test.go 9.1 KB

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