Merge pull request #2958 from bessbd/allow-drilldown-list-of-lists

Allow drilldown for search to always be specified as list of lists
diff --git a/src/dreyfus/src/dreyfus_httpd.erl b/src/dreyfus/src/dreyfus_httpd.erl
index f0a130e..007dace 100644
--- a/src/dreyfus/src/dreyfus_httpd.erl
+++ b/src/dreyfus/src/dreyfus_httpd.erl
@@ -239,6 +239,8 @@
     Args#index_query_args{counts=Value};
 validate_index_query(ranges, Value, Args) ->
     Args#index_query_args{ranges=Value};
+validate_index_query(drilldown, [[_|_]|_] = Value, Args) ->
+    Args#index_query_args{drilldown=Value};
 validate_index_query(drilldown, Value, Args) ->
     DrillDown = Args#index_query_args.drilldown,
     Args#index_query_args{drilldown=[Value|DrillDown]};
diff --git a/src/dreyfus/test/elixir/test/search_test.exs b/src/dreyfus/test/elixir/test/search_test.exs
new file mode 100644
index 0000000..e524a5c
--- /dev/null
+++ b/src/dreyfus/test/elixir/test/search_test.exs
@@ -0,0 +1,201 @@
+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 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
+end