summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2019-04-29 14:28:41 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2019-04-29 14:28:41 -0500
commitca63700f98ba59459bac3b3283048614ccfc2d5d (patch)
treed48d4f8ca480560645f96780c18a2064b76c078e
parentee45f1123aaecc096ab4b2ceb11f1f1045a9be82 (diff)
downloadcouchdb-ca63700f98ba59459bac3b3283048614ccfc2d5d.tar.gz
More tests
-rw-r--r--src/fabric/src/fabric2_db.erl91
-rw-r--r--src/fabric/src/fabric2_fdb.erl2
-rw-r--r--src/fabric/src/fabric2_util.erl7
-rw-r--r--src/fabric/test/fabric2_db_misc_tests.erl34
-rw-r--r--src/fabric/test/fabric2_doc_crud_tests.erl374
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