blob: 21a691cf480a91e8318193fce2626546607193ba [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_filtered_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch_replicator/src/couch_replicator.hrl").
-include_lib("fabric/test/fabric2_test.hrl").
-define(DDOC_ID, <<"_design/filter_ddoc">>).
-define(DDOC, #{
<<"_id">> => ?DDOC_ID,
<<"filters">> => #{
<<"testfilter">> =>
<<"\n"
" function(doc, req){if (doc.class == 'mammal') return true;}\n"
" ">>,
<<"queryfilter">> =>
<<"\n"
" function(doc, req) {\n"
" if (doc.class && req.query.starts) {\n"
" return doc.class.indexOf(req.query.starts) === 0;\n"
" }\n"
" else {\n"
" return false;\n"
" }\n"
" }\n"
" ">>
},
<<"views">> => #{
<<"mammals">> => #{
<<"map">> =>
<<"\n"
" function(doc) {\n"
" if (doc.class == 'mammal') {\n"
" emit(doc._id, null);\n"
" }\n"
" }\n"
" ">>
}
}
}).
filtered_replication_test_() ->
{
"Replications with filters tests",
{
setup,
fun couch_replicator_test_helper:start_couch/0,
fun couch_replicator_test_helper:stop_couch/1,
{
foreach,
fun setup/0,
fun teardown/1,
[
?TDEF_FE(filtered_replication_test),
?TDEF_FE(query_filtered_replication_test),
?TDEF_FE(view_filtered_replication_test),
?TDEF_FE(replication_id_changes_if_filter_changes, 15)
]
}
}
}.
setup() ->
Source = couch_replicator_test_helper:create_db(),
create_docs(Source),
Target = couch_replicator_test_helper:create_db(),
config:set("replicator", "stats_update_interval_sec", "0", false),
config:set("replicator", "interval_sec", "1", false),
{Source, Target}.
teardown({Source, Target}) ->
config:delete("replicator", "stats_update_interval_sec", false),
config:delete("replicator", "checkpoint_interval", false),
config:delete("replicator", "interval_sec", false),
couch_replicator_test_helper:delete_db(Source),
couch_replicator_test_helper:delete_db(Target).
filtered_replication_test({Source, Target}) ->
RepObject =
{[
{<<"source">>, Source},
{<<"target">>, Target},
{<<"filter">>, <<"filter_ddoc/testfilter">>}
]},
{ok, _} = couch_replicator_test_helper:replicate(RepObject),
FilterFun = fun(_DocId, {Props}) ->
couch_util:get_value(<<"class">>, Props) == <<"mammal">>
end,
{ok, TargetDbInfo, AllReplies} = compare_dbs(Source, Target, FilterFun),
?assertEqual(1, proplists:get_value(doc_count, TargetDbInfo)),
?assertEqual(0, proplists:get_value(doc_del_count, TargetDbInfo)),
?assert(lists:all(fun(Valid) -> Valid end, AllReplies)).
query_filtered_replication_test({Source, Target}) ->
RepObject =
{[
{<<"source">>, Source},
{<<"target">>, Target},
{<<"filter">>, <<"filter_ddoc/queryfilter">>},
{<<"query_params">>,
{[
{<<"starts">>, <<"a">>}
]}}
]},
{ok, _} = couch_replicator_test_helper:replicate(RepObject),
FilterFun = fun(_DocId, {Props}) ->
case couch_util:get_value(<<"class">>, Props) of
<<"a", _/binary>> -> true;
_ -> false
end
end,
{ok, TargetDbInfo, AllReplies} = compare_dbs(Source, Target, FilterFun),
?assertEqual(2, proplists:get_value(doc_count, TargetDbInfo)),
?assertEqual(0, proplists:get_value(doc_del_count, TargetDbInfo)),
?assert(lists:all(fun(Valid) -> Valid end, AllReplies)).
view_filtered_replication_test({Source, Target}) ->
RepObject =
{[
{<<"source">>, Source},
{<<"target">>, Target},
{<<"filter">>, <<"_view">>},
{<<"query_params">>,
{[
{<<"view">>, <<"filter_ddoc/mammals">>}
]}}
]},
{ok, _} = couch_replicator_test_helper:replicate(RepObject),
FilterFun = fun(_DocId, {Props}) ->
couch_util:get_value(<<"class">>, Props) == <<"mammal">>
end,
{ok, TargetDbInfo, AllReplies} = compare_dbs(Source, Target, FilterFun),
?assertEqual(1, proplists:get_value(doc_count, TargetDbInfo)),
?assertEqual(0, proplists:get_value(doc_del_count, TargetDbInfo)),
?assert(lists:all(fun(Valid) -> Valid end, AllReplies)).
replication_id_changes_if_filter_changes({Source, Target}) ->
config:set("replicator", "checkpoint_interval", "500", false),
Rep =
{[
{<<"source">>, Source},
{<<"target">>, Target},
{<<"filter">>, <<"filter_ddoc/testfilter">>},
{<<"continuous">>, true}
]},
{ok, _, RepId1} = couch_replicator_test_helper:replicate_continuous(Rep),
wait_scheduler_docs_written(1),
?assertMatch(
[#{<<"id">> := RepId1}],
couch_replicator_test_helper:scheduler_jobs()
),
FilterFun1 = fun(_, {Props}) ->
couch_util:get_value(<<"class">>, Props) == <<"mammal">>
end,
{ok, TargetDbInfo1, AllReplies1} = compare_dbs(Source, Target, FilterFun1),
?assertEqual(1, proplists:get_value(doc_count, TargetDbInfo1)),
?assert(lists:all(fun(Valid) -> Valid end, AllReplies1)),
{ok, SourceDb} = fabric2_db:open(Source, [?ADMIN_CTX]),
{ok, DDoc1} = fabric2_db:open_doc(SourceDb, ?DDOC_ID),
Flt = <<"function(doc, req) {if (doc.class == 'reptiles') return true};">>,
DDoc2 = DDoc1#doc{
body =
{[
{<<"filters">>,
{[
{<<"testfilter">>, Flt}
]}}
]}
},
{ok, {_, _}} = fabric2_db:update_doc(SourceDb, DDoc2),
Info = wait_scheduler_repid_change(RepId1),
RepId2 = maps:get(<<"id">>, Info),
?assert(RepId1 =/= RepId2),
wait_scheduler_docs_written(1),
FilterFun2 = fun(_, {Props}) ->
Class = couch_util:get_value(<<"class">>, Props),
Class == <<"mammal">> orelse Class == <<"reptiles">>
end,
{ok, TargetDbInfo2, AllReplies2} = compare_dbs(Source, Target, FilterFun2),
?assertEqual(2, proplists:get_value(doc_count, TargetDbInfo2)),
?assert(lists:all(fun(Valid) -> Valid end, AllReplies2)),
couch_replicator_test_helper:cancel(RepId2).
compare_dbs(Source, Target, FilterFun) ->
{ok, TargetDb} = fabric2_db:open(Target, [?ADMIN_CTX]),
{ok, TargetDbInfo} = fabric2_db:get_db_info(TargetDb),
Fun = fun(SrcDoc, TgtDoc, Acc) ->
case FilterFun(SrcDoc#doc.id, SrcDoc#doc.body) of
true -> [SrcDoc == TgtDoc | Acc];
false -> [not_found == TgtDoc | Acc]
end
end,
Res = couch_replicator_test_helper:compare_fold(Source, Target, Fun, []),
{ok, TargetDbInfo, Res}.
create_docs(DbName) ->
couch_replicator_test_helper:create_docs(DbName, [
?DDOC,
#{
<<"_id">> => <<"doc1">>,
<<"class">> => <<"mammal">>,
<<"value">> => 1
},
#{
<<"_id">> => <<"doc2">>,
<<"class">> => <<"amphibians">>,
<<"value">> => 2
},
#{
<<"_id">> => <<"doc3">>,
<<"class">> => <<"reptiles">>,
<<"value">> => 3
},
#{
<<"_id">> => <<"doc4">>,
<<"class">> => <<"arthropods">>,
<<"value">> => 2
}
]).
wait_scheduler_docs_written(DocsWritten) ->
test_util:wait(
fun() ->
case couch_replicator_test_helper:scheduler_jobs() of
[] ->
wait;
[#{<<"info">> := null}] ->
wait;
[#{<<"info">> := Info}] ->
case Info of
#{<<"docs_written">> := DocsWritten} -> Info;
#{} -> wait
end
end
end,
10000,
250
).
wait_scheduler_repid_change(OldRepId) ->
test_util:wait(
fun() ->
case couch_replicator_test_helper:scheduler_jobs() of
[] ->
wait;
[#{<<"id">> := OldRepId}] ->
wait;
[#{<<"id">> := null}] ->
wait;
[#{<<"id">> := NewId} = Info] when is_binary(NewId) ->
Info
end
end,
10000,
250
).