diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-30 13:41:50 -0500 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-30 13:41:50 -0500 |
commit | 5c55d1905fc83c7c0b73024228a72bf0a11c10a3 (patch) | |
tree | 1a801ee89f9938e73e79a20fc51b9316d3dc07b1 | |
parent | add48a531221703f8d87494fbd2dbc6ccf1f3735 (diff) | |
download | couchdb-5c55d1905fc83c7c0b73024228a72bf0a11c10a3.tar.gz |
More test coverage of doc CRUD
-rw-r--r-- | src/fabric/src/fabric2_db.erl | 61 | ||||
-rw-r--r-- | src/fabric/src/fabric2_fdb.erl | 89 | ||||
-rw-r--r-- | src/fabric/test/fabric2_doc_crud_tests.erl | 338 |
3 files changed, 476 insertions, 12 deletions
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl index ed43feae8..5be707bfa 100644 --- a/src/fabric/src/fabric2_db.erl +++ b/src/fabric/src/fabric2_db.erl @@ -390,6 +390,14 @@ open_doc(#{} = Db, DocId) -> open_doc(Db, DocId, []). +open_doc(#{} = Db, <<?LOCAL_DOC_PREFIX, _/binary>> = DocId, _Options) -> + fabric2_fdb:transactional(Db, fun(TxDb) -> + case fabric2_fdb:get_local_doc(TxDb, DocId) of + #doc{} = Doc -> {ok, Doc}; + Else -> Else + end + end); + open_doc(#{} = Db, DocId, _Options) -> fabric2_fdb:transactional(Db, fun(TxDb) -> case fabric2_fdb:get_winning_revs(TxDb, DocId, 1) of @@ -407,7 +415,7 @@ open_doc(#{} = Db, DocId, _Options) -> open_doc_revs(Db, DocId, Revs, Options) -> Latest = lists:member(latest, Options), fabric2_fdb:transactional(Db, fun(TxDb) -> - AllRevInfos = fabric2_fdb:get_all_revs(Db, DocId), + AllRevInfos = fabric2_fdb:get_all_revs(TxDb, DocId), RevTree = lists:foldl(fun(RI, TreeAcc) -> RIPath = fabric2_util:revinfo_to_path(RI), {Merged, _} = couch_key_tree:merge(TreeAcc, RIPath), @@ -421,13 +429,17 @@ open_doc_revs(Db, DocId, Revs, Options) -> _ -> couch_key_tree:get(RevTree, Revs) end, - Docs = lists:map(fun({Value, {Pos, [Rev | _]} = RevPath}) -> + Docs = lists:map(fun({Value, {Pos, [Rev | RevPath]}}) -> case Value of ?REV_MISSING -> % We have the rev in our list but know nothing about it {{not_found, missing}, {Pos, Rev}}; _ -> - case fabric2_fdb:get_doc_body(TxDb, DocId, RevPath) of + RevInfo = #{ + rev_id => {Pos, Rev}, + rev_path => RevPath + }, + case fabric2_fdb:get_doc_body(TxDb, DocId, RevInfo) of #doc{} = Doc -> {ok, Doc}; Else -> {Else, {Pos, Rev}} end @@ -467,7 +479,7 @@ update_docs(Db, Docs, Options) -> case update_doc_int(TxDb, Doc, Options) of {ok, _} = Resp -> {Resp, Acc}; - {error, _} = Resp -> + Resp -> {Resp, error} end end) @@ -626,10 +638,16 @@ get_members(SecProps) -> % TODO: Handle _local docs separately. update_doc_int(#{} = Db, #doc{} = Doc, Options) -> + IsLocal = case Doc#doc.id of + <<?LOCAL_DOC_PREFIX, _/binary>> -> true; + _ -> false + end, + IsReplicated = lists:member(replicated_changes, Options), try - case lists:member(replicated_changes, Options) of - false -> update_doc_interactive(Db, Doc, Options); - true -> update_doc_replicated(Db, Doc, Options) + case {IsLocal, IsReplicated} of + {false, false} -> update_doc_interactive(Db, Doc, Options); + {false, true} -> update_doc_replicated(Db, Doc, Options); + {true, _} -> update_local_doc(Db, Doc, Options) end catch throw:{?MODULE, Return} -> Return @@ -839,6 +857,18 @@ update_doc_replicated(Db, Doc0, _Options) -> {ok, []}. +update_local_doc(Db, Doc0, _Options) -> + Doc1 = case increment_local_doc_rev(Doc0) of + {ok, Updated} -> Updated; + {error, _} = Error -> ?RETURN(Error) + end, + + ok = fabric2_fdb:write_local_doc(Db, Doc1), + + #doc{revs = {0, [Rev]}} = Doc1, + {ok, {0, integer_to_binary(Rev)}}. + + prep_and_validate(Db, Doc, PrevRevInfo) -> HasStubs = couch_doc:has_stubs(Doc), HasVDUs = [] /= maps:get(validate_doc_update_funs, Db), @@ -871,8 +901,6 @@ validate_doc_update(Db, #doc{id = <<"_design/", _/binary>>} = Doc, _) -> ok -> validate_ddoc(Db, Doc); Error -> ?RETURN(Error) end; -validate_doc_update(_Db, #doc{id = <<"_local/", _/binary>>}, _) -> - ok; validate_doc_update(Db, Doc, PrevDoc) -> #{ security_doc := Security, @@ -927,6 +955,21 @@ find_prev_revinfo(_Pos, [{_Rev, #{} = RevInfo} | _]) -> RevInfo. +increment_local_doc_rev(#doc{deleted = true} = Doc) -> + {ok, Doc#doc{revs = {0, [0]}}}; +increment_local_doc_rev(#doc{revs = {0, []}} = Doc) -> + {ok, Doc#doc{revs = {0, [1]}}}; +increment_local_doc_rev(#doc{revs = {0, [RevStr | _]}} = Doc) -> + try + PrevRev = binary_to_integer(RevStr), + {ok, Doc#doc{revs = {0, [PrevRev + 1]}}} + catch error:badarg -> + {error, <<"Invalid rev format">>} + end; +increment_local_doc_rev(#doc{}) -> + {error, <<"Invalid rev format">>}. + + doc_to_revid(#doc{revs = Revs}) -> case Revs of {0, []} -> {0, <<>>}; diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl index e8c780a7e..b0f85feb2 100644 --- a/src/fabric/src/fabric2_fdb.erl +++ b/src/fabric/src/fabric2_fdb.erl @@ -38,8 +38,10 @@ get_non_deleted_rev/3, get_doc_body/3, + get_local_doc/2, write_doc/6, + write_local_doc/2, fold_docs/4, fold_changes/5, @@ -396,6 +398,17 @@ get_doc_body(#{} = Db0, DocId, RevInfo) -> fdb_to_doc(Db, DocId, RevPos, [Rev | RevPath], Val). +get_local_doc(#{} = Db0, <<?LOCAL_DOC_PREFIX, _/binary>> = DocId) -> + #{ + tx := Tx, + db_prefix := DbPrefix + } = Db = ensure_current(Db0), + + Key = erlfdb_tuple:pack({?DB_LOCAL_DOCS, DocId}, DbPrefix), + Val = erlfdb:wait(erlfdb:get(Tx, Key)), + fdb_to_local_doc(Db, DocId, Val). + + write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) -> #{ tx := Tx, @@ -468,13 +481,27 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) -> ok = write_doc_body(Db, Doc), + IsDDoc = case Doc#doc.id of + <<?DESIGN_DOC_PREFIX, _/binary>> -> true; + _ -> false + end, + case UpdateStatus of created -> + if not IsDDoc -> ok; true -> + incr_stat(Db, <<"doc_design_count">>, 1) + end, incr_stat(Db, <<"doc_count">>, 1); recreated -> + if not IsDDoc -> ok; true -> + incr_stat(Db, <<"doc_design_count">>, 1) + end, incr_stat(Db, <<"doc_count">>, 1), incr_stat(Db, <<"doc_del_count">>, -1); deleted -> + if not IsDDoc -> ok; true -> + incr_stat(Db, <<"doc_design_count">>, -1) + end, incr_stat(Db, <<"doc_count">>, -1), incr_stat(Db, <<"doc_del_count">>, 1); updated -> @@ -484,6 +511,35 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) -> ok. +write_local_doc(#{} = Db0, Doc) -> + #{ + tx := Tx + } = Db = ensure_current(Db0), + + {LDocKey, LDocVal} = local_doc_to_fdb(Db, Doc), + + WasDeleted = case erlfdb:wait(erlfdb:get(Tx, LDocKey)) of + <<_/binary>> -> false; + not_found -> true + end, + + case Doc#doc.deleted of + true -> erlfdb:clear(Tx, LDocKey); + false -> erlfdb:set(Tx, LDocKey, LDocVal) + end, + + case {WasDeleted, Doc#doc.deleted} of + {true, false} -> + incr_stat(Db, <<"doc_local_count">>, 1); + {false, true} -> + incr_stat(Db, <<"doc_local_count">>, -1); + _ -> + ok + end, + + ok. + + write_doc_body(#{} = Db0, #doc{} = Doc) -> #{ tx := Tx @@ -699,6 +755,39 @@ fdb_to_doc(_Db, _DocId, _Pos, _Path, not_found) -> {not_found, missing}. +local_doc_to_fdb(Db, #doc{} = Doc) -> + #{ + db_prefix := DbPrefix + } = Db, + + #doc{ + id = Id, + revs = {0, [Rev]}, + body = Body + } = Doc, + + StoreRev = case Rev of + _ when is_integer(Rev) -> integer_to_binary(Rev); + _ when is_binary(Rev) -> Rev + end, + + Key = erlfdb_tuple:pack({?DB_LOCAL_DOCS, Id}, DbPrefix), + Val = {StoreRev, Body}, + {Key, term_to_binary(Val, [{minor_version, 1}])}. + + +fdb_to_local_doc(_Db, DocId, Bin) when is_binary(Bin) -> + {Rev, Body} = binary_to_term(Bin, [safe]), + #doc{ + id = DocId, + revs = {0, [Rev]}, + deleted = false, + body = Body + }; +fdb_to_local_doc(_Db, _DocId, not_found) -> + {not_found, missing}. + + get_db_handle() -> case get(?PDICT_DB_KEY) of undefined -> diff --git a/src/fabric/test/fabric2_doc_crud_tests.erl b/src/fabric/test/fabric2_doc_crud_tests.erl index dc0f8347c..86dbcc8ce 100644 --- a/src/fabric/test/fabric2_doc_crud_tests.erl +++ b/src/fabric/test/fabric2_doc_crud_tests.erl @@ -28,7 +28,12 @@ doc_crud_test_() -> {with, [ fun open_missing_doc/1, fun create_new_doc/1, + fun create_ddoc_basic/1, + fun create_ddoc_requires_admin/1, + fun create_ddoc_requires_validation/1, + fun create_ddoc_requires_compilation/1, fun update_doc_basic/1, + fun update_ddoc_basic/1, fun update_doc_replicated/1, fun update_doc_replicated_add_conflict/1, fun update_doc_replicated_changes_winner/1, @@ -43,7 +48,17 @@ doc_crud_test_() -> 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 + fun conflict_on_extend_deleted/1, + fun open_doc_revs_basic/1, + fun open_doc_revs_all/1, + fun open_doc_revs_latest/1, + fun open_missing_local_doc/1, + fun create_local_doc_basic/1, + fun update_local_doc_basic/1, + fun delete_local_doc_basic/1, + fun recreate_local_doc/1, + fun create_local_doc_bad_rev/1, + fun create_local_doc_random_rev/1 ]} } }. @@ -51,7 +66,7 @@ doc_crud_test_() -> setup() -> Ctx = test_util:start_couch([fabric]), - {ok, Db} = fabric2_db:create(?tempdb(), []), + {ok, Db} = fabric2_db:create(?tempdb(), [{user_ctx, ?ADMIN_USER}]), {Db, Ctx}. @@ -74,6 +89,69 @@ create_new_doc({Db, _}) -> ?assertEqual({ok, NewDoc}, fabric2_db:open_doc(Db, Doc#doc.id)). +create_ddoc_basic({Db, _}) -> + UUID = fabric2_util:uuid(), + DDocId = <<"_design/", UUID/binary>>, + Doc = #doc{ + id = DDocId, + 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)). + + +create_ddoc_requires_admin({Db, _}) -> + Db2 = fabric2_db:set_user_ctx(Db, #user_ctx{}), + UUID = fabric2_util:uuid(), + DDocId = <<"_design/", UUID/binary>>, + Doc = #doc{ + id = DDocId, + body = {[{<<"foo">>, <<"bar">>}]} + }, + ?assertThrow({unauthorized, _}, fabric2_db:update_doc(Db2, Doc)). + + +create_ddoc_requires_validation({Db, _}) -> + UUID = fabric2_util:uuid(), + DDocId = <<"_design/", UUID/binary>>, + Doc = #doc{ + id = DDocId, + body = {[ + {<<"views">>, {[ + {<<"foo">>, {[ + {<<"map">>, <<"function(doc) {}">>}, + {<<"reduce">>, <<"_not_a_builtin_reduce">>} + ]}} + ]}} + ]} + }, + ?assertThrow( + {bad_request, invalid_design_doc, _}, + fabric2_db:update_doc(Db, Doc) + ). + + +create_ddoc_requires_compilation({Db, _}) -> + UUID = fabric2_util:uuid(), + DDocId = <<"_design/", UUID/binary>>, + Doc = #doc{ + id = DDocId, + body = {[ + {<<"language">>, <<"javascript">>}, + {<<"views">>, {[ + {<<"foo">>, {[ + {<<"map">>, <<"Hopefully this is invalid JavaScript">>} + ]}} + ]}} + ]} + }, + ?assertThrow( + {bad_request, compilation_error, _}, + fabric2_db:update_doc(Db, Doc) + ). + + update_doc_basic({Db, _}) -> Doc1 = #doc{ id = fabric2_util:uuid(), @@ -91,6 +169,25 @@ update_doc_basic({Db, _}) -> ?assertEqual({ok, Doc3}, fabric2_db:open_doc(Db, Doc2#doc.id)). +update_ddoc_basic({Db, _}) -> + UUID = fabric2_util:uuid(), + DDocId = <<"_design/", UUID/binary>>, + Doc1 = #doc{ + id = DDocId, + 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(), @@ -371,4 +468,239 @@ conflict_on_extend_deleted({Db, _}) -> deleted = false, body = {[{<<"state">>, 3}]} }, - ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc3)).
\ No newline at end of file + ?assertThrow({error, conflict}, fabric2_db:update_doc(Db, Doc3)). + + +open_doc_revs_basic({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + DocId = fabric2_util:uuid(), + Doc1 = #doc{ + id = DocId, + 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]), + + {ok, [{ok, Doc3}]} = fabric2_db:open_doc_revs(Db, DocId, [{2, Rev3}], []), + ?assertEqual(Doc1, Doc3), + + {ok, [{ok, Doc4}]} = fabric2_db:open_doc_revs(Db, DocId, [{2, Rev2}], []), + ?assertEqual(Doc2, Doc4), + + Revs = [{2, Rev3}, {2, Rev2}, {1, Rev1}], + {ok, Docs} = fabric2_db:open_doc_revs(Db, DocId, Revs, []), + ?assert(length(Docs) == 3), + ?assert(lists:member({ok, Doc1}, Docs)), + ?assert(lists:member({ok, Doc2}, Docs)), + ?assert(lists:member({{not_found, missing}, {1, Rev1}}, Docs)), + + % Make sure crazy madeup revisions are accepted + MissingRevs = [{5, fabric2_util:uuid()}, {1, fabric2_util:uuid()}], + {ok, NFMissing} = fabric2_db:open_doc_revs(Db, DocId, MissingRevs, []), + ?assertEqual(2, length(NFMissing)), + lists:foreach(fun(MR) -> + ?assert(lists:member({{not_found, missing}, MR}, NFMissing)) + end, MissingRevs). + + +open_doc_revs_all({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + DocId = fabric2_util:uuid(), + Doc1 = #doc{ + id = DocId, + 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]), + + {ok, Docs} = fabric2_db:open_doc_revs(Db, DocId, all, []), + ?assert(length(Docs) == 2), + ?assert(lists:member({ok, Doc1}, Docs)), + ?assert(lists:member({ok, Doc2}, Docs)). + + +open_doc_revs_latest({Db, _}) -> + [Rev1, Rev2, Rev3] = lists:sort([ + fabric2_util:uuid(), + fabric2_util:uuid(), + fabric2_util:uuid() + ]), + DocId = fabric2_util:uuid(), + Doc1 = #doc{ + id = DocId, + 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]), + + Opts = [latest], + {ok, [{ok, Doc3}]} = fabric2_db:open_doc_revs(Db, DocId, [{2, Rev3}], Opts), + ?assertEqual(Doc1, Doc3), + + {ok, Docs} = fabric2_db:open_doc_revs(Db, DocId, [{1, Rev1}], Opts), + ?assert(length(Docs) == 2), + ?assert(lists:member({ok, Doc1}, Docs)), + ?assert(lists:member({ok, Doc2}, Docs)). + + +open_missing_local_doc({Db, _}) -> + ?assertEqual( + {not_found, missing}, + fabric2_db:open_doc(Db, <<"_local/foo">>, []) + ). + + +create_local_doc_basic({Db, _}) -> + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, []}, + deleted = false, + body = {[{<<"ohai">>, <<"there">>}]} + }, + ?assertEqual({ok, {0, <<"1">>}}, fabric2_db:update_doc(Db, Doc1)), + {ok, Doc2} = fabric2_db:open_doc(Db, Doc1#doc.id, []), + ?assertEqual(Doc1#doc{revs = {0, [<<"1">>]}}, Doc2). + + +update_local_doc_basic({Db, _}) -> + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, []}, + deleted = false, + body = {[{<<"ohai">>, <<"there">>}]} + }, + ?assertEqual({ok, {0, <<"1">>}}, fabric2_db:update_doc(Db, Doc1)), + Doc2 = Doc1#doc{ + revs = {0, [<<"1">>]}, + body = {[{<<"whiz">>, <<"bang">>}]} + }, + ?assertEqual({ok, {0, <<"2">>}}, fabric2_db:update_doc(Db, Doc2)), + {ok, Doc3} = fabric2_db:open_doc(Db, Doc1#doc.id, []), + ?assertEqual(Doc2#doc{revs = {0, [<<"2">>]}}, Doc3). + + +delete_local_doc_basic({Db, _}) -> + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, []}, + deleted = false, + body = {[{<<"ohai">>, <<"there">>}]} + }, + ?assertEqual({ok, {0, <<"1">>}}, fabric2_db:update_doc(Db, Doc1)), + Doc2 = Doc1#doc{ + revs = {0, [<<"1">>]}, + deleted = true, + body = {[]} + }, + ?assertEqual({ok, {0, <<"0">>}}, fabric2_db:update_doc(Db, Doc2)), + ?assertEqual( + {not_found, missing}, + fabric2_db:open_doc(Db, LDocId) + ). + + +recreate_local_doc({Db, _}) -> + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, []}, + deleted = false, + body = {[{<<"ohai">>, <<"there">>}]} + }, + ?assertEqual({ok, {0, <<"1">>}}, fabric2_db:update_doc(Db, Doc1)), + Doc2 = Doc1#doc{ + revs = {0, [<<"1">>]}, + deleted = true, + body = {[]} + }, + ?assertEqual({ok, {0, <<"0">>}}, fabric2_db:update_doc(Db, Doc2)), + ?assertEqual( + {not_found, missing}, + fabric2_db:open_doc(Db, LDocId) + ), + + ?assertEqual({ok, {0, <<"1">>}}, fabric2_db:update_doc(Db, Doc1)), + {ok, Doc3} = fabric2_db:open_doc(Db, LDocId), + ?assertEqual(Doc1#doc{revs = {0, [<<"1">>]}}, Doc3). + + +create_local_doc_bad_rev({Db, _}) -> + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, [<<"not a number">>]} + }, + ?assertThrow( + {error, <<"Invalid rev format">>}, + fabric2_db:update_doc(Db, Doc1) + ), + + Doc2 = Doc1#doc{ + revs = bad_bad_rev_roy_brown + }, + ?assertThrow( + {error, <<"Invalid rev format">>}, + fabric2_db:update_doc(Db, Doc2) + ). + + +create_local_doc_random_rev({Db, _}) -> + % Local docs don't care what rev is passed as long + % as long as its a number. + UUID = fabric2_util:uuid(), + LDocId = <<?LOCAL_DOC_PREFIX, UUID/binary>>, + Doc1 = #doc{ + id = LDocId, + revs = {0, [<<"42">>]}, + body = {[{<<"state">>, 1}]} + }, + ?assertEqual({ok, {0, <<"43">>}}, fabric2_db:update_doc(Db, Doc1)), + {ok, Doc2} = fabric2_db:open_doc(Db, LDocId, []), + ?assertEqual(Doc1#doc{revs = {0, [<<"43">>]}}, Doc2), + + Doc3 = Doc1#doc{ + revs = {0, [<<"1234567890">>]}, + body = {[{<<"state">>, 2}]} + }, + ?assertEqual({ok, {0, <<"1234567891">>}}, fabric2_db:update_doc(Db, Doc3)), + {ok, Doc4} = fabric2_db:open_doc(Db, LDocId, []), + ?assertEqual(Doc3#doc{revs = {0, [<<"1234567891">>]}}, Doc4), + + Doc5 = Doc1#doc{ + revs = {0, [<<"1">>]}, + body = {[{<<"state">>, 3}]} + }, + ?assertEqual({ok, {0, <<"2">>}}, fabric2_db:update_doc(Db, Doc5)), + {ok, Doc6} = fabric2_db:open_doc(Db, LDocId, []), + ?assertEqual(Doc5#doc{revs = {0, [<<"2">>]}}, Doc6). |