From 395c083026f9735a2a34cda3012151ef94466136 Mon Sep 17 00:00:00 2001 From: Jay Doane Date: Tue, 14 Feb 2023 14:51:20 -0800 Subject: Move elixir search tests to be with other elixir tests Now that dreyfus is integrated into CouchDB, its elixir tests can be moved to be with the other elixir integration tests. As a bonus, remove some unnecessary configuration. --- test/elixir/test/config/search.elixir | 32 ++++ test/elixir/test/config/test-config.ini | 3 + test/elixir/test/partition_search_test.exs | 247 +++++++++++++++++++++++++++++ test/elixir/test/search_test.exs | 226 ++++++++++++++++++++++++++ 4 files changed, 508 insertions(+) create mode 100644 test/elixir/test/config/search.elixir create mode 100644 test/elixir/test/partition_search_test.exs create mode 100644 test/elixir/test/search_test.exs (limited to 'test') diff --git a/test/elixir/test/config/search.elixir b/test/elixir/test/config/search.elixir new file mode 100644 index 000000000..12450865d --- /dev/null +++ b/test/elixir/test/config/search.elixir @@ -0,0 +1,32 @@ +%{ + "PartitionSearchTest": [ + "Cannot do global query with partition view", + "Cannot do partition query with global search ddoc", + "Only returns docs in partition not those in shard", + "Simple query returns partitioned search results", + "Works with bookmarks and limit", + "Works with limit using POST for on non-partitioned db", + "Works with limit using POST for partitioned db", + "normal search on non-partitioned dbs still work", + "normal search on non-partitioned dbs with limit", + "normal search on non-partitioned dbs with over limit", + "normal search on non-partitioned dbs without limit", + "rejects conflicting partition values", + "restricted parameters are not allowed in query or body" + ], + "SearchTest": [ + "clean up search index with invalid design document", + "drilldown multiple keys multiple values for POST", + "drilldown multiple keys single values for GET", + "drilldown multiple keys single values for POST", + "drilldown multiple query definitions for GET", + "drilldown multiple query definitions for POST", + "drilldown single key multiple values for GET", + "drilldown single key multiple values for POST", + "drilldown single key single value for GET", + "drilldown single key single value for POST", + "drilldown three keys single values for POST", + "search returns all items for GET", + "search returns all items for POST" + ] +} diff --git a/test/elixir/test/config/test-config.ini b/test/elixir/test/config/test-config.ini index 1980139d1..190067643 100644 --- a/test/elixir/test/config/test-config.ini +++ b/test/elixir/test/config/test-config.ini @@ -1,2 +1,5 @@ [chttpd] authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} + +[dreyfus] +name = clouseau@127.0.0.1 diff --git a/test/elixir/test/partition_search_test.exs b/test/elixir/test/partition_search_test.exs new file mode 100644 index 000000000..121995449 --- /dev/null +++ b/test/elixir/test/partition_search_test.exs @@ -0,0 +1,247 @@ +defmodule PartitionSearchTest do + use CouchTestCase + + @moduletag :search + + @moduledoc """ + Test Partition functionality with search + """ + + def create_search_docs(db_name, pk1 \\ "foo", pk2 \\ "bar") do + docs = for i <- 1..10 do + id = if rem(i, 2) == 0 do + "#{pk1}:#{i}" + else + "#{pk2}:#{i}" + end + %{ + :_id => id, + :value => i, + :some => "field" + } + end + + resp = Couch.post("/#{db_name}/_bulk_docs", headers: ["Content-Type": "application/json"], body: %{:docs => docs}, query: %{w: 3}) + assert resp.status_code in [201, 202] + end + + def create_ddoc(db_name, opts \\ %{}) do + index_fn = "function(doc) {\n if (doc.some) {\n index('some', doc.some);\n }\n}" + default_ddoc = %{ + indexes: %{ + books: %{ + analyzer: %{name: "standard"}, + index: index_fn + } + } + } + + ddoc = Enum.into(opts, default_ddoc) + + resp = Couch.put("/#{db_name}/_design/library", body: ddoc) + assert resp.status_code in [201, 202] + assert Map.has_key?(resp.body, "ok") == true + end + + def get_ids (resp) do + %{:body => %{"rows" => rows}} = resp + Enum.map(rows, fn row -> row["id"] end) + end + + @tag :with_partitioned_db + test "Simple query returns partitioned search results", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] + + url = "/#{db_name}/_partition/bar/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["bar:1", "bar:3", "bar:5", "bar:7", "bar:9"] + end + + @tag :with_partitioned_db + test "Only returns docs in partition not those in shard", context do + db_name = context[:db_name] + create_search_docs(db_name, "foo", "bar42") + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] + end + + @tag :with_partitioned_db + test "Works with bookmarks and limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 3}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4"] + + %{:body => %{"bookmark" => bookmark}} = resp + + resp = Couch.get(url, query: %{q: "some:field", limit: 3, bookmark: bookmark}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:6", "foo:8"] + + resp = Couch.get(url, query: %{q: "some:field", limit: 2000, bookmark: bookmark}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:6", "foo:8"] + + resp = Couch.get(url, query: %{q: "some:field", limit: 2001, bookmark: bookmark}) + assert resp.status_code == 400 + end + + @tag :with_db + test "Works with limit using POST for on non-partitioned db", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) + assert resp.status_code == 200 + end + + @tag :with_partitioned_db + test "Works with limit using POST for partitioned db", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) + assert resp.status_code == 200 + end + + @tag :with_partitioned_db + test "Cannot do global query with partition view", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 400 + %{:body => %{"reason" => reason}} = resp + assert Regex.match?(~r/mandatory for queries to this index./, reason) + end + + @tag :with_partitioned_db + test "Cannot do partition query with global search ddoc", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name, options: %{partitioned: false}) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 400 + %{:body => %{"reason" => reason}} = resp + assert reason == "`partition` not supported on this index" + end + + @tag :with_db + test "normal search on non-partitioned dbs still work", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert Enum.sort(ids) == Enum.sort(["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"]) + end + + @tag :with_db + test "normal search on non-partitioned dbs without limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert Enum.sort(ids) == Enum.sort(["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"]) + end + + @tag :with_db + test "normal search on non-partitioned dbs with limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 3}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert Enum.sort(ids) == Enum.sort(["bar:1", "bar:5", "bar:9"]) + end + + @tag :with_db + test "normal search on non-partitioned dbs with over limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 201}) + assert resp.status_code == 400 + end + + @tag :with_partitioned_db + test "rejects conflicting partition values", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.post(url, body: %{q: "some:field", partition: "bar"}) + assert resp.status_code == 400 + end + + @tag :with_partitioned_db + test "restricted parameters are not allowed in query or body", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + body = %{q: "some:field", partition: "foo"} + + Enum.each( + [ + {:counts, "[\"type\"]"}, + {:group_field, "some"}, + {:ranges, :jiffy.encode(%{price: %{cheap: "[0 TO 100]"}})}, + {:drilldown, "[\"key\",\"a\"]"}, + ], + fn {key, value} -> + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + bannedparam = Map.put(body, key, value) + get_resp = Couch.get(url, query: bannedparam) + %{:body => %{"reason" => get_reason}} = get_resp + assert Regex.match?(~r/are incompatible/, get_reason) + post_resp = Couch.post(url, body: bannedparam) + %{:body => %{"reason" => post_reason}} = post_resp + assert Regex.match?(~r/are incompatible/, post_reason) + end + ) + end +end diff --git a/test/elixir/test/search_test.exs b/test/elixir/test/search_test.exs new file mode 100644 index 000000000..829b3395f --- /dev/null +++ b/test/elixir/test/search_test.exs @@ -0,0 +1,226 @@ +defmodule SearchTest do + use CouchTestCase + + @moduletag :search + + @moduledoc """ + Test search + """ + + def create_search_docs(db_name) do + resp = Couch.post("/#{db_name}/_bulk_docs", + headers: ["Content-Type": "application/json"], + body: %{:docs => [ + %{"item" => "apple", "place" => "kitchen", "state" => "new"}, + %{"item" => "banana", "place" => "kitchen", "state" => "new"}, + %{"item" => "carrot", "place" => "kitchen", "state" => "old"}, + %{"item" => "date", "place" => "lobby", "state" => "unknown"}, + ]} + ) + assert resp.status_code in [201, 202] + end + + def create_ddoc(db_name, opts \\ %{}) do + default_ddoc = %{ + indexes: %{ + fruits: %{ + analyzer: %{name: "standard"}, + index: "function (doc) {\n index(\"item\", doc.item, {facet: true});\n index(\"place\", doc.place, {facet: true});\n index(\"state\", doc.state, {facet: true});\n}" + } + } + } + + ddoc = Enum.into(opts, default_ddoc) + + resp = Couch.put("/#{db_name}/_design/inventory", body: ddoc) + assert resp.status_code in [201, 202] + assert Map.has_key?(resp.body, "ok") == true + end + + def create_invalid_ddoc(db_name, opts \\ %{}) do + invalid_ddoc = %{ + :indexes => [ + %{"name" => "foo", "ddoc" => "bar", "type" => "text"}, + ] + } + + ddoc = Enum.into(opts, invalid_ddoc) + + resp = Couch.put("/#{db_name}/_design/search", body: ddoc) + assert resp.status_code in [201, 202] + assert Map.has_key?(resp.body, "ok") == true + end + + def get_items (resp) do + %{:body => %{"rows" => rows}} = resp + Enum.map(rows, fn row -> row["doc"]["item"] end) + end + + @tag :with_db + test "search returns all items for GET", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.get(url, query: %{q: "*:*", include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot", "date"]) + end + + @tag :with_db + test "drilldown single key single value for GET", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["place", "kitchen"]), include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot"]) + end + + @tag :with_db + test "drilldown single key multiple values for GET", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["state", "new", "unknown"]), include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "date"]) + end + + @tag :with_db + test "drilldown multiple keys single values for GET", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode([["state", "old"], ["item", "apple"]]), include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == [] + end + + @tag :with_db + test "drilldown multiple query definitions for GET", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits?q=*:*&drilldown=[\"state\",\"old\"]&drilldown=[\"item\",\"apple\"]&include_docs=true" + resp = Couch.get(url) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == [] + end + + + @tag :with_db + test "search returns all items for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{q: "*:*", include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot", "date"]) + end + + @tag :with_db + test "drilldown single key single value for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{query: "*:*", drilldown: ["place", "kitchen"], include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot"]) + end + + @tag :with_db + test "drilldown single key multiple values for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{query: "*:*", drilldown: ["state", "new", "unknown"], include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == Enum.sort(["apple", "banana", "date"]) + end + + @tag :with_db + test "drilldown multiple keys single values for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old"], ["item", "apple"]], include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == [] + end + + @tag :with_db + test "drilldown three keys single values for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["place", "kitchen"], ["state", "new"], ["item", "apple"]], include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == ["apple"] + end + + @tag :with_db + test "drilldown multiple keys multiple values for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old", "new"], ["item", "apple"]], include_docs: true}) + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == ["apple"] + end + + @tag :with_db + test "drilldown multiple query definitions for POST", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/inventory/_search/fruits" + resp = Couch.post(url, body: "{\"include_docs\": true, \"q\": \"*:*\", \"drilldown\": [\"state\", \"old\"], \"drilldown\": [\"item\", \"apple\"]}") + assert resp.status_code == 200 + ids = get_items(resp) + assert Enum.sort(ids) == ["apple"] + end + + @tag :with_db + test "clean up search index with invalid design document", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + create_invalid_ddoc(db_name) + + resp = Couch.post("/#{db_name}/_search_cleanup") + assert resp.status_code in [201, 202] + end +end -- cgit v1.2.1