You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ju...@apache.org on 2020/10/01 20:30:30 UTC

[couchdb] 03/04: Port show_documents and list_views to Elixir

This is an automated email from the ASF dual-hosted git repository.

juanjo pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit afea168331c55afaec5203c7f75f477e32802390
Author: Juanjo Rodriguez <ju...@apache.org>
AuthorDate: Tue Sep 29 09:49:06 2020 +0200

    Port show_documents and list_views to Elixir
---
 test/elixir/README.md                    |   4 +-
 test/elixir/test/list_views_test.exs     | 581 +++++++++++++++++++++++++++++++
 test/elixir/test/show_documents_test.exs | 448 ++++++++++++++++++++++++
 test/javascript/tests/list_views.js      |   2 +-
 test/javascript/tests/show_documents.js  |   2 +-
 5 files changed, 1033 insertions(+), 4 deletions(-)

diff --git a/test/elixir/README.md b/test/elixir/README.md
index 0ef0d03..de9642b 100644
--- a/test/elixir/README.md
+++ b/test/elixir/README.md
@@ -54,7 +54,7 @@ X means done, - means partially
   - [X] Port invalid_docids.js
   - [X] Port jsonp.js
   - [X] Port large_docs.js
-  - [ ] Port list_views.js
+  - [X] Port list_views.js
   - [X] Port lorem_b64.txt
   - [X] Port lorem.txt
   - [X] Port lots_of_docs.js
@@ -91,7 +91,7 @@ X means done, - means partially
   - [X] Port rewrite.js
   - [X] Port rewrite_js.js
   - [X] Port security_validation.js
-  - [ ] Port show_documents.js
+  - [X] Port show_documents.js
   - [ ] Port stats.js
   - [X] Port update_documents.js
   - [X] Port users_db.js
diff --git a/test/elixir/test/list_views_test.exs b/test/elixir/test/list_views_test.exs
new file mode 100644
index 0000000..8e6314d
--- /dev/null
+++ b/test/elixir/test/list_views_test.exs
@@ -0,0 +1,581 @@
+defmodule ListViewsTest do
+  use CouchTestCase
+
+  @moduletag kind: :single_node
+
+  @ddoc %{
+    _id: "_design/lists",
+    language: "javascript",
+    views: %{
+      basicView: %{
+        map: """
+          function(doc) {
+          emit(doc.integer, doc.string);
+        }
+        """
+      },
+      withReduce: %{
+        map: """
+         function(doc) {
+          emit(doc.integer, doc.string);
+        }
+        """,
+        reduce: """
+          function(keys, values, rereduce) {
+          if (rereduce) {
+            return sum(values);
+          } else {
+            return values.length;
+          }
+        }
+        """
+      }
+    },
+    lists: %{
+      basicBasic: """
+       function(head, req) {
+        send("head");
+        var row;
+        while(row = getRow()) {
+          send(row.key);
+        };
+        return "tail";
+      }
+      """,
+      basicJSON: """
+        function(head, req) {
+        start({"headers":{"Content-Type" : "application/json"}});
+        send('{"head":'+toJSON(head)+', ');
+        send('"req":'+toJSON(req)+', ');
+        send('"rows":[');
+        var row, sep = '';
+        while (row = getRow()) {
+          send(sep + toJSON(row));
+          sep = ', ';
+        }
+        return "]}";
+      }
+      """,
+      simpleForm: """
+        function(head, req) {
+        send('<ul>');
+        var row, row_number = 0, prevKey, firstKey = null;
+        while (row = getRow()) {
+          row_number += 1;
+          if (!firstKey) firstKey = row.key;
+          prevKey = row.key;
+          send('\\n<li>Key: '+row.key
+          +' Value: '+row.value
+          +' LineNo: '+row_number+'</li>');
+        }
+        return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>';
+      }
+      """,
+      acceptSwitch: """
+        function(head, req) {
+        // respondWith takes care of setting the proper headers
+        provides("html", function() {
+          send("HTML <ul>");
+
+          var row, num = 0;
+          while (row = getRow()) {
+            num ++;
+            send('\\n<li>Key: '
+              +row.key+' Value: '+row.value
+              +' LineNo: '+num+'</li>');
+          }
+
+          // tail
+          return '</ul>';
+        });
+      }
+      """,
+      qsParams: """
+        function(head, req) {
+        return toJSON(req.query) + "\\n";
+      }
+      """,
+      stopIter: """
+        function(req) {
+        send("head");
+        var row, row_number = 0;
+        while(row = getRow()) {
+          if(row_number > 2) break;
+          send(" " + row_number);
+          row_number += 1;
+        };
+        return " tail";
+      }
+      """,
+      stopIter2: """
+        function(head, req) {
+        provides("html", function() {
+          send("head");
+          var row, row_number = 0;
+          while(row = getRow()) {
+            if(row_number > 2) break;
+            send(" " + row_number);
+            row_number += 1;
+          };
+          return " tail";
+        });
+      }
+      """,
+      tooManyGetRows: """
+       function() {
+        send("head");
+        var row;
+        while(row = getRow()) {
+          send(row.key);
+        };
+        getRow();
+        getRow();
+        getRow();
+        row = getRow();
+        return "after row: "+toJSON(row);
+      }
+      """,
+      emptyList: """
+        function() {
+        return " ";
+      }
+      """,
+      rowError: """
+        function(head, req) {
+        send("head");
+        var row = getRow();
+        send(fooBarBam); // intentional error
+        return "tail";
+      }
+      """,
+      docReference: """
+        function(head, req) {
+        send("head");
+        var row = getRow();
+        send(row.doc.integer);
+        return "tail";
+      }
+      """,
+      secObj: """
+        function(head, req) {
+        return toJSON(req.secObj);
+      }
+      """,
+      setHeaderAfterGotRow: """
+        function(head, req) {
+        getRow();
+        start({
+          code: 400,
+          headers: {
+            "X-My-Header": "MyHeader"
+          }
+        });
+        send("bad request");
+      }
+      """,
+      allDocs: """
+        function(head, req){
+        start({'headers': {'Content-Type': 'application/json'}});
+        var resp = head;
+        var rows = [];
+        while(row=getRow()){
+          rows.push(row);
+        }
+        resp.rows = rows;
+        return toJSON(resp);
+      }
+      """
+    }
+  }
+
+  @view_only_design_doc %{
+    _id: "_design/views",
+    language: "javascript",
+    views: %{
+      basicView: %{
+        map: """
+         function(doc) {
+          emit(-doc.integer, doc.string);
+        }
+        """
+      }
+    }
+  }
+
+  @erl_list_doc %{
+    _id: "_design/erlang",
+    language: "erlang",
+    lists: %{
+      simple: """
+      fun(Head, {Req}) ->
+        Send(<<"[">>),
+        Fun = fun({Row}, Sep) ->
+          Val = couch_util:get_value(<<"key">>, Row, 23),
+          Send(list_to_binary(Sep ++ integer_to_list(Val))),
+          {ok, ","}
+        end,
+        {ok, _} = FoldRows(Fun, ""),
+        Send(<<"]">>)
+      end.
+      """
+    }
+  }
+
+  setup_all do
+    db_name = random_db_name()
+    {:ok, _} = create_db(db_name)
+    on_exit(fn -> delete_db(db_name) end)
+
+    {:ok, _} = create_doc(db_name, @ddoc)
+    bulk_save(db_name, make_docs(0..9))
+
+    # Check setup
+    resp = view(db_name, "lists/basicView")
+    assert resp.body["total_rows"] == 10
+
+    db_name_cross = "#{db_name}_cross"
+    {:ok, _} = create_db(db_name_cross)
+    on_exit(fn -> delete_db(db_name_cross) end)
+
+    {:ok, _} = create_doc(db_name_cross, @ddoc)
+    {:ok, _} = create_doc(db_name_cross, @view_only_design_doc)
+    bulk_save(db_name_cross, make_docs(0..9))
+
+    db_name_erlang = "#{db_name}_erlang"
+    {:ok, _} = create_db(db_name_erlang)
+    on_exit(fn -> delete_db(db_name_erlang) end)
+
+    {:ok, _} = create_doc(db_name_erlang, @erl_list_doc)
+    {:ok, _} = create_doc(db_name_erlang, @view_only_design_doc)
+    bulk_save(db_name_erlang, make_docs(0..9))
+
+    {:ok,
+     [db_name: db_name, db_name_cross: db_name_cross, db_name_erlang: db_name_erlang]}
+  end
+
+  test "standard GET", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/basicBasic/basicView")
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/head0123456789tail/)
+  end
+
+  test "standard OPTIONS", context do
+    db_name = context[:db_name]
+    resp = Rawresp.options("/#{db_name}/_design/lists/_list/basicBasic/basicView")
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/head0123456789tail/)
+  end
+
+  test "the richness of the arguments", context do
+    db_name = context[:db_name]
+
+    resp =
+      Couch.get("/#{db_name}/_design/lists/_list/basicJSON/basicView?update_seq=true")
+
+    assert resp.status_code == 200
+    assert resp.body["head"]["total_rows"] == 10
+    assert resp.body["head"]["offset"] == 0
+    assert length(resp.body["rows"]) == 10
+    assert Enum.at(resp.body["rows"], 0) == %{"id" => "0", "key" => 0, "value" => "0"}
+    assert resp.body["req"]["info"]["db_name"] == db_name
+    assert resp.body["req"]["method"] == "GET"
+
+    assert resp.body["req"]["path"] == [
+             db_name,
+             "_design",
+             "lists",
+             "_list",
+             "basicJSON",
+             "basicView"
+           ]
+
+    assert Map.has_key?(resp.body["req"]["headers"], "Host") == true
+    assert Map.has_key?(resp.body["req"]["headers"], "User-Agent") == true
+    assert Map.has_key?(resp.body["req"], "cookie")
+
+    assert resp.body["req"]["raw_path"] ==
+             "/#{db_name}/_design/lists/_list/basicJSON/basicView?update_seq=true"
+  end
+
+  test "get with query params", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get(
+        "/#{db_name}/_design/lists/_list/simpleForm/basicView?startkey=3&endkey=8"
+      )
+
+    assert resp.status_code == 200
+    assert not String.match?(resp.body, ~r/Key: 1/)
+    assert String.match?(resp.body, ~r/FirstKey: 3/)
+    assert String.match?(resp.body, ~r/LastKey: 8/)
+  end
+
+  test "with 0 rows", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/simpleForm/basicView?startkey=30")
+
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/<\/ul>/)
+  end
+
+  test "too many Get Rows", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/tooManyGetRows/basicView")
+
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/9after row: null/)
+  end
+
+  test "reduce with 0 rows", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/simpleForm/withReduce?startkey=30")
+
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/LastKey: undefined/)
+  end
+
+  test "when there is a reduce present, but not used", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/simpleForm/withReduce?reduce=false")
+
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/Key: 1/)
+  end
+
+  test "when there is a reduce present, and used", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/simpleForm/withReduce?group=true")
+
+    assert resp.status_code == 200
+    assert String.match?(resp.body, ~r/Key: 1/)
+  end
+
+  test "empty list", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/emptyList/basicView")
+    assert String.match?(resp.body, ~r/^ $/)
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/emptyList/withReduce?group=true")
+    assert String.match?(resp.body, ~r/^ $/)
+  end
+
+  test "multi-key fetch with POST", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.post("/#{db_name}/_design/lists/_list/simpleForm/basicView",
+        body: %{keys: [2, 4, 5, 7]}
+      )
+
+    assert resp.status_code == 200
+    assert not String.match?(resp.body, ~r/Key: 1/)
+    assert String.match?(resp.body, ~r/Key: 2/)
+    assert String.match?(resp.body, ~r/FirstKey: 2/)
+    assert String.match?(resp.body, ~r/LastKey: 7/)
+  end
+
+  test "multi-key fetch with GET", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/simpleForm/basicView?keys=[2,4,5,7]")
+
+    assert resp.status_code == 200
+    assert not String.match?(resp.body, ~r/Key: 1/)
+    assert String.match?(resp.body, ~r/Key: 2/)
+    assert String.match?(resp.body, ~r/FirstKey: 2/)
+    assert String.match?(resp.body, ~r/LastKey: 7/)
+  end
+
+  test "no multi-key fetch allowed when group=false", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.post("/#{db_name}/_design/lists/_list/simpleForm/withReduce?group=false",
+        body: %{keys: [2, 4, 5, 7]}
+      )
+
+    assert resp.status_code == 400
+    assert String.match?(resp.body, ~r/query_parse_error/)
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/rowError/basicView")
+    assert String.match?(resp.body, ~r/ReferenceError/)
+  end
+
+  test "with include_docs and a reference to the doc", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get(
+        "/#{db_name}/_design/lists/_list/docReference/basicView?include_docs=true"
+      )
+
+    assert String.match?(resp.body, ~r/head0tail/)
+  end
+
+  test "extra qs params", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/qsParams/basicView?foo=blam")
+    assert String.match?(resp.body, ~r/blam/)
+  end
+
+  test "stop iteration", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/stopIter/basicView")
+    assert String.match?(resp.body, ~r/^head 0 1 2 tail$/)
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/stopIter2/basicView",
+        headers: [Accept: "text/html"]
+      )
+
+    assert String.match?(resp.body, ~r/^head 0 1 2 tail$/)
+  end
+
+  test "abort iteration with reduce", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/stopIter/withReduce?group=true")
+    assert String.match?(resp.body, ~r/^head 0 1 2 tail$/)
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/stopIter2/withReduce?group=true",
+        headers: [Accept: "text/html"]
+      )
+
+    assert String.match?(resp.body, ~r/^head 0 1 2 tail$/)
+  end
+
+  test "with accept headers for HTML", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/lists/_list/acceptSwitch/basicView",
+        headers: [Accept: "text/html"]
+      )
+
+    assert resp.headers["Content-Type"] == "text/html; charset=utf-8"
+    assert String.match?(resp.body, ~r/HTML/)
+    assert String.match?(resp.body, ~r/Value/)
+  end
+
+  test "we can run lists and views from separate docs", context do
+    db_name = context[:db_name_cross]
+
+    resp =
+      Rawresp.get(
+        "/#{db_name}/_design/lists/_list/simpleForm/views/basicView?startkey=-3"
+      )
+
+    assert resp.status_code == 200
+    assert not String.match?(resp.body, ~r/Key: -4/)
+    assert String.match?(resp.body, ~r/FirstKey: -3/)
+    assert String.match?(resp.body, ~r/LastKey: 0/)
+  end
+
+  test "we do multi-key requests on lists and views in separate docs", context do
+    db_name = context[:db_name_cross]
+
+    resp =
+      Rawresp.post(
+        "/#{db_name}/_design/lists/_list/simpleForm/views/basicView",
+        body: %{keys: [-2, -4, -5, -7]}
+      )
+
+    assert resp.status_code == 200
+    assert not String.match?(resp.body, ~r/Key: -3/)
+    assert String.match?(resp.body, ~r/Key: -7/)
+    assert String.match?(resp.body, ~r/FirstKey: -2/)
+    assert String.match?(resp.body, ~r/LastKey: -7/)
+  end
+
+  test "secObj is available", context do
+    db_name = context[:db_name]
+
+    resp = Couch.get("/#{db_name}/_design/lists/_list/secObj/basicView")
+    assert resp.status_code == 200
+    assert is_map(resp.body)
+  end
+
+  test "multiple languages in design docs", context do
+    db_name = context[:db_name_erlang]
+
+    resp =
+      Couch.get("/#{db_name}/_design/erlang/_list/simple/views/basicView?startkey=-3")
+
+    assert resp.status_code == 200
+    assert length(resp.body) == 4
+
+    for i <- 0..3 do
+      assert Enum.at(resp.body, i) + 3 == i
+    end
+  end
+
+  @tag :with_db
+  test "COUCHDB-1113", context do
+    db_name = context[:db_name]
+
+    ddoc = %{
+      _id: "_design/test",
+      views: %{
+        me: %{
+          map: "function(doc) { emit(null,null)}"
+        }
+      },
+      lists: %{
+        you: """
+         function(head, req) {
+          var row;
+          while(row = getRow()) {
+            send(row);
+          }
+        }
+        """
+      }
+    }
+
+    {:ok, _} = create_doc(db_name, ddoc)
+
+    resp =
+      Couch.get("/#{db_name}/_design/test/_list/you/me",
+        headers: [
+          "Content-Type": "application/x-www-form-urlencoded"
+        ]
+      )
+
+    assert resp.status_code == 200
+  end
+
+  test "HTTP header response set after getRow() called in _list function", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/lists/_list/setHeaderAfterGotRow/basicView")
+    assert resp.status_code == 400
+    assert resp.headers["X-My-Header"] == "MyHeader"
+    assert String.match?(resp.body, ~r/^bad request$/)
+  end
+
+  test "handling _all_docs by _list functions. the result should be equal", context do
+    db_name = context[:db_name]
+
+    resp_list = Couch.get("/#{db_name}/_design/lists/_list/allDocs/_all_docs")
+    assert resp_list.status_code == 200
+
+    resp_alldocs = Couch.get("/#{db_name}/_all_docs")
+
+    assert resp_list.body["total_rows"] == resp_alldocs.body["total_rows"]
+    assert resp_list.body["offset"] == resp_alldocs.body["offset"]
+    assert length(resp_list.body["rows"]) == length(resp_alldocs.body["rows"])
+    assert resp_list.body["rows"] == resp_alldocs.body["rows"]
+  end
+end
diff --git a/test/elixir/test/show_documents_test.exs b/test/elixir/test/show_documents_test.exs
new file mode 100644
index 0000000..a574c72
--- /dev/null
+++ b/test/elixir/test/show_documents_test.exs
@@ -0,0 +1,448 @@
+defmodule ShowDocumentsTest do
+  use CouchTestCase
+
+  @moduletag kind: :single_node
+
+  @ddoc %{
+    _id: "_design/template",
+    language: "javascript",
+    shows: %{
+      hello: """
+       function(doc, req) {
+        if (doc) {
+          return "Hello World";
+        } else {
+          if(req.id) {
+            return "New World";
+          } else {
+            return "Empty World";
+          }
+        }
+      }
+      """,
+      "just-name": """
+        function(doc, req) {
+        if (doc) {
+          return {
+            body : "Just " + doc.name
+          };
+        } else {
+          return {
+            body : "No such doc",
+            code : 404
+          };
+        }
+      }
+      """,
+      json: """
+       function(doc, req) {
+        return {
+          json : doc
+        }
+      }
+      """,
+      "req-info": """
+        function(doc, req) {
+        return {
+          json : req
+        }
+      }
+      """,
+      "show-deleted": """
+       function(doc, req) {
+        if(doc) {
+          return doc._id;
+        } else {
+          return "No doc " + req.id;
+        }
+      }
+      """,
+      "render-error": """
+        function(doc, req) {
+        return noSuchVariable;
+      }
+      """,
+      empty: """
+      function(doc, req) {
+        return "";
+      }
+      """,
+      fail: """
+       function(doc, req) {
+        return doc._id;
+      }
+      """,
+      "no-set-etag": """
+       function(doc, req) {
+        return {
+          headers : {
+            "Etag" : "skipped"
+          },
+          "body" : "something"
+        }
+      }
+      """,
+      "list-api": """
+       function(doc, req) {
+        start({"X-Couch-Test-Header": "Yeah"});
+        send("Hey");
+      }
+      """,
+      "list-api-provides": """
+       function(doc, req) {
+        provides("text", function(){
+            send("foo, ");
+            send("bar, ");
+            send("baz!");
+        })
+      }
+      """,
+      "list-api-provides-and-return": """
+       function(doc, req) {
+        provides("text", function(){
+            send("4, ");
+            send("5, ");
+            send("6, ");
+            return "7!";
+        })
+        send("1, ");
+        send("2, ");
+        return "3, ";
+      }
+      """,
+      "list-api-mix": """
+       function(doc, req) {
+        start({"X-Couch-Test-Header": "Yeah"});
+        send("Hey ");
+        return "Dude";
+      }
+      """,
+      "list-api-mix-with-header": """
+       function(doc, req) {
+        start({"X-Couch-Test-Header": "Yeah"});
+        send("Hey ");
+        return {
+          headers: {
+            "X-Couch-Test-Header-Awesome": "Oh Yeah!"
+          },
+          body: "Dude"
+        };
+      }
+      """,
+      "accept-switch": """
+       function(doc, req) {
+        if (req.headers["Accept"].match(/image/)) {
+          return {
+            // a 16x16 px version of the CouchDB logo
+            "base64" :
+      ["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV",
+      "BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/",
+      "AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7",
+      "/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6",
+      "wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA",
+      "AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5",
+      "zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx",
+      "vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT",
+      "LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''),
+            headers : {
+              "Content-Type" : "image/png",
+              "Vary" : "Accept" // we set this for proxy caches
+            }
+          };
+        } else {
+          return {
+            "body" : "accepting text requests",
+            headers : {
+              "Content-Type" : "text/html",
+              "Vary" : "Accept"
+            }
+          };
+        }
+      }
+      """,
+      provides: """
+       function(doc, req) {
+        registerType("foo", "application/foo","application/x-foo");
+
+        provides("html", function() {
+          return "Ha ha, you said \\"" + doc.word + "\\".";
+        });
+
+        provides("foo", function() {
+          return "foofoo";
+        });
+      }
+      """,
+      withSlash: """
+       function(doc, req) {
+        return { json: doc }
+      }
+      """,
+      secObj: """
+        function(doc, req) {
+        return { json: req.secObj };
+      }
+      """
+    }
+  }
+
+  setup_all do
+    db_name = random_db_name()
+    {:ok, _} = create_db(db_name)
+    on_exit(fn -> delete_db(db_name) end)
+
+    {:ok, _} = create_doc(db_name, @ddoc)
+
+    create_doc(db_name, %{_id: "test-doc-id", word: "plankton", name: "Rusty"})
+
+    {:ok, [db_name: db_name]}
+  end
+
+  test "show error", context do
+    db_name = context[:db_name]
+
+    resp = Couch.get("/#{db_name}/_design/template/_show/")
+    assert resp.status_code == 404
+    assert resp.body["reason"] == "Invalid path."
+  end
+
+  test "show with existing doc", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/hello/test-doc-id")
+    assert resp.body == "Hello World"
+    assert String.match?(resp.headers["Content-Type"], ~r/charset=utf-8/)
+
+    # Fix for COUCHDB-379
+    assert String.match?(resp.headers["Server"], ~r/^CouchDB/)
+  end
+
+  test "show without docid", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/hello")
+    assert resp.body == "Empty World"
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/empty")
+    assert resp.body == ""
+  end
+
+  test "show fail with non-existing docid", context do
+    db_name = context[:db_name]
+    resp = Couch.get("/#{db_name}/_design/template/_show/fail/nonExistingDoc")
+    assert resp.status_code == 404
+    assert resp.body["error"] == "not_found"
+  end
+
+  test "show with doc", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/just-name/test-doc-id")
+    assert resp.body == "Just Rusty"
+  end
+
+  test "show with missing doc", context do
+    db_name = context[:db_name]
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/just-name/missingdoc")
+    assert resp.status_code == 404
+    assert resp.body == "No such doc"
+  end
+
+  test "missing design doc", context do
+    db_name = context[:db_name]
+    resp = Couch.get("/#{db_name}/_design/missingddoc/_show/just-name/test-doc-id")
+    assert resp.status_code == 404
+    assert resp.body["error"] == "not_found"
+  end
+
+  test "show query parameters", context do
+    db_name = context[:db_name]
+
+    resp =
+      Couch.get("/#{db_name}/_design/template/_show/req-info/test-doc-id?foo=bar",
+        headers: [Accept: "text/html;text/plain;*/*", "X-Foo": "bar"]
+      )
+
+    assert resp.body["headers"]["X-Foo"] == "bar"
+    assert resp.body["query"] == %{"foo" => "bar"}
+    assert resp.body["method"] == "GET"
+    assert Enum.at(resp.body["path"], 5) == "test-doc-id"
+    assert resp.body["info"]["db_name"] == db_name
+  end
+
+  test "accept header switching - different mime has different etag", context do
+    db_name = context[:db_name]
+
+    resp =
+      Couch.get("/#{db_name}/_design/template/_show/accept-switch/test-doc-id",
+        headers: [Accept: "text/html;text/plain;*/*"]
+      )
+
+    assert String.match?(resp.headers["Content-Type"], ~r/text\/html/)
+    assert resp.headers["Vary"] == "Accept"
+
+    etag = resp.headers["etag"]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/template/_show/accept-switch/test-doc-id",
+        headers: [Accept: "image/png;*/*"]
+      )
+
+    assert String.match?(resp.body, ~r/PNG/)
+    assert resp.headers["Content-Type"] == "image/png"
+
+    etag2 = resp.headers["etag"]
+
+    assert etag != etag2
+  end
+
+  test "show with doc - etags", context do
+    db_name = context[:db_name]
+
+    doc = %{"_id" => "test-doc-id2", word: "plankton", name: "Rusty"}
+    doc = save(db_name, doc)
+
+    resp = Couch.get("/#{db_name}/_design/template/_show/just-name/test-doc-id2")
+
+    etag = resp.headers["etag"]
+
+    resp =
+      Couch.get("/#{db_name}/_design/template/_show/just-name/test-doc-id2",
+        headers: ["if-none-match": etag]
+      )
+
+    assert resp.status_code == 304
+
+    doc = Map.put(doc, "name", "Crusty")
+    save(db_name, doc)
+
+    resp =
+      Couch.get("/#{db_name}/_design/template/_show/just-name/test-doc-id2",
+        headers: ["if-none-match": etag]
+      )
+
+    assert resp.status_code == 200
+  end
+
+  test "JS can't set etag", context do
+    db_name = context[:db_name]
+
+    resp = Couch.get("/#{db_name}/_design/template/_show/no-set-etag/test-doc-id")
+    assert resp.headers["etag"] != "skipped"
+  end
+
+  test "the provides mime matcher", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/template/_show/provides/test-doc-id",
+        headers: [Accept: "text/html,application/atom+xml; q=0.9"]
+      )
+
+    assert String.match?(resp.headers["Content-Type"], ~r/text\/html/)
+    assert String.match?(resp.headers["Content-Type"], ~r/charset=utf-8/)
+    assert resp.body == "Ha ha, you said \"plankton\"."
+  end
+
+  test "registering types works", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/template/_show/provides/test-doc-id",
+        headers: [Accept: "application/x-foo"]
+      )
+
+    assert resp.headers["Content-Type"] == "application/x-foo"
+    assert String.match?(resp.body, ~r/foofoo/)
+  end
+
+  test "the provides mime matcher without a match", context do
+    db_name = context[:db_name]
+
+    resp =
+      Couch.get("/#{db_name}/_design/template/_show/provides/test-doc-id",
+        headers: [Accept: "text/monkeys"]
+      )
+
+    assert resp.body["error"] == "not_acceptable"
+  end
+
+  test "id with slash", context do
+    db_name = context[:db_name]
+
+    doc3 = %{"_id" => "a/b/c", "a" => 1}
+    save(db_name, doc3)
+    resp = Couch.get("/#{db_name}/_design/template/_show/withSlash/a/b/c")
+    assert resp.status_code == 200
+  end
+
+  test "show with non-existing docid", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/hello/nonExistingDoc")
+    assert resp.body == "New World"
+  end
+
+  test "list() compatible API", context do
+    db_name = context[:db_name]
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/list-api/foo")
+    assert resp.body == "Hey"
+    assert resp.headers["X-Couch-Test-Header"] == "Yeah"
+  end
+
+  test "list() compatible API with provides function", context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get("/#{db_name}/_design/template/_show/list-api-provides/foo?format=text")
+
+    assert resp.body == "foo, bar, baz!"
+  end
+
+  test "should keep next result order: chunks + return value + provided chunks + provided return value",
+       context do
+    db_name = context[:db_name]
+
+    resp =
+      Rawresp.get(
+        "/#{db_name}/_design/template/_show/list-api-provides-and-return/foo?format=text"
+      )
+
+    assert resp.body == "1, 2, 3, 4, 5, 6, 7!"
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/list-api-mix/foo")
+    assert resp.body == "Hey Dude"
+    assert resp.headers["X-Couch-Test-Header"] == "Yeah"
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/list-api-mix-with-header/foo")
+    assert resp.body == "Hey Dude"
+    assert resp.headers["X-Couch-Test-Header"] == "Yeah"
+    assert resp.headers["X-Couch-Test-Header-Awesome"] == "Oh Yeah!"
+  end
+
+  test "deleted docs", context do
+    db_name = context[:db_name]
+
+    doc = save(db_name, %{"_id" => "testdoc", "foo" => 1})
+
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/show-deleted/testdoc")
+    assert resp.body == "testdoc"
+
+    Couch.delete("/#{db_name}/testdoc?rev=#{doc["_rev"]}")
+    resp = Rawresp.get("/#{db_name}/_design/template/_show/show-deleted/testdoc")
+    assert resp.body == "No doc testdoc"
+  end
+
+  @tag :with_db
+  test "security object", context do
+    db_name = context[:db_name]
+    {:ok, _} = create_doc(db_name, @ddoc)
+    {:ok, _} = create_doc(db_name, %{_id: "testdoc", foo: 1})
+
+    Couch.put("/#{db_name}/_security", body: %{foo: true})
+
+    retry_until(fn ->
+      resp = Couch.get("/#{db_name}/_design/template/_show/secObj")
+      assert resp.body["foo"]
+    end)
+  end
+end
diff --git a/test/javascript/tests/list_views.js b/test/javascript/tests/list_views.js
index e255e15..2d74586 100644
--- a/test/javascript/tests/list_views.js
+++ b/test/javascript/tests/list_views.js
@@ -9,7 +9,7 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-
+couchTests.elixir = true;
 couchTests.list_views = function(debug) {
 
   var db_name = get_random_db_name();
diff --git a/test/javascript/tests/show_documents.js b/test/javascript/tests/show_documents.js
index 172a795..e604f30 100644
--- a/test/javascript/tests/show_documents.js
+++ b/test/javascript/tests/show_documents.js
@@ -9,7 +9,7 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-
+couchTests.elixir = true
 couchTests.show_documents = function(debug) {
 
   var db_name = get_random_db_name();