| defmodule RevisionTest do |
| use CouchTestCase |
| |
| @moduletag :conflicts |
| |
| @moduledoc """ |
| Test CouchDB conflicts |
| This is a port of conflicts.js |
| (but is arguably more focused on revisions than conflicts) |
| """ |
| |
| setup context do |
| # Generate a doc with _rev field for each test |
| doc = %{_id: "doc-1", a: 1, b: 1} |
| doc = rev(doc, put(context[:db_name], doc)) |
| %{doc: doc} |
| end |
| |
| @tag :with_db |
| test "multiple updates with same _rev raise conflict errors", context do |
| db = context[:db_name] |
| doc = context[:doc] |
| # doc and doc2 have same _rev |
| doc2 = %{doc | a: 2, b: 2} |
| # doc updated with new _rev |
| _doc = rev(doc, put(db, doc)) |
| |
| retry_until(fn -> |
| assert_conflict(Couch.put("/#{db}/#{doc2._id}", body: doc2)) |
| |
| resp = Couch.get("/#{db}/_changes") |
| assert length(resp.body["results"]) == 1 |
| |
| doc2 = Map.delete(doc2, :_rev) |
| assert_conflict(Couch.put("/#{db}/#{doc2._id}", body: doc2)) |
| end) |
| end |
| |
| @tag :with_db |
| test "mismatched rev in body and query string returns error", context do |
| db = context[:db_name] |
| doc = context[:doc] |
| resp = Couch.put("/#{db}/#{doc._id}?rev=1-foobar", body: doc) |
| |
| expected_reason = |
| "Document rev from request body and query string " <> "have different values" |
| |
| assert_bad_request(resp, expected_reason) |
| end |
| |
| @tag :with_db |
| test "mismatched rev in body and etag returns error", context do |
| opts = [body: context[:doc], headers: [{:"If-Match", "1-foobar"}]] |
| resp = Couch.put("/#{context[:db_name]}/foobar", opts) |
| expected_reason = "Document rev and etag have different values" |
| assert_bad_request(resp, expected_reason) |
| end |
| |
| @tag :with_db |
| test "`new_edits: false` prevents bulk updates (COUCHDB-1178)", context do |
| db = context[:db_name] |
| |
| ddoc = %{_id: "_design/couchdb-1178", validate_doc_update: "function(){}"} |
| assert put(db, ddoc)["ok"] == true |
| |
| r0 = %{_id: "doc", val: "r0"} |
| r1 = %{_id: "doc", val: "r1", _rev: "1-47f3268e7546965196b57572099f4372"} |
| r2 = %{_id: "doc", val: "r2", _rev: "2-1d8171ab3a91475cfece749291e6f897"} |
| r3 = %{_id: "doc", val: "r3", _rev: "3-3fb0a342d2ce092fdcc77856dbe8a2ef"} |
| assert put(db, r0)["ok"] == true |
| assert put(db, r1)["ok"] == true |
| assert put(db, r2)["ok"] == true |
| # N.b. that we *do not* put r3 |
| |
| expected = %{ |
| "_id" => "doc", |
| "_rev" => r3._rev, |
| "_revisions" => %{ |
| "ids" => for(r <- [r3._rev, r2._rev, r1._rev], do: suffix(r)), |
| "start" => 3 |
| }, |
| "val" => r2.val |
| } |
| |
| assert Couch.get("/#{db}/doc?revs=true").body == expected |
| |
| opts = [body: %{docs: [r3, r2, r1], new_edits: false}] |
| assert Couch.post("/#{db}/_bulk_docs", opts).body == [] |
| end |
| |
| defp put(db, doc) do |
| Couch.put("/#{db}/#{doc._id}", body: doc).body |
| end |
| |
| defp suffix(rev) do |
| hd(tl(String.split(rev, "-"))) |
| end |
| |
| defp assert_conflict(resp) do |
| assert resp.status_code == 409 |
| assert resp.body["error"] == "conflict" |
| assert resp.body["reason"] == "Document update conflict." |
| end |
| |
| defp assert_bad_request(resp, reason) do |
| assert resp.status_code == 400 |
| assert resp.body["error"] == "bad_request" |
| assert resp.body["reason"] == reason |
| end |
| end |