blob: e478e664015a27af68d937875f36386f20f0ece4 [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_view_test).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(USER, "chttpd_view_test_admin").
-define(PASS, "pass").
-define(AUTH, {basic_auth, {?USER, ?PASS}}).
-define(CONTENT_JSON, {"Content-Type", "application/json"}).
-define(DOCS, #{
<<"docs">> => [
#{<<"_id">> => <<"a">>, <<"key">> => <<"a">>, <<"value">> => 1},
#{<<"_id">> => <<"b">>, <<"key">> => <<"b">>, <<"value">> => 2},
#{<<"_id">> => <<"c">>, <<"key">> => <<"c">>, <<"value">> => 3},
#{<<"_id">> => <<"d">>, <<"key">> => <<"d">>, <<"_deleted">> => true}
]
}).
-define(DDOC, #{
<<"_id">> => <<"_design/ddoc">>,
<<"views">> => #{
<<"map">> => #{<<"map">> => <<"function(doc) { emit(doc.key, doc.value) }">>},
<<"map_reduce">> => #{
<<"map">> => <<"function(doc) { emit(doc.key, doc.value) }">>,
<<"reduce">> => <<"_sum">>
}
}
}).
-define(ERROR_KEYS_INCOMPATIBLE, #{
<<"error">> := <<"query_parse_error">>,
<<"reason">> := <<"`keys` is incompatible with `key`, `start_key` and `end_key`">>
}).
% seconds
-define(TIMEOUT, 60).
setup() ->
Hashed = couch_passwords:hash_admin_password(?PASS),
ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
Db = ?tempdb(),
ok = create_db(Db),
ok = create_docs(Db),
ok = create_ddoc(Db),
Db.
teardown(Db) ->
ok = fabric:delete_db(Db),
ok = config:delete("admins", ?USER, _Persist = false).
view_test_() ->
{
"chttpd view 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_view_with_queries_keys, ?TIMEOUT),
?TDEF_FE(t_view_with_queries_limit_skip, ?TIMEOUT),
?TDEF_FE(t_view_with_multiple_queries, ?TIMEOUT),
?TDEF_FE(t_view_with_key_and_start_key),
?TDEF_FE(t_view_with_key_and_end_key),
?TDEF_FE(t_view_with_single_keys_and_start_key),
?TDEF_FE(t_view_with_keys_and_start_key),
?TDEF_FE(t_view_with_key_non_existent_docs),
?TDEF_FE(t_view_with_keys_non_existent_docs),
?TDEF_FE(t_view_with_key_deleted_docs),
?TDEF_FE(t_view_with_keys_deleted_docs),
?TDEF_FE(t_view_map_reduce_with_key),
?TDEF_FE(t_view_map_reduce_with_single_keys),
?TDEF_FE(t_view_map_reduce_with_single_keys_and_group),
?TDEF_FE(t_view_map_reduce_with_keys),
?TDEF_FE(t_view_map_reduce_with_keys_and_group)
]
}
}
}.
all_docs_test_() ->
{
"chttpd all docs 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_all_docs_with_key_and_start_key),
?TDEF_FE(t_all_docs_with_key_and_end_key),
?TDEF_FE(t_all_docs_with_single_keys_and_start_key),
?TDEF_FE(t_all_docs_with_keys_and_start_key),
?TDEF_FE(t_all_docs_with_key_non_existent_docs),
?TDEF_FE(t_all_docs_with_keys_non_existent_docs),
?TDEF_FE(t_all_docs_with_key_deleted_docs),
?TDEF_FE(t_all_docs_with_keys_deleted_docs)
]
}
}
}.
t_view_with_queries_keys(Db) ->
QueryDoc = #{<<"queries">> => [#{<<"keys">> => [<<"a">>, <<"c">>]}]},
{Code, Res} = req(post, url(Db, "_design/ddoc/_view/map/queries"), QueryDoc),
?assertMatch(
#{
<<"results">> := [
#{
<<"total_rows">> := 3,
<<"offset">> := 1,
<<"rows">> := [#{<<"id">> := <<"a">>}, #{<<"id">> := <<"c">>}]
}
]
},
Res
),
?assertEqual(200, Code).
t_view_with_queries_limit_skip(Db) ->
QueryDoc = #{<<"queries">> => [#{<<"limit">> => 1, <<"skip">> => 1}]},
{Code, Res} = req(post, url(Db, "_design/ddoc/_view/map/queries/"), QueryDoc),
?assertMatch(
#{
<<"results">> := [
#{<<"total_rows">> := 3, <<"offset">> := 1, <<"rows">> := [#{<<"id">> := <<"b">>}]}
]
},
Res
),
?assertEqual(200, Code).
t_view_with_multiple_queries(Db) ->
QueryDoc = #{
<<"queries">> => [#{<<"keys">> => [<<"a">>, <<"c">>], <<"limit">> => 1, <<"skip">> => 1}]
},
{Code, Res} = req(post, url(Db, "_design/ddoc/_view/map/queries/"), QueryDoc),
?assertMatch(
#{
<<"results">> := [
#{<<"total_rows">> := 3, <<"offset">> := 2, <<"rows">> := [#{<<"id">> := <<"c">>}]}
]
},
Res
),
?assertEqual(200, Code).
t_view_with_key_and_start_key(Db) ->
{Code1, Res1} = req(get, url(Db, "_design/ddoc/_view/map", "key=\"a\"&startkey=\"b\"")),
{Code2, Res2} = req(get, url(Db, "_design/ddoc/_view/map", "startkey=\"b\"&key=\"a\"")),
?assertMatch(
#{
<<"error">> := <<"query_parse_error">>,
<<"reason">> :=
<<"No rows can match your key range, reverse your start_key and end_key or set descending=true">>
},
Res1
),
?assertMatch(#{<<"rows">> := [#{<<"id">> := <<"a">>}]}, Res2),
?assertEqual(400, Code1),
?assertEqual(200, Code2).
t_all_docs_with_key_and_start_key(Db) ->
{Code1, Res1} = req(get, url(Db, "_all_docs", "key=\"a\"&startkey=\"b\"")),
{Code2, Res2} = req(get, url(Db, "_all_docs", "startkey=\"b\"&key=\"a\"")),
?assertMatch(#{<<"rows">> := []}, Res1),
?assertMatch(#{<<"rows">> := [#{<<"id">> := <<"a">>}]}, Res2),
?assertEqual(200, Code1),
?assertEqual(200, Code2).
t_view_with_key_and_end_key(Db) ->
test_helper_key_and_end_key(Db, "_design/ddoc/_view/map").
t_all_docs_with_key_and_end_key(Db) ->
test_helper_key_and_end_key(Db, "_all_docs").
test_helper_key_and_end_key(Db, Path) ->
{Code1, Res1} = req(get, url(Db, Path, "key=\"a\"&endkey=\"b\"")),
{Code2, Res2} = req(get, url(Db, Path, "endkey=\"b\"&key=\"a\"")),
?assertMatch(#{<<"rows">> := [#{<<"id">> := <<"a">>}, #{<<"id">> := <<"b">>}]}, Res1),
?assertMatch(#{<<"rows">> := [#{<<"id">> := <<"a">>}]}, Res2),
?assertEqual(200, Code1),
?assertEqual(200, Code2).
t_view_with_single_keys_and_start_key(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map?keys=[\"a\"]&startkey=\"b\"")),
?assertMatch(
#{
<<"error">> := <<"query_parse_error">>,
<<"reason">> :=
<<"No rows can match your key range, reverse your start_key and end_key or set descending=true">>
},
Res
),
?assertEqual(400, Code).
t_all_docs_with_single_keys_and_start_key(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs?keys=[\"a\"]&startkey=\"b\"")),
?assertMatch(?ERROR_KEYS_INCOMPATIBLE, Res),
?assertEqual(400, Code).
t_view_with_keys_and_start_key(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map", "keys=[\"a\",\"b\"]&start_key=\"b\"")),
?assertMatch(?ERROR_KEYS_INCOMPATIBLE, Res),
?assertEqual(400, Code).
t_all_docs_with_keys_and_start_key(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs", "keys=[\"a\",\"b\"]&start_key=\"b\"")),
?assertMatch(?ERROR_KEYS_INCOMPATIBLE, Res),
?assertEqual(400, Code).
t_view_with_key_non_existent_docs(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map", "key=\"not_exist\"")),
?assertMatch(#{<<"total_rows">> := 3, <<"offset">> := 3, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map"), #{<<"key">> => <<"not_exist">>}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_all_docs_with_key_non_existent_docs(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs", "key=\"not_exist\"")),
?assertMatch(#{<<"total_rows">> := 4, <<"offset">> := 4, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_all_docs"), #{<<"key">> => <<"not_exist">>}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_with_keys_non_existent_docs(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map", "keys=[\"not_exist\"]")),
?assertMatch(#{<<"total_rows">> := 3, <<"offset">> := 3, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map"), #{<<"keys">> => [<<"not_exist">>]}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_all_docs_with_keys_non_existent_docs(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs", "keys=[\"not_exist\"]")),
?assertMatch(
#{
<<"total_rows">> := 4,
<<"offset">> := null,
<<"rows">> := [#{<<"key">> := <<"not_exist">>, <<"error">> := <<"not_found">>}]
},
Res
),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_all_docs"), #{<<"keys">> => [<<"not_exist">>]}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_with_key_deleted_docs(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map", "key=\"d\"")),
?assertMatch(#{<<"total_rows">> := 3, <<"offset">> := 3, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map"), #{<<"key">> => <<"d">>}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_all_docs_with_key_deleted_docs(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs", "key=\"d\"")),
?assertMatch(#{<<"total_rows">> := 4, <<"offset">> := 4, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_all_docs"), #{<<"key">> => <<"d">>}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_with_keys_deleted_docs(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map", "keys=[\"d\"]")),
?assertMatch(#{<<"total_rows">> := 3, <<"offset">> := 3, <<"rows">> := []}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map"), #{<<"keys">> => [<<"d">>]}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_all_docs_with_keys_deleted_docs(Db) ->
{Code, Res} = req(get, url(Db, "_all_docs", "keys=[\"d\"]")),
?assertMatch(
#{
<<"total_rows">> := 4,
<<"offset">> := null,
<<"rows">> := [
#{
<<"id">> := <<"d">>,
<<"key">> := <<"d">>,
<<"value">> := #{<<"deleted">> := true}
}
]
},
Res
),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_all_docs"), #{<<"keys">> => [<<"d">>]}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_map_reduce_with_key(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map_reduce", "key=\"a\"")),
?assertMatch(#{<<"rows">> := [#{<<"key">> := null, <<"value">> := 1}]}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map_reduce"), #{<<"key">> => <<"a">>}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_map_reduce_with_single_keys(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map_reduce", "keys=[\"a\"]")),
?assertMatch(#{<<"rows">> := [#{<<"key">> := null, <<"value">> := 1}]}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map_reduce"), #{<<"keys">> => [<<"a">>]}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_map_reduce_with_single_keys_and_group(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map_reduce?keys=[\"a\"]&group=true")),
?assertMatch(#{<<"rows">> := [#{<<"key">> := <<"a">>, <<"value">> := 1}]}, Res),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map_reduce"), #{
<<"keys">> => [<<"a">>], <<"group">> => <<"true">>
}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_map_reduce_with_keys(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map_reduce?keys=[\"a\",\"b\"]")),
?assertMatch(
#{
<<"error">> := <<"query_parse_error">>,
<<"reason">> := <<"Multi-key fetches for reduce views must use `group=true`">>
},
Res
),
?assertEqual(400, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map_reduce"), #{
<<"keys">> => [<<"a">>, <<"b">>]
}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
t_view_map_reduce_with_keys_and_group(Db) ->
{Code, Res} = req(get, url(Db, "_design/ddoc/_view/map_reduce?keys=[\"a\",\"b\"]&group=true")),
?assertMatch(
#{
<<"rows">> := [
#{<<"key">> := <<"a">>, <<"value">> := 1},
#{<<"key">> := <<"b">>, <<"value">> := 2}
]
},
Res
),
?assertEqual(200, Code),
{Code1, Res1} = req(post, url(Db, "_design/ddoc/_view/map_reduce"), #{
<<"keys">> => [<<"a">>, <<"b">>], <<"group">> => true
}),
?assertEqual(Res, Res1),
?assertEqual(Code, Code1).
%%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
url(Db) ->
Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
Port = mochiweb_socket_server:get(chttpd, port),
lists:concat(["http://", Addr, ":", Port, "/", ?b2l(Db)]).
url(Db, Path) ->
url(Db) ++ "/" ++ Path.
url(Db, Path, Parameters) ->
url(Db) ++ "/" ++ Path ++ "?" ++ Parameters.
create_db(Db) ->
case req(put, url(Db)) of
{201, #{}} -> ok;
Error -> error({failed_to_create_test_db, Db, Error})
end.
create_docs(Db) ->
case req(post, url(Db) ++ "/_bulk_docs", ?DOCS) of
{201, _} -> ok;
Error -> error({failed_to_create_docs, Db, Error})
end.
create_ddoc(Db) ->
case req(post, url(Db), ?DDOC) of
{201, _} -> ok;
Error -> error({failed_to_create_ddocs, Db, Error})
end.
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])}.