keys_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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. "fmt"
  17. "io/ioutil"
  18. "net/http"
  19. "net/url"
  20. "reflect"
  21. "testing"
  22. "time"
  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. sorted bool
  92. wantQuery string
  93. }{
  94. {
  95. recursive: false,
  96. sorted: false,
  97. wantQuery: "recursive=false&sorted=false",
  98. },
  99. {
  100. recursive: true,
  101. sorted: false,
  102. wantQuery: "recursive=true&sorted=false",
  103. },
  104. {
  105. recursive: false,
  106. sorted: true,
  107. wantQuery: "recursive=false&sorted=true",
  108. },
  109. {
  110. recursive: true,
  111. sorted: true,
  112. wantQuery: "recursive=true&sorted=true",
  113. },
  114. }
  115. for i, tt := range tests {
  116. f := getAction{
  117. Key: "/foo/bar",
  118. Recursive: tt.recursive,
  119. Sorted: tt.sorted,
  120. }
  121. got := *f.HTTPRequest(ep)
  122. wantURL := baseWantURL
  123. wantURL.RawQuery = tt.wantQuery
  124. err := assertRequest(got, "GET", wantURL, wantHeader, nil)
  125. if err != nil {
  126. t.Errorf("#%d: %v", i, err)
  127. }
  128. }
  129. }
  130. func TestWaitAction(t *testing.T) {
  131. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  132. baseWantURL := &url.URL{
  133. Scheme: "http",
  134. Host: "example.com",
  135. Path: "/v2/keys/foo/bar",
  136. }
  137. wantHeader := http.Header{}
  138. tests := []struct {
  139. afterIndex uint64
  140. recursive bool
  141. wantQuery string
  142. }{
  143. {
  144. recursive: false,
  145. afterIndex: uint64(0),
  146. wantQuery: "recursive=false&wait=true&waitIndex=0",
  147. },
  148. {
  149. recursive: false,
  150. afterIndex: uint64(12),
  151. wantQuery: "recursive=false&wait=true&waitIndex=12",
  152. },
  153. {
  154. recursive: true,
  155. afterIndex: uint64(12),
  156. wantQuery: "recursive=true&wait=true&waitIndex=12",
  157. },
  158. }
  159. for i, tt := range tests {
  160. f := waitAction{
  161. Key: "/foo/bar",
  162. AfterIndex: tt.afterIndex,
  163. Recursive: tt.recursive,
  164. }
  165. got := *f.HTTPRequest(ep)
  166. wantURL := baseWantURL
  167. wantURL.RawQuery = tt.wantQuery
  168. err := assertRequest(got, "GET", wantURL, wantHeader, nil)
  169. if err != nil {
  170. t.Errorf("#%d: %v", i, err)
  171. }
  172. }
  173. }
  174. func TestSetAction(t *testing.T) {
  175. wantHeader := http.Header(map[string][]string{
  176. "Content-Type": []string{"application/x-www-form-urlencoded"},
  177. })
  178. tests := []struct {
  179. act setAction
  180. wantURL string
  181. wantBody string
  182. }{
  183. // default prefix
  184. {
  185. act: setAction{
  186. Prefix: defaultV2KeysPrefix,
  187. Key: "foo",
  188. },
  189. wantURL: "http://example.com/v2/keys/foo",
  190. wantBody: "value=",
  191. },
  192. // non-default prefix
  193. {
  194. act: setAction{
  195. Prefix: "/pfx",
  196. Key: "foo",
  197. },
  198. wantURL: "http://example.com/pfx/foo",
  199. wantBody: "value=",
  200. },
  201. // no prefix
  202. {
  203. act: setAction{
  204. Key: "foo",
  205. },
  206. wantURL: "http://example.com/foo",
  207. wantBody: "value=",
  208. },
  209. // Key with path separators
  210. {
  211. act: setAction{
  212. Prefix: defaultV2KeysPrefix,
  213. Key: "foo/bar/baz",
  214. },
  215. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  216. wantBody: "value=",
  217. },
  218. // Key with leading slash, Prefix with trailing slash
  219. {
  220. act: setAction{
  221. Prefix: "/foo/",
  222. Key: "/bar",
  223. },
  224. wantURL: "http://example.com/foo/bar",
  225. wantBody: "value=",
  226. },
  227. // Key with trailing slash
  228. {
  229. act: setAction{
  230. Key: "/foo/",
  231. },
  232. wantURL: "http://example.com/foo",
  233. wantBody: "value=",
  234. },
  235. // Value is set
  236. {
  237. act: setAction{
  238. Key: "foo",
  239. Value: "baz",
  240. },
  241. wantURL: "http://example.com/foo",
  242. wantBody: "value=baz",
  243. },
  244. // PrevExist set, but still ignored
  245. {
  246. act: setAction{
  247. Key: "foo",
  248. PrevExist: PrevIgnore,
  249. },
  250. wantURL: "http://example.com/foo",
  251. wantBody: "value=",
  252. },
  253. // PrevExist set to true
  254. {
  255. act: setAction{
  256. Key: "foo",
  257. PrevExist: PrevExist,
  258. },
  259. wantURL: "http://example.com/foo?prevExist=true",
  260. wantBody: "value=",
  261. },
  262. // PrevExist set to false
  263. {
  264. act: setAction{
  265. Key: "foo",
  266. PrevExist: PrevNoExist,
  267. },
  268. wantURL: "http://example.com/foo?prevExist=false",
  269. wantBody: "value=",
  270. },
  271. // PrevValue is urlencoded
  272. {
  273. act: setAction{
  274. Key: "foo",
  275. PrevValue: "bar baz",
  276. },
  277. wantURL: "http://example.com/foo?prevValue=bar+baz",
  278. wantBody: "value=",
  279. },
  280. // PrevIndex is set
  281. {
  282. act: setAction{
  283. Key: "foo",
  284. PrevIndex: uint64(12),
  285. },
  286. wantURL: "http://example.com/foo?prevIndex=12",
  287. wantBody: "value=",
  288. },
  289. // TTL is set
  290. {
  291. act: setAction{
  292. Key: "foo",
  293. TTL: 3 * time.Minute,
  294. },
  295. wantURL: "http://example.com/foo",
  296. wantBody: "ttl=180&value=",
  297. },
  298. }
  299. for i, tt := range tests {
  300. u, err := url.Parse(tt.wantURL)
  301. if err != nil {
  302. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  303. }
  304. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  305. if err := assertRequest(*got, "PUT", u, wantHeader, []byte(tt.wantBody)); err != nil {
  306. t.Errorf("#%d: %v", i, err)
  307. }
  308. }
  309. }
  310. func TestDeleteAction(t *testing.T) {
  311. wantHeader := http.Header(map[string][]string{
  312. "Content-Type": []string{"application/x-www-form-urlencoded"},
  313. })
  314. tests := []struct {
  315. act deleteAction
  316. wantURL string
  317. }{
  318. // default prefix
  319. {
  320. act: deleteAction{
  321. Prefix: defaultV2KeysPrefix,
  322. Key: "foo",
  323. },
  324. wantURL: "http://example.com/v2/keys/foo",
  325. },
  326. // non-default prefix
  327. {
  328. act: deleteAction{
  329. Prefix: "/pfx",
  330. Key: "foo",
  331. },
  332. wantURL: "http://example.com/pfx/foo",
  333. },
  334. // no prefix
  335. {
  336. act: deleteAction{
  337. Key: "foo",
  338. },
  339. wantURL: "http://example.com/foo",
  340. },
  341. // Key with path separators
  342. {
  343. act: deleteAction{
  344. Prefix: defaultV2KeysPrefix,
  345. Key: "foo/bar/baz",
  346. },
  347. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  348. },
  349. // Key with leading slash, Prefix with trailing slash
  350. {
  351. act: deleteAction{
  352. Prefix: "/foo/",
  353. Key: "/bar",
  354. },
  355. wantURL: "http://example.com/foo/bar",
  356. },
  357. // Key with trailing slash
  358. {
  359. act: deleteAction{
  360. Key: "/foo/",
  361. },
  362. wantURL: "http://example.com/foo",
  363. },
  364. // Recursive set to true
  365. {
  366. act: deleteAction{
  367. Key: "foo",
  368. Recursive: true,
  369. },
  370. wantURL: "http://example.com/foo?recursive=true",
  371. },
  372. // PrevValue is urlencoded
  373. {
  374. act: deleteAction{
  375. Key: "foo",
  376. PrevValue: "bar baz",
  377. },
  378. wantURL: "http://example.com/foo?prevValue=bar+baz",
  379. },
  380. // PrevIndex is set
  381. {
  382. act: deleteAction{
  383. Key: "foo",
  384. PrevIndex: uint64(12),
  385. },
  386. wantURL: "http://example.com/foo?prevIndex=12",
  387. },
  388. }
  389. for i, tt := range tests {
  390. u, err := url.Parse(tt.wantURL)
  391. if err != nil {
  392. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  393. }
  394. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  395. if err := assertRequest(*got, "DELETE", u, wantHeader, nil); err != nil {
  396. t.Errorf("#%d: %v", i, err)
  397. }
  398. }
  399. }
  400. func assertRequest(got http.Request, wantMethod string, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  401. if wantMethod != got.Method {
  402. return fmt.Errorf("want.Method=%#v got.Method=%#v", wantMethod, got.Method)
  403. }
  404. if !reflect.DeepEqual(wantURL, got.URL) {
  405. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  406. }
  407. if !reflect.DeepEqual(wantHeader, got.Header) {
  408. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  409. }
  410. if got.Body == nil {
  411. if wantBody != nil {
  412. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  413. }
  414. } else {
  415. if wantBody == nil {
  416. return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
  417. } else {
  418. gotBytes, err := ioutil.ReadAll(got.Body)
  419. if err != nil {
  420. return err
  421. }
  422. if !reflect.DeepEqual(wantBody, gotBytes) {
  423. return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
  424. }
  425. }
  426. }
  427. return nil
  428. }
  429. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  430. tests := []struct {
  431. hdr string
  432. body string
  433. wantRes *Response
  434. wantErr bool
  435. }{
  436. // Neither PrevNode or Node
  437. {
  438. hdr: "1",
  439. body: `{"action":"delete"}`,
  440. wantRes: &Response{Action: "delete", Index: 1},
  441. wantErr: false,
  442. },
  443. // PrevNode
  444. {
  445. hdr: "15",
  446. body: `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  447. wantRes: &Response{
  448. Action: "delete",
  449. Index: 15,
  450. Node: nil,
  451. PrevNode: &Node{
  452. Key: "/foo",
  453. Value: "bar",
  454. ModifiedIndex: 12,
  455. CreatedIndex: 10,
  456. },
  457. },
  458. wantErr: false,
  459. },
  460. // Node
  461. {
  462. hdr: "15",
  463. body: `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  464. wantRes: &Response{
  465. Action: "get",
  466. Index: 15,
  467. Node: &Node{
  468. Key: "/foo",
  469. Value: "bar",
  470. ModifiedIndex: 12,
  471. CreatedIndex: 10,
  472. },
  473. PrevNode: nil,
  474. },
  475. wantErr: false,
  476. },
  477. // PrevNode and Node
  478. {
  479. hdr: "15",
  480. body: `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  481. wantRes: &Response{
  482. Action: "update",
  483. Index: 15,
  484. PrevNode: &Node{
  485. Key: "/foo",
  486. Value: "baz",
  487. ModifiedIndex: 10,
  488. CreatedIndex: 10,
  489. },
  490. Node: &Node{
  491. Key: "/foo",
  492. Value: "bar",
  493. ModifiedIndex: 12,
  494. CreatedIndex: 10,
  495. },
  496. },
  497. wantErr: false,
  498. },
  499. // Garbage in body
  500. {
  501. hdr: "",
  502. body: `garbage`,
  503. wantRes: nil,
  504. wantErr: true,
  505. },
  506. // non-integer index
  507. {
  508. hdr: "poo",
  509. body: `{}`,
  510. wantRes: nil,
  511. wantErr: true,
  512. },
  513. }
  514. for i, tt := range tests {
  515. h := make(http.Header)
  516. h.Add("X-Etcd-Index", tt.hdr)
  517. res, err := unmarshalSuccessfulKeysResponse(h, []byte(tt.body))
  518. if tt.wantErr != (err != nil) {
  519. t.Errorf("#%d: wantErr=%t, err=%v", i, tt.wantErr, err)
  520. }
  521. if (res == nil) != (tt.wantRes == nil) {
  522. t.Errorf("#%d: received res=%#v, but expected res=%#v", i, res, tt.wantRes)
  523. continue
  524. } else if tt.wantRes == nil {
  525. // expected and successfully got nil response
  526. continue
  527. }
  528. if res.Action != tt.wantRes.Action {
  529. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.wantRes.Action)
  530. }
  531. if res.Index != tt.wantRes.Index {
  532. t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.wantRes.Index)
  533. }
  534. if !reflect.DeepEqual(res.Node, tt.wantRes.Node) {
  535. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.wantRes.Node)
  536. }
  537. }
  538. }
  539. func TestUnmarshalFailedKeysResponse(t *testing.T) {
  540. body := []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`)
  541. wantErr := Error{
  542. Code: 100,
  543. Message: "Key not found",
  544. Cause: "/foo",
  545. Index: uint64(18),
  546. }
  547. gotErr := unmarshalFailedKeysResponse(body)
  548. if !reflect.DeepEqual(wantErr, gotErr) {
  549. t.Errorf("unexpected error: want=%#v got=%#v", wantErr, gotErr)
  550. }
  551. }
  552. func TestUnmarshalFailedKeysResponseBadJSON(t *testing.T) {
  553. err := unmarshalFailedKeysResponse([]byte(`{"er`))
  554. if err == nil {
  555. t.Errorf("got nil error")
  556. } else if _, ok := err.(Error); ok {
  557. t.Errorf("error is of incorrect type *Error: %#v", err)
  558. }
  559. }