blob: c636ec6a922defbbc2a4c4f5e81322a0eba1f5b3 [file]
% 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_misc_test).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(USER, "chttpd_misc_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),
Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
Port = mochiweb_socket_server:get(chttpd, port),
Url = lists:concat(["http://", Addr, ":", Port, "/"]),
Url.
teardown(_Url) ->
meck:unload(),
Persist = false,
ok = config:delete("couchdb", "maintenance_mode", Persist),
ok = config:delete("cluster", "seedlist", Persist),
ok = config:delete("couchdb", "js_engine", Persist),
ok = config:delete("admins", ?USER, Persist).
welcome_test_() ->
{
"chttpd welcome endpoint 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(should_have_version),
?TDEF_FE(should_have_features),
?TDEF_FE(should_have_uuid)
]
}
}
}.
should_have_uuid(Url) ->
{ok, Status, _, Body} = req_get(Url),
?assertEqual(200, Status),
#{
<<"couchdb">> := CouchDB,
<<"uuid">> := Uuid
} = Body,
?assertEqual(<<"Welcome">>, CouchDB),
RealUuid = couch_server:get_uuid(),
?assertEqual(RealUuid, Uuid).
should_have_version(Url) ->
{ok, Status, _, Body} = req_get(Url),
?assertEqual(200, Status),
#{
<<"couchdb">> := CouchDB,
<<"version">> := Version,
<<"git_sha">> := Sha
} = Body,
?assertNotEqual(Sha, undefined),
?assertEqual(<<"Welcome">>, CouchDB),
RealVersion = list_to_binary(couch_server:get_version()),
?assertEqual(RealVersion, Version).
should_have_features(Url) ->
config:enable_feature(snek),
{ok, 200, _, Body1} = req_get(Url),
#{<<"features">> := Features1} = Body1,
?assert(is_list(Features1)),
?assert(lists:member(<<"snek">>, Features1)),
config:disable_feature(snek),
{ok, 200, _, Body2} = req_get(Url),
#{<<"features">> := Features2} = Body2,
?assert(is_list(Features2)),
?assertNot(lists:member(<<"snek">>, Features2)),
config:set("couchdb", "js_engine", "quickjs", false),
{ok, 200, _, Body3} = req_get(Url),
#{<<"features">> := Features3} = Body3,
?assert(lists:member(<<"quickjs">>, Features3)),
config:delete("couchdb", "js_engine", false).
up_test_() ->
{
"chttpd _up endpoint 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_basic),
?TDEF_FE(t_maintenance_mode),
?TDEF_FE(t_nolb_mode),
?TDEF_FE(t_with_seeds)
]
}
}
}.
t_basic(Url) ->
{ok, Code, _, Body} = req_get(Url ++ "/_up"),
?assertEqual(200, Code),
?assertMatch(#{<<"status">> := <<"ok">>, <<"seeds">> := #{}}, Body).
t_maintenance_mode(Url) ->
config:set("couchdb", "maintenance_mode", "true", _Persist = false),
{ok, Code, _, Body} = req_get(Url ++ "/_up"),
?assertEqual(404, Code),
?assertMatch(#{<<"status">> := <<"maintenance_mode">>}, Body).
t_nolb_mode(Url) ->
config:set("couchdb", "maintenance_mode", "nolb", _Persist = false),
{ok, Code, _, Body} = req_get(Url ++ "/_up"),
?assertEqual(404, Code),
?assertMatch(#{<<"status">> := <<"nolb">>}, Body).
t_with_seeds(Url) ->
CfgSeeds = "couchdb@node1.example.com,couchdb@node2.example.com",
config:set("cluster", "seedlist", CfgSeeds, false),
ok = gen_server:stop(mem3_seeds),
WaitFun = fun() ->
case catch mem3_seeds:get_status() of
{ok, #{}} -> ok;
_ -> wait
end
end,
test_util:wait(WaitFun),
{ok, Code, _, Body} = req_get(Url ++ "/_up"),
?assertEqual(404, Code),
?assertMatch(#{<<"seeds">> := #{}}, Body),
?assertMatch(#{<<"status">> := <<"seeding">>}, Body),
#{<<"seeds">> := Seeds} = Body,
?assertMatch(#{<<"couchdb@node1.example.com">> := #{}}, Seeds),
?assertMatch(#{<<"couchdb@node2.example.com">> := #{}}, Seeds).
uuid_test_() ->
{
"chttpd _uuid endpoint 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_one_uuid),
?TDEF_FE(t_multiple_uuids)
]
}
}
}.
t_one_uuid(Url) ->
{ok, Code, _, Body} = req_get(Url ++ "/_uuids"),
?assertEqual(200, Code),
?assertMatch(#{<<"uuids">> := [_]}, Body),
#{<<"uuids">> := [Uuid]} = Body,
?assertEqual(32, byte_size(Uuid)).
t_multiple_uuids(Url) ->
{ok, Code, _, Body} = req_get(Url ++ "/_uuids?count=3"),
?assertEqual(200, Code),
?assertMatch(#{<<"uuids">> := [_, _, _]}, Body),
#{<<"uuids">> := [Uuid1, Uuid2, Uuid3]} = Body,
?assert(Uuid1 /= Uuid2),
?assert(Uuid1 /= Uuid3).
-define(MOCKED_CLOUSEAU_VERSION, <<"2.24.0">>).
node_test_() ->
{
"chttpd _node/{node_name}/* endpoint 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_system),
?TDEF_FE(t_versions_with_clouseau),
?TDEF_FE(t_versions_without_clouseau)
]
}
}
}.
t_system(Url) ->
{ok, Code, _, Body} = req_get(Url ++ "/_node/_local/_system"),
?assertEqual(200, Code),
% The body is quite large, so test a general subset of functionality:
% - Metrics from the VM (context_switches), some simple, and some histograms
% - Custom computed metrics like internal_replication_jobs
% - Simple, histogrammed and aggregated message queues, from the vm/dependencies/couch services
%
?assertMatch(
#{
<<"context_switches">> := _,
<<"distribution">> := #{},
<<"internal_replication_jobs">> := _,
<<"memory">> := #{
<<"binary">> := _,
<<"processes">> := _
},
<<"message_queues">> := #{
<<"couch_db_updater">> := #{},
<<"couch_event_server">> := _,
<<"couch_file">> := #{},
<<"couch_server">> := _,
<<"couch_server_1">> := _,
<<"ibrowse">> := _,
<<"index_server">> := _,
<<"index_server_1">> := _,
<<"init">> := _,
<<"logger">> := _,
<<"rexi_buffer">> := _,
<<"rexi_server">> := _
}
},
Body
).
t_versions_with_clouseau(Url) ->
ok = meck:expect(clouseau_rpc, version, 0, {ok, ?MOCKED_CLOUSEAU_VERSION}),
{ok, Code, _, Body} = req_get(Url ++ "/_node/_local/_versions"),
?assertEqual(200, Code),
?assertMatch(
#{
<<"javascript_engine">> := #{<<"name">> := _},
<<"collation_driver">> := #{<<"name">> := <<"libicu">>},
<<"erlang">> := #{
<<"supported_hashes">> := [_ | _],
<<"version">> := _
},
<<"clouseau">> := #{
<<"version">> := ?MOCKED_CLOUSEAU_VERSION
}
},
Body
).
t_versions_without_clouseau(Url) ->
ok = meck:expect(clouseau_rpc, version, 0, {'EXIT', noconnection}),
{ok, Code, _, Body} = req_get(Url ++ "/_node/_local/_versions"),
?assertEqual(200, Code),
?assertNot(maps:is_key(<<"search">>, Body)),
?assertMatch(
#{
<<"javascript_engine">> := #{<<"name">> := _},
<<"collation_driver">> := #{<<"name">> := <<"libicu">>},
<<"erlang">> := #{
<<"supported_hashes">> := [_ | _],
<<"version">> := _
}
},
Body
).
req_get(Url) ->
{ok, Code, Headers, Body} = test_request:get(Url, [?CONTENT_JSON, ?AUTH]),
{ok, Code, Headers, jiffy:decode(Body, [return_maps])}.