%%%------------------------------------------------------------------- %%% @doc API-тесты автомодерации (admin settings + hits queue). %%%------------------------------------------------------------------- -module(admin_automod_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). -spec test() -> ok. test() -> ct:pal("=== Admin Automod Tests ==="), Admin = api_test_runner:get_admin_token(), Moder = api_test_runner:get_moderator_token(), test_get_settings(Admin), test_put_settings(Admin), test_put_settings_forbidden_moderator(Moder), Unique = integer_to_binary(erlang:system_time()), BadWord = <<"am_spam_", Unique/binary>>, test_seed_banned_word(Admin, BadWord), test_flag_creates_hit(Admin, BadWord), test_list_hits_filter(Admin), test_approve_restores(Admin, Moder, BadWord), test_reject_keeps_frozen(Admin, BadWord), test_restore_defaults(Admin), ct:pal("=== All admin automod tests passed ==="), ok. test_get_settings(Token) -> ct:pal(" TEST: GET automod settings"), S = api_test_runner:admin_get(<<"/v1/admin/automod/settings">>, Token), ?assert(maps:is_key(<<"keyword_action">>, S)), ?assert(maps:is_key(<<"match_mode">>, S)), ?assert(maps:is_key(<<"report_threshold">>, S)), ct:pal(" OK"). test_put_settings(Token) -> ct:pal(" TEST: PUT automod settings"), S = api_test_runner:admin_put(<<"/v1/admin/automod/settings">>, Token, #{ <<"keyword_action">> => <<"flag">>, <<"match_mode">> => <<"word_boundary">>, <<"report_threshold">> => 3 }), ?assertEqual(<<"flag">>, maps:get(<<"keyword_action">>, S)), ?assertEqual(3, maps:get(<<"report_threshold">>, S)), ct:pal(" OK"). test_put_settings_forbidden_moderator(Token) -> ct:pal(" TEST: moderator cannot PUT settings"), {ok, Status, _, _} = api_test_runner:admin_request(put, <<"/v1/admin/automod/settings">>, Token, jsx:encode(#{<<"report_threshold">> => 9})), ?assertEqual(403, Status), ct:pal(" OK"). test_seed_banned_word(Token, Word) -> ct:pal(" TEST: seed banned word ~s", [Word]), _ = api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => Word}), ct:pal(" OK"). test_flag_creates_hit(AdminToken, BadWord) -> ct:pal(" TEST: flag policy creates hit via user content"), api_test_runner:admin_put(<<"/v1/admin/automod/settings">>, AdminToken, #{ <<"keyword_action">> => <<"flag">>, <<"match_mode">> => <<"word_boundary">>, <<"report_threshold">> => 3 }), User = api_test_runner:get_user_token(), CalId = api_test_runner:create_calendar(User, #{title => <<"AutomodCal">>}), Title = <<"Party with ", BadWord/binary>>, {ok, 201, _, Body} = api_test_runner:client_request(post, <<"/v1/calendars/", CalId/binary, "/events">>, User, jsx:encode(#{ title => Title, start_time => api_test_runner:future_date_iso8601(), duration => 60 })), #{<<"id">> := EventId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]), ?assertEqual(<<"frozen">>, Status), Hits = api_test_runner:admin_get(<<"/v1/admin/automod/hits?status=open">>, AdminToken), ?assert(lists:any(fun(H) -> maps:get(<<"entity_id">>, H) =:= EventId andalso maps:get(<<"trigger">>, H) =:= <<"keyword">> end, Hits)), put({automod_event, BadWord}, EventId), ct:pal(" OK: event ~s frozen + hit", [EventId]). test_list_hits_filter(Token) -> ct:pal(" TEST: list hits filter by trigger"), Hits = api_test_runner:admin_get(<<"/v1/admin/automod/hits?trigger=keyword">>, Token), ?assert(is_list(Hits)), ?assert(lists:all(fun(H) -> maps:get(<<"trigger">>, H) =:= <<"keyword">> end, Hits)), ct:pal(" OK: ~p", [length(Hits)]). test_approve_restores(AdminToken, ModerToken, BadWord) -> ct:pal(" TEST: approve hit restores entity"), EventId = get({automod_event, BadWord}), Hits = api_test_runner:admin_get(<<"/v1/admin/automod/hits?status=open">>, AdminToken), Hit = hd([H || H <- Hits, maps:get(<<"entity_id">>, H) =:= EventId]), HitId = maps:get(<<"id">>, Hit), Resolved = api_test_runner:admin_put( <<"/v1/admin/automod/hits/", HitId/binary>>, ModerToken, #{<<"status">> => <<"approved">>}), ?assertEqual(<<"approved">>, maps:get(<<"status">>, Resolved)), %% admin event fetch Ev = api_test_runner:admin_get(<<"/v1/admin/events/", EventId/binary>>, AdminToken), ?assertEqual(<<"active">>, maps:get(<<"status">>, Ev)), ct:pal(" OK"). test_reject_keeps_frozen(AdminToken, BadWord) -> ct:pal(" TEST: reject hit keeps freeze"), User = api_test_runner:get_user_token(), CalId = api_test_runner:create_calendar(User, #{title => <<"AutomodCal2">>}), Title = <<"Again ", BadWord/binary>>, {ok, 201, _, Body} = api_test_runner:client_request(post, <<"/v1/calendars/", CalId/binary, "/events">>, User, jsx:encode(#{ title => Title, start_time => api_test_runner:future_date_iso8601(), duration => 45 })), #{<<"id">> := EventId} = jsx:decode(list_to_binary(Body), [return_maps]), Hits = api_test_runner:admin_get(<<"/v1/admin/automod/hits?status=open">>, AdminToken), Hit = hd([H || H <- Hits, maps:get(<<"entity_id">>, H) =:= EventId]), HitId = maps:get(<<"id">>, Hit), _ = api_test_runner:admin_put(<<"/v1/admin/automod/hits/", HitId/binary>>, AdminToken, #{<<"status">> => <<"rejected">>}), Ev = api_test_runner:admin_get(<<"/v1/admin/events/", EventId/binary>>, AdminToken), ?assertEqual(<<"frozen">>, maps:get(<<"status">>, Ev)), ct:pal(" OK"). test_restore_defaults(Token) -> ct:pal(" TEST: restore default settings"), _ = api_test_runner:admin_put(<<"/v1/admin/automod/settings">>, Token, #{ <<"keyword_action">> => <<"flag">>, <<"match_mode">> => <<"word_boundary">>, <<"report_threshold">> => 3 }), ct:pal(" OK").