-module(admin_handler_automod_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). -define(TABLES, [admin, admin_audit, automod_settings, automod_hit, banned_word, user, calendar, event]). -define(ADMIN_ID, <<"adm_automod">>). -define(MOD_ID, <<"mod_automod">>). setup() -> eh_test_support:start_mnesia(), eh_test_support:ensure_tables(?TABLES), eh_test_support:ensure_jwt(), eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => admin}), eh_test_support:seed_admin(#{id => ?MOD_ID, role => moderator, email => <<"m@t.local">>}), core_automod_settings:ensure_defaults(), ok. cleanup(_) -> eh_test_support:unload_cowboy(), eh_test_support:delete_tables(?TABLES), eh_test_support:stop_mnesia(), ok. admin_handler_automod_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"GET settings – 200", fun test_get_settings/0}, {"GET settings – 401", fun test_get_settings_unauth/0}, {"PUT settings – admin ok", fun test_put_settings/0}, {"PUT settings – moderator forbidden", fun test_put_settings_moderator/0}, {"PUT settings – invalid threshold", fun test_put_settings_bad/0}, {"GET hits – list and filter", fun test_list_hits/0}, {"PUT hit approve", fun test_approve_hit/0}, {"PUT hit reject", fun test_reject_hit/0}, {"PUT hit not found", fun test_hit_not_found/0}, {"PUT hit already resolved", fun test_hit_already_resolved/0}, {"moderator can list and resolve hits", fun test_moderator_hits/0} ]}. test_get_settings() -> {Status, _, Body} = eh_test_support:call(admin_handler_automod, #{ method => <<"GET">>, path => <<"/v1/admin/automod/settings">>, auth => ?ADMIN_ID }), ?assertEqual(200, Status), Map = jsx:decode(Body, [return_maps]), ?assertEqual(<<"flag">>, maps:get(<<"keyword_action">>, Map)), ?assertEqual(<<"word_boundary">>, maps:get(<<"match_mode">>, Map)), ?assertEqual(3, maps:get(<<"report_threshold">>, Map)). test_get_settings_unauth() -> {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"GET">>, path => <<"/v1/admin/automod/settings">>, auth => none }), ?assertEqual(401, Status). test_put_settings() -> {Status, _, Body} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/settings">>, auth => ?ADMIN_ID, body => jsx:encode(#{ <<"keyword_action">> => <<"reject">>, <<"match_mode">> => <<"substring">>, <<"report_threshold">> => 7 }) }), ?assertEqual(200, Status), Map = jsx:decode(Body, [return_maps]), ?assertEqual(<<"reject">>, maps:get(<<"keyword_action">>, Map)), ?assertEqual(<<"substring">>, maps:get(<<"match_mode">>, Map)), ?assertEqual(7, maps:get(<<"report_threshold">>, Map)). test_put_settings_moderator() -> {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/settings">>, auth => ?MOD_ID, body => jsx:encode(#{<<"report_threshold">> => 2}) }), ?assertEqual(403, Status). test_put_settings_bad() -> {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/settings">>, auth => ?ADMIN_ID, body => jsx:encode(#{<<"report_threshold">> => 0}) }), ?assertEqual(400, Status). test_list_hits() -> {ok, H1} = core_automod_hit:create(event, <<"e1">>, keyword, [<<"a">>], flag), {ok, _} = core_automod_hit:create(calendar, <<"c1">>, report_threshold, [], freeze), {ok, _} = core_automod_hit:resolve(H1#automod_hit.id, approved, ?ADMIN_ID), {Status, _, Body} = eh_test_support:call(admin_handler_automod, #{ method => <<"GET">>, path => <<"/v1/admin/automod/hits">>, auth => ?ADMIN_ID, qs => [{<<"status">>, <<"open">>}] }), ?assertEqual(200, Status), List = jsx:decode(Body, [return_maps]), ?assertEqual(1, length(List)), ?assertEqual(<<"open">>, maps:get(<<"status">>, hd(List))). test_approve_hit() -> Owner = seed_user(), {ok, Cal} = core_calendar:create(Owner, <<"C">>, <<>>, manual), {ok, Ev} = core_event:create(Cal#calendar.id, <<"t">>, eh_test_support:future_start(), 60), _ = core_event:freeze(Ev#event.id, <<"auto:keyword">>), {ok, Hit} = core_automod_hit:create(event, Ev#event.id, keyword, [<<"x">>], flag), {Status, _, Body} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/hits/", (Hit#automod_hit.id)/binary>>, auth => ?ADMIN_ID, bindings => #{id => Hit#automod_hit.id}, body => jsx:encode(#{<<"status">> => <<"approved">>}) }), ?assertEqual(200, Status), Map = jsx:decode(Body, [return_maps]), ?assertEqual(<<"approved">>, maps:get(<<"status">>, Map)), {ok, Active} = core_event:get_by_id(Ev#event.id), ?assertEqual(active, Active#event.status). test_reject_hit() -> Owner = seed_user(), {ok, Cal} = core_calendar:create(Owner, <<"C">>, <<>>, manual), {ok, Ev} = core_event:create(Cal#calendar.id, <<"t">>, eh_test_support:future_start(), 60), {ok, Hit} = core_automod_hit:create(event, Ev#event.id, keyword, [<<"x">>], flag), {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/hits/", (Hit#automod_hit.id)/binary>>, auth => ?ADMIN_ID, bindings => #{id => Hit#automod_hit.id}, body => jsx:encode(#{<<"status">> => <<"rejected">>}) }), ?assertEqual(200, Status), {ok, Frozen} = core_event:get_by_id(Ev#event.id), ?assertEqual(frozen, Frozen#event.status). test_hit_not_found() -> {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/hits/missing">>, auth => ?ADMIN_ID, bindings => #{id => <<"missing">>}, body => jsx:encode(#{<<"status">> => <<"approved">>}) }), ?assertEqual(404, Status). test_hit_already_resolved() -> {ok, Hit} = core_automod_hit:create(event, <<"e">>, keyword, [], flag), {ok, _} = core_automod_hit:resolve(Hit#automod_hit.id, approved, ?ADMIN_ID), {Status, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/hits/", (Hit#automod_hit.id)/binary>>, auth => ?ADMIN_ID, bindings => #{id => Hit#automod_hit.id}, body => jsx:encode(#{<<"status">> => <<"rejected">>}) }), ?assertEqual(409, Status). test_moderator_hits() -> {ok, Hit} = core_automod_hit:create(event, <<"e2">>, keyword, [<<"z">>], flag), {StatusList, _, _} = eh_test_support:call(admin_handler_automod, #{ method => <<"GET">>, path => <<"/v1/admin/automod/hits">>, auth => ?MOD_ID }), ?assertEqual(200, StatusList), {StatusPut, _, Body} = eh_test_support:call(admin_handler_automod, #{ method => <<"PUT">>, path => <<"/v1/admin/automod/hits/", (Hit#automod_hit.id)/binary>>, auth => ?MOD_ID, bindings => #{id => Hit#automod_hit.id}, body => jsx:encode(#{<<"status">> => <<"approved">>}) }), ?assertEqual(200, StatusPut), ?assertEqual(<<"approved">>, maps:get(<<"status">>, jsx:decode(Body, [return_maps]))). seed_user() -> Id = base64:encode(crypto:strong_rand_bytes(10), #{mode => urlsafe, padding => false}), mnesia:dirty_write(eh_test_support:make_user(#{id => Id, email => <>})), Id.