blob: 9514125be4fb01d38e61c26300f9e18a58da8434 [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(chttpd_purge_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(USER, "chttpd_db_test_admin").
-define(PASS, "pass").
-define(AUTH, {basic_auth, {?USER, ?PASS}}).
-define(CONTENT_JSON, {"Content-Type", "application/json"}).
setup() ->
Hashed = couch_passwords:hash_admin_password(?PASS),
ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
Db = ?tempdb(),
Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
Port = mochiweb_socket_server:get(chttpd, port),
DbUrl = lists:concat(["http://", Addr, ":", Port, "/", ?b2l(Db)]),
create_db(DbUrl),
DbUrl.
teardown(DbUrl) ->
delete_db(DbUrl),
ok = config:delete("admins", ?USER, _Persist = false).
purge_test_() ->
{
"chttpd purge tests",
{
setup,
fun chttpd_test_util:start_couch/0,
fun chttpd_test_util:stop_couch/1,
{
foreach,
fun setup/0,
fun teardown/1,
[
?TDEF_FE(t_purge_only_post_allowed),
?TDEF_FE(t_empty_purge_request),
?TDEF_FE(t_ok_purge_request),
?TDEF_FE(t_ok_purge_with_max_document_id_number),
?TDEF_FE(t_accepted_purge_request),
?TDEF_FE(t_partial_purge_request),
?TDEF_FE(t_mixed_purge_request),
?TDEF_FE(t_over_many_ids_or_revs_purge_request),
?TDEF_FE(t_purged_infos_limit_only_get_put_allowed),
?TDEF_FE(t_exceed_limits_on_purge_infos),
?TDEF_FE(t_should_error_set_purged_docs_limit_to_0),
?TDEF_FE(t_timeout_set_purged_infos_limit),
?TDEF_FE(t_purged_infos_only_get_allowed),
?TDEF_FE(t_empty_purged_infos),
?TDEF_FE(t_purged_infos_after_purge_request),
?TDEF_FE(t_purged_infos_after_multiple_purge_requests)
]
}
}
}.
t_purge_only_post_allowed(DbUrl) ->
{Status, Response} = req(put, url(DbUrl, "_purge")),
?assertMatch(#{<<"reason">> := <<"Only POST allowed">>}, Response),
?assert(Status =:= 405).
t_empty_purge_request(DbUrl) ->
{Status, Response} = req(post, url(DbUrl, "_purge"), #{}),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := #{}}, Response),
?assert(Status =:= 201 orelse Status =:= 202).
t_ok_purge_request(DbUrl) ->
{201, Response1} = create_docs(DbUrl, docs(3)),
IdsRevs = ids_revs(Response1),
{Status, Response2} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response2),
?assert(Status =:= 201 orelse Status =:= 202).
t_ok_purge_with_max_document_id_number(DbUrl) ->
PurgedDocsNum = 101,
{201, Response1} = create_docs(DbUrl, docs(PurgedDocsNum)),
IdsRevs = ids_revs(Response1),
{400, #{<<"reason">> := Error}} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertEqual(<<"Exceeded maximum number of documents.">>, Error),
ok = config:set("purge", "max_document_id_number", "101", _Persist = false),
try
{Status, Response2} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response2),
?assert(Status =:= 201 orelse Status =:= 202)
after
ok = config:delete("purge", "max_document_id_number", _Persist)
end.
t_accepted_purge_request(DbUrl) ->
try
meck:new(fabric, [passthrough]),
meck:expect(fabric, purge_docs, fun(_, _, _) ->
{accepted, [
{accepted, [
{1, <<187, 82, 160, 135, 14, 97, 52, 47, 28, 172, 13, 249, 96, 182, 127, 97>>}
]}
]}
end),
{_, IdsRevs} = get_id_rev_map(DbUrl, "doc1"),
{Status, Response} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response),
?assert(Status =:= 202)
after
meck:unload(fabric)
end.
t_partial_purge_request(DbUrl) ->
IdsRevs = create_and_update_doc(DbUrl, "doc1"),
{Status1, Response1} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response1),
?assert(Status1 =:= 201 orelse Status1 =:= 202),
{Status2, #{<<"content">> := Content}} = req(get, url(DbUrl, "doc1")),
?assertEqual(<<"updated">>, Content),
?assert(Status2 =:= 200).
t_mixed_purge_request(DbUrl) ->
Doc1IdRevs = create_and_update_doc(DbUrl, "doc1"),
[Rev1] = maps:get(?l2b("doc1"), Doc1IdRevs),
get_id_rev_map(DbUrl, "doc2"),
{Rev3, _} = get_id_rev_map(DbUrl, "doc3"),
IdsRevs = #{
% partial purge
<<"doc1">> => [Rev1],
% correct format, but invalid rev
<<"doc2">> => [Rev1, Rev3],
% correct format and rev
<<"doc3">> => [Rev3]
},
{Status1, Response} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(
#{
<<"purge_seq">> := null,
<<"purged">> := #{<<"doc1">> := [Rev1], <<"doc2">> := [], <<"doc3">> := [Rev3]}
},
Response
),
?assert(Status1 =:= 201 orelse Status1 =:= 202),
{Status2, #{<<"content">> := Content}} = req(get, url(DbUrl, "doc1")),
?assertEqual(<<"updated">>, Content),
?assert(Status2 =:= 200).
t_over_many_ids_or_revs_purge_request(DbUrl) ->
Doc1IdRevs = create_and_update_doc(DbUrl, "doc1"),
[Rev1] = maps:get(?l2b("doc1"), Doc1IdRevs),
get_id_rev_map(DbUrl, "doc2"),
{Rev3, _} = get_id_rev_map(DbUrl, "doc3"),
IdsRevs = #{
% partial purge
<<"doc1">> => [Rev1],
% correct format, but invalid rev
<<"doc2">> => [Rev1, Rev3],
% correct format and rev
<<"doc3">> => [Rev3]
},
% Ids larger than expected
config:set("purge", "max_document_id_number", "1", _Persist = false),
try
{Status1, #{<<"reason">> := Error1}} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertEqual(<<"Exceeded maximum number of documents.">>, Error1),
?assertEqual(400, Status1)
after
config:delete("purge", "max_document_id_number", _Persist)
end,
% Revs larger than expected
config:set("purge", "max_revisions_number", "1", _Persist),
try
{Status2, #{<<"reason">> := Error2}} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertEqual(<<"Exceeded maximum number of revisions.">>, Error2),
?assertEqual(400, Status2)
after
config:delete("purge", "max_revisions_number", _Persist)
end.
t_purged_infos_limit_only_get_put_allowed(DbUrl) ->
{Status, Response} = req(post, url(DbUrl, "_purged_infos_limit"), "2"),
?assertMatch(#{<<"reason">> := <<"Only GET,PUT allowed">>}, Response),
?assert(Status =:= 405).
t_exceed_limits_on_purge_infos(DbUrl) ->
{200, _} = req(put, url(DbUrl, "_purged_infos_limit"), "2"),
{201, Response1} = create_docs(DbUrl, docs(3)),
IdsRevs = ids_revs(Response1),
{Status2, Response2} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response2),
?assert(Status2 =:= 201 orelse Status2 =:= 202).
t_should_error_set_purged_docs_limit_to_0(DbUrl) ->
{Status, Res} = req(put, url(DbUrl, "_purged_infos_limit"), "0"),
?assertMatch(
#{
<<"error">> := <<"bad_request">>,
<<"reason">> := <<"`purged_infos_limit` must be positive integer">>
},
Res
),
?assert(Status =:= 400).
t_timeout_set_purged_infos_limit(DbUrl) ->
try
meck:new(fabric, [passthrough]),
meck:expect(fabric, set_purge_infos_limit, fun(_, _, _) -> {error, timeout} end),
{Status, #{<<"reason">> := Error}} = req(put, url(DbUrl, "_purged_infos_limit"), "2"),
?assertEqual(<<"timeout">>, Error),
?assert(Status =:= 500)
after
meck:unload(fabric)
end.
t_purged_infos_only_get_allowed(DbUrl) ->
{Status, Response} = req(post, url(DbUrl, "_purged_infos")),
?assertMatch(#{<<"reason">> := <<"Only GET allowed">>}, Response),
?assert(Status =:= 405).
t_empty_purged_infos(DbUrl) ->
{Status, Response} = req(get, url(DbUrl, "_purged_infos")),
?assertMatch(#{<<"purged_infos">> := []}, Response),
?assert(Status =:= 200).
t_purged_infos_after_purge_request(DbUrl) ->
PurgedDocsNum = 3,
{Status1, Response1} = create_docs(DbUrl, docs(PurgedDocsNum)),
?assert(Status1 =:= 201 orelse Status1 =:= 202),
IdsRevs = ids_revs(Response1),
{Status2, Response2} = req(post, url(DbUrl, "_purge"), IdsRevs),
?assert(Status2 =:= 201 orelse Status2 =:= 202),
?assertMatch(#{<<"purge_seq">> := null, <<"purged">> := IdsRevs}, Response2),
{Status3, Response3} = req(get, url(DbUrl, "_purged_infos")),
?assert(Status3 =:= 200),
?assertMatch(#{<<"purged_infos">> := [_ | _]}, Response3),
Info = maps:get(<<"purged_infos">>, Response3),
?assertEqual(3, length(Info)).
t_purged_infos_after_multiple_purge_requests(DbUrl) ->
{Rev1, Doc1IdRevs} = get_id_rev_map(DbUrl, "doc1"),
{Rev2, Doc2IdRevs} = get_id_rev_map(DbUrl, "doc2"),
{201, #{<<"rev">> := Rev1New}} =
req(put, url(DbUrl, "doc1?rev=" ++ ?b2l(Rev1)), #{<<"val">> => <<"updated">>}),
req(post, url(DbUrl, "_purge"), Doc1IdRevs),
req(post, url(DbUrl, "_purge"), Doc2IdRevs),
req(post, url(DbUrl, "_purge"), #{<<"doc1">> => [Rev1New]}),
{Status, #{<<"purged_infos">> := IdRevsList}} = req(get, url(DbUrl, "_purged_infos")),
?assert(lists:member(#{<<"id">> => <<"doc1">>, <<"revs">> => [Rev1]}, IdRevsList)),
?assert(lists:member(#{<<"id">> => <<"doc1">>, <<"revs">> => [Rev1New]}, IdRevsList)),
?assert(lists:member(#{<<"id">> => <<"doc2">>, <<"revs">> => [Rev2]}, IdRevsList)),
?assert(Status =:= 200).
%%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
url(Url, Path) ->
Url ++ "/" ++ Path.
create_db(Url) ->
case req(put, Url) of
{201, #{}} -> ok;
Error -> error({failed_to_create_test_db, Error})
end.
delete_db(Url) ->
case req(delete, Url) of
{200, #{}} -> ok;
Error -> error({failed_to_delete_test_db, Error})
end.
create_doc(Url, Id) ->
req(put, url(Url, Id), #{<<"val">> => ?l2b(Id)}).
create_docs(Url, Docs) ->
req(post, url(Url, "_bulk_docs"), #{<<"docs">> => Docs}).
docs(Counter) ->
lists:foldl(
fun(I, Acc) ->
Id = ?l2b(integer_to_list(I)),
Doc = #{<<"_id">> => Id, <<"val">> => I},
[Doc | Acc]
end,
[],
lists:seq(1, Counter)
).
ids_revs(Response) ->
IdsRevs = lists:map(
fun(DocResp) ->
Id = maps:get(<<"id">>, DocResp),
Rev = maps:get(<<"rev">>, DocResp),
{Id, [Rev]}
end,
Response
),
maps:from_list(IdsRevs).
get_id_rev_map(Url, Id) ->
{_, #{<<"rev">> := Rev}} = create_doc(Url, Id),
{Rev, #{?l2b(Id) => [Rev]}}.
create_and_update_doc(Url, Id) ->
{Rev, IdRevs} = get_id_rev_map(Url, Id),
NewDoc = new_doc(Rev),
{201, _} = req(post, url(Url, "_bulk_docs"), NewDoc),
IdRevs.
new_doc(Rev) ->
#{
<<"new_edits">> => false,
<<"docs">> => [
#{
<<"_id">> => <<"doc1">>,
<<"_revisions">> => #{
<<"start">> => 1,
<<"ids">> => [<<"12345">>, <<"67890">>]
},
<<"content">> => <<"updated">>,
<<"_rev">> => Rev
}
]
}.
req(Method, Url) ->
Headers = [?CONTENT_JSON, ?AUTH],
{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 = [?CONTENT_JSON, ?AUTH],
{ok, Code, _, Res} = test_request:request(Method, Url, Headers, Body),
{Code, jiffy:decode(Res, [return_maps])}.