keys_test.go 9.3 KB

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