-module(admin_handler_admins_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). -define(TABLES, [admin, admin_audit, ticket]). -define(ADMIN_ID, <<"adm_test_1">>). -define(PLAIN_ID, <<"adm_plain_1">>). setup() -> eh_test_support:start_mnesia(), eh_test_support:ensure_tables(?TABLES), eh_test_support:ensure_jwt(), catch application:ensure_all_started(argon2), eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => superadmin}), ok. cleanup(_) -> eh_test_support:unload_cowboy(), eh_test_support:delete_tables(?TABLES), eh_test_support:stop_mnesia(), ok. admin_admins_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"GET list – success", {timeout, 60, fun test_list/0}}, {"GET list – filter by role", {timeout, 60, fun test_list_filter/0}}, {"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}}, {"POST create – success", {timeout, 60, fun test_create_ok/0}}, {"POST create – forbidden for non-superadmin", {timeout, 60, fun test_create_forbidden/0}}, {"POST create – missing fields", {timeout, 60, fun test_create_missing_fields/0}}, {"DELETE – method not allowed", {timeout, 60, fun test_wrong_method/0}} ]}. argon2_ready() -> case catch argon2:hash(<<"probe">>) of {ok, _} -> true; _ -> false end. require_argon2(Fun) -> case argon2_ready() of true -> Fun(); false -> {skip, "argon2 NIF not available"} end. seed_extra_admins() -> ok = mnesia:dirty_write(eh_test_support:make_admin(#{ id => <<"adm_mod">>, email => <<"mod@test.local">>, role => moderator, nickname => <<"mod">> })), ok = mnesia:dirty_write(eh_test_support:make_admin(#{ id => <<"adm_sup">>, email => <<"support@test.local">>, role => support, nickname => <<"sup">> })), ok. test_list() -> seed_extra_admins(), {Status, _, Body} = eh_test_support:call(admin_handler_admins, #{ method => <<"GET">>, path => <<"/v1/admin/admins">>, auth => ?ADMIN_ID }), ?assertEqual(200, Status), Result = jsx:decode(Body, [return_maps]), ?assertEqual(3, length(Result)). test_list_filter() -> seed_extra_admins(), {Status, _, Body} = eh_test_support:call(admin_handler_admins, #{ method => <<"GET">>, path => <<"/v1/admin/admins">>, auth => ?ADMIN_ID, qs => [{<<"role">>, <<"moderator">>}] }), ?assertEqual(200, Status), Result = jsx:decode(Body, [return_maps]), ?assertEqual(1, length(Result)), [Only] = Result, ?assertEqual(<<"mod@test.local">>, maps:get(<<"email">>, Only)). test_unauthorized() -> {Status, _, _} = eh_test_support:call(admin_handler_admins, #{ method => <<"GET">>, path => <<"/v1/admin/admins">>, auth => none }), ?assertEqual(401, Status). test_create_ok() -> require_argon2(fun() -> {Status, _, Body} = eh_test_support:call(admin_handler_admins, #{ method => <<"POST">>, path => <<"/v1/admin/admins">>, auth => ?ADMIN_ID, body => jsx:encode(#{ <<"email">> => <<"newadmin@test.local">>, <<"password">> => <<"SecretPass1!">>, <<"role">> => <<"admin">> }) }), ?assertEqual(201, Status), Result = jsx:decode(Body, [return_maps]), ?assertEqual(<<"newadmin@test.local">>, maps:get(<<"email">>, Result)), ?assertEqual(<<"admin">>, maps:get(<<"role">>, Result)), Audits = core_admin_audit:list(), ?assert(length(Audits) >= 1) end). test_create_forbidden() -> ok = mnesia:dirty_write(eh_test_support:make_admin(#{ id => ?PLAIN_ID, email => <<"plain@test.local">>, role => admin })), {Status, _, _} = eh_test_support:call(admin_handler_admins, #{ method => <<"POST">>, path => <<"/v1/admin/admins">>, auth => ?PLAIN_ID, body => jsx:encode(#{ <<"email">> => <<"x@test.local">>, <<"password">> => <<"SecretPass1!">>, <<"role">> => <<"admin">> }) }), ?assertEqual(403, Status). test_create_missing_fields() -> {Status, _, _} = eh_test_support:call(admin_handler_admins, #{ method => <<"POST">>, path => <<"/v1/admin/admins">>, auth => ?ADMIN_ID, body => jsx:encode(#{<<"email">> => <<"x@test.local">>}) }), ?assertEqual(400, Status). test_wrong_method() -> {Status, _, _} = eh_test_support:call(admin_handler_admins, #{ method => <<"DELETE">>, path => <<"/v1/admin/admins">>, auth => ?ADMIN_ID }), ?assertEqual(405, Status).