| defmodule ViewErrorsTest do |
| use CouchTestCase |
| |
| @moduletag kind: :single_node |
| |
| @document %{integer: 1, string: "1", array: [1, 2, 3]} |
| |
| @tag :with_db |
| test "emit undefined key results as null", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| map_fun = """ |
| function(doc) { |
| emit(doc.undef, null); |
| } |
| """ |
| |
| # emitting a key value that is undefined should result in that row |
| # being included in the view results as null |
| results = query(db_name, map_fun) |
| assert results["total_rows"] == 1 |
| assert Enum.at(results["rows"], 0)["key"] == :null |
| end |
| |
| @tag :with_db |
| test "exception in map function", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| map_fun = """ |
| function(doc) { |
| doc.undef(); // throws an error |
| } |
| """ |
| |
| # if a view function throws an exception, its results are not included in |
| # the view index, but the view does not itself raise an error |
| results = query(db_name, map_fun) |
| assert results["total_rows"] == 0 |
| end |
| |
| @tag :with_db |
| test "emit undefined value results as null", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| map_fun = """ |
| function(doc) { |
| emit([doc._id, doc.undef], null); |
| } |
| """ |
| |
| # if a view function includes an undefined value in the emitted key or |
| # value, it is treated as null |
| results = query(db_name, map_fun) |
| assert results["total_rows"] == 1 |
| |
| key = |
| results["rows"] |
| |> Enum.at(0) |
| |> Map.get("key") |
| |> Enum.at(1) |
| |
| assert key == :null |
| end |
| |
| @tag :with_db |
| test "query view with invalid params", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| body = %{ |
| language: "javascript", |
| map: "function(doc){emit(doc.integer)}" |
| } |
| |
| # querying a view with invalid params should give a resonable error message |
| resp = |
| Couch.post("/#{db_name}/_all_docs?startkey=foo", |
| headers: ["Content-Type": "application/json"], |
| body: body |
| ) |
| |
| assert resp.body["error"] == "bad_request" |
| |
| resp = |
| Couch.post("/#{db_name}/_all_docs", |
| headers: ["Content-Type": "application/x-www-form-urlencoded"], |
| body: body |
| ) |
| |
| assert resp.status_code == 415 |
| end |
| |
| @tag :with_db |
| test "query parse error", context do |
| db_name = context[:db_name] |
| |
| map_fun = """ |
| function(doc) { |
| emit(doc.integer, doc.integer); |
| } |
| """ |
| |
| ddoc_name = create_view(db_name, map_fun) |
| |
| resp = Couch.get("/#{db_name}/#{ddoc_name}/_view/view", query: [group: true]) |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| |
| map_fun = "function() {emit(null, null)}" |
| ddoc_name = create_view(db_name, map_fun) |
| |
| resp = |
| Couch.get("/#{db_name}/#{ddoc_name}/_view/view", query: [startkey: 2, endkey: 1]) |
| |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| assert String.contains?(resp.body["reason"], "No rows can match") |
| |
| design_doc = %{ |
| _id: "_design/test", |
| language: "javascript", |
| views: %{ |
| no_reduce: %{map: "function(doc) {emit(doc._id, null);}"}, |
| with_reduce: %{ |
| map: "function (doc) {emit(doc.integer, doc.integer)};", |
| reduce: "function (keys, values) { return sum(values); };" |
| } |
| } |
| } |
| |
| {:ok, _} = create_doc(db_name, design_doc) |
| |
| resp = Couch.get("/#{db_name}/_design/test/_view/no_reduce", query: [group: true]) |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| |
| resp = Couch.get("/#{db_name}/_design/test/_view/no_reduce", query: [group_level: 1]) |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| |
| resp = Couch.get("/#{db_name}/_design/test/_view/no_reduce", query: [reduce: true]) |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| |
| resp = Couch.get("/#{db_name}/_design/test/_view/no_reduce", query: [reduce: false]) |
| assert resp.status_code == 200 |
| |
| resp = |
| Couch.get("/#{db_name}/_design/test/_view/with_reduce", |
| query: [group: true, reduce: false] |
| ) |
| |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| |
| resp = |
| Couch.get("/#{db_name}/_design/test/_view/with_reduce", |
| query: [group_level: 1, reduce: false] |
| ) |
| |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "query_parse_error" |
| end |
| |
| @tag :with_db |
| test "infinite loop", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| design_doc3 = %{ |
| _id: "_design/infinite", |
| language: "javascript", |
| views: %{ |
| infinite_loop: %{ |
| map: "function(doc) {while(true){emit(doc,doc);}};" |
| } |
| } |
| } |
| |
| {:ok, _} = create_doc(db_name, design_doc3) |
| |
| resp = Couch.get("/#{db_name}/_design/infinite/_view/infinite_loop") |
| assert resp.status_code == 500 |
| # This test has two different races. The first is whether |
| # the while loop exhausts the JavaScript RAM limits before |
| # timing. The second is a race between which of two timeouts |
| # fires first. The first timeout is the couch_os_process |
| # waiting for data back from couchjs. The second is the |
| # gen_server call to couch_os_process. |
| err_name = resp.body["error"] |
| assert Enum.member?(["os_process_error", "timeout", "InternalError"], err_name) |
| end |
| |
| @tag :with_db |
| test "error responses for invalid multi-get bodies", context do |
| db_name = context[:db_name] |
| |
| design_doc = %{ |
| _id: "_design/test", |
| language: "javascript", |
| views: %{ |
| no_reduce: %{map: "function(doc) {emit(doc._id, null);}"}, |
| with_reduce: %{ |
| map: "function (doc) {emit(doc.integer, doc.integer)};", |
| reduce: "function (keys, values) { return sum(values); };" |
| } |
| } |
| } |
| |
| {:ok, _} = create_doc(db_name, design_doc) |
| |
| resp = |
| Couch.post("/#{db_name}/_design/test/_view/no_reduce", |
| body: "[]" |
| ) |
| |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "bad_request" |
| assert resp.body["reason"] == "Request body must be a JSON object" |
| |
| resp = |
| Couch.post("/#{db_name}/_design/test/_view/no_reduce", |
| body: %{keys: 1} |
| ) |
| |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "bad_request" |
| assert resp.body["reason"] == "`keys` member must be an array." |
| end |
| |
| @tag :with_db |
| test "reduce overflow error", context do |
| db_name = context[:db_name] |
| {:ok, _} = create_doc(db_name, @document) |
| |
| design_doc2 = %{ |
| _id: "_design/testbig", |
| language: "javascript", |
| views: %{ |
| reduce_too_big: %{ |
| map: "function (doc) {emit(doc.integer, doc.integer)};", |
| reduce: |
| "function (keys, values) { var chars = []; for (var i=0; i < 1000; i++) {chars.push('wazzap');};return chars; };" |
| } |
| } |
| } |
| |
| {:ok, _} = create_doc(db_name, design_doc2) |
| |
| resp = Couch.get("/#{db_name}/_design/testbig/_view/reduce_too_big") |
| assert resp.status_code == 200 |
| # if the reduce grows to fast, throw an overflow error |
| assert Enum.at(resp.body["rows"], 0)["error"] == "reduce_overflow_error" |
| end |
| |
| @tag :with_db |
| test "temporary view should give error message", context do |
| db_name = context[:db_name] |
| |
| resp = |
| Couch.post("/#{db_name}/_temp_view", |
| headers: ["Content-Type": "application/json"], |
| body: %{ |
| language: "javascript", |
| map: "function(doc){emit(doc.integer)}" |
| } |
| ) |
| |
| assert resp.status_code == 410 |
| assert resp.body["error"] == "gone" |
| assert resp.body["reason"] == "Temporary views are not supported in CouchDB" |
| end |
| |
| defp create_view(db_name, map_fun) do |
| ddoc_name = "_design/temp_#{System.unique_integer([:positive])}" |
| |
| ddoc = %{ |
| _id: ddoc_name, |
| language: "javascript", |
| views: %{ |
| view: %{map: map_fun} |
| } |
| } |
| |
| {:ok, _} = create_doc(db_name, ddoc) |
| ddoc_name |
| end |
| end |