handlers_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. // Copyright (c) 2014 Couchbase, 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 http
  15. import (
  16. "bytes"
  17. "io/ioutil"
  18. "net/http"
  19. "net/http/httptest"
  20. "net/url"
  21. "os"
  22. "reflect"
  23. "testing"
  24. )
  25. func docIDLookup(req *http.Request) string {
  26. return req.FormValue("docID")
  27. }
  28. func indexNameLookup(req *http.Request) string {
  29. return req.FormValue("indexName")
  30. }
  31. func TestHandlers(t *testing.T) {
  32. basePath := "testbase"
  33. err := os.MkdirAll(basePath, 0700)
  34. if err != nil {
  35. t.Fatal(err)
  36. }
  37. defer func() {
  38. err := os.RemoveAll(basePath)
  39. if err != nil {
  40. t.Fatal(err)
  41. }
  42. }()
  43. createIndexHandler := NewCreateIndexHandler(basePath)
  44. createIndexHandler.IndexNameLookup = indexNameLookup
  45. getIndexHandler := NewGetIndexHandler()
  46. getIndexHandler.IndexNameLookup = indexNameLookup
  47. deleteIndexHandler := NewDeleteIndexHandler(basePath)
  48. deleteIndexHandler.IndexNameLookup = indexNameLookup
  49. listIndexesHandler := NewListIndexesHandler()
  50. docIndexHandler := NewDocIndexHandler("")
  51. docIndexHandler.IndexNameLookup = indexNameLookup
  52. docIndexHandler.DocIDLookup = docIDLookup
  53. docCountHandler := NewDocCountHandler("")
  54. docCountHandler.IndexNameLookup = indexNameLookup
  55. docGetHandler := NewDocGetHandler("")
  56. docGetHandler.IndexNameLookup = indexNameLookup
  57. docGetHandler.DocIDLookup = docIDLookup
  58. docDeleteHandler := NewDocDeleteHandler("")
  59. docDeleteHandler.IndexNameLookup = indexNameLookup
  60. docDeleteHandler.DocIDLookup = docIDLookup
  61. searchHandler := NewSearchHandler("")
  62. searchHandler.IndexNameLookup = indexNameLookup
  63. listFieldsHandler := NewListFieldsHandler("")
  64. listFieldsHandler.IndexNameLookup = indexNameLookup
  65. debugHandler := NewDebugDocumentHandler("")
  66. debugHandler.IndexNameLookup = indexNameLookup
  67. debugHandler.DocIDLookup = docIDLookup
  68. aliasHandler := NewAliasHandler()
  69. tests := []struct {
  70. Desc string
  71. Handler http.Handler
  72. Path string
  73. Method string
  74. Params url.Values
  75. Body []byte
  76. Status int
  77. ResponseBody []byte
  78. ResponseMatch map[string]bool
  79. }{
  80. {
  81. Desc: "create index",
  82. Handler: createIndexHandler,
  83. Path: "/create",
  84. Method: "PUT",
  85. Params: url.Values{"indexName": []string{"ti1"}},
  86. Body: []byte("{}"),
  87. Status: http.StatusOK,
  88. ResponseBody: []byte(`{"status":"ok"}`),
  89. },
  90. {
  91. Desc: "create existing index",
  92. Handler: createIndexHandler,
  93. Path: "/create",
  94. Method: "PUT",
  95. Params: url.Values{"indexName": []string{"ti1"}},
  96. Body: []byte("{}"),
  97. Status: http.StatusInternalServerError,
  98. ResponseMatch: map[string]bool{
  99. `path already exists`: true,
  100. },
  101. },
  102. {
  103. Desc: "create index missing index",
  104. Handler: createIndexHandler,
  105. Path: "/create",
  106. Method: "PUT",
  107. Body: []byte("{}"),
  108. Status: http.StatusBadRequest,
  109. ResponseBody: []byte(`index name is required`),
  110. },
  111. {
  112. Desc: "create index invalid json",
  113. Handler: createIndexHandler,
  114. Path: "/create",
  115. Method: "PUT",
  116. Params: url.Values{"indexName": []string{"ti9"}},
  117. Body: []byte("{"),
  118. Status: http.StatusBadRequest,
  119. ResponseMatch: map[string]bool{
  120. `error parsing index mapping`: true,
  121. },
  122. },
  123. {
  124. Desc: "get index",
  125. Handler: getIndexHandler,
  126. Path: "/get",
  127. Method: "GET",
  128. Params: url.Values{"indexName": []string{"ti1"}},
  129. Status: http.StatusOK,
  130. ResponseMatch: map[string]bool{
  131. `"status":"ok"`: true,
  132. `"name":"ti1"`: true,
  133. },
  134. },
  135. {
  136. Desc: "get index does not exist",
  137. Handler: getIndexHandler,
  138. Path: "/get",
  139. Method: "GET",
  140. Params: url.Values{"indexName": []string{"dne"}},
  141. Status: http.StatusNotFound,
  142. ResponseMatch: map[string]bool{
  143. `no such index`: true,
  144. },
  145. },
  146. {
  147. Desc: "get index missing name",
  148. Handler: getIndexHandler,
  149. Path: "/get",
  150. Method: "GET",
  151. Status: http.StatusBadRequest,
  152. ResponseBody: []byte(`index name is required`),
  153. },
  154. {
  155. Desc: "create another index",
  156. Handler: createIndexHandler,
  157. Path: "/create",
  158. Method: "PUT",
  159. Params: url.Values{"indexName": []string{"ti2"}},
  160. Body: []byte("{}"),
  161. Status: http.StatusOK,
  162. ResponseBody: []byte(`{"status":"ok"}`),
  163. },
  164. {
  165. Desc: "list indexes",
  166. Handler: listIndexesHandler,
  167. Path: "/list",
  168. Method: "GET",
  169. Status: http.StatusOK,
  170. ResponseMatch: map[string]bool{
  171. `"status":"ok"`: true,
  172. `"ti1"`: true,
  173. `"ti2"`: true,
  174. },
  175. },
  176. {
  177. Desc: "delete index",
  178. Handler: deleteIndexHandler,
  179. Path: "/delete",
  180. Method: "DELETE",
  181. Params: url.Values{"indexName": []string{"ti2"}},
  182. Status: http.StatusOK,
  183. ResponseBody: []byte(`{"status":"ok"}`),
  184. },
  185. {
  186. Desc: "delete index missing name",
  187. Handler: deleteIndexHandler,
  188. Path: "/delete",
  189. Method: "DELETE",
  190. Status: http.StatusBadRequest,
  191. ResponseBody: []byte(`index name is required`),
  192. },
  193. {
  194. Desc: "list indexes after delete",
  195. Handler: listIndexesHandler,
  196. Path: "/list",
  197. Method: "GET",
  198. Status: http.StatusOK,
  199. ResponseMatch: map[string]bool{
  200. `"status":"ok"`: true,
  201. `"ti1"`: true,
  202. `"ti2"`: false,
  203. },
  204. },
  205. {
  206. Desc: "index doc",
  207. Handler: docIndexHandler,
  208. Path: "/ti1/a",
  209. Method: "PUT",
  210. Params: url.Values{
  211. "indexName": []string{"ti1"},
  212. "docID": []string{"a"},
  213. },
  214. Body: []byte(`{"name":"a","body":"test","rating":7,"created":"2014-11-26","former_ratings":[3,4,2]}`),
  215. Status: http.StatusOK,
  216. ResponseBody: []byte(`{"status":"ok"}`),
  217. },
  218. {
  219. Desc: "index doc invalid index",
  220. Handler: docIndexHandler,
  221. Path: "/tix/a",
  222. Method: "PUT",
  223. Params: url.Values{
  224. "indexName": []string{"tix"},
  225. "docID": []string{"a"},
  226. },
  227. Body: []byte(`{"name":"a","body":"test","rating":7,"created":"2014-11-26","former_ratings":[3,4,2]}`),
  228. Status: http.StatusNotFound,
  229. ResponseBody: []byte(`no such index 'tix'`),
  230. },
  231. {
  232. Desc: "index doc missing ID",
  233. Handler: docIndexHandler,
  234. Path: "/ti1/a",
  235. Method: "PUT",
  236. Params: url.Values{
  237. "indexName": []string{"ti1"},
  238. },
  239. Body: []byte(`{"name":"a","body":"test","rating":7,"created":"2014-11-26","former_ratings":[3,4,2]}`),
  240. Status: http.StatusBadRequest,
  241. ResponseBody: []byte(`document id cannot be empty`),
  242. },
  243. {
  244. Desc: "doc count",
  245. Handler: docCountHandler,
  246. Path: "/ti1/count",
  247. Method: "GET",
  248. Params: url.Values{
  249. "indexName": []string{"ti1"},
  250. },
  251. Status: http.StatusOK,
  252. ResponseBody: []byte(`{"status":"ok","count":1}`),
  253. },
  254. {
  255. Desc: "doc count invalid index",
  256. Handler: docCountHandler,
  257. Path: "/tix/count",
  258. Method: "GET",
  259. Params: url.Values{
  260. "indexName": []string{"tix"},
  261. },
  262. Status: http.StatusNotFound,
  263. ResponseBody: []byte(`no such index 'tix'`),
  264. },
  265. {
  266. Desc: "doc get",
  267. Handler: docGetHandler,
  268. Path: "/ti1/a",
  269. Method: "GET",
  270. Params: url.Values{
  271. "indexName": []string{"ti1"},
  272. "docID": []string{"a"},
  273. },
  274. Status: http.StatusOK,
  275. ResponseMatch: map[string]bool{
  276. `"id":"a"`: true,
  277. `"body":"test"`: true,
  278. `"name":"a"`: true,
  279. },
  280. },
  281. {
  282. Desc: "doc get invalid index",
  283. Handler: docGetHandler,
  284. Path: "/tix/a",
  285. Method: "GET",
  286. Params: url.Values{
  287. "indexName": []string{"tix"},
  288. "docID": []string{"a"},
  289. },
  290. Status: http.StatusNotFound,
  291. ResponseBody: []byte(`no such index 'tix'`),
  292. },
  293. {
  294. Desc: "doc get missing ID",
  295. Handler: docGetHandler,
  296. Path: "/ti1/a",
  297. Method: "GET",
  298. Params: url.Values{
  299. "indexName": []string{"ti1"},
  300. },
  301. Status: http.StatusBadRequest,
  302. ResponseBody: []byte(`document id cannot be empty`),
  303. },
  304. {
  305. Desc: "index another doc",
  306. Handler: docIndexHandler,
  307. Path: "/ti1/b",
  308. Method: "PUT",
  309. Params: url.Values{
  310. "indexName": []string{"ti1"},
  311. "docID": []string{"b"},
  312. },
  313. Body: []byte(`{"name":"b","body":"del"}`),
  314. Status: http.StatusOK,
  315. ResponseBody: []byte(`{"status":"ok"}`),
  316. },
  317. {
  318. Desc: "doc count again",
  319. Handler: docCountHandler,
  320. Path: "/ti1/count",
  321. Method: "GET",
  322. Params: url.Values{
  323. "indexName": []string{"ti1"},
  324. },
  325. Status: http.StatusOK,
  326. ResponseBody: []byte(`{"status":"ok","count":2}`),
  327. },
  328. {
  329. Desc: "delete doc",
  330. Handler: docDeleteHandler,
  331. Path: "/ti1/b",
  332. Method: "DELETE",
  333. Params: url.Values{
  334. "indexName": []string{"ti1"},
  335. "docID": []string{"b"},
  336. },
  337. Status: http.StatusOK,
  338. ResponseBody: []byte(`{"status":"ok"}`),
  339. },
  340. {
  341. Desc: "delete doc invalid index",
  342. Handler: docDeleteHandler,
  343. Path: "/tix/b",
  344. Method: "DELETE",
  345. Params: url.Values{
  346. "indexName": []string{"tix"},
  347. "docID": []string{"b"},
  348. },
  349. Status: http.StatusNotFound,
  350. ResponseBody: []byte(`no such index 'tix'`),
  351. },
  352. {
  353. Desc: "delete doc missing docID",
  354. Handler: docDeleteHandler,
  355. Path: "/ti1/b",
  356. Method: "DELETE",
  357. Params: url.Values{
  358. "indexName": []string{"ti1"},
  359. },
  360. Status: http.StatusBadRequest,
  361. ResponseBody: []byte(`document id cannot be empty`),
  362. },
  363. {
  364. Desc: "doc get",
  365. Handler: docGetHandler,
  366. Path: "/ti1/b",
  367. Method: "GET",
  368. Params: url.Values{
  369. "indexName": []string{"ti1"},
  370. "docID": []string{"b"},
  371. },
  372. Status: http.StatusNotFound,
  373. ResponseMatch: map[string]bool{
  374. `no such document`: true,
  375. },
  376. },
  377. {
  378. Desc: "search",
  379. Handler: searchHandler,
  380. Path: "/ti1/search",
  381. Method: "POST",
  382. Params: url.Values{
  383. "indexName": []string{"ti1"},
  384. },
  385. Body: []byte(`{
  386. "from": 0,
  387. "size": 10,
  388. "query": {
  389. "fuzziness": 0,
  390. "prefix_length": 0,
  391. "field": "body",
  392. "match": "test"
  393. }
  394. }`),
  395. Status: http.StatusOK,
  396. ResponseMatch: map[string]bool{
  397. `"total_hits":1`: true,
  398. `"id":"a"`: true,
  399. },
  400. },
  401. {
  402. Desc: "search index doesn't exist",
  403. Handler: searchHandler,
  404. Path: "/tix/search",
  405. Method: "POST",
  406. Params: url.Values{
  407. "indexName": []string{"tix"},
  408. },
  409. Body: []byte(`{
  410. "from": 0,
  411. "size": 10,
  412. "query": {
  413. "fuzziness": 0,
  414. "prefix_length": 0,
  415. "field": "body",
  416. "match": "test"
  417. }
  418. }`),
  419. Status: http.StatusNotFound,
  420. ResponseBody: []byte(`no such index 'tix'`),
  421. },
  422. {
  423. Desc: "search invalid json",
  424. Handler: searchHandler,
  425. Path: "/ti1/search",
  426. Method: "POST",
  427. Params: url.Values{
  428. "indexName": []string{"ti1"},
  429. },
  430. Body: []byte(`{`),
  431. Status: http.StatusBadRequest,
  432. ResponseMatch: map[string]bool{
  433. `error parsing query`: true,
  434. },
  435. },
  436. {
  437. Desc: "search query does not validate",
  438. Handler: searchHandler,
  439. Path: "/ti1/search",
  440. Method: "POST",
  441. Params: url.Values{
  442. "indexName": []string{"ti1"},
  443. },
  444. Body: []byte(`{
  445. "from": 0,
  446. "size": 10,
  447. "query": {
  448. "field": "body",
  449. "terms": []
  450. }
  451. }`),
  452. Status: http.StatusBadRequest,
  453. ResponseMatch: map[string]bool{
  454. `error validating query`: true,
  455. },
  456. },
  457. {
  458. Desc: "list fields",
  459. Handler: listFieldsHandler,
  460. Path: "/ti1/fields",
  461. Method: "GET",
  462. Params: url.Values{
  463. "indexName": []string{"ti1"},
  464. },
  465. Status: http.StatusOK,
  466. ResponseMatch: map[string]bool{
  467. `"fields":`: true,
  468. `"name"`: true,
  469. `"body"`: true,
  470. `"_all"`: true,
  471. },
  472. },
  473. {
  474. Desc: "list fields invalid index",
  475. Handler: listFieldsHandler,
  476. Path: "/tix/fields",
  477. Method: "GET",
  478. Params: url.Values{
  479. "indexName": []string{"tix"},
  480. },
  481. Status: http.StatusNotFound,
  482. ResponseBody: []byte(`no such index 'tix'`),
  483. },
  484. {
  485. Desc: "create alias",
  486. Handler: aliasHandler,
  487. Path: "/alias",
  488. Method: "POST",
  489. Body: []byte(`{
  490. "alias": "a1",
  491. "add": ["ti1"]
  492. }`),
  493. Status: http.StatusOK,
  494. ResponseBody: []byte(`{"status":"ok"}`),
  495. },
  496. {
  497. Desc: "create alias invalid json",
  498. Handler: aliasHandler,
  499. Path: "/alias",
  500. Method: "POST",
  501. Body: []byte(`{`),
  502. Status: http.StatusBadRequest,
  503. ResponseMatch: map[string]bool{
  504. `error parsing alias actions`: true,
  505. },
  506. },
  507. {
  508. Desc: "create alias empty",
  509. Handler: aliasHandler,
  510. Path: "/alias",
  511. Method: "POST",
  512. Body: []byte(``),
  513. Status: http.StatusBadRequest,
  514. ResponseMatch: map[string]bool{
  515. `request body must contain alias actions`: true,
  516. },
  517. },
  518. {
  519. Desc: "create alias referring to non-existent index",
  520. Handler: aliasHandler,
  521. Path: "/alias",
  522. Method: "POST",
  523. Body: []byte(`{
  524. "alias": "a2",
  525. "add": ["tix"]
  526. }`),
  527. Status: http.StatusBadRequest,
  528. ResponseMatch: map[string]bool{
  529. `index named 'tix' does not exist`: true,
  530. },
  531. },
  532. {
  533. Desc: "create alias removing from new",
  534. Handler: aliasHandler,
  535. Path: "/alias",
  536. Method: "POST",
  537. Body: []byte(`{
  538. "alias": "a2",
  539. "remove": ["ti1"]
  540. }`),
  541. Status: http.StatusBadRequest,
  542. ResponseMatch: map[string]bool{
  543. `cannot remove indexes from a new alias`: true,
  544. },
  545. },
  546. {
  547. Desc: "create alias same name as index",
  548. Handler: aliasHandler,
  549. Path: "/alias",
  550. Method: "POST",
  551. Body: []byte(`{
  552. "alias": "ti1",
  553. "remove": ["ti1"]
  554. }`),
  555. Status: http.StatusBadRequest,
  556. ResponseMatch: map[string]bool{
  557. `is not an alias`: true,
  558. },
  559. },
  560. {
  561. Desc: "search alias",
  562. Handler: searchHandler,
  563. Path: "/a1/search",
  564. Method: "POST",
  565. Params: url.Values{
  566. "indexName": []string{"a1"},
  567. },
  568. Body: []byte(`{
  569. "from": 0,
  570. "size": 10,
  571. "query": {
  572. "fuzziness": 0,
  573. "prefix_length": 0,
  574. "field": "body",
  575. "match": "test"
  576. }
  577. }`),
  578. Status: http.StatusOK,
  579. ResponseMatch: map[string]bool{
  580. `"total_hits":1`: true,
  581. `"id":"a"`: true,
  582. },
  583. },
  584. {
  585. Desc: "create index to add to alias",
  586. Handler: createIndexHandler,
  587. Path: "/create",
  588. Method: "PUT",
  589. Params: url.Values{"indexName": []string{"ti6"}},
  590. Body: []byte("{}"),
  591. Status: http.StatusOK,
  592. ResponseBody: []byte(`{"status":"ok"}`),
  593. },
  594. {
  595. Desc: "update alias add ti6",
  596. Handler: aliasHandler,
  597. Path: "/alias",
  598. Method: "POST",
  599. Body: []byte(`{
  600. "alias": "a1",
  601. "add": ["ti6"]
  602. }`),
  603. Status: http.StatusOK,
  604. ResponseBody: []byte(`{"status":"ok"}`),
  605. },
  606. {
  607. Desc: "update alias add doesn't exist",
  608. Handler: aliasHandler,
  609. Path: "/alias",
  610. Method: "POST",
  611. Body: []byte(`{
  612. "alias": "a1",
  613. "add": ["ti99"]
  614. }`),
  615. Status: http.StatusBadRequest,
  616. ResponseBody: []byte(`error updating alias: index named 'ti99' does not exist`),
  617. },
  618. {
  619. Desc: "update alias remove ti6",
  620. Handler: aliasHandler,
  621. Path: "/alias",
  622. Method: "POST",
  623. Body: []byte(`{
  624. "alias": "a1",
  625. "remove": ["ti6"]
  626. }`),
  627. Status: http.StatusOK,
  628. ResponseBody: []byte(`{"status":"ok"}`),
  629. },
  630. {
  631. Desc: "update alias remove doesn't exist",
  632. Handler: aliasHandler,
  633. Path: "/alias",
  634. Method: "POST",
  635. Body: []byte(`{
  636. "alias": "a1",
  637. "remove": ["ti98"]
  638. }`),
  639. Status: http.StatusBadRequest,
  640. ResponseBody: []byte(`error updating alias: index named 'ti98' does not exist`),
  641. },
  642. }
  643. for _, test := range tests {
  644. record := httptest.NewRecorder()
  645. req := &http.Request{
  646. Method: test.Method,
  647. URL: &url.URL{Path: test.Path},
  648. Form: test.Params,
  649. Body: ioutil.NopCloser(bytes.NewBuffer(test.Body)),
  650. }
  651. test.Handler.ServeHTTP(record, req)
  652. if got, want := record.Code, test.Status; got != want {
  653. t.Errorf("%s: response code = %d, want %d", test.Desc, got, want)
  654. t.Errorf("%s: response body = %s", test.Desc, record.Body)
  655. }
  656. got := bytes.TrimRight(record.Body.Bytes(), "\n")
  657. if test.ResponseBody != nil {
  658. if !reflect.DeepEqual(got, test.ResponseBody) {
  659. t.Errorf("%s: expected: '%s', got: '%s'", test.Desc, test.ResponseBody, got)
  660. }
  661. }
  662. for pattern, shouldMatch := range test.ResponseMatch {
  663. didMatch := bytes.Contains(got, []byte(pattern))
  664. if didMatch != shouldMatch {
  665. t.Errorf("%s: expected match %t for pattern %s, got %t", test.Desc, shouldMatch, pattern, didMatch)
  666. t.Errorf("%s: response body was: %s", test.Desc, got)
  667. }
  668. }
  669. }
  670. // close indexes
  671. for _, indexName := range IndexNames() {
  672. index := UnregisterIndexByName(indexName)
  673. if index != nil {
  674. err := index.Close()
  675. if err != nil {
  676. t.Errorf("error closing index %s: %v", indexName, err)
  677. }
  678. }
  679. }
  680. }