blob: 192d113c68a8fa769e0911d0aba359a36d4da859 [file] [log] [blame]
% 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(couch_replicator_scheduler_docs_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(JSON, {"Content-Type", "application/json"}).
setup_replicator_db(Prefix) ->
RepDb =
case Prefix of
<<>> -> <<"_replicator">>;
<<_/binary>> -> <<Prefix/binary, "/_replicator">>
end,
Opts = [{q, 1}, {n, 1}, ?ADMIN_CTX],
case fabric:create_db(RepDb, Opts) of
ok -> ok;
{error, file_exists} -> ok
end,
RepDb.
setup_main_replicator_db() ->
{Ctx, {Source, Target}} = couch_replicator_test_helper:test_setup(),
RepDb = setup_replicator_db(<<>>),
{Ctx, {RepDb, Source, Target}}.
setup_prefixed_replicator_db() ->
{Ctx, {Source, Target}} = couch_replicator_test_helper:test_setup(),
RepDb = setup_replicator_db(?tempdb()),
{Ctx, {RepDb, Source, Target}}.
setup_prefixed_replicator_db_with_update_docs_true() ->
{Ctx, {Source, Target}} = couch_replicator_test_helper:test_setup(),
config:set("replicator", "update_docs", "true", _Persist = false),
RepDb = setup_replicator_db(?tempdb()),
{Ctx, {RepDb, Source, Target}}.
teardown({Ctx, {RepDb, Source, Target}}) ->
ok = fabric:delete_db(RepDb, [?ADMIN_CTX]),
config:delete("replicator", "update_docs", _Persist = false),
couch_replicator_test_helper:test_teardown({Ctx, {Source, Target}}).
scheduler_docs_test_main_db_test_() ->
{
foreach,
fun setup_main_replicator_db/0,
fun teardown/1,
[
?TDEF_FE(t_scheduler_docs_total_rows, 10)
]
}.
scheduler_docs_test_prefixed_db_test_() ->
{
foreach,
fun setup_prefixed_replicator_db/0,
fun teardown/1,
[
?TDEF_FE(t_scheduler_docs_total_rows, 10)
]
}.
replicator_bdu_test_main_db_test_() ->
{
setup,
fun setup_prefixed_replicator_db/0,
fun teardown/1,
with([
?TDEF(t_local_docs_can_be_written),
?TDEF(t_design_docs_can_be_written),
?TDEF(t_malformed_docs_are_rejected)
])
}.
replicator_bdu_test_prefixed_db_test_() ->
{
setup,
fun setup_prefixed_replicator_db/0,
fun teardown/1,
with([
?TDEF(t_local_docs_can_be_written),
?TDEF(t_design_docs_can_be_written),
?TDEF(t_malformed_docs_are_rejected)
])
}.
t_replicator_doc_state_fields_test_() ->
{
setup,
fun setup_prefixed_replicator_db/0,
fun teardown/1,
with([
?TDEF(t_doc_fields_are_updated, 10),
?TDEF(t_doc_fields_are_ignored, 10)
])
}.
t_replicator_doc_state_fields_update_docs_true_test_() ->
{
setup,
fun setup_prefixed_replicator_db_with_update_docs_true/0,
fun teardown/1,
with([
?TDEF(t_doc_fields_are_updated, 10),
?TDEF(t_doc_fields_are_ignored, 10)
])
}.
t_scheduler_docs_total_rows({_Ctx, {RepDb, Source, Target}}) ->
SourceUrl = couch_replicator_test_helper:cluster_db_url(Source),
TargetUrl = couch_replicator_test_helper:cluster_db_url(Target),
RepDoc = #{<<"source">> => SourceUrl, <<"target">> => TargetUrl},
RepDocUrl = rep_doc_url(RepDb, ?docid()),
{201, _} = req(put, RepDocUrl, RepDoc),
SchedulerDocsUrl =
case RepDb of
<<"_replicator">> -> url(<<"/_scheduler/docs">>);
<<_/binary>> -> url(<<"/_scheduler/docs/", RepDb/binary>>)
end,
Body = test_util:wait(
fun() ->
case req(get, SchedulerDocsUrl) of
{200, #{<<"docs">> := [_ | _]} = Decoded} -> Decoded;
{_, #{}} -> wait
end
end,
10000,
1000
),
Docs = maps:get(<<"docs">>, Body),
TotalRows = maps:get(<<"total_rows">>, Body),
?assertEqual(TotalRows, length(Docs)),
ok.
t_local_docs_can_be_written({_Ctx, {RepDb, _, _}}) ->
DocUrl1 = rep_doc_url(RepDb, <<"_local/doc1">>),
?assertMatch({201, _}, req(put, DocUrl1, #{})),
DocUrl2 = rep_doc_url(RepDb, <<"_local/doc2">>),
?assertMatch({201, _}, req(put, DocUrl2, #{<<"foo">> => <<"bar">>})).
t_design_docs_can_be_written({_Ctx, {RepDb, _, _}}) ->
DocUrl1 = rep_doc_url(RepDb, <<"_design/ddoc1">>),
?assertMatch({201, _}, req(put, DocUrl1, #{})),
DocUrl2 = rep_doc_url(RepDb, <<"_design/ddoc2">>),
?assertMatch({201, _}, req(put, DocUrl2, #{<<"foo">> => <<"bar">>})).
t_malformed_docs_are_rejected({_Ctx, {RepDb, _, _}}) ->
% couch_replicator_parse holds most of the BDU validation logic
% Here we just test that the BDU works with a few basic cases
DocUrl1 = rep_doc_url(RepDb, <<"rep1">>),
?assertMatch({403, _}, req(put, DocUrl1, #{})),
DocUrl2 = rep_doc_url(RepDb, <<"rep2">>),
?assertMatch({403, _}, req(put, DocUrl2, #{<<"foo">> => <<"bar">>})).
t_doc_fields_are_updated({_Ctx, {RepDb, Source, Target}}) ->
SourceUrl = couch_replicator_test_helper:cluster_db_url(Source),
TargetUrl = couch_replicator_test_helper:cluster_db_url(Target),
RepDoc = #{
<<"source">> => SourceUrl,
<<"target">> => TargetUrl,
<<"_replication_id">> => <<"foo3">>,
<<"_replication_state">> => <<"triggered">>,
<<"_replication_state_time">> => <<"foo5">>,
<<"_replication_state_reason">> => <<"foo6">>
},
RepDocUrl = rep_doc_url(RepDb, ?docid()),
{201, _} = req(put, RepDocUrl, RepDoc),
StateDoc = test_util:wait(
fun() ->
case req(get, RepDocUrl) of
{200, #{<<"_replication_state">> := <<"completed">>} = StDoc} -> StDoc;
{_, #{}} -> wait
end
end,
10000,
1000
),
?assertMatch(
#{
<<"_replication_state">> := <<"completed">>,
<<"_replication_state_time">> := <<_/binary>>,
<<"_replication_stats">> := #{}
},
StateDoc
),
#{<<"_replication_state_time">> := StateTime} = StateDoc,
?assertNotEqual(<<"foo5">>, StateTime),
?assertNot(is_map_key(<<"_replicator_state_reason">>, StateDoc)),
case config:get_boolean("replicator", "update_docs", false) of
true ->
?assertMatch(#{<<"_replication_id">> := <<_/binary>>}, StateDoc),
#{<<"_replication_id">> := RepId} = StateDoc,
?assertNotEqual(<<"foo3">>, RepId);
false ->
?assertNot(is_map_key(<<"_replication_id">>, StateDoc))
end.
t_doc_fields_are_ignored({_Ctx, {RepDb, Source, Target}}) ->
SourceUrl = couch_replicator_test_helper:cluster_db_url(Source),
TargetUrl = couch_replicator_test_helper:cluster_db_url(Target),
RepDoc = #{
<<"source">> => SourceUrl,
<<"target">> => TargetUrl,
<<"replication_id">> => <<"foo1">>,
<<"id">> => <<"foo2">>,
<<"other_junk">> => true
},
RepDocUrl = rep_doc_url(RepDb, ?docid()),
{201, _} = req(put, RepDocUrl, RepDoc),
StateDoc = test_util:wait(
fun() ->
case req(get, RepDocUrl) of
{200, #{<<"_replication_state">> := <<"completed">>} = StDoc} -> StDoc;
{_, #{}} -> wait
end
end,
10000,
1000
),
?assertMatch(
#{
<<"replication_id">> := <<"foo1">>,
<<"id">> := <<"foo2">>,
<<"other_junk">> := true
},
StateDoc
).
rep_doc_url(RepDb, DocId) when is_binary(RepDb) ->
rep_doc_url(binary_to_list(RepDb), DocId);
rep_doc_url(RepDb, DocId) when is_binary(DocId) ->
rep_doc_url(RepDb, binary_to_list(DocId));
rep_doc_url(RepDb, DocId) when is_list(RepDb), is_list(DocId) ->
UrlQuotedRepDb = mochiweb_util:quote_plus(RepDb),
url(UrlQuotedRepDb ++ "/" ++ DocId).
url(UrlPath) ->
binary_to_list(couch_replicator_test_helper:cluster_db_url(UrlPath)).
req(Method, Url) ->
Headers = [?JSON],
{ok, Code, _, Res} = test_request:request(Method, Url, Headers),
{Code, jiffy:decode(Res, [return_maps])}.
req(Method, Url, #{} = Body) ->
req(Method, Url, jiffy:encode(Body));
req(Method, Url, Body) ->
Headers = [?JSON],
{ok, Code, _, Res} = test_request:request(Method, Url, Headers, Body),
{Code, jiffy:decode(Res, [return_maps])}.