diff --git a/src/mango/test/01-index-crud-test.py b/src/mango/test/01-index-crud-test.py deleted file mode 100644 index 8084a6bf76a..00000000000 --- a/src/mango/test/01-index-crud-test.py +++ /dev/null @@ -1,435 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -import random -from functools import partial - -import mango -import copy -import unittest - -DOCS = [ - {"_id": "1", "name": "Jimi", "age": 10, "cars": 1}, - {"_id": "2", "name": "kate", "age": 8, "cars": 0}, -] - - -class IndexCrudTests(mango.DbPerClass): - def setUp(self): - super().setUp(db_per_test=True) - - def test_bad_fields(self): - bad_fields = [ - None, - True, - False, - "bing", - 2.0, - {"foo": "bar"}, - [{"foo": 2}], - [{"foo": "asc", "bar": "desc"}], - [{"foo": "asc"}, {"bar": "desc"}], - [""], - ] - for fields in bad_fields: - try: - self.db.create_index(fields) - except Exception as e: - self.assertEqual(e.response.status_code, 400) - else: - raise AssertionError("bad create index") - - def test_bad_types(self): - bad_types = [ - None, - True, - False, - 1.5, - "foo", # Future support - "geo", # Future support - {"foo": "bar"}, - ["baz", 3.0], - ] - for bt in bad_types: - try: - self.db.create_index(["foo"], idx_type=bt) - except Exception as e: - self.assertEqual( - e.response.status_code, 400, (bt, e.response.status_code) - ) - else: - raise AssertionError("bad create index") - - def test_bad_names(self): - bad_names = ["", True, False, 1.5, {"foo": "bar"}, [None, False]] - for bn in bad_names: - try: - self.db.create_index(["foo"], name=bn) - except Exception as e: - self.assertEqual(e.response.status_code, 400) - else: - raise AssertionError("bad create index") - - def test_bad_ddocs(self): - bad_ddocs = ["", "_design/", True, False, 1.5, {"foo": "bar"}, [None, False]] - for bd in bad_ddocs: - try: - self.db.create_index(["foo"], ddoc=bd) - except Exception as e: - self.assertEqual(e.response.status_code, 400) - else: - raise AssertionError("bad create index") - - def test_bad_urls(self): - # These are only the negative test cases because ideally the - # positive ones are implicitly tested by other ones. - - all_methods = [ - ("PUT", self.db.sess.put), - ("GET", self.db.sess.get), - ("POST", self.db.sess.post), - ("PATCH", self.db.sess.get), - ("DELETE", self.db.sess.delete), - ("HEAD", self.db.sess.head), - ("COPY", partial(self.db.sess.request, "COPY")), - ("OPTIONS", partial(self.db.sess.request, "OPTIONS")), - ("TRACE", partial(self.db.sess.request, "TRACE")), - ("CONNECT", partial(self.db.sess.request, "CONNECT")), - ] - - def all_but(method): - return list(filter(lambda x: x[0] != method, all_methods)) - - # Three-element subpaths are used as a shorthand to delete - # indexes via design documents, see below. - for subpath in ["a", "a/b", "a/b/c/d", "a/b/c/d/e", "a/b/c/d/e/f"]: - path = self.db.path("_index/{}".format(subpath)) - for method_name, method in all_methods: - with self.subTest(path=path, method=method_name): - r = method(path) - self.assertEqual(r.status_code, 404) - - for method_name, method in all_but("POST"): - path = self.db.path("_index/_bulk_delete") - with self.subTest(path=path, method=method_name): - r = method(path) - self.assertEqual(r.status_code, 405) - - fields = ["foo", "bar"] - ddoc = "dd" - idx = "idx_01" - ret = self.db.create_index(fields, name=idx, ddoc=ddoc) - assert ret is True - for subpath in [ - "{}/json/{}".format(ddoc, idx), - "_design/{}/json/{}".format(ddoc, idx), - ]: - path = self.db.path("_index/{}".format(subpath)) - for method_name, method in all_but("DELETE"): - r = method(path) - with self.subTest(path=path, method=method_name): - self.assertEqual(r.status_code, 405) - - def test_create_idx_01(self): - fields = ["foo", "bar"] - ret = self.db.create_index(fields, name="idx_01") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_01": - continue - self.assertEqual(idx["def"]["fields"], [{"foo": "asc"}, {"bar": "asc"}]) - return - raise AssertionError("index not created") - - def test_create_idx_01_exists(self): - fields = ["foo", "bar"] - ret = self.db.create_index(fields, name="idx_01") - assert ret is True - ret = self.db.create_index(fields, name="idx_01") - assert ret is False - - def test_create_idx_02(self): - fields = ["baz", "foo"] - ret = self.db.create_index(fields, name="idx_02") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_02": - continue - self.assertEqual(idx["def"]["fields"], [{"baz": "asc"}, {"foo": "asc"}]) - return - raise AssertionError("index not created") - - def test_read_idx_doc(self): - self.db.create_index(["foo", "bar"], name="idx_01") - self.db.create_index(["hello", "bar"]) - for idx in self.db.list_indexes(): - if idx["type"] == "special": - continue - ddocid = idx["ddoc"] - doc = self.db.open_doc(ddocid) - self.assertEqual(doc["_id"], ddocid) - info = self.db.ddoc_info(ddocid) - self.assertEqual(info["name"], ddocid.split("_design/")[-1]) - - def test_delete_idx_escaped(self): - self.db.create_index(["foo", "bar"], name="idx_01") - pre_indexes = self.db.list_indexes() - ret = self.db.create_index(["bing"], name="idx_del_1") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_del_1": - continue - self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}]) - self.db.delete_index(idx["ddoc"].replace("/", "%2F"), idx["name"]) - post_indexes = self.db.list_indexes() - self.assertEqual(pre_indexes, post_indexes) - - def test_delete_idx_unescaped(self): - pre_indexes = self.db.list_indexes() - ret = self.db.create_index(["bing"], name="idx_del_2") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_del_2": - continue - self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}]) - self.db.delete_index(idx["ddoc"], idx["name"]) - post_indexes = self.db.list_indexes() - self.assertEqual(pre_indexes, post_indexes) - - def test_delete_idx_no_design(self): - pre_indexes = self.db.list_indexes() - ret = self.db.create_index(["bing"], name="idx_del_3") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_del_3": - continue - self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}]) - self.db.delete_index(idx["ddoc"].split("/")[-1], idx["name"]) - post_indexes = self.db.list_indexes() - self.assertEqual(pre_indexes, post_indexes) - - def test_bulk_delete(self): - fields = ["field1"] - ret = self.db.create_index(fields, name="idx_01") - assert ret is True - - fields = ["field2"] - ret = self.db.create_index(fields, name="idx_02") - assert ret is True - - fields = ["field3"] - ret = self.db.create_index(fields, name="idx_03") - assert ret is True - - docids = [] - - for idx in self.db.list_indexes(): - if idx["ddoc"] is not None: - docids.append(idx["ddoc"]) - - docids.append("_design/this_is_not_an_index_name") - - ret = self.db.bulk_delete(docids) - - self.assertEqual(ret["fail"][0]["id"], "_design/this_is_not_an_index_name") - self.assertEqual(len(ret["success"]), 3) - - for idx in self.db.list_indexes(): - assert idx["type"] != "json" - assert idx["type"] != "text" - - def test_recreate_index(self): - pre_indexes = self.db.list_indexes() - for i in range(5): - ret = self.db.create_index(["bing"], name="idx_recreate") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "idx_recreate": - continue - self.assertEqual(idx["def"]["fields"], [{"bing": "asc"}]) - self.db.delete_index(idx["ddoc"], idx["name"]) - break - post_indexes = self.db.list_indexes() - self.assertEqual(pre_indexes, post_indexes) - - def test_delete_missing(self): - # Missing design doc - try: - self.db.delete_index("this_is_not_a_design_doc_id", "foo") - except Exception as e: - self.assertEqual(e.response.status_code, 404) - else: - raise AssertionError("bad index delete") - - # Missing view name - ret = self.db.create_index(["fields"], name="idx_01") - indexes = self.db.list_indexes() - not_special = [idx for idx in indexes if idx["type"] != "special"] - idx = random.choice(not_special) - ddocid = idx["ddoc"].split("/")[-1] - try: - self.db.delete_index(ddocid, "this_is_not_an_index_name") - except Exception as e: - self.assertEqual(e.response.status_code, 404) - else: - raise AssertionError("bad index delete") - - # Bad view type - try: - self.db.delete_index(ddocid, idx["name"], idx_type="not_a_real_type") - except Exception as e: - self.assertEqual(e.response.status_code, 404) - else: - raise AssertionError("bad index delete") - - def test_limit_skip_index(self): - fields = ["field1"] - ret = self.db.create_index(fields, name="idx_01") - assert ret is True - - fields = ["field2"] - ret = self.db.create_index(fields, name="idx_02") - assert ret is True - - fields = ["field3"] - ret = self.db.create_index(fields, name="idx_03") - assert ret is True - - fields = ["field4"] - ret = self.db.create_index(fields, name="idx_04") - assert ret is True - - fields = ["field5"] - ret = self.db.create_index(fields, name="idx_05") - assert ret is True - - self.assertEqual(len(self.db.list_indexes(limit=2)), 2) - self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2) - self.assertEqual(len(self.db.list_indexes(skip=5)), 1) - self.assertEqual(len(self.db.list_indexes(skip=6)), 0) - self.assertEqual(len(self.db.list_indexes(skip=100)), 0) - self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6) - - try: - self.db.list_indexes(skip=-1) - except Exception as e: - self.assertEqual(e.response.status_code, 500) - - try: - self.db.list_indexes(limit=0) - except Exception as e: - self.assertEqual(e.response.status_code, 500) - - def test_out_of_sync(self): - self.db.save_docs(copy.deepcopy(DOCS)) - self.db.create_index(["age"], name="age") - - selector = {"age": {"$gt": 0}} - docs = self.db.find( - selector, use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4" - ) - self.assertEqual(len(docs), 2) - - self.db.delete_doc("1") - - docs1 = self.db.find( - selector, - update="False", - use_index="_design/a017b603a47036005de93034ff689bbbb6a873c4", - ) - self.assertEqual(len(docs1), 1) - - -@unittest.skipUnless(mango.has_text_service(), "requires text service") -class IndexCrudTextTests(mango.DbPerClass): - def setUp(self): - super().setUp(db_per_test=True) - - def test_create_text_idx(self): - fields = [ - {"name": "stringidx", "type": "string"}, - {"name": "booleanidx", "type": "boolean"}, - ] - ret = self.db.create_text_index(fields=fields, name="text_idx_01") - assert ret is True - for idx in self.db.list_indexes(): - if idx["name"] != "text_idx_01": - continue - self.assertEqual( - idx["def"]["fields"], - [{"stringidx": "string"}, {"booleanidx": "boolean"}], - ) - return - raise AssertionError("index not created") - - def test_create_bad_text_idx(self): - bad_fields = [ - True, - False, - "bing", - 2.0, - ["foo", "bar"], - [{"name": "foo2"}], - [{"name": "foo3", "type": "garbage"}], - [{"type": "number"}], - [{"name": "age", "type": "number"}, {"name": "bad"}], - [{"name": "age", "type": "number"}, "bla"], - [{"name": "", "type": "number"}, "bla"], - ] - for fields in bad_fields: - try: - self.db.create_text_index(fields=fields) - except Exception as e: - self.assertEqual(e.response.status_code, 400) - else: - raise AssertionError("bad create text index") - - def test_limit_skip_index(self): - fields = ["field1"] - ret = self.db.create_index(fields, name="idx_01") - assert ret is True - - fields = ["field2"] - ret = self.db.create_index(fields, name="idx_02") - assert ret is True - - fields = ["field3"] - ret = self.db.create_index(fields, name="idx_03") - assert ret is True - - fields = ["field4"] - ret = self.db.create_index(fields, name="idx_04") - assert ret is True - - fields = [ - {"name": "stringidx", "type": "string"}, - {"name": "booleanidx", "type": "boolean"}, - ] - ret = self.db.create_text_index(fields=fields, name="idx_05") - assert ret is True - - self.assertEqual(len(self.db.list_indexes(limit=2)), 2) - self.assertEqual(len(self.db.list_indexes(limit=5, skip=4)), 2) - self.assertEqual(len(self.db.list_indexes(skip=5)), 1) - self.assertEqual(len(self.db.list_indexes(skip=6)), 0) - self.assertEqual(len(self.db.list_indexes(skip=100)), 0) - self.assertEqual(len(self.db.list_indexes(limit=10000000)), 6) - - try: - self.db.list_indexes(skip=-1) - except Exception as e: - self.assertEqual(e.response.status_code, 500) - - try: - self.db.list_indexes(limit=0) - except Exception as e: - self.assertEqual(e.response.status_code, 500) diff --git a/test/elixir/test/config/search.elixir b/test/elixir/test/config/search.elixir index 87715d4caf3..dee62e19a89 100644 --- a/test/elixir/test/config/search.elixir +++ b/test/elixir/test/config/search.elixir @@ -33,6 +33,11 @@ "facet ranges, empty", "facet ranges, non-empty" ], + "IndexCrudTextTests": [ + "create text idx", + "create bad text idx", + "limit skip idx" + ], "ElemMatchTests": [ "elem match non object" ], diff --git a/test/elixir/test/config/suite.elixir b/test/elixir/test/config/suite.elixir index b3fb950846c..0c134db3ed4 100644 --- a/test/elixir/test/config/suite.elixir +++ b/test/elixir/test/config/suite.elixir @@ -730,6 +730,25 @@ "Creating/Deleting DB should return 202-Acepted", "Creating/Updating/Deleting doc should return 202-Acepted" ], + "IndexCrudTests": [ + "bad fields", + "bad types", + "bad names", + "bad ddocs", + "bad urls", + "create idx 01", + "create idx 01 exists", + "create idx 02", + "read idx doc", + "delete idx escaped", + "delete idx unescaped", + "delete idx no design", + "bulk delete", + "recreate index", + "delete missing", + "limit skip index", + "out of sync" + ], "BasicFindTest": [ "simple find" ], diff --git a/test/elixir/test/mango/01_index_crud_test.exs b/test/elixir/test/mango/01_index_crud_test.exs new file mode 100644 index 00000000000..f457059c2cf --- /dev/null +++ b/test/elixir/test/mango/01_index_crud_test.exs @@ -0,0 +1,512 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +defmodule IndexCrudTests do + use CouchTestCase + + @users_db "_users" + @db_name "index-crud" + + @moduletag config: [ + { + "chttpd_auth", + "authentication_db", + @users_db + }, + { + "couch_httpd_auth", + "authentication_db", + @users_db + }, + { + "chttpd_auth", + "iterations", + "1" + }, + { + "admins", + "jan", + "apple" + } + ] + + + setup do + MangoDatabase.recreate(@db_name) + MangoDatabase.recreate(@users_db) + :ok + end + + test "bad fields" do + bad_fields = [ + nil, + true, + false, + "bing", + 2.0, + %{"foo" => "bar"}, + [%{"foo" => 2}], + [%{"foo" => "asc", "bar" => "desc"}], + [%{"foo" => "asc"}, %{"bar" => "desc"}], + [""], + ] + + Enum.each(bad_fields, fn bad_field -> + {:error, resp} = MangoDatabase.create_index(@db_name, bad_field) + assert resp.status_code == 400 + end) + end + + test "bad types" do + bad_types = [ + nil, + true, + false, + 1.5, + "foo", # Future support + "geo", # Future support + %{"foo" => "bar"}, + ["baz", 3.0] + ] + + Enum.each(bad_types, fn bad_type -> + {:error, resp} = MangoDatabase.create_index( + @db_name, + ["foo"], + name: "bad field", + idx_type: bad_type + ) + assert resp.status_code == 400 + end) + end + + test "bad names" do + bad_names = ["", true, false, 1.5, %{"foo" => "bar"}, [nil, false]] + + Enum.each(bad_names, fn bad_name -> + {:error, resp} = MangoDatabase.create_index( + @db_name, + ["foo"], + name: bad_name + ) + assert resp.status_code == 400 + end) + end + + test "bad ddocs" do + bad_ddocs = [ + "", + "_design/", + true, + false, + 1.5, + %{"foo" => "bar"}, + [nil, false] + ] + + Enum.each(bad_ddocs, fn bad_ddoc -> + {:error, resp} = MangoDatabase.create_index( + @db_name, + ["foo"], + ddoc: bad_ddoc + ) + assert resp.status_code == 400 + end) + end + + defp all_but(method, methods) do + Enum.reject(methods, fn {m, _} -> m == method end) + end + + test "bad urls" do + # These are only the negative test cases because ideally the + # positive ones are implicitly tested by other ones. + sess = Couch.login("jan", "apple") + + # The connect method got lost in the python to elixir conversion + # and needs further research + all_methods = [ + {"PUT", fn path -> Couch.Session.put(sess, path) end}, + {"GET", fn path -> Couch.Session.get(sess, path) end}, + {"POST", fn path -> Couch.Session.post(sess, path) end}, + {"PATCH", fn path -> Couch.Session.get(sess, path) end}, + {"DELETE", fn path -> Couch.Session.delete(sess, path) end}, + {"HEAD", fn path -> Couch.Session.go(sess, :head, path, []) end}, + {"COPY", fn path -> Couch.Session.go(sess, :copy, path, []) end}, + {"OPTIONS", fn path -> Couch.Session.go(sess, :options, path, []) end}, + {"TRACE", fn path -> Couch.Session.go(sess, :trace, path, []) end}, + # {"CONNECT", fn path -> Couch.Session.go(sess, :connect, path, parse_response: true) end} + ] + + # These are only the negative test cases. + subpaths = ["a", "a/b", "a/b/c/d", "a/b/c/d/e", "a/b/c/d/e/f"] + Enum.each(subpaths, fn subpath -> + path = MangoDatabase.path(@db_name, "_index/#{subpath}") + Enum.each(all_methods, fn {_, method} -> + response = method.(path) + assert response.status_code == 404 + end) + end) + + path = MangoDatabase.path(@db_name, "_index/_bulk_delete") + Enum.each(all_but("POST", all_methods), fn {_, method} -> + response = method.(path) + assert response.status_code == 405 + end) + + fields = ["foo", "bar"] + ddoc = "dd" + idx = "idx_01" + {:ok, ret} = MangoDatabase.create_index(@db_name, fields, name: idx, ddoc: ddoc) + assert ret == true + + subpaths = ["#{ddoc}/json/#{idx}", "_design/#{ddoc}/json/#{idx}"] + Enum.each(subpaths, fn subpath -> + path = MangoDatabase.path(@db_name, "_index/#{subpath}") + + Enum.each(all_but("DELETE", all_methods), fn {method_name, method} -> + response = method.(path) + assert response.status_code == 405, + "Expected 405 for #{method_name} on path #{path}" + end) + end) + end + + test "create idx 01" do + fields = ["foo", "bar"] + ret = MangoDatabase.create_index(@db_name, fields, name: "idx_1") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_01" do + assert idx["def"]["fields"] == [%{"foo" => "asc"}, %{"bar" => "asc"}] + end + end) + end + + test "create idx 01 exists" do + fields = ["foo", "bar"] + ret = MangoDatabase.create_index(@db_name, fields, name: "idx_1") + assert ret == {:ok, true} + + {:ok, resp} = MangoDatabase.create_index(@db_name, fields, name: "idx_1") + assert resp == false + end + + test "create idx 02" do + fields = ["baz", "foo"] + ret = MangoDatabase.create_index(@db_name, fields, name: "idx_2") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_02" do + assert idx["def"]["fields"] == [%{"baz" => "asc"}, %{"foo" => "asc"}] + end + end) + end + + test "read idx doc" do + MangoDatabase.create_index(@db_name, ["foo", "bar"], name: "idx_1") + MangoDatabase.create_index(@db_name, ["hello", "bar"]) + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + Enum.each(indexes, fn idx -> + if idx["type"] != "special" do + ddocid = idx["ddoc"] + doc = MangoDatabase.open_doc(@db_name, ddocid) + info = MangoDatabase.ddoc_info(@db_name, ddocid) + + assert doc["_id"] == ddocid + [_prefix, expected_name] = String.split(ddocid, "_design/") + assert info["name"] == expected_name + end + end) + end + + test "delete idx escaped" do + MangoDatabase.create_index(@db_name, ["foo", "bar"], name: "idx_1") + {:ok, pre_indexes} = MangoDatabase.list_indexes(@db_name) + ret = MangoDatabase.create_index(@db_name, ["bing"], name: "idx_del_1") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_del_1" do + assert idx["def"]["fields"] == [%{"bing" => "asc"}] + + ddoc_escaped = String.replace(idx["ddoc"], "/", "%2F") + MangoDatabase.delete_index(@db_name, ddoc_escaped, idx["name"]) + end + end) + {:ok, post_indexes} = MangoDatabase.list_indexes(@db_name) + assert pre_indexes == post_indexes + end + + test "delete idx unescaped" do + {:ok, pre_indexes} = MangoDatabase.list_indexes(@db_name) + ret = MangoDatabase.create_index(@db_name, ["bing"], name: "idx_del_2") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_del_2" do + assert idx["def"]["fields"] == [%{"bing" => "asc"}] + + MangoDatabase.delete_index(@db_name, idx["ddoc"], idx["name"]) + end + end) + {:ok, post_indexes} = MangoDatabase.list_indexes(@db_name) + assert pre_indexes == post_indexes + end + + test "delete idx no design" do + {:ok, pre_indexes} = MangoDatabase.list_indexes(@db_name) + ret = MangoDatabase.create_index(@db_name, ["bing"], name: "idx_del_3") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_del_3" do + assert idx["def"]["fields"] == [%{"bing" => "asc"}] + + ddocid = List.last(String.split(idx["ddoc"], "/")) + MangoDatabase.delete_index(@db_name, ddocid, idx["name"]) + end + end) + {:ok, post_indexes} = MangoDatabase.list_indexes(@db_name) + assert pre_indexes == post_indexes + end + + test "bulk delete" do + ret = MangoDatabase.create_index(@db_name, ["field1"], name: "idx_01") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field2"], name: "idx_02") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field3"], name: "idx_03") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + docids = [] + docids = Enum.reduce(indexes, docids, fn idx, acc -> + case idx["ddoc"] do + nil -> acc + ddoc -> acc ++ [ddoc] + end + end) + docids = docids ++ ["_design/this_is_not_an_index_name"] + + ret = MangoDatabase.bulk_delete(@db_name, docids) + + assert Enum.at(ret["fail"], 0)["id"] == "_design/this_is_not_an_index_name" + assert length(ret["success"]) == 3 + end + + test "recreate index" do + {:ok, pre_indexes} = MangoDatabase.list_indexes(@db_name) + + Enum.each(0..4, fn _i -> + ret = MangoDatabase.create_index(@db_name, ["bing"], name: "idx_recreate") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + + Enum.each(indexes, fn idx -> + if idx["name"] == "idx_recreate" do + assert idx["def"]["fields"] == [%{"bing" => "asc"}] + MangoDatabase.delete_index(@db_name, idx["ddoc"], idx["name"]) + end + end) + + {:ok, post_indexes} = MangoDatabase.list_indexes(@db_name) + assert pre_indexes == post_indexes + end) + end + + test "delete missing" do + # Missing design doc + ret = MangoDatabase.delete_index(@db_name, "this_is_not_a_design_doc_id", "foo") + assert ret.status_code == 404 + + MangoDatabase.create_index(@db_name, ["fields"], name: "idx_01") + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + + # Missing view name + not_special = Enum.filter(indexes, &(&1["type"] != "special")) + idx = Enum.random(not_special) + ddocid = List.last(String.split(idx["ddoc"], "/")) + ret = MangoDatabase.delete_index(@db_name, ddocid, "this_is_not_an_index_name") + assert ret.status_code == 404 + + # Bad view type + ret = MangoDatabase.delete_index(@db_name, ddocid, idx["name"], "not_a_real_type") + assert ret.status_code == 404 + end + + test "limit skip index" do + ret = MangoDatabase.create_index(@db_name, ["field1"], name: "idx_01") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field2"], name: "idx_02") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field3"], name: "idx_03") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field4"], name: "idx_04") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field5"], name: "idx_05") + assert ret == {:ok, true} + + {:ok, limit2} = MangoDatabase.list_indexes(@db_name, limit: 2) + {:ok, limit5_skip4} = MangoDatabase.list_indexes(@db_name, limit: 5, skip: 4) + {:ok, skip5} = MangoDatabase.list_indexes(@db_name, skip: 5) + {:ok, skip6} = MangoDatabase.list_indexes(@db_name, skip: 6) + {:ok, skip100} = MangoDatabase.list_indexes(@db_name, skip: 100) + {:ok, limit1} = MangoDatabase.list_indexes(@db_name, limit: 10_000_000) + + assert length(limit2) == 2 + assert length(limit5_skip4) == 2 + assert length(skip5) == 1 + assert Enum.empty?(skip6) + assert Enum.empty?(skip100) + assert length(limit1) == 6 + + {:error, bad_skip} = MangoDatabase.list_indexes(@db_name, skip: -1) + assert bad_skip.status_code == 500 + + {:error, limit0} = MangoDatabase.list_indexes(@db_name, limit: 0) + assert limit0.status_code == 500 + end + + test "out of sync" do + docs = [ + %{"_id" => "1", "name" => "Jimi", "age" => 10, "cars" => 1}, + %{"_id" => "2", "name" => "kate", "age" => 8, "cars" => 0} + ] + MangoDatabase.save_docs(@db_name, docs) + MangoDatabase.create_index(@db_name, ["age"], name: "age") + + selector = %{"age" => %{"$gt" => 0}} + {:ok, docs} = MangoDatabase.find( + @db_name, selector, use_index: "_design/a017b603a47036005de93034ff689bbbb6a873c4" + ) + assert length(docs) == 2 + + MangoDatabase.delete_doc(@db_name, "1") + + {:ok, docs1} = MangoDatabase.find( + @db_name, + selector, + update: false, + use_index: "_design/a017b603a47036005de93034ff689bbbb6a873c4" + ) + assert length(docs1) == 1 + end +end + +defmodule IndexCrudTextTests do + use CouchTestCase + + @db_name "index-crud-text" + + setup do + MangoDatabase.recreate(@db_name) + :ok + end + + test "create text idx" do + fields = [ + %{"name" => "stringidx", "type" => "string"}, + %{"name" => "booleanidx", "type" => "boolean"}, + ] + + ret = MangoDatabase.create_text_index(@db_name, fields: fields, name: "text_idx_01") + assert ret == {:ok, true} + + {:ok, indexes} = MangoDatabase.list_indexes(@db_name) + Enum.each(indexes, fn idx -> + if idx["name"] == "text_idx_01" do + assert idx["def"]["fields"] == [%{"stringidx" => "string"}, %{"booleanidx" => "boolean"}] + end + end) + end + + test "create bad text idx" do + bad_fields = [ + true, + false, + "bing", + 2.0, + %{"foo" => "bar"}, + [%{"foo" => 2}], + [%{"name" => "foo2"}], + [%{"name" => "foo3", "type" => "garbage"}], + [%{"type" => "number"}], + [%{"name" => "age", "type" => "number"}, %{"name" => "bad"}], + [%{"name" => "age", "type" => "number"}, "bla"], + [%{"name" => "", "type" => "number"}, "bla"], + ] + + Enum.each(bad_fields, fn bad_field -> + {:error, resp} = MangoDatabase.create_text_index(@db_name, fields: bad_field) + assert resp.status_code == 400 + end) + end + + test "limit skip index" do + ret = MangoDatabase.create_index(@db_name, ["field1"], name: "idx_01") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field2"], name: "idx_02") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field3"], name: "idx_03") + assert ret == {:ok, true} + + ret = MangoDatabase.create_index(@db_name, ["field4"], name: "idx_04") + assert ret == {:ok, true} + + fields = [ + %{"name" => "stringidx", "type" => "string"}, + %{"name" => "booleanidx", "type" => "boolean"}, + ] + ret = MangoDatabase.create_text_index(fields: fields, name: "idx_05") + assert ret == {:ok, true} + + {:ok, limit2} = MangoDatabase.list_indexes(@db_name, limit: 2) + {:ok, limit5_skip4} = MangoDatabase.list_indexes(@db_name, limit: 5, skip: 4) + {:ok, skip5} = MangoDatabase.list_indexes(@db_name, skip: 5) + {:ok, skip6} = MangoDatabase.list_indexes(@db_name, skip: 6) + {:ok, skip100} = MangoDatabase.list_indexes(@db_name, skip: 100) + {:ok, limit1} = MangoDatabase.list_indexes(@db_name, limit: 10_000_000) + + assert length(limit2) == 2 + assert length(limit5_skip4) == 2 + assert length(skip5) == 1 + assert Enum.empty?(skip6) + assert Enum.empty?(skip100) + assert length(limit1) == 6 + + {:error, bad_skip} = MangoDatabase.list_indexes(@db_name, skip: -1) + assert bad_skip.status_code == 500 + + {:error, limit0} = MangoDatabase.list_indexes(@db_name, limit: 0) + assert limit0.status_code == 500 + end +end diff --git a/test/elixir/test/support/mango_database.ex b/test/elixir/test/support/mango_database.ex index 1b5b4ab63a8..0612a1be4ab 100644 --- a/test/elixir/test/support/mango_database.ex +++ b/test/elixir/test/support/mango_database.ex @@ -17,6 +17,16 @@ defmodule MangoDatabase do "search" in resp.body["features"] end + def path(db, parts) do + parts_list = + case parts do + p when is_binary(p) -> [p] + p when is_list(p) -> p + end + + Path.join(["/#{db}" | parts_list]) + end + def recreate(db, opts \\ []) do resp = Couch.get("/#{db}") if resp.status_code == 200 do @@ -48,6 +58,23 @@ defmodule MangoDatabase do Couch.post("/#{db}/_bulk_docs", body: body) end + def open_doc(db, docid) do + response = Couch.get("/#{db}/#{docid}") + response.body + end + + def delete_doc(db, docid) do + path = "/#{db}/#{URI.encode(docid)}" + doc = Couch.get(path) + original_rev = doc.body["_rev"] + Couch.delete(path, query: %{"rev" => original_rev}) + end + + def ddoc_info(db, ddocid) do + response = Couch.get("/#{db}/#{ddocid}/_info") + response.body + end + # If a certain keyword like sort or field is passed in the options, # then it is added to the request body. defp put_if_set(map, key, opts, opts_key) do @@ -157,6 +184,17 @@ defmodule MangoDatabase do end end + def delete_index(db, ddocid, name, idx_type \\ "json") do + path = Path.join(["_index", ddocid, idx_type, name]) + Couch.delete("/#{db}/#{path}", params: %{"w" => "3"}) + end + + def bulk_delete(db, docs) do + body = %{"docids" => docs, "w" => 3} + resp = Couch.post("/#{db}/_index/_bulk_delete", body: body) + resp.body + end + def find(db, selector, opts \\ []) do defaults = [ use_index: nil,