keys_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  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. "time"
  24. )
  25. func TestV2KeysURLHelper(t *testing.T) {
  26. tests := []struct {
  27. endpoint url.URL
  28. prefix string
  29. key string
  30. want url.URL
  31. }{
  32. // key is empty, no problem
  33. {
  34. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  35. prefix: "",
  36. key: "",
  37. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  38. },
  39. // key is joined to path
  40. {
  41. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
  42. prefix: "",
  43. key: "/foo/bar",
  44. want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
  45. },
  46. // key is joined to path when path is empty
  47. {
  48. endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
  49. prefix: "",
  50. key: "/foo/bar",
  51. want: url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
  52. },
  53. // Host field carries through with port
  54. {
  55. endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
  56. prefix: "",
  57. key: "",
  58. want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
  59. },
  60. // Scheme carries through
  61. {
  62. endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
  63. prefix: "",
  64. key: "",
  65. want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
  66. },
  67. // Prefix is applied
  68. {
  69. endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
  70. prefix: "/bar",
  71. key: "/baz",
  72. want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"},
  73. },
  74. }
  75. for i, tt := range tests {
  76. got := v2KeysURL(tt.endpoint, tt.prefix, tt.key)
  77. if tt.want != *got {
  78. t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
  79. }
  80. }
  81. }
  82. func TestGetAction(t *testing.T) {
  83. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  84. baseWantURL := &url.URL{
  85. Scheme: "http",
  86. Host: "example.com",
  87. Path: "/v2/keys/foo/bar",
  88. }
  89. wantHeader := http.Header{}
  90. tests := []struct {
  91. recursive bool
  92. wantQuery string
  93. }{
  94. {
  95. recursive: false,
  96. wantQuery: "recursive=false",
  97. },
  98. {
  99. recursive: true,
  100. wantQuery: "recursive=true",
  101. },
  102. }
  103. for i, tt := range tests {
  104. f := getAction{
  105. Key: "/foo/bar",
  106. Recursive: tt.recursive,
  107. }
  108. got := *f.HTTPRequest(ep)
  109. wantURL := baseWantURL
  110. wantURL.RawQuery = tt.wantQuery
  111. err := assertRequest(got, "GET", wantURL, wantHeader, nil)
  112. if err != nil {
  113. t.Errorf("#%d: %v", i, err)
  114. }
  115. }
  116. }
  117. func TestWaitAction(t *testing.T) {
  118. ep := url.URL{Scheme: "http", Host: "example.com/v2/keys"}
  119. baseWantURL := &url.URL{
  120. Scheme: "http",
  121. Host: "example.com",
  122. Path: "/v2/keys/foo/bar",
  123. }
  124. wantHeader := http.Header{}
  125. tests := []struct {
  126. waitIndex uint64
  127. recursive bool
  128. wantQuery string
  129. }{
  130. {
  131. recursive: false,
  132. waitIndex: uint64(0),
  133. wantQuery: "recursive=false&wait=true&waitIndex=0",
  134. },
  135. {
  136. recursive: false,
  137. waitIndex: uint64(12),
  138. wantQuery: "recursive=false&wait=true&waitIndex=12",
  139. },
  140. {
  141. recursive: true,
  142. waitIndex: uint64(12),
  143. wantQuery: "recursive=true&wait=true&waitIndex=12",
  144. },
  145. }
  146. for i, tt := range tests {
  147. f := waitAction{
  148. Key: "/foo/bar",
  149. WaitIndex: tt.waitIndex,
  150. Recursive: tt.recursive,
  151. }
  152. got := *f.HTTPRequest(ep)
  153. wantURL := baseWantURL
  154. wantURL.RawQuery = tt.wantQuery
  155. err := assertRequest(got, "GET", wantURL, wantHeader, nil)
  156. if err != nil {
  157. t.Errorf("#%d: %v", i, err)
  158. }
  159. }
  160. }
  161. func TestSetAction(t *testing.T) {
  162. wantHeader := http.Header(map[string][]string{
  163. "Content-Type": []string{"application/x-www-form-urlencoded"},
  164. })
  165. tests := []struct {
  166. act setAction
  167. wantURL string
  168. wantBody string
  169. }{
  170. // default prefix
  171. {
  172. act: setAction{
  173. Prefix: DefaultV2KeysPrefix,
  174. Key: "foo",
  175. },
  176. wantURL: "http://example.com/v2/keys/foo",
  177. wantBody: "value=",
  178. },
  179. // non-default prefix
  180. {
  181. act: setAction{
  182. Prefix: "/pfx",
  183. Key: "foo",
  184. },
  185. wantURL: "http://example.com/pfx/foo",
  186. wantBody: "value=",
  187. },
  188. // no prefix
  189. {
  190. act: setAction{
  191. Key: "foo",
  192. },
  193. wantURL: "http://example.com/foo",
  194. wantBody: "value=",
  195. },
  196. // Key with path separators
  197. {
  198. act: setAction{
  199. Prefix: DefaultV2KeysPrefix,
  200. Key: "foo/bar/baz",
  201. },
  202. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  203. wantBody: "value=",
  204. },
  205. // Key with leading slash, Prefix with trailing slash
  206. {
  207. act: setAction{
  208. Prefix: "/foo/",
  209. Key: "/bar",
  210. },
  211. wantURL: "http://example.com/foo/bar",
  212. wantBody: "value=",
  213. },
  214. // Key with trailing slash
  215. {
  216. act: setAction{
  217. Key: "/foo/",
  218. },
  219. wantURL: "http://example.com/foo",
  220. wantBody: "value=",
  221. },
  222. // Value is set
  223. {
  224. act: setAction{
  225. Key: "foo",
  226. Value: "baz",
  227. },
  228. wantURL: "http://example.com/foo",
  229. wantBody: "value=baz",
  230. },
  231. // PrevExist set, but still ignored
  232. {
  233. act: setAction{
  234. Key: "foo",
  235. PrevExist: PrevIgnore,
  236. },
  237. wantURL: "http://example.com/foo",
  238. wantBody: "value=",
  239. },
  240. // PrevExist set to true
  241. {
  242. act: setAction{
  243. Key: "foo",
  244. PrevExist: PrevExist,
  245. },
  246. wantURL: "http://example.com/foo?prevExist=true",
  247. wantBody: "value=",
  248. },
  249. // PrevExist set to false
  250. {
  251. act: setAction{
  252. Key: "foo",
  253. PrevExist: PrevNoExist,
  254. },
  255. wantURL: "http://example.com/foo?prevExist=false",
  256. wantBody: "value=",
  257. },
  258. // PrevValue is urlencoded
  259. {
  260. act: setAction{
  261. Key: "foo",
  262. PrevValue: "bar baz",
  263. },
  264. wantURL: "http://example.com/foo?prevValue=bar+baz",
  265. wantBody: "value=",
  266. },
  267. // PrevIndex is set
  268. {
  269. act: setAction{
  270. Key: "foo",
  271. PrevIndex: uint64(12),
  272. },
  273. wantURL: "http://example.com/foo?prevIndex=12",
  274. wantBody: "value=",
  275. },
  276. // TTL is set
  277. {
  278. act: setAction{
  279. Key: "foo",
  280. TTL: 3 * time.Minute,
  281. },
  282. wantURL: "http://example.com/foo",
  283. wantBody: "ttl=180&value=",
  284. },
  285. }
  286. for i, tt := range tests {
  287. u, err := url.Parse(tt.wantURL)
  288. if err != nil {
  289. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  290. }
  291. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  292. if err := assertRequest(*got, "PUT", u, wantHeader, []byte(tt.wantBody)); err != nil {
  293. t.Errorf("#%d: %v", i, err)
  294. }
  295. }
  296. }
  297. func TestDeleteAction(t *testing.T) {
  298. wantHeader := http.Header(map[string][]string{
  299. "Content-Type": []string{"application/x-www-form-urlencoded"},
  300. })
  301. tests := []struct {
  302. act deleteAction
  303. wantURL string
  304. }{
  305. // default prefix
  306. {
  307. act: deleteAction{
  308. Prefix: DefaultV2KeysPrefix,
  309. Key: "foo",
  310. },
  311. wantURL: "http://example.com/v2/keys/foo",
  312. },
  313. // non-default prefix
  314. {
  315. act: deleteAction{
  316. Prefix: "/pfx",
  317. Key: "foo",
  318. },
  319. wantURL: "http://example.com/pfx/foo",
  320. },
  321. // no prefix
  322. {
  323. act: deleteAction{
  324. Key: "foo",
  325. },
  326. wantURL: "http://example.com/foo",
  327. },
  328. // Key with path separators
  329. {
  330. act: deleteAction{
  331. Prefix: DefaultV2KeysPrefix,
  332. Key: "foo/bar/baz",
  333. },
  334. wantURL: "http://example.com/v2/keys/foo/bar/baz",
  335. },
  336. // Key with leading slash, Prefix with trailing slash
  337. {
  338. act: deleteAction{
  339. Prefix: "/foo/",
  340. Key: "/bar",
  341. },
  342. wantURL: "http://example.com/foo/bar",
  343. },
  344. // Key with trailing slash
  345. {
  346. act: deleteAction{
  347. Key: "/foo/",
  348. },
  349. wantURL: "http://example.com/foo",
  350. },
  351. // Recursive set to true
  352. {
  353. act: deleteAction{
  354. Key: "foo",
  355. Recursive: true,
  356. },
  357. wantURL: "http://example.com/foo?recursive=true",
  358. },
  359. // PrevValue is urlencoded
  360. {
  361. act: deleteAction{
  362. Key: "foo",
  363. PrevValue: "bar baz",
  364. },
  365. wantURL: "http://example.com/foo?prevValue=bar+baz",
  366. },
  367. // PrevIndex is set
  368. {
  369. act: deleteAction{
  370. Key: "foo",
  371. PrevIndex: uint64(12),
  372. },
  373. wantURL: "http://example.com/foo?prevIndex=12",
  374. },
  375. }
  376. for i, tt := range tests {
  377. u, err := url.Parse(tt.wantURL)
  378. if err != nil {
  379. t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
  380. }
  381. got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
  382. if err := assertRequest(*got, "DELETE", u, wantHeader, nil); err != nil {
  383. t.Errorf("#%d: %v", i, err)
  384. }
  385. }
  386. }
  387. func assertRequest(got http.Request, wantMethod string, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
  388. if wantMethod != got.Method {
  389. return fmt.Errorf("want.Method=%#v got.Method=%#v", wantMethod, got.Method)
  390. }
  391. if !reflect.DeepEqual(wantURL, got.URL) {
  392. return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
  393. }
  394. if !reflect.DeepEqual(wantHeader, got.Header) {
  395. return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
  396. }
  397. if got.Body == nil {
  398. if wantBody != nil {
  399. return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
  400. }
  401. } else {
  402. if wantBody == nil {
  403. return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
  404. } else {
  405. gotBytes, err := ioutil.ReadAll(got.Body)
  406. if err != nil {
  407. return err
  408. }
  409. if !reflect.DeepEqual(wantBody, gotBytes) {
  410. return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
  411. }
  412. }
  413. }
  414. return nil
  415. }
  416. func TestUnmarshalSuccessfulResponse(t *testing.T) {
  417. tests := []struct {
  418. indexHeader string
  419. body string
  420. res *Response
  421. expectError bool
  422. }{
  423. // Neither PrevNode or Node
  424. {
  425. "1",
  426. `{"action":"delete"}`,
  427. &Response{Action: "delete", Index: 1},
  428. false,
  429. },
  430. // PrevNode
  431. {
  432. "15",
  433. `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  434. &Response{Action: "delete", Index: 15, PrevNode: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  435. false,
  436. },
  437. // Node
  438. {
  439. "15",
  440. `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  441. &Response{Action: "get", Index: 15, Node: &Node{Key: "/foo", Value: "bar", ModifiedIndex: 12, CreatedIndex: 10}},
  442. false,
  443. },
  444. // PrevNode and Node
  445. {
  446. "15",
  447. `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
  448. &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}},
  449. false,
  450. },
  451. // Garbage in body
  452. {
  453. "",
  454. `garbage`,
  455. nil,
  456. true,
  457. },
  458. }
  459. for i, tt := range tests {
  460. h := make(http.Header)
  461. h.Add("X-Etcd-Index", tt.indexHeader)
  462. res, err := unmarshalSuccessfulResponse(h, []byte(tt.body))
  463. if tt.expectError != (err != nil) {
  464. t.Errorf("#%d: expectError=%t, err=%v", i, tt.expectError, err)
  465. }
  466. if (res == nil) != (tt.res == nil) {
  467. t.Errorf("#%d: received res==%v, but expected res==%v", i, res, tt.res)
  468. continue
  469. } else if tt.res == nil {
  470. // expected and successfully got nil response
  471. continue
  472. }
  473. if res.Action != tt.res.Action {
  474. t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.res.Action)
  475. }
  476. if res.Index != tt.res.Index {
  477. t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.res.Index)
  478. }
  479. if !reflect.DeepEqual(res.Node, tt.res.Node) {
  480. t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.res.Node)
  481. }
  482. }
  483. }
  484. func TestUnmarshalErrorResponse(t *testing.T) {
  485. unrecognized := errors.New("test fixture")
  486. tests := []struct {
  487. code int
  488. want error
  489. }{
  490. {http.StatusBadRequest, unrecognized},
  491. {http.StatusUnauthorized, unrecognized},
  492. {http.StatusPaymentRequired, unrecognized},
  493. {http.StatusForbidden, unrecognized},
  494. {http.StatusNotFound, ErrKeyNoExist},
  495. {http.StatusMethodNotAllowed, unrecognized},
  496. {http.StatusNotAcceptable, unrecognized},
  497. {http.StatusProxyAuthRequired, unrecognized},
  498. {http.StatusRequestTimeout, unrecognized},
  499. {http.StatusConflict, unrecognized},
  500. {http.StatusGone, unrecognized},
  501. {http.StatusLengthRequired, unrecognized},
  502. {http.StatusPreconditionFailed, ErrKeyExists},
  503. {http.StatusRequestEntityTooLarge, unrecognized},
  504. {http.StatusRequestURITooLong, unrecognized},
  505. {http.StatusUnsupportedMediaType, unrecognized},
  506. {http.StatusRequestedRangeNotSatisfiable, unrecognized},
  507. {http.StatusExpectationFailed, unrecognized},
  508. {http.StatusTeapot, unrecognized},
  509. {http.StatusInternalServerError, ErrNoLeader},
  510. {http.StatusNotImplemented, unrecognized},
  511. {http.StatusBadGateway, unrecognized},
  512. {http.StatusServiceUnavailable, unrecognized},
  513. {http.StatusGatewayTimeout, ErrTimeout},
  514. {http.StatusHTTPVersionNotSupported, unrecognized},
  515. }
  516. for i, tt := range tests {
  517. want := tt.want
  518. if reflect.DeepEqual(unrecognized, want) {
  519. want = fmt.Errorf("unrecognized HTTP status code %d", tt.code)
  520. }
  521. got := unmarshalErrorResponse(tt.code)
  522. if !reflect.DeepEqual(want, got) {
  523. t.Errorf("#%d: want=%v, got=%v", i, want, got)
  524. }
  525. }
  526. }