| defmodule AllDocsTest do |
| use CouchTestCase |
| |
| @moduletag :all_docs |
| |
| @moduledoc """ |
| Test CouchDB _all_docs |
| This is a port of the all_docs.js suite |
| """ |
| |
| # TODO: do we need to bring this in? |
| # var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"}, {w: 3}); |
| |
| @tag :with_db |
| test "All Docs tests", context do |
| db_name = context[:db_name] |
| resp1 = Couch.post("/#{db_name}", body: %{:_id => "0", :a => 1, :b => 1}).body |
| resp2 = Couch.post("/#{db_name}", body: %{:_id => "3", :a => 4, :b => 16}).body |
| resp3 = Couch.post("/#{db_name}", body: %{:_id => "1", :a => 2, :b => 4}).body |
| resp4 = Couch.post("/#{db_name}", body: %{:_id => "2", :a => 3, :b => 9}).body |
| |
| assert resp1["ok"] |
| assert resp2["ok"] |
| assert resp3["ok"] |
| assert resp4["ok"] |
| |
| revs = [resp1["rev"], resp2["rev"], resp3["rev"], resp4["rev"]] |
| |
| # Check _all_docs |
| resp = Couch.get("/#{db_name}/_all_docs").body |
| rows = resp["rows"] |
| assert resp["total_rows"] == length(rows) |
| |
| Enum.each(rows, fn row -> |
| assert row["id"] >= "0" && row["id"] <= "4" |
| end) |
| |
| # Check _all_docs with descending=true |
| resp = Couch.get("/#{db_name}/_all_docs", query: %{:descending => true}).body |
| rows = resp["rows"] |
| assert resp["total_rows"] == length(rows) |
| |
| # Check _all_docs offset |
| retry_until(fn -> |
| resp = Couch.get("/#{db_name}/_all_docs", query: %{:startkey => "\"2\""}).body |
| assert resp["offset"] == 2 |
| end) |
| |
| # Confirm that queries may assume raw collation |
| resp = |
| Couch.get( |
| "/#{db_name}/_all_docs", |
| query: %{ |
| :startkey => "\"org.couchdb.user:\"", |
| :endkey => "\"org.couchdb.user;\"" |
| } |
| ) |
| |
| assert Enum.empty?(resp.body["rows"]) |
| |
| # Check that all docs show up in the changes feed; order can vary |
| resp = Couch.get("/#{db_name}/_changes").body |
| |
| Enum.each(resp["results"], fn row -> |
| assert Enum.member?(revs, hd(row["changes"])["rev"]), |
| "doc #{row["id"]} should be in changes" |
| end) |
| |
| # Check that deletions also show up right |
| doc1 = Couch.get("/#{db_name}/1").body |
| assert Couch.delete("/#{db_name}/1", query: %{:rev => doc1["_rev"]}).body["ok"] |
| changes = Couch.get("/#{db_name}/_changes").body["results"] |
| assert length(changes) == 4 |
| |
| retry_until(fn -> |
| deleted = Enum.filter(changes, fn row -> row["deleted"] end) |
| assert length(deleted) == 1 |
| assert hd(deleted)["id"] == "1" |
| end) |
| |
| # (remember old seq) |
| orig_doc = Enum.find(changes, fn row -> row["id"] == "3" end) |
| # Perform an update |
| doc3 = Couch.get("/#{db_name}/3").body |
| doc3 = Map.put(doc3, :updated, "totally") |
| assert Couch.put("/#{db_name}/3", body: doc3).body["ok"] |
| |
| # The update should make doc id 3 have another seq num |
| # (not nec. higher or the last though) |
| changes = Couch.get("/#{db_name}/_changes").body["results"] |
| assert length(changes) == 4 |
| updated_doc = Enum.find(changes, fn row -> row["id"] == "3" end) |
| assert orig_doc["seq"] != updated_doc["seq"], "seq num should be different" |
| |
| # Ok, now let's see what happens with include docs |
| changes = |
| Couch.get("/#{db_name}/_changes", query: %{:include_docs => true}).body["results"] |
| |
| assert length(changes) == 4 |
| updated_doc = Enum.find(changes, fn row -> row["id"] == doc3["_id"] end) |
| assert updated_doc["doc"]["updated"] == "totally" |
| |
| deleted_doc = Enum.find(changes, fn row -> row["deleted"] end) |
| assert deleted_doc["doc"]["_deleted"] |
| |
| # Test _all_docs with keys |
| rows = |
| Couch.post( |
| "/#{db_name}/_all_docs", |
| query: %{:include_docs => true}, |
| body: %{:keys => ["1"]} |
| ).body["rows"] |
| |
| row = hd(rows) |
| assert length(rows) == 1 |
| assert row["key"] == "1" |
| assert row["id"] == "1" |
| assert row["value"]["deleted"] |
| assert row["doc"] == :null |
| |
| # Add conflicts |
| conflicted_doc1 = %{ |
| :_id => "3", |
| :_rev => "2-aa01552213fafa022e6167113ed01087", |
| :value => "X" |
| } |
| |
| conflicted_doc2 = %{ |
| :_id => "3", |
| :_rev => "2-ff01552213fafa022e6167113ed01087", |
| :value => "Z" |
| } |
| |
| assert Couch.put( |
| "/#{db_name}/3", |
| query: %{:new_edits => false}, |
| body: conflicted_doc1 |
| ).body["ok"] |
| |
| assert Couch.put( |
| "/#{db_name}/3", |
| query: %{:new_edits => false}, |
| body: conflicted_doc2 |
| ).body["ok"] |
| |
| win_rev = Couch.get("/#{db_name}/3").body |
| |
| changes = |
| Couch.get( |
| "/#{db_name}/_changes", |
| query: %{:include_docs => true, :conflicts => true, :style => "all_docs"} |
| ).body["results"] |
| |
| doc3 = Enum.find(changes, fn row -> row["id"] == "3" end) |
| assert doc3["id"] == "3" |
| assert length(doc3["changes"]) == 3 |
| assert win_rev["_rev"] == hd(doc3["changes"])["rev"] |
| assert is_list(doc3["doc"]["_conflicts"]) |
| assert length(doc3["doc"]["_conflicts"]) == 2 |
| |
| rows = |
| Couch.get( |
| "/#{db_name}/_all_docs", |
| query: %{:include_docs => true, :conflicts => true} |
| ).body["rows"] |
| |
| assert length(rows) == 3 |
| change = hd(tl(tl(rows))) |
| assert change["key"] == "3" |
| assert change["id"] == "3" |
| assert change["value"]["rev"] == win_rev["_rev"] |
| assert change["doc"]["_rev"] == win_rev["_rev"] |
| assert change["doc"]["_id"] == "3" |
| assert is_list(change["doc"]["_conflicts"]) |
| assert length(change["doc"]["_conflicts"]) == 2 |
| |
| # Test that _all_docs collates sanely |
| assert Couch.post("/#{db_name}", body: %{:_id => "Z", :foo => "Z"}).body["ok"] |
| assert Couch.post("/#{db_name}", body: %{:_id => "a", :foo => "a"}).body["ok"] |
| |
| rows = |
| Couch.get( |
| "/#{db_name}/_all_docs", |
| query: %{:startkey => "\"Z\"", :endkey => "\"Z\""} |
| ).body["rows"] |
| |
| assert length(rows) == 1 |
| end |
| |
| @tag :with_db |
| test "GET with one key", context do |
| db_name = context[:db_name] |
| |
| {:ok, _} = create_doc( |
| db_name, |
| %{ |
| _id: "foo", |
| bar: "baz" |
| } |
| ) |
| |
| {:ok, _} = create_doc( |
| db_name, |
| %{ |
| _id: "foo2", |
| bar: "baz2" |
| } |
| ) |
| |
| resp = Couch.get( |
| "/#{db_name}/_all_docs", |
| query: %{ |
| :key => "\"foo\"", |
| } |
| ) |
| |
| assert resp.status_code == 200 |
| assert length(Map.get(resp, :body)["rows"]) == 1 |
| end |
| |
| |
| @tag :with_db |
| test "POST with empty body", context do |
| db_name = context[:db_name] |
| |
| resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..2)}) |
| assert resp.status_code == 201 |
| |
| resp = Couch.post( |
| "/#{db_name}/_all_docs", |
| body: %{} |
| ) |
| |
| assert resp.status_code == 200 |
| assert length(Map.get(resp, :body)["rows"]) == 3 |
| end |
| |
| @tag :with_db |
| test "POST with keys and limit", context do |
| db_name = context[:db_name] |
| |
| resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)}) |
| assert resp.status_code == 201 |
| |
| resp = Couch.post( |
| "/#{db_name}/_all_docs", |
| body: %{ |
| :keys => [1, 2], |
| :limit => 1 |
| } |
| ) |
| |
| assert resp.status_code == 200 |
| assert length(Map.get(resp, :body)["rows"]) == 1 |
| end |
| |
| @tag :with_db |
| test "POST with query parameter and JSON body", context do |
| db_name = context[:db_name] |
| |
| resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)}) |
| assert resp.status_code == 201 |
| |
| resp = Couch.post( |
| "/#{db_name}/_all_docs", |
| query: %{ |
| :limit => 1 |
| }, |
| body: %{ |
| :keys => [1, 2] |
| } |
| ) |
| |
| assert resp.status_code == 200 |
| assert length(Map.get(resp, :body)["rows"]) == 1 |
| end |
| |
| @tag :with_db |
| test "POST edge case with colliding parameters - query takes precedence", context do |
| db_name = context[:db_name] |
| |
| resp = Couch.post("/#{db_name}/_bulk_docs", body: %{docs: create_docs(0..3)}) |
| assert resp.status_code == 201 |
| |
| resp = Couch.post( |
| "/#{db_name}/_all_docs", |
| query: %{ |
| :limit => 1 |
| }, |
| body: %{ |
| :keys => [1, 2], |
| :limit => 2 |
| } |
| ) |
| |
| assert resp.status_code == 200 |
| assert length(Map.get(resp, :body)["rows"]) == 1 |
| end |
| end |