keys_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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, "GET", 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, "GET", 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, "PUT", u, wantHeader, []byte(tt.wantBody)); err != nil {
  271. t.Errorf("#%d: %v", i, err)
  272. }
  273. }
  274. }
  275. func TestDeleteAction(t *testing.T) {
  276. wantHeader := http.Header(map[string][]string{
  277. "Content-Type": []string{"application/x-www-form-urlencoded"},
  278. })
  279. tests := []struct {
  280. act deleteAction
  281. wantURL string
  282. }{
  283. // default prefix
  284. {
  285. act: deleteAction{
  286. Prefix: DefaultV2KeysPrefix,
  287. Key: "foo",
  288. },
  289. wantURL: "http://example.com/v2/keys/foo",
  290. },
  291. // non-default prefix
  292. {
  293. act: deleteAction{
  294. Prefix: "/pfx",
  295. Key: "foo",
  296. },
  297. wantURL: "http://example.com/pfx/foo",
  298. },
  299. // no prefix
  300. {
  301. act: deleteAction{
  302. Key: "foo",
  303. },
  304. wantURL: "http://example.com/foo",
  305. },
  306. // Key with path separators
  307. {
  308. act: deleteAction{
  309. Prefix: DefaultV2KeysPrefix,
  310. Key: "foo/bar/baz",
  311. },
  312. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  313. },
  314. // Key with leading slash, Prefix with trailing slash
  315. {
  316. act: deleteAction{
  317. Prefix: "/foo/",
  318. Key: "/bar",
  319. },
  320. wantURL: "http://example.com/foo/bar",
  321. },
  322. // Key with trailing slash
  323. {
  324. act: deleteAction{
  325. Key: "/foo/",
  326. },
  327. wantURL: "http://example.com/foo",
  328. },
  329. // Recursive set to true
  330. {
  331. act: deleteAction{
  332. Key: "foo",
  333. Options: DeleteOptions{
  334. Recursive: true,
  335. },
  336. },
  337. wantURL: "http://example.com/foo?recursive=true",
  338. },
  339. }
  340. for i, tt := range tests {
  341. u, err := url.Parse(tt.wantURL)
  342. if err != nil {
  343. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  344. }
  345. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  346. if err := assertRequest(*got, "DELETE", u, wantHeader, nil); err != nil {
  347. t.Errorf("#%d: %v", i, err)
  348. }
  349. }
  350. }
  351. func assertRequest(got http.Request, wantMethod string, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  352. if wantMethod != got.Method {
  353. return fmt.Errorf("want.Method=%#v got.Method=%#v", wantMethod, got.Method)
  354. }
  355. if !reflect.DeepEqual(wantURL, got.URL) {
  356. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  357. }
  358. if !reflect.DeepEqual(wantHeader, got.Header) {
  359. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  360. }
  361. if got.Body == nil {
  362. if wantBody != nil {
  363. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  364. }
  365. } else {
  366. if wantBody == nil {
  367. return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
  368. } else {
  369. gotBytes, err := ioutil.ReadAll(got.Body)
  370. if err != nil {
  371. return err
  372. }
  373. if !reflect.DeepEqual(wantBody, gotBytes) {
  374. return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
  375. }
  376. }
  377. }
  378. return nil
  379. }
  380. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  381. tests := []struct {
  382. indexHeader string
  383. body string
  384. res *Response
  385. expectError bool
  386. }{
  387. // Neither PrevNode or Node
  388. {
  389. "1",
  390. `{"action":"delete"}`,
  391. &Response{Action: "delete", Index: 1},
  392. false,
  393. },
  394. // PrevNode
  395. {
  396. "15",
  397. `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  398. &Response{Action: "delete", Index: 15, PrevNode: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  399. false,
  400. },
  401. // Node
  402. {
  403. "15",
  404. `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  405. &Response{Action: "get", Index: 15, Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  406. false,
  407. },
  408. // PrevNode and Node
  409. {
  410. "15",
  411. `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  412. &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}},
  413. false,
  414. },
  415. // Garbage in body
  416. {
  417. "",
  418. `garbage`,
  419. nil,
  420. true,
  421. },
  422. }
  423. for i, tt := range tests {
  424. h := make(http.Header)
  425. h.Add("X-Etcd-Index", tt.indexHeader)
  426. res, err := unmarshalSuccessfulResponse(h, []byte(tt.body))
  427. if tt.expectError != (err != nil) {
  428. t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err)
  429. }
  430. if (res == nil) != (tt.res == nil) {
  431. t.Errorf("#%d: received res==%v, but expected res==%v", i, res, tt.res)
  432. continue
  433. } else if tt.res == nil {
  434. // expected and successfully got nil response
  435. continue
  436. }
  437. if res.Action != tt.res.Action {
  438. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.res.Action)
  439. }
  440. if res.Index != tt.res.Index {
  441. t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.res.Index)
  442. }
  443. if !reflect.DeepEqual(res.Node, tt.res.Node) {
  444. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.res.Node)
  445. }
  446. }
  447. }
  448. func TestUnmarshalErrorResponse(t *testing.T) {
  449. unrecognized := errors.New("test fixture")
  450. tests := []struct {
  451. code int
  452. want error
  453. }{
  454. {http.StatusBadRequest, unrecognized},
  455. {http.StatusUnauthorized, unrecognized},
  456. {http.StatusPaymentRequired, unrecognized},
  457. {http.StatusForbidden, unrecognized},
  458. {http.StatusNotFound, ErrKeyNoExist},
  459. {http.StatusMethodNotAllowed, unrecognized},
  460. {http.StatusNotAcceptable, unrecognized},
  461. {http.StatusProxyAuthRequired, unrecognized},
  462. {http.StatusRequestTimeout, unrecognized},
  463. {http.StatusConflict, unrecognized},
  464. {http.StatusGone, unrecognized},
  465. {http.StatusLengthRequired, unrecognized},
  466. {http.StatusPreconditionFailed, ErrKeyExists},
  467. {http.StatusRequestEntityTooLarge, unrecognized},
  468. {http.StatusRequestURITooLong, unrecognized},
  469. {http.StatusUnsupportedMediaType, unrecognized},
  470. {http.StatusRequestedRangeNotSatisfiable, unrecognized},
  471. {http.StatusExpectationFailed, unrecognized},
  472. {http.StatusTeapot, unrecognized},
  473. {http.StatusInternalServerError, ErrNoLeader},
  474. {http.StatusNotImplemented, unrecognized},
  475. {http.StatusBadGateway, unrecognized},
  476. {http.StatusServiceUnavailable, unrecognized},
  477. {http.StatusGatewayTimeout, ErrTimeout},
  478. {http.StatusHTTPVersionNotSupported, unrecognized},
  479. }
  480. for i, tt := range tests {
  481. want := tt.want
  482. if reflect.DeepEqual(unrecognized, want) {
  483. want = fmt.Errorf("unrecognized HTTP status code %d", tt.code)
  484. }
  485. got := unmarshalErrorResponse(tt.code)
  486. if !reflect.DeepEqual(want, got) {
  487. t.Errorf("#%d: want=%v, got=%v", i, want, got)
  488. }
  489. }
  490. }