diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-29 14:28:41 -0500 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-29 14:28:41 -0500 |
commit | ca63700f98ba59459bac3b3283048614ccfc2d5d (patch) | |
tree | d48d4f8ca480560645f96780c18a2064b76c078e | |
parent | ee45f1123aaecc096ab4b2ceb11f1f1045a9be82 (diff) | |
download | couchdb-ca63700f98ba59459bac3b3283048614ccfc2d5d.tar.gz |
More tests
-rw-r--r-- | src/fabric/src/fabric2_db.erl | 91 | ||||
-rw-r--r-- | src/fabric/src/fabric2_fdb.erl | 2 | ||||
-rw-r--r-- | src/fabric/src/fabric2_util.erl | 7 | ||||
-rw-r--r-- | src/fabric/test/fabric2_db_misc_tests.erl | 34 | ||||
-rw-r--r-- | src/fabric/test/fabric2_doc_crud_tests.erl | 374 |
5 files changed, 469 insertions, 39 deletions
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl index eaa2faf1f..ed43feae8 100644 --- a/src/fabric/src/fabric2_db.erl +++ b/src/fabric/src/fabric2_db.erl @@ -306,12 +306,13 @@ get_security(#{security_doc := SecurityDoc}) -> get_update_seq(#{} = Db) -> - case fabric2_fdb:get_changes(Db, [{limit, 1}, {reverse, true}]) of - [] -> - fabric2_util:to_hex(<<0:80>>); - [{Seq, _}] -> - Seq - end. + fabric2_fdb:transactional(Db, fun(TxDb) -> + Opts = [{limit, 1}, {reverse, true}], + case fabric2_fdb:get_changes(TxDb, Opts) of + [] -> fabric2_util:to_hex(<<0:80>>); + [{Seq, _}] -> Seq + end + end). get_user_ctx(#{user_ctx := UserCtx}) -> @@ -406,9 +407,12 @@ open_doc(#{} = Db, DocId, _Options) -> open_doc_revs(Db, DocId, Revs, Options) -> Latest = lists:member(latest, Options), fabric2_fdb:transactional(Db, fun(TxDb) -> - #full_doc_info{ - rev_tree = RevTree - } = fabric2_db:get_full_doc_info(TxDb, DocId), + AllRevInfos = fabric2_fdb:get_all_revs(Db, DocId), + RevTree = lists:foldl(fun(RI, TreeAcc) -> + RIPath = fabric2_util:revinfo_to_path(RI), + {Merged, _} = couch_key_tree:merge(TreeAcc, RIPath), + Merged + end, [], AllRevInfos), {Found, Missing} = case Revs of all -> {couch_key_tree:get_all_leafs(RevTree), []}; @@ -439,9 +443,18 @@ update_doc(Db, Doc) -> update_doc(Db, Doc, Options) -> - fabric2_fdb:transactional(Db, fun(TxDb) -> - update_doc_int(TxDb, Doc, Options) - end). + case update_docs(Db, [Doc], Options) of + {ok, [{ok, NewRev}]} -> + {ok, NewRev}; + {ok, [{{_Id, _Rev}, Error}]} -> + throw(Error); + {error, [Error]} -> + throw(Error); + {ok, []} -> + % replication success + {Pos, [RevId | _]} = Doc#doc.revs, + {ok, {Pos, RevId}} + end. update_docs(Db, Docs) -> @@ -449,7 +462,7 @@ update_docs(Db, Docs) -> update_docs(Db, Docs, Options) -> - {Resps, Status} = lists:mapfoldl(fun(Doc, Acc) -> + {Resps0, Status} = lists:mapfoldl(fun(Doc, Acc) -> fabric2_fdb:transactional(Db, fun(TxDb) -> case update_doc_int(TxDb, Doc, Options) of {ok, _} = Resp -> @@ -459,7 +472,12 @@ update_docs(Db, Docs, Options) -> end end) end, ok, Docs), - {Status, Resps}. + Resps1 = case lists:member(replicated_changes, Options) of + true -> [R || R <- Resps0, R /= {ok, []}]; + false -> Resps0 + end, + %io:format(standard_error, "~nRESPS: ~p :: ~p~n~n", [Resps0, Resps1]), + {Status, Resps1}. fold_docs(Db, UserFun, UserAcc) -> @@ -677,11 +695,8 @@ update_doc_interactive(Db, Doc0, _Options) -> Doc1 = case Winner of #{deleted := true} when not Doc0#doc.deleted -> {WinnerRevPos, WinnerRev} = maps:get(rev_id, Winner), - Doc0#doc{revs = {WinnerRevPos, [WinnerRev]}}; - #{deleted := true} when Doc0#doc.deleted -> - % We disable extending deleted revisions with - % new deletions during interactive updates - ?RETURN({error, conflict}); + WinnerRevPath = maps:get(rev_path, Winner), + Doc0#doc{revs = {WinnerRevPos, [WinnerRev | WinnerRevPath]}}; _ -> Doc0 end, @@ -722,7 +737,11 @@ update_doc_interactive(Db, Doc0, _Options) -> [W, NW] -> {W, NW} end, - NewWinner = NewWinner0#{branch_count := maps:get(branch_count, Winner)}, + BranchCount = case Winner of + not_found -> 1; + #{branch_count := BC} -> BC + end, + NewWinner = NewWinner0#{branch_count := BranchCount}, ToUpdate = if NonWinner == not_found -> []; true -> [NonWinner] end, ToRemove = if Target == not_found -> []; true -> [Target] end, @@ -745,7 +764,7 @@ update_doc_replicated(Db, Doc0, _Options) -> revs = {RevPos, [Rev | RevPath]} } = Doc0, - DocRevInfo = #{ + DocRevInfo0 = #{ winner => undefined, deleted => Deleted, rev_id => {RevPos, Rev}, @@ -762,7 +781,7 @@ update_doc_replicated(Db, Doc0, _Options) -> Merged end, [], AllRevInfos), - DocRevPath = fabric2_util:revinfo_to_path(DocRevInfo), + DocRevPath = fabric2_util:revinfo_to_path(DocRevInfo0), {NewTree, Status} = couch_key_tree:merge(RevTree, DocRevPath), if Status /= internal_node -> ok; true -> % We already know this revision so nothing @@ -770,10 +789,25 @@ update_doc_replicated(Db, Doc0, _Options) -> ?RETURN({ok, []}) end, + % Its possible to have a replication with fewer than $revs_limit + % revisions which extends an existing branch. To avoid + % losing revision history we extract the new node from the + % tree and use the combined path after stemming. + {[{_, {RevPos, UnstemmedRevs}}], []} + = couch_key_tree:get(NewTree, [{RevPos, Rev}]), + RevsLimit = fabric2_db:get_revs_limit(Db), + Doc1 = Doc0#doc{ + revs = {RevPos, lists:sublist(UnstemmedRevs, RevsLimit)} + }, + {RevPos, [Rev | NewRevPath]} = Doc1#doc.revs, + DocRevInfo1 = DocRevInfo0#{rev_path := NewRevPath}, + + % Find any previous revision we knew about for + % validation and attachment handling. AllLeafsFull = couch_key_tree:get_all_leafs_full(NewTree), LeafPath = get_leaf_path(RevPos, Rev, AllLeafsFull), PrevRevInfo = find_prev_revinfo(RevPos, LeafPath), - Doc1 = prep_and_validate(Db, Doc0, PrevRevInfo), + Doc2 = prep_and_validate(Db, Doc1, PrevRevInfo), % Possible winners are the previous winner and % the new DocRevInfo @@ -783,9 +817,9 @@ update_doc_replicated(Db, Doc0, _Options) -> end, {NewWinner0, NonWinner} = case Winner == PrevRevInfo of true -> - {DocRevInfo, not_found}; + {DocRevInfo1, not_found}; false -> - [W, NW] = fabric2_util:sort_revinfos([Winner, DocRevInfo]), + [W, NW] = fabric2_util:sort_revinfos([Winner, DocRevInfo1]), {W, NW} end, @@ -795,17 +829,14 @@ update_doc_replicated(Db, Doc0, _Options) -> ok = fabric2_fdb:write_doc( Db, - Doc1, + Doc2, NewWinner, Winner, ToUpdate, ToRemove ), - #doc{ - revs = {NewRevPos, [NewRev | _]} - } = Doc1, - {ok, {NewRevPos, NewRev}}. + {ok, []}. prep_and_validate(Db, Doc, PrevRevInfo) -> diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl index 13f903811..e8c780a7e 100644 --- a/src/fabric/src/fabric2_fdb.erl +++ b/src/fabric/src/fabric2_fdb.erl @@ -128,7 +128,7 @@ create(#{} = Db0, Options) -> validate_doc_update_funs => [], before_doc_update => undefined, - after_doc_update => undefined, + after_doc_read => undefined, % All other db things as we add features, db_options => Options diff --git a/src/fabric/src/fabric2_util.erl b/src/fabric/src/fabric2_util.erl index 09ff876e4..6edd1bc26 100644 --- a/src/fabric/src/fabric2_util.erl +++ b/src/fabric/src/fabric2_util.erl @@ -47,12 +47,7 @@ revinfo_to_path(RevInfo, [Rev | Rest]) -> sort_revinfos(RevInfos) -> - CmpFun = fun(A, B) -> - case rev_sort_key(A) > rev_sort_key(B) of - true -> A; - false -> B - end - end, + CmpFun = fun(A, B) -> rev_sort_key(A) > rev_sort_key(B) end, lists:sort(CmpFun, RevInfos). diff --git a/src/fabric/test/fabric2_db_misc_tests.erl b/src/fabric/test/fabric2_db_misc_tests.erl index 2b6ae5d41..83d6a6ab9 100644 --- a/src/fabric/test/fabric2_db_misc_tests.erl +++ b/src/fabric/test/fabric2_db_misc_tests.erl @@ -29,7 +29,9 @@ misc_test_() -> fun cleanup/1, {with, [ fun empty_db_info/1, - fun accessors/1 + fun accessors/1, + fun is_system_db/1, + fun ensure_full_commit/1 ]} } }. @@ -56,6 +58,34 @@ empty_db_info({DbName, Db, _}) -> accessors({DbName, Db, _}) -> + SeqZero = fabric2_util:to_hex(<<0:80>>), ?assertEqual(DbName, fabric2_db:name(Db)), ?assertEqual(0, fabric2_db:get_instance_start_time(Db)), - ?assertEqual(nil, fabric2_db:get_pid(Db)). + ?assertEqual(nil, fabric2_db:get_pid(Db)), + ?assertEqual(undefined, fabric2_db:get_before_doc_update_fun(Db)), + ?assertEqual(undefined, fabric2_db:get_after_doc_read_fun(Db)), + ?assertEqual(SeqZero, fabric2_db:get_committed_update_seq(Db)), + ?assertEqual(SeqZero, fabric2_db:get_compacted_seq(Db)), + ?assertEqual(SeqZero, fabric2_db:get_update_seq(Db)), + ?assertEqual(nil, fabric2_db:get_compactor_pid(Db)), + ?assertEqual(1000, fabric2_db:get_revs_limit(Db)), + ?assertMatch(<<_:32/binary>>, fabric2_db:get_uuid(Db)), + ?assertEqual(true, fabric2_db:is_db(Db)), + ?assertEqual(false, fabric2_db:is_db(#{})), + ?assertEqual(false, fabric2_db:is_partitioned(Db)). + + +is_system_db({DbName, Db, _}) -> + ?assertEqual(false, fabric2_db:is_system_db(Db)), + ?assertEqual(false, fabric2_db:is_system_db_name("foo")), + ?assertEqual(false, fabric2_db:is_system_db_name(DbName)), + ?assertEqual(true, fabric2_db:is_system_db_name(<<"_replicator">>)), + ?assertEqual(true, fabric2_db:is_system_db_name("_replicator")), + ?assertEqual(true, fabric2_db:is_system_db_name(<<"foo/_replicator">>)), + ?assertEqual(false, fabric2_db:is_system_db_name(<<"f.o/_replicator">>)), + ?assertEqual(false, fabric2_db:is_system_db_name(<<"foo/bar">>)). + + +ensure_full_commit({_, Db, _}) -> + ?assertEqual({ok, 0}, fabric2_db:ensure_full_commit(Db)), + ?assertEqual({ok, 0}, fabric2_db:ensure_full_commit(Db, 5)). diff --git a/src/fabric/test/fabric2_doc_crud_tests.erl b/src/fabric/test/fabric2_doc_crud_tests.erl new file mode 100644 index 000000000..dc0f8347c --- /dev/null +++ b/src/fabric/test/fabric2_doc_crud_tests.erl @@ -0,0 +1,374 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(fabric2_doc_crud_tests). + + +-include_lib("couch/include/couch_db.hrl"). +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + +doc_crud_test_() -> + { + "Test document CRUD operations", + { + setup, + fun setup/0, + fun cleanup/1, + {with, [ + fun open_missing_doc/1, + fun create_new_doc/1, + fun update_doc_basic/1, + fun update_doc_replicated/1, + fun update_doc_replicated_add_conflict/1, + fun update_doc_replicated_changes_winner/1, + fun update_doc_replicated_extension/1, + fun update_doc_replicate_existing_rev/1, + fun update_winning_conflict_branch/1, + fun update_non_winning_conflict_branch/1, + fun delete_doc_basic/1, + fun delete_changes_winner/1, + fun recreate_doc_basic/1, + fun conflict_on_create_new_with_rev/1, + fun conflict_on_update_with_no_rev/1, + fun conflict_on_create_as_deleted/1, + fun conflict_on_recreate_as_deleted/1, + fun conflict_on_extend_deleted/1 + ]} + } + }. + + +setup() -> + Ctx = test_util:start_couch([fabric]), + {ok, Db} = fabric2_db:create(?tempdb(), []), + {Db, Ctx}. + + +cleanup({Db, Ctx}) -> + ok = fabric2_db:delete(fabric2_db:name(Db), []), + test_util:stop_couch(Ctx). + + +open_missing_doc({Db, _}) -> + ?assertEqual({not_found, missing}, fabric2_db:open_doc(Db, <<"foo">>)). + + +create_new_doc({Db, _}) -> + Doc = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {RevPos, Rev}} = fabric2_db:update_doc(Db, Doc), + NewDoc = Doc#doc{revs = {RevPos, [Rev]}}, + ?assertEqual({ok, NewDoc}, fabric2_db:open_doc(Db, Doc#doc.id)). + + +update_doc_basic({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, {Pos1, Rev1}} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {Pos1, [Rev1]}, + body = {[{<<"state">>, 2}]} + }, + {ok, {Pos2, Rev2}} = fabric2_db:update_doc(Db, Doc2), + Doc3 = Doc2#doc{ + revs = {Pos2, [Rev2, Rev1]} + }, + ?assertEqual({ok, Doc3}, fabric2_db:open_doc(Db, Doc2#doc.id)). + + +update_doc_replicated({Db, _}) -> + Doc = #doc{ + id = fabric2_util:uuid(), + revs = {2, [fabric2_util:uuid(), fabric2_util:uuid()]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc, [replicated_changes]), + ?assertEqual({ok, Doc}, fabric2_db:open_doc(Db, Doc#doc.id)). + + +update_doc_replicated_add_conflict({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev3, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + ?assertEqual({ok, Doc1}, fabric2_db:open_doc(Db, Doc1#doc.id)), + Doc2 = Doc1#doc{ + revs = {2, [Rev2, Rev1]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + ?assertEqual({ok, Doc1}, fabric2_db:open_doc(Db, Doc2#doc.id)). + + +update_doc_replicated_changes_winner({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev2, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + ?assertEqual({ok, Doc1}, fabric2_db:open_doc(Db, Doc1#doc.id)), + Doc2 = Doc1#doc{ + revs = {2, [Rev3, Rev1]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + ?assertEqual({ok, Doc2}, fabric2_db:open_doc(Db, Doc2#doc.id)). + + +update_doc_replicated_extension({Db, _}) -> + % No sort necessary and avoided on purpose to + % demonstrate that this is not sort dependent + Rev1 = fabric2_util:uuid(), + Rev2 = fabric2_util:uuid(), + Rev3 = fabric2_util:uuid(), + Rev4 = fabric2_util:uuid(), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev2, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + Doc2 = Doc1#doc{ + revs = {4, [Rev4, Rev3, Rev2]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {4, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + {ok, Doc3} = fabric2_db:open_doc(Db, Doc2#doc.id), + ?assertEqual({4, [Rev4, Rev3, Rev2, Rev1]}, Doc3#doc.revs), + ?assertEqual(Doc2#doc{revs = undefined}, Doc3#doc{revs = undefined}). + + +update_doc_replicate_existing_rev({Db, _}) -> + Rev1 = fabric2_util:uuid(), + Rev2 = fabric2_util:uuid(), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev2, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + {ok, []} = fabric2_db:update_docs(Db, [Doc1], [replicated_changes]), + ?assertEqual({ok, Doc1}, fabric2_db:open_doc(Db, Doc1#doc.id)). + + +update_winning_conflict_branch({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev3, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + Doc2 = Doc1#doc{ + revs = {2, [Rev2, Rev1]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + % Update the winning branch + Doc3 = Doc1#doc{ + revs = {2, [Rev3, Rev1]}, + body = {[{<<"baz">>, 2}]} + }, + {ok, {3, Rev4}} = fabric2_db:update_doc(Db, Doc3), + {ok, Doc4} = fabric2_db:open_doc(Db, Doc3#doc.id), + % Assert we've got the correct winner + ?assertEqual({3, [Rev4, Rev3, Rev1]}, Doc4#doc.revs), + ?assertEqual(Doc3#doc{revs = undefined}, Doc4#doc{revs = undefined}). + + +update_non_winning_conflict_branch({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev3, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + Doc2 = Doc1#doc{ + revs = {2, [Rev2, Rev1]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + % Update the non winning branch + Doc3 = Doc1#doc{ + revs = {2, [Rev2, Rev1]}, + body = {[{<<"baz">>, 2}]} + }, + {ok, {3, Rev4}} = fabric2_db:update_doc(Db, Doc3), + {ok, Doc4} = fabric2_db:open_doc(Db, Doc3#doc.id), + % Assert we've got the correct winner + ?assertEqual({3, [Rev4, Rev2, Rev1]}, Doc4#doc.revs), + ?assertEqual(Doc3#doc{revs = undefined}, Doc4#doc{revs = undefined}). + + +delete_doc_basic({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, {Pos1, Rev1}} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {Pos1, [Rev1]}, + deleted = true, + body = {[{<<"state">>, 2}]} + }, + {ok, {Pos2, Rev2}} = fabric2_db:update_doc(Db, Doc2), + Doc3 = Doc2#doc{revs = {Pos2, [Rev2, Rev1]}}, + ?assertEqual({ok, Doc3}, fabric2_db:open_doc(Db, Doc2#doc.id)). + + +delete_changes_winner({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + Doc1 = #doc{ + id = fabric2_util:uuid(), + revs = {2, [Rev3, Rev1]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc1, [replicated_changes]), + Doc2 = Doc1#doc{ + revs = {2, [Rev2, Rev1]}, + body = {[{<<"bar">>, <<"foo">>}]} + }, + {ok, {2, _}} = fabric2_db:update_doc(Db, Doc2, [replicated_changes]), + % Delete the winning branch + Doc3 = Doc1#doc{ + revs = {2, [Rev3, Rev1]}, + deleted = true, + body = {[]} + }, + {ok, {3, _}} = fabric2_db:update_doc(Db, Doc3), + ?assertEqual({ok, Doc2}, fabric2_db:open_doc(Db, Doc3#doc.id)). + + +recreate_doc_basic({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, {1, Rev1}} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {1, [Rev1]}, + deleted = true, + body = {[{<<"state">>, 2}]} + }, + {ok, {2, Rev2}} = fabric2_db:update_doc(Db, Doc2), + Doc3 = Doc1#doc{ + revs = {0, []}, + deleted = false, + body = {[{<<"state">>, 3}]} + }, + {ok, {3, Rev3}} = fabric2_db:update_doc(Db, Doc3), + {ok, Doc4} = fabric2_db:open_doc(Db, Doc3#doc.id), + ?assertEqual({3, [Rev3, Rev2, Rev1]}, Doc4#doc.revs), + ?assertEqual(Doc3#doc{revs = undefined}, Doc4#doc{revs = undefined}). + + +conflict_on_create_new_with_rev({Db, _}) -> + Doc = #doc{ + id = fabric2_util:uuid(), + revs = {1, [fabric2_util:uuid()]}, + body = {[{<<"foo">>, <<"bar">>}]} + }, + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc)). + + +conflict_on_update_with_no_rev({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, _} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {0, []}, + body = {[{<<"state">>, 2}]} + }, + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc2)). + + +conflict_on_create_as_deleted({Db, _}) -> + Doc = #doc{ + id = fabric2_util:uuid(), + deleted = true, + body = {[{<<"foo">>, <<"bar">>}]} + }, + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc)). + + +conflict_on_recreate_as_deleted({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, {Pos1, Rev1}} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {Pos1, [Rev1]}, + deleted = true, + body = {[{<<"state">>, 2}]} + }, + {ok, _} = fabric2_db:update_doc(Db, Doc2), + Doc3 = Doc1#doc{ + revs = {0, []}, + deleted = true, + body = {[{<<"state">>, 3}]} + }, + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc3)). + + +conflict_on_extend_deleted({Db, _}) -> + Doc1 = #doc{ + id = fabric2_util:uuid(), + body = {[{<<"state">>, 1}]} + }, + {ok, {Pos1, Rev1}} = fabric2_db:update_doc(Db, Doc1), + Doc2 = Doc1#doc{ + revs = {Pos1, [Rev1]}, + deleted = true, + body = {[{<<"state">>, 2}]} + }, + {ok, {Pos2, Rev2}} = fabric2_db:update_doc(Db, Doc2), + Doc3 = Doc1#doc{ + revs = {Pos2, [Rev2]}, + deleted = false, + body = {[{<<"state">>, 3}]} + }, + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc3)).
\ No newline at end of file |