keys_test.go 13 KB

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