summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoan Touzet <wohali@users.noreply.github.com>2020-07-22 16:33:00 +0000
committerGitHub <noreply@github.com>2020-07-22 16:33:00 +0000
commite9284356c67c12b84b7c9996ae0fe0a64b1e47c5 (patch)
tree2bc0bcac35458b4d5407c26eb7679d2b83d4b0d3
parent8f838497407cb9ee0114e13f8b6527c2564b6a38 (diff)
parentb518f01a4def8eac085c76599ecf1b03aeff06f8 (diff)
downloadcouchdb-asmyaml.tar.gz
Merge branch 'master' into asmyamlasmyaml
-rw-r--r--test/elixir/README.md4
-rw-r--r--test/elixir/test/update_documents_test.exs324
-rw-r--r--test/elixir/test/view_sandboxing_test.exs191
-rw-r--r--test/javascript/tests/update_documents.js2
-rw-r--r--test/javascript/tests/view_sandboxing.js1
5 files changed, 519 insertions, 3 deletions
diff --git a/test/elixir/README.md b/test/elixir/README.md
index cf529438d..566364a34 100644
--- a/test/elixir/README.md
+++ b/test/elixir/README.md
@@ -93,7 +93,7 @@ X means done, - means partially
- [X] Port security_validation.js
- [ ] Port show_documents.js
- [ ] Port stats.js
- - [ ] Port update_documents.js
+ - [X] Port update_documents.js
- [X] Port users_db.js
- [ ] Port users_db_security.js
- [X] Port utf8.js
@@ -109,7 +109,7 @@ X means done, - means partially
- [ ] Port view_multi_key_temp.js
- [X] Port view_offsets.js
- [X] Port view_pagination.js
- - [ ] Port view_sandboxing.js
+ - [X] Port view_sandboxing.js
- [X] Port view_update_seq.js
# Using ExUnit to write unit tests
diff --git a/test/elixir/test/update_documents_test.exs b/test/elixir/test/update_documents_test.exs
new file mode 100644
index 000000000..c29b31a4d
--- /dev/null
+++ b/test/elixir/test/update_documents_test.exs
@@ -0,0 +1,324 @@
+defmodule UpdateDocumentsTest do
+ use CouchTestCase
+
+ @ddoc %{
+ _id: "_design/update",
+ language: "javascript",
+ updates: %{
+ hello: """
+ function(doc, req) {
+ if (!doc) {
+ if (req.id) {
+ return [
+ // Creates a new document with the PUT docid,
+ { _id : req.id,
+ reqs : [req] },
+ // and returns an HTML response to the client.
+ "<p>New World</p>"];
+ };
+ //
+ return [null, "<p>Empty World</p>"];
+ };
+ // we can update the document inline
+ doc.world = "hello";
+ // we can record aspects of the request or use them in application logic.
+ doc.reqs && doc.reqs.push(req);
+ doc.edited_by = req.userCtx;
+ return [doc, "<p>hello doc</p>"];
+ }
+ """,
+ "in-place": """
+ function(doc, req) {
+ var field = req.query.field;
+ var value = req.query.value;
+ var message = "set "+field+" to "+value;
+ doc[field] = value;
+ return [doc, message];
+ }
+ """,
+ "form-update": """
+ function(doc, req) {
+ for (var field in req.form) {
+ doc[field] = req.form[field];
+ }
+ var message = "updated doc from form";
+ return [doc, message];
+ }
+ """,
+ "bump-counter": """
+ function(doc, req) {
+ if (!doc.counter) doc.counter = 0;
+ doc.counter += 1;
+ var message = "<h1>bumped it!</h1>";
+ return [doc, message];
+ }
+ """,
+ error: """
+ function(doc, req) {
+ superFail.badCrash;
+ }
+ """,
+ "get-uuid": """
+ function(doc, req) {
+ return [null, req.uuid];
+ }
+ """,
+ "code-n-bump": """
+ function(doc,req) {
+ if (!doc.counter) doc.counter = 0;
+ doc.counter += 1;
+ var message = "<h1>bumped it!</h1>";
+ resp = {"code": 302, "body": message}
+ return [doc, resp];
+ }
+ """,
+ "resp-code": """
+ function(doc,req) {
+ resp = {"code": 302}
+ return [null, resp];
+ }
+ """,
+ "resp-code-and-json": """
+ function(doc,req) {
+ resp = {"code": 302, "json": {"ok": true}}
+ return [{"_id": req["uuid"]}, resp];
+ }
+ """,
+ binary: """
+ function(doc, req) {
+ var resp = {
+ "headers" : {
+ "Content-Type" : "application/octet-stream"
+ },
+ "base64" : "aGVsbG8gd29ybGQh" // "hello world!" encoded
+ };
+ return [doc, resp];
+ }
+ """,
+ empty: """
+ function(doc, req) {
+ return [{}, 'oops'];
+ }
+ """
+ }
+ }
+
+ @document %{word: "plankton", name: "Rusty"}
+
+ @tag :with_db
+ test "update error invalid path", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ resp = Couch.post("/#{db_name}/_design/update/_update/")
+ assert resp.status_code == 404
+ assert resp.body["reason"] == "Invalid path."
+ end
+
+ @tag :with_db
+ test "update document", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/hello/#{docid}")
+ assert resp.status_code == 201
+ assert resp.body == "<p>hello doc</p>"
+ assert String.contains?(resp.headers["Content-Type"], "charset=utf-8")
+ assert resp.headers["X-Couch-Id"] == docid
+
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["world"] == "hello"
+
+ # Fix for COUCHDB-379
+ assert String.starts_with?(resp.headers["Server"], "CouchDB")
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/hello")
+ assert resp.status_code == 200
+ assert resp.body == "<p>Empty World</p>"
+ end
+
+ @tag :with_db
+ test "GET is not allowed", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ resp = Couch.get("/#{db_name}/_design/update/_update/hello")
+ assert resp.body["error"] == "method_not_allowed"
+ end
+
+ @tag :with_db
+ test "doc can be created", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ resp = Couch.get("/#{db_name}/nonExistingDoc")
+ assert resp.status_code == 404
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/hello/nonExistingDoc")
+ assert resp.status_code == 201
+ assert resp.body == "<p>New World</p>"
+
+ resp = Couch.get("/#{db_name}/nonExistingDoc")
+ assert resp.status_code == 200
+ end
+
+ @tag :with_db
+ test "in place update", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ resp =
+ Couch.put(
+ "/#{db_name}/_design/update/_update/in-place/#{docid}?field=title&value=test"
+ )
+
+ assert resp.status_code == 201
+ assert resp.body == "set title to test"
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["title"] == "test"
+ end
+
+ @tag :with_db
+ test "form update via application/x-www-form-urlencoded", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ resp =
+ Couch.put(
+ "/#{db_name}/_design/update/_update/form-update/#{docid}",
+ headers: ["Content-Type": "application/x-www-form-urlencoded"],
+ body: "formfoo=bar&formbar=foo"
+ )
+
+ assert resp.status_code == 201
+ assert resp.body == "updated doc from form"
+
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["formfoo"] == "bar"
+ assert resp.body["formbar"] == "foo"
+ end
+
+ @tag :with_db
+ test "bump counter", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ resp =
+ Couch.put("/#{db_name}/_design/update/_update/bump-counter/#{docid}",
+ headers: ["X-Couch-Full-Commit": "true"]
+ )
+
+ assert resp.status_code == 201
+ assert resp.body == "<h1>bumped it!</h1>"
+
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["counter"] == 1
+
+ resp =
+ Couch.put("/#{db_name}/_design/update/_update/bump-counter/#{docid}",
+ headers: ["X-Couch-Full-Commit": "true"]
+ )
+
+ newrev = resp.headers["X-Couch-Update-NewRev"]
+
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["counter"] == 2
+ assert resp.body["_rev"] == newrev
+ end
+
+ @tag :with_db
+ test "Server provides UUID when POSTing without an ID in the URL", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+ resp = Couch.put("/#{db_name}/_design/update/_update/get-uuid/")
+ assert resp.status_code == 200
+ assert String.length(resp.body) == 32
+ end
+
+ @tag :with_db
+ test "COUCHDB-1229 - allow slashes in doc ids for update handlers", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ create_doc(db_name, %{_id: "with/slash", counter: 1})
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/bump-counter/with/slash")
+ assert resp.status_code == 201
+ assert resp.body == "<h1>bumped it!</h1>"
+
+ resp = Couch.get("/#{db_name}/with%2Fslash")
+ assert resp.status_code == 200
+ assert resp.body["counter"] == 2
+ end
+
+ @tag :with_db
+ test "COUCHDB-648 - the code in the JSON response should be honored", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ Couch.put("/#{db_name}/_design/update/_update/bump-counter/#{docid}")
+ Couch.put("/#{db_name}/_design/update/_update/bump-counter/#{docid}")
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/code-n-bump/#{docid}")
+ assert resp.status_code == 302
+ assert resp.body == "<h1>bumped it!</h1>"
+
+ resp = Couch.get("/#{db_name}/#{docid}")
+ assert resp.status_code == 200
+ assert resp.body["counter"] == 3
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/resp-code/")
+ assert resp.status_code == 302
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/resp-code-and-json/")
+ assert resp.status_code == 302
+ assert resp.body["ok"] == true
+ end
+
+ @tag :with_db
+ test "base64 response", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ {:ok, resp} = create_doc(db_name, @document)
+ docid = resp.body["id"]
+
+ resp =
+ Couch.put("/#{db_name}/_design/update/_update/binary/#{docid}",
+ body: "rubbish"
+ )
+
+ assert resp.status_code == 201
+ assert resp.body == "hello world!"
+ assert String.contains?(resp.headers["Content-Type"], "application/octet-stream")
+ end
+
+ @tag :with_db
+ test "Insert doc with empty id", context do
+ db_name = context[:db_name]
+ create_doc(db_name, @ddoc)
+
+ resp = Couch.put("/#{db_name}/_design/update/_update/empty/foo")
+ assert resp.status_code == 400
+ assert resp.body["reason"] == "Document id must not be empty"
+ end
+end
diff --git a/test/elixir/test/view_sandboxing_test.exs b/test/elixir/test/view_sandboxing_test.exs
new file mode 100644
index 000000000..af0928efa
--- /dev/null
+++ b/test/elixir/test/view_sandboxing_test.exs
@@ -0,0 +1,191 @@
+defmodule ViewSandboxingTest do
+ use CouchTestCase
+
+ @document %{integer: 1, string: "1", array: [1, 2, 3]}
+
+ @tag :with_db
+ test "attempting to change the document has no effect", context do
+ db_name = context[:db_name]
+
+ {:ok, _} = create_doc(db_name, @document)
+
+ map_fun = """
+ function(doc) {
+ doc.integer = 2;
+ emit(null, doc);
+ }
+ """
+
+ resp = query(db_name, map_fun, nil, %{include_docs: true})
+ rows = resp["rows"]
+ # either we have an error or our doc is unchanged
+ assert resp["total_rows"] == 0 or Enum.at(rows, 0)["doc"]["integer"] == 1
+
+ map_fun = """
+ function(doc) {
+ doc.array[0] = 0;
+ emit(null, doc);
+ }
+ """
+
+ resp = query(db_name, map_fun, nil, %{include_docs: true})
+ row = Enum.at(resp["rows"], 0)
+ # either we have an error or our doc is unchanged
+ assert resp["total_rows"] == 0 or Enum.at(row["doc"]["array"], 0) == 1
+ end
+
+ @tag :with_db
+ test "view cannot invoke interpreter internals", context do
+ db_name = context[:db_name]
+ {:ok, _} = create_doc(db_name, @document)
+
+ map_fun = """
+ function(doc) {
+ gc();
+ emit(null, doc);
+ }
+ """
+
+ # make sure that a view cannot invoke interpreter internals such as the
+ # garbage collector
+ resp = query(db_name, map_fun)
+ assert resp["total_rows"] == 0
+ end
+
+ @tag :with_db
+ test "view cannot access the map_funs and map_results array", context do
+ db_name = context[:db_name]
+ {:ok, _} = create_doc(db_name, @document)
+
+ map_fun = """
+ function(doc) {
+ map_funs.push(1);
+ emit(null, doc);
+ }
+ """
+
+ resp = query(db_name, map_fun)
+ assert resp["total_rows"] == 0
+
+ map_fun = """
+ function(doc) {
+ map_results.push(1);
+ emit(null, doc);
+ }
+ """
+
+ resp = query(db_name, map_fun)
+ assert resp["total_rows"] == 0
+ end
+
+ @tag :with_db
+ test "COUCHDB-925 - altering 'doc' variable in map function affects other map functions",
+ context do
+ db_name = context[:db_name]
+
+ ddoc = %{
+ _id: "_design/foobar",
+ language: "javascript",
+ views: %{
+ view1: %{
+ map: """
+ function(doc) {
+ if (doc.values) {
+ doc.values = [666];
+ }
+ if (doc.tags) {
+ doc.tags.push("qwerty");
+ }
+ if (doc.tokens) {
+ doc.tokens["c"] = 3;
+ }
+ }
+ """
+ },
+ view2: %{
+ map: """
+ function(doc) {
+ if (doc.values) {
+ emit(doc._id, doc.values);
+ }
+ if (doc.tags) {
+ emit(doc._id, doc.tags);
+ }
+ if (doc.tokens) {
+ emit(doc._id, doc.tokens);
+ }
+ }
+ """
+ }
+ }
+ }
+
+ doc1 = %{
+ _id: "doc1",
+ values: [1, 2, 3]
+ }
+
+ doc2 = %{
+ _id: "doc2",
+ tags: ["foo", "bar"],
+ tokens: %{a: 1, b: 2}
+ }
+
+ {:ok, _} = create_doc(db_name, ddoc)
+ {:ok, _} = create_doc(db_name, doc1)
+ {:ok, _} = create_doc(db_name, doc2)
+
+ resp1 = view(db_name, "foobar/view1")
+ resp2 = view(db_name, "foobar/view2")
+
+ assert Enum.empty?(resp1.body["rows"])
+ assert length(resp2.body["rows"]) == 3
+
+ assert doc1[:_id] == Enum.at(resp2.body["rows"], 0)["key"]
+ assert doc2[:_id] == Enum.at(resp2.body["rows"], 1)["key"]
+ assert doc2[:_id] == Enum.at(resp2.body["rows"], 2)["key"]
+
+ assert length(Enum.at(resp2.body["rows"], 0)["value"]) == 3
+
+ row0_values = Enum.at(resp2.body["rows"], 0)["value"]
+
+ assert Enum.at(row0_values, 0) == 1
+ assert Enum.at(row0_values, 1) == 2
+ assert Enum.at(row0_values, 2) == 3
+
+ row1_values = Enum.at(resp2.body["rows"], 1)["value"]
+ row2_values = Enum.at(resp2.body["rows"], 2)["value"]
+
+ # we can't be 100% sure about the order for the same key
+ assert (is_map(row1_values) and row1_values["a"] == 1) or
+ (is_list(row1_values) and Enum.at(row1_values, 0) == "foo")
+
+ assert (is_map(row1_values) and row1_values["b"] == 2) or
+ (is_list(row1_values) and Enum.at(row1_values, 1) == "bar")
+
+ assert (is_map(row2_values) and row2_values["a"] == 1) or
+ (is_list(row2_values) and Enum.at(row2_values, 0) == "foo")
+
+ assert (is_map(row2_values) and row2_values["b"] == 2) or
+ (is_list(row2_values) and Enum.at(row2_values, 1) == "bar")
+
+ assert is_list(row1_values) or !Map.has_key?(row1_values, "c")
+ assert is_list(row2_values) or !Map.has_key?(row2_values, "c")
+ end
+
+ @tag :with_db
+ test "runtime code evaluation can be prevented", context do
+ db_name = context[:db_name]
+ {:ok, _} = create_doc(db_name, @document)
+
+ map_fun = """
+ function(doc) {
+ var glob = emit.constructor('return this')();
+ emit(doc._id, null);
+ }
+ """
+
+ resp = query(db_name, map_fun)
+ assert resp["total_rows"] == 0
+ end
+end
diff --git a/test/javascript/tests/update_documents.js b/test/javascript/tests/update_documents.js
index 6cd4a91d6..913c99a57 100644
--- a/test/javascript/tests/update_documents.js
+++ b/test/javascript/tests/update_documents.js
@@ -10,7 +10,7 @@
// License for the specific language governing permissions and limitations under
// the License.
-
+couchTests.elixir = true
couchTests.update_documents = function(debug) {
var db_name = get_random_db_name();
var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});
diff --git a/test/javascript/tests/view_sandboxing.js b/test/javascript/tests/view_sandboxing.js
index 1cdd815de..0e5f308a9 100644
--- a/test/javascript/tests/view_sandboxing.js
+++ b/test/javascript/tests/view_sandboxing.js
@@ -10,6 +10,7 @@
// License for the specific language governing permissions and limitations under
// the License.
+couchTests.elixir = true
couchTests.view_sandboxing = function(debug) {
var db_name = get_random_db_name();
var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"});