% 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(global_changes_hooks_tests).

-export([allowed_owner/2]).

-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").

-define(t2l(V), lists:flatten(io_lib:format("~p", [V]))).

start() ->
    Ctx = test_util:start_couch([chttpd, global_changes]),
    DbName = ?tempdb(),
    ok = fabric:create_db(DbName, [?ADMIN_CTX]),
    application:set_env(global_changes, dbname, DbName),
    {Ctx, DbName}.

stop({Ctx, DbName}) ->
    ok = fabric:delete_db(DbName, [?ADMIN_CTX]),
    test_util:stop_couch(Ctx),
    ok.

setup(default) ->
    add_admin("admin", <<"pass">>),
    config:delete("chttpd_auth", "authentication_redirect", false),
    config:set("chttpd", "require_valid_user", "false", false),
    get_host();
setup(A) ->
    Host = setup(default),
    ok = config:set(
        "global_changes",
        "allowed_owner",
        ?t2l({?MODULE, allowed_owner, A}),
        false
    ),
    Host.

teardown(_) ->
    delete_admin("admin"),
    config:delete("global_changes", "allowed_owner", false),
    ok.

allowed_owner(_Req, "throw") ->
    throw({unauthorized, <<"Exception thrown.">>});
allowed_owner(_Req, "pass") ->
    "super".

allowed_owner_hook_test_() ->
    {
        "Check allowed_owner hook",
        {
            setup,
            fun start/0,
            fun stop/1,
            [
                disabled_allowed_owner_integration_point(),
                enabled_allowed_owner_integration_point()
            ]
        }
    }.

disabled_allowed_owner_integration_point() ->
    {
        "disabled allowed_owner integration point",
        {
            foreach,
            fun() -> setup(default) end,
            fun teardown/1,
            [
                fun should_not_fail_for_admin/1,
                fun should_fail_for_non_admin/1
            ]
        }
    }.

enabled_allowed_owner_integration_point() ->
    {
        "enabled allowed_owner integration point",
        [
            {
                foreach,
                fun() -> setup("throw") end,
                fun teardown/1,
                [fun should_throw/1]
            },
            {
                foreach,
                fun() -> setup("pass") end,
                fun teardown/1,
                [fun should_pass/1]
            }
        ]
    }.

should_not_fail_for_admin(Host) ->
    ?_test(begin
        Headers = [{basic_auth, {"admin", "pass"}}],
        {Status, [Error, Reason]} =
            request(Host, Headers, [<<"error">>, <<"reason">>]),
        ?assertEqual(200, Status),
        ?assertEqual(undefined, Error),
        ?assertEqual(undefined, Reason)
    end).

should_fail_for_non_admin(Host) ->
    ?_test(begin
        Headers = [],
        {Status, [Error, Reason]} =
            request(Host, Headers, [<<"error">>, <<"reason">>]),
        ?assertEqual(401, Status),
        ?assertEqual(<<"unauthorized">>, Error),
        ?assertEqual(<<"You are not a server admin.">>, Reason)
    end).

should_pass(Host) ->
    ?_test(begin
        Headers = [{basic_auth, {"admin", "pass"}}],
        {Status, [Error, Reason]} =
            request(Host, Headers, [<<"error">>, <<"reason">>]),
        ?assertEqual(200, Status),
        ?assertEqual(undefined, Error),
        ?assertEqual(undefined, Reason)
    end).

should_throw(Host) ->
    ?_test(begin
        Headers = [{basic_auth, {"admin", "pass"}}],
        {Status, [Error, Reason]} =
            request(Host, Headers, [<<"error">>, <<"reason">>]),
        ?assertEqual(401, Status),
        ?assertEqual(<<"unauthorized">>, Error),
        ?assertEqual(<<"Exception thrown.">>, Reason)
    end).

request(Host, Headers, ToDecode) ->
    Url = Host ++ "/_db_updates",
    {ok, Status, _Headers, BinBody} = test_request:get(Url, Headers),
    {Body} = jiffy:decode(BinBody),
    Values = [couch_util:get_value(Key, Body) || Key <- ToDecode],
    {Status, Values}.

add_admin(User, Pass) ->
    Hashed = couch_passwords:hash_admin_password(Pass),
    config:set("admins", User, ?b2l(Hashed), false).

delete_admin(User) ->
    config:delete("admins", User, false).

get_host() ->
    Addr = config:get("httpd", "bind_address", "127.0.0.1"),
    Port = integer_to_list(mochiweb_socket_server:get(chttpd, port)),
    Host = "http://" ++ Addr ++ ":" ++ Port,
    Host.
