keys_test.go 13 KB

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