keys_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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 := assertRequest(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 := assertRequest(got, wantURL, wantHeader, nil)
  155. if err != nil {
  156. t.Errorf("#%d: %v", i, err)
  157. }
  158. }
  159. }
  160. func TestSetAction(t *testing.T) {
  161. wantHeader := http.Header(map[string][]string{
  162. "Content-Type": []string{"application/x-www-form-urlencoded"},
  163. })
  164. tests := []struct {
  165. act setAction
  166. wantURL string
  167. wantBody string
  168. }{
  169. // default prefix
  170. {
  171. act: setAction{
  172. Prefix: DefaultV2KeysPrefix,
  173. Key: "foo",
  174. },
  175. wantURL: "http://example.com/v2/keys/foo",
  176. wantBody: "value=",
  177. },
  178. // non-default prefix
  179. {
  180. act: setAction{
  181. Prefix: "/pfx",
  182. Key: "foo",
  183. },
  184. wantURL: "http://example.com/pfx/foo",
  185. wantBody: "value=",
  186. },
  187. // no prefix
  188. {
  189. act: setAction{
  190. Key: "foo",
  191. },
  192. wantURL: "http://example.com/foo",
  193. wantBody: "value=",
  194. },
  195. // Key with path separators
  196. {
  197. act: setAction{
  198. Prefix: DefaultV2KeysPrefix,
  199. Key: "foo/bar/baz",
  200. },
  201. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  202. wantBody: "value=",
  203. },
  204. // Key with leading slash, Prefix with trailing slash
  205. {
  206. act: setAction{
  207. Prefix: "/foo/",
  208. Key: "/bar",
  209. },
  210. wantURL: "http://example.com/foo/bar",
  211. wantBody: "value=",
  212. },
  213. // Key with trailing slash
  214. {
  215. act: setAction{
  216. Key: "/foo/",
  217. },
  218. wantURL: "http://example.com/foo",
  219. wantBody: "value=",
  220. },
  221. // Value is set
  222. {
  223. act: setAction{
  224. Key: "foo",
  225. Value: "baz",
  226. },
  227. wantURL: "http://example.com/foo",
  228. wantBody: "value=baz",
  229. },
  230. // PrevExist set, but still ignored
  231. {
  232. act: setAction{
  233. Key: "foo",
  234. Options: SetOptions{
  235. PrevExist: PrevIgnore,
  236. },
  237. },
  238. wantURL: "http://example.com/foo",
  239. wantBody: "value=",
  240. },
  241. // PrevExist set to true
  242. {
  243. act: setAction{
  244. Key: "foo",
  245. Options: SetOptions{
  246. PrevExist: PrevExist,
  247. },
  248. },
  249. wantURL: "http://example.com/foo?prevExist=true",
  250. wantBody: "value=",
  251. },
  252. // PrevExist set to false
  253. {
  254. act: setAction{
  255. Key: "foo",
  256. Options: SetOptions{
  257. PrevExist: PrevNoExist,
  258. },
  259. },
  260. wantURL: "http://example.com/foo?prevExist=false",
  261. wantBody: "value=",
  262. },
  263. }
  264. for i, tt := range tests {
  265. u, err := url.Parse(tt.wantURL)
  266. if err != nil {
  267. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  268. }
  269. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  270. if err := assertRequest(*got, u, wantHeader, []byte(tt.wantBody)); err != nil {
  271. t.Errorf("#%d: %v", i, err)
  272. }
  273. }
  274. }
  275. func assertRequest(got http.Request, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  276. if !reflect.DeepEqual(wantURL, got.URL) {
  277. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  278. }
  279. if !reflect.DeepEqual(wantHeader, got.Header) {
  280. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  281. }
  282. if got.Body == nil {
  283. if wantBody != nil {
  284. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  285. }
  286. } else {
  287. if wantBody == nil {
  288. return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
  289. } else {
  290. gotBytes, err := ioutil.ReadAll(got.Body)
  291. if err != nil {
  292. return err
  293. }
  294. if !reflect.DeepEqual(wantBody, gotBytes) {
  295. return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
  296. }
  297. }
  298. }
  299. return nil
  300. }
  301. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  302. tests := []struct {
  303. indexHeader string
  304. body string
  305. res *Response
  306. expectError bool
  307. }{
  308. // Neither PrevNode or Node
  309. {
  310. "1",
  311. `{"action":"delete"}`,
  312. &Response{Action: "delete", Index: 1},
  313. false,
  314. },
  315. // PrevNode
  316. {
  317. "15",
  318. `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  319. &Response{Action: "delete", Index: 15, PrevNode: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  320. false,
  321. },
  322. // Node
  323. {
  324. "15",
  325. `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  326. &Response{Action: "get", Index: 15, Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  327. false,
  328. },
  329. // PrevNode and Node
  330. {
  331. "15",
  332. `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  333. &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}},
  334. false,
  335. },
  336. // Garbage in body
  337. {
  338. "",
  339. `garbage`,
  340. nil,
  341. true,
  342. },
  343. }
  344. for i, tt := range tests {
  345. h := make(http.Header)
  346. h.Add("X-Etcd-Index", tt.indexHeader)
  347. res, err := unmarshalSuccessfulResponse(h, []byte(tt.body))
  348. if tt.expectError != (err != nil) {
  349. t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err)
  350. }
  351. if (res == nil) != (tt.res == nil) {
  352. t.Errorf("#%d: received res==%v, but expected res==%v", i, res, tt.res)
  353. continue
  354. } else if tt.res == nil {
  355. // expected and successfully got nil response
  356. continue
  357. }
  358. if res.Action != tt.res.Action {
  359. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.res.Action)
  360. }
  361. if res.Index != tt.res.Index {
  362. t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.res.Index)
  363. }
  364. if !reflect.DeepEqual(res.Node, tt.res.Node) {
  365. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.res.Node)
  366. }
  367. }
  368. }
  369. func TestUnmarshalErrorResponse(t *testing.T) {
  370. unrecognized := errors.New("test fixture")
  371. tests := []struct {
  372. code int
  373. want error
  374. }{
  375. {http.StatusBadRequest, unrecognized},
  376. {http.StatusUnauthorized, unrecognized},
  377. {http.StatusPaymentRequired, unrecognized},
  378. {http.StatusForbidden, unrecognized},
  379. {http.StatusNotFound, ErrKeyNoExist},
  380. {http.StatusMethodNotAllowed, unrecognized},
  381. {http.StatusNotAcceptable, unrecognized},
  382. {http.StatusProxyAuthRequired, unrecognized},
  383. {http.StatusRequestTimeout, unrecognized},
  384. {http.StatusConflict, unrecognized},
  385. {http.StatusGone, unrecognized},
  386. {http.StatusLengthRequired, unrecognized},
  387. {http.StatusPreconditionFailed, ErrKeyExists},
  388. {http.StatusRequestEntityTooLarge, unrecognized},
  389. {http.StatusRequestURITooLong, unrecognized},
  390. {http.StatusUnsupportedMediaType, unrecognized},
  391. {http.StatusRequestedRangeNotSatisfiable, unrecognized},
  392. {http.StatusExpectationFailed, unrecognized},
  393. {http.StatusTeapot, unrecognized},
  394. {http.StatusInternalServerError, ErrNoLeader},
  395. {http.StatusNotImplemented, unrecognized},
  396. {http.StatusBadGateway, unrecognized},
  397. {http.StatusServiceUnavailable, unrecognized},
  398. {http.StatusGatewayTimeout, ErrTimeout},
  399. {http.StatusHTTPVersionNotSupported, unrecognized},
  400. }
  401. for i, tt := range tests {
  402. want := tt.want
  403. if reflect.DeepEqual(unrecognized, want) {
  404. want = fmt.Errorf("unrecognized HTTP status code %d", tt.code)
  405. }
  406. got := unmarshalErrorResponse(tt.code)
  407. if !reflect.DeepEqual(want, got) {
  408. t.Errorf("#%d: want=%v, got=%v", i, want, got)
  409. }
  410. }
  411. }