%%%------------------------------------------------------------------- %%% @doc Тесты административного API для управления бан-словами. %%% %%% Покрывает эндпоинты: %%% GET /v1/admin/banned-words %%% POST /v1/admin/banned-words %%% PUT /v1/admin/banned-words/:word %%% DELETE /v1/admin/banned-words/:word %%% POST /v1/admin/banned-words/batch %%% %%% Проверяет: %%% - получение списка слов %%% - добавление нового слова %%% - редактирование существующего слова %%% - удаление слова %%% - массовую загрузку списком %%% - пагинацию %%% @end %%%------------------------------------------------------------------- -module(admin_banned_words_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). %%%=================================================================== %%% Главная тестовая функция %%%=================================================================== -spec test() -> ok. test() -> ct:pal("=== Admin Banned Words Tests ==="), Token = api_test_runner:get_admin_token(), % Добавляем два исходных слова Unique = integer_to_binary(erlang:system_time()), Word1 = <<"badword1_", Unique/binary>>, Word2 = <<"badword2_", Unique/binary>>, api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => Word1}), api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => Word2}), test_list_words(Token, Word1, Word2), test_add_word(Token), test_update_word(Token, Word1), test_batch_add_words(Token), test_words_pagination(Token), test_delete_word(Token, Word2), test_update_word_conflict(Token), test_batch_add_invalid(Token), test_batch_add_duplicates(Token), ct:pal("=== All admin banned words tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== %% @doc GET /v1/admin/banned-words – список всех слов. -spec test_list_words(binary(), binary(), binary()) -> ok. test_list_words(Token, Word1, Word2) -> ct:pal(" TEST: List all banned words"), Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token), ?assert(is_list(Words)), ?assert(length(Words) >= 2), ?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word1 orelse maps:get(<<"word">>, W) =:= Word2 end, Words)), ct:pal(" OK: ~p words", [length(Words)]). %% @doc POST /v1/admin/banned-words – добавление нового слова. -spec test_add_word(binary()) -> ok. test_add_word(Token) -> ct:pal(" TEST: Add a new banned word"), Unique = integer_to_binary(erlang:system_time()), NewWord = <<"newbadword", Unique/binary>>, Result = api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => NewWord}), ?assertEqual(<<"added">>, maps:get(<<"status">>, Result)), Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token), ?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= NewWord end, Words)), ct:pal(" OK"). %% @doc PUT /v1/admin/banned-words/:word – редактирование слова. -spec test_update_word(binary(), binary()) -> ok. test_update_word(Token, Word) -> ct:pal(" TEST: Update a banned word"), NewWord = <>, Result = api_test_runner:admin_put(<<"/v1/admin/banned-words/", Word/binary>>, Token, #{<<"word">> => NewWord}), ?assertEqual(<<"updated">>, maps:get(<<"status">>, Result)), % Проверяем, что старое слово отсутствует, а новое присутствует Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token), ?assertNot(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word end, Words)), ?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= NewWord end, Words)), ct:pal(" OK"). %% @doc POST /v1/admin/banned-words/batch – массовая загрузка. -spec test_batch_add_words(binary()) -> ok. test_batch_add_words(Token) -> ct:pal(" TEST: Batch add banned words"), Unique = integer_to_binary(erlang:system_time()), Words = [ <<"batch1_", Unique/binary>>, <<"batch2_", Unique/binary>>, <<"batch3_", Unique/binary>> ], Body = jsx:encode(#{<<"words">> => Words}), {ok, 200, _, RespBody} = api_test_runner:admin_request(post, <<"/v1/admin/banned-words/batch">>, Token, Body), Result = jsx:decode(list_to_binary(RespBody), [return_maps]), ?assertEqual(3, maps:get(<<"added">>, Result)), ?assertEqual(0, maps:get(<<"skipped">>, Result)), % Проверяем наличие всех слов AllWords = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token), [?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word end, AllWords)) || Word <- Words], ct:pal(" OK"). %% @doc DELETE /v1/admin/banned-words/:word – удаление слова. -spec test_delete_word(binary(), binary()) -> ok. test_delete_word(Token, Word) -> ct:pal(" TEST: Delete a banned word"), Path = <<"/v1/admin/banned-words/", Word/binary>>, Result = api_test_runner:admin_delete(Path, Token), ?assertEqual(<<"deleted">>, maps:get(<<"status">>, Result)), Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token), ?assertNot(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word end, Words)), ct:pal(" OK"). %% @doc GET /v1/admin/banned-words?limit=...&offset=... – пагинация. -spec test_words_pagination(binary()) -> ok. test_words_pagination(Token) -> ct:pal(" TEST: Banned words pagination"), Page1 = api_test_runner:admin_get(<<"/v1/admin/banned-words?limit=1&offset=0">>, Token), ?assert(length(Page1) >= 1), Page2 = api_test_runner:admin_get(<<"/v1/admin/banned-words?limit=1&offset=1">>, Token), ?assert(length(Page2) >= 1), Id1 = maps:get(<<"id">>, hd(Page1)), Id2 = maps:get(<<"id">>, hd(Page2)), ?assertNotEqual(Id1, Id2), ct:pal(" OK"). %% @doc PUT /v1/admin/banned-words/:word – попытка переименовать в уже существующее слово. test_update_word_conflict(Token) -> ct:pal(" TEST: Update banned word to existing word"), Unique = integer_to_binary(erlang:system_time()), WordA = <<"conflictA_", Unique/binary>>, WordB = <<"conflictB_", Unique/binary>>, api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => WordA}), api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => WordB}), Path = <<"/v1/admin/banned-words/", WordA/binary>>, {ok, 409, _, _} = api_test_runner:admin_request(put, Path, Token, jsx:encode(#{<<"word">> => WordB})), ct:pal(" OK: got 409 conflict"). %% @doc POST /v1/admin/banned-words/batch – с невалидными элементами. test_batch_add_invalid(Token) -> ct:pal(" TEST: Batch add with invalid items"), Unique = integer_to_binary(erlang:system_time()), Words = [ <<"valid_", Unique/binary>>, null, 12345, <<"valid2_", Unique/binary>> ], Body = jsx:encode(#{<<"words">> => Words}), {ok, 200, _, RespBody} = api_test_runner:admin_request(post, <<"/v1/admin/banned-words/batch">>, Token, Body), Result = jsx:decode(list_to_binary(RespBody), [return_maps]), ?assertEqual(2, maps:get(<<"added">>, Result)), ?assertEqual(0, maps:get(<<"skipped">>, Result)), ?assertEqual(2, maps:get(<<"invalid_count">>, Result)), ct:pal(" OK: invalid items filtered"). %% @doc POST /v1/admin/banned-words/batch – с дубликатами. test_batch_add_duplicates(Token) -> ct:pal(" TEST: Batch add with duplicates"), Unique = integer_to_binary(erlang:system_time()), Word1 = <<"dup1_", Unique/binary>>, Word2 = <<"dup2_", Unique/binary>>, Words = [Word1, Word2, Word1, Word2], Body = jsx:encode(#{<<"words">> => Words}), {ok, 200, _, RespBody} = api_test_runner:admin_request(post, <<"/v1/admin/banned-words/batch">>, Token, Body), Result = jsx:decode(list_to_binary(RespBody), [return_maps]), ?assertEqual(2, maps:get(<<"added">>, Result)), ?assertEqual(2, maps:get(<<"skipped">>, Result)), ct:pal(" OK: duplicates handled").