-module(admin_handler_login_tests). -include_lib("eunit/include/eunit.hrl"). -define(JWT_SECRET, <<"test-user-secret-key-32-byt!">>). -define(ADMIN_JWT_SECRET, <<"test-admin-secret-key-32-b">>). setup() -> ok = meck:new(logic_auth, [non_strict]), ok = meck:new(cowboy_req, [non_strict]), application:set_env(eventhub, jwt_secret, ?JWT_SECRET), application:set_env(eventhub, admin_jwt_secret, ?ADMIN_JWT_SECRET), {ok, _} = application:ensure_all_started(jose). cleanup(_) -> application:unset_env(eventhub, jwt_secret), application:unset_env(eventhub, admin_jwt_secret), application:stop(jose), meck:unload(cowboy_req), meck:unload(logic_auth), ok. admin_handler_login_test_() -> {setup, fun setup/0, fun cleanup/1, [ {"Valid admin login returns 200 and token", fun test_valid_admin_login/0}, {"Invalid credentials return 401", fun test_invalid_credentials/0}, {"Non‑admin role returns 403", fun test_insufficient_permissions/0}, {"Malformed JSON returns 400", fun test_malformed_json/0}, {"Missing body returns 400", fun test_missing_body/0}, {"Wrong HTTP method returns 405", fun test_wrong_method/0} ]}. %% ── Вспомогательная функция для создания запроса и ожидания reply ── prepare_req(Method, HasBody, Body) -> ok = meck:expect(cowboy_req, method, fun(_) -> Method end), ok = meck:expect(cowboy_req, has_body, fun(_) -> HasBody end), case {HasBody, Body} of {true, undefined} -> ok; {true, _} -> ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, Body, Req} end); {false, _} -> ok end, % Устанавливаем мок на reply, который сохраняет ответ в словаре процесса meck:expect(cowboy_req, reply, fun(Code, Headers, RespBody, Req) -> put(test_reply, {Code, Headers, RespBody}), Req end), req. %% ── Тесты ──────────────────────────────────────────────────── test_valid_admin_login() -> UserMap = #{id => <<"adm1">>, email => <<"admin@test.com">>, role => <<"superadmin">>}, ok = meck:expect(logic_auth, authenticate_user, fun(<<"admin@test.com">>, <<"pass">>) -> {ok, UserMap} end), Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"admin@test.com">>, password => <<"pass">>})), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, Headers, Body} = get(test_reply), ?assertEqual(200, Code), ?assertEqual(<<"application/json">>, maps:get(<<"content-type">>, Headers)), Resp = jsx:decode(Body, [return_maps]), ?assert(is_map_key(<<"token">>, Resp)), ?assertEqual(<<"superadmin">>, maps:get(<<"role">>, maps:get(<<"user">>, Resp))). test_invalid_credentials() -> ok = meck:expect(logic_auth, authenticate_user, fun(_, _) -> {error, bad_credentials} end), Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"bad@test.com">>, password => <<"wrong">>})), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, _, Body} = get(test_reply), ?assertEqual(401, Code), #{<<"error">> := <<"bad_credentials">>} = jsx:decode(Body, [return_maps]). test_insufficient_permissions() -> UserMap = #{id => <<"user1">>, email => <<"user@test.com">>, role => <<"user">>}, ok = meck:expect(logic_auth, authenticate_user, fun(_, _) -> {ok, UserMap} end), Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"user@test.com">>, password => <<"pass">>})), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, _, Body} = get(test_reply), ?assertEqual(403, Code), #{<<"error">> := <<"insufficient_permissions">>} = jsx:decode(Body, [return_maps]). test_malformed_json() -> Req0 = prepare_req(<<"POST">>, true, <<"not a json">>), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, _, Body} = get(test_reply), ?assertEqual(400, Code), #{<<"error">> := <<"invalid_request">>} = jsx:decode(Body, [return_maps]). test_missing_body() -> Req0 = prepare_req(<<"POST">>, false, undefined), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, _, Body} = get(test_reply), ?assertEqual(400, Code), #{<<"error">> := <<"Missing request body">>} = jsx:decode(Body, [return_maps]). test_wrong_method() -> Req0 = prepare_req(<<"GET">>, false, undefined), {ok, _, _} = admin_handler_login:init(Req0, []), {Code, _, Body} = get(test_reply), ?assertEqual(405, Code), #{<<"error">> := <<"Method not allowed">>} = jsx:decode(Body, [return_maps]).