Files
EventHubBack/test/api/admins/admin_audit_tests.erl
T
2026-07-13 14:53:51 +03:00

212 lines
9.5 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
%%%-------------------------------------------------------------------
%%% @doc Тесты административного API для аудита.
%%%
%%% Покрывает эндпоинты:
%%% GET /v1/admin/audit
%%%
%%% Проверяет:
%%% - получение списка записей аудита
%%% - фильтрацию по admin_id
%%% - фильтрацию по action
%%% - комбинацию фильтров (admin_id + action)
%%% - пагинацию (limit/offset, x-total-count)
%%% - сортировку по timestamp, action, admin_id
%%% - комбинации сортировки с фильтрацией и пагинацией
%%% @end
%%%-------------------------------------------------------------------
-module(admin_audit_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
%%%===================================================================
%%% Главная тестовая функция
%%%===================================================================
-spec test() -> ok.
test() ->
ct:pal("=== Admin Audit Tests ==="),
UserToken = api_test_runner:get_user_token(),
#{<<"id">> := UserId} = api_test_runner:client_get(<<"/v1/user/me">>, UserToken),
AdminToken = api_test_runner:get_admin_token(),
SuperToken = api_test_runner:get_superadmin_token(),
% Создаём тестовые записи аудита
Me = api_test_runner:admin_get(<<"/v1/admin/me">>, AdminToken),
AdminId = maps:get(<<"id">>, Me),
Path = <<"/v1/admin/user/", UserId/binary>>,
Body = #{<<"action">> => <<"block">>, <<"reason">> => <<"Test">>},
api_test_runner:admin_put(Path, AdminToken, Body),
ct:sleep(200),
Body2 = #{<<"action">> => <<"unblock">>, <<"reason">> => <<"Test">>},
api_test_runner:admin_put(Path, AdminToken, Body2),
% ── Основные тесты ──
test_list_audit(SuperToken),
test_list_admin_forbidden(AdminToken),
test_login_audit_ip(SuperToken),
% ── Фильтрация ──
test_filter_audit_by_admin_id(SuperToken, AdminId),
test_filter_audit_by_action(SuperToken),
% ── Комбинация фильтров ──
test_filter_audit_combined(SuperToken, AdminId),
% ── Пагинация ──
test_audit_pagination(SuperToken),
test_audit_pagination_headers(SuperToken),
% ── Сортировка ──
test_sort_by_timestamp_desc(SuperToken),
test_sort_by_action_asc(SuperToken),
test_sort_by_admin_id_asc(SuperToken),
% ── Комбинации сортировки с фильтрацией и пагинацией ──
test_sort_combined_with_filter(SuperToken, AdminId),
test_sort_combined_with_filter_and_pagination(SuperToken, AdminId),
ct:pal("=== All admin audit tests passed ==="),
ok.
%%%===================================================================
%%% Тестовые функции
%%%===================================================================
%% @doc GET /v1/admin/audit список всех записей аудита.
test_list_audit(Token) ->
ct:pal(" TEST: List all audit records"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit">>, Token),
?assert(is_list(Records)),
?assert(length(Records) >= 1),
ct:pal(" OK: ~p records", [length(Records)]).
%% @doc GET /v1/admin/audit запрет для обычного админа.
test_list_admin_forbidden(Token) ->
ct:pal(" TEST: List audit as non-superadmin (403)"),
Resp = api_test_runner:admin_request(get, <<"/v1/admin/audit">>, Token),
?assertMatch({ok, 403, _, _}, Resp),
ct:pal(" OK: got 403").
%% @doc Логин с X-Forwarded-For записывает IP клиента в аудит.
test_login_audit_ip(Token) ->
ct:pal(" TEST: Login audit stores X-Forwarded-For IP"),
Email = api_test_runner:admin_super_email(),
Password = api_test_runner:admin_super_password(),
Body = jsx:encode(#{<<"email">> => Email, <<"password">> => Password}),
{ok, 200, _, _} = api_test_runner:admin_request(
post, <<"/v1/admin/login">>, <<>>, Body,
[{"X-Forwarded-For", "203.0.113.99"}]
),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?action=login">>, Token),
Forwarded = [R || R <- Records, maps:get(<<"ip">>, R) =:= <<"203.0.113.99">>],
?assert(length(Forwarded) >= 1),
Login = hd(Forwarded),
?assertEqual(<<"203.0.113.99">>, maps:get(<<"ip">>, Login)),
ct:pal(" OK: ip=~s", [maps:get(<<"ip">>, Login)]).
%% ── Фильтрация ──
%% @doc Фильтрация по admin_id.
test_filter_audit_by_admin_id(Token, AdminId) ->
ct:pal(" TEST: Filter audit by admin_id"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?admin_id=", AdminId/binary>>, Token),
?assert(is_list(Records)),
[?assertEqual(AdminId, maps:get(<<"admin_id">>, R)) || R <- Records],
ct:pal(" OK: ~p records", [length(Records)]).
%% @doc Фильтрация по action.
test_filter_audit_by_action(Token) ->
ct:pal(" TEST: Filter audit by action"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?action=block_user">>, Token),
?assert(is_list(Records)),
[?assertEqual(<<"block_user">>, maps:get(<<"action">>, R)) || R <- Records],
ct:pal(" OK: ~p records with action=block_user", [length(Records)]).
%% @doc Комбинация фильтров: admin_id + action.
test_filter_audit_combined(Token, AdminId) ->
ct:pal(" TEST: Combined filter (admin_id + action)"),
Records = api_test_runner:admin_get(
<<"/v1/admin/audit?admin_id=", AdminId/binary, "&action=unblock_user">>, Token),
?assert(is_list(Records)),
[?assertEqual(AdminId, maps:get(<<"admin_id">>, R)) || R <- Records],
[?assertEqual(<<"unblock_user">>, maps:get(<<"action">>, R)) || R <- Records],
ct:pal(" OK: ~p records match both criteria", [length(Records)]).
%% ── Пагинация ──
%% @doc Пагинация: limit=1, offset=0.
test_audit_pagination(Token) ->
ct:pal(" TEST: Audit pagination"),
Page1 = api_test_runner:admin_get(<<"/v1/admin/audit?limit=1&offset=0">>, Token),
?assert(length(Page1) >= 1),
Page2 = api_test_runner:admin_get(<<"/v1/admin/audit?limit=1&offset=1">>, Token),
?assert(length(Page2) >= 0),
case {Page1, Page2} of
{[First|_], [Second|_]} ->
Id1 = maps:get(<<"id">>, First),
Id2 = maps:get(<<"id">>, Second),
?assertNotEqual(Id1, Id2);
_ -> ok
end,
ct:pal(" OK").
%% @doc Проверка заголовков пагинации (x-total-count).
test_audit_pagination_headers(Token) ->
ct:pal(" TEST: Audit pagination headers (x-total-count)"),
{ok, 200, Headers, _} = api_test_runner:admin_request(get,
<<"/v1/admin/audit?limit=2&offset=0">>, Token),
Total = list_to_integer(
proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).
%% ── Сортировка ──
%% @doc Сортировка по timestamp по убыванию.
test_sort_by_timestamp_desc(Token) ->
ct:pal(" TEST: Sort audit by timestamp desc"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?sort=timestamp&order=desc">>, Token),
Timestamps = [maps:get(<<"timestamp">>, R) || R <- Records],
?assertEqual(lists:reverse(lists:sort(Timestamps)), Timestamps),
ct:pal(" OK: sorted by timestamp desc").
%% @doc Сортировка по action по возрастанию.
test_sort_by_action_asc(Token) ->
ct:pal(" TEST: Sort audit by action asc"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?sort=action&order=asc">>, Token),
Actions = [maps:get(<<"action">>, R) || R <- Records],
?assertEqual(lists:sort(Actions), Actions),
ct:pal(" OK: sorted by action asc").
%% @doc Сортировка по admin_id по возрастанию.
test_sort_by_admin_id_asc(Token) ->
ct:pal(" TEST: Sort audit by admin_id asc"),
Records = api_test_runner:admin_get(<<"/v1/admin/audit?sort=admin_id&order=asc">>, Token),
AdminIds = [maps:get(<<"admin_id">>, R) || R <- Records],
?assertEqual(lists:sort(AdminIds), AdminIds),
ct:pal(" OK: sorted by admin_id asc").
%% ── Комбинации сортировки с фильтрацией и пагинацией ──
%% @doc Сортировка + фильтр: admin_id + action, сортировка по timestamp asc.
test_sort_combined_with_filter(Token, AdminId) ->
ct:pal(" TEST: Sort audit (timestamp asc) filtered by admin_id + action"),
Records = api_test_runner:admin_get(
<<"/v1/admin/audit?admin_id=", AdminId/binary, "&action=unblock_user&sort=timestamp&order=asc">>, Token),
[?assertEqual(AdminId, maps:get(<<"admin_id">>, R)) || R <- Records],
[?assertEqual(<<"unblock_user">>, maps:get(<<"action">>, R)) || R <- Records],
Timestamps = [maps:get(<<"timestamp">>, R) || R <- Records],
?assertEqual(lists:sort(Timestamps), Timestamps),
ct:pal(" OK: ~p records sorted", [length(Records)]).
%% @doc Сортировка + фильтр + пагинация: action=block, сортировка по timestamp desc, limit=1.
test_sort_combined_with_filter_and_pagination(Token, _AdminId) ->
ct:pal(" TEST: Filter + sort + pagination (action=block_user, sort=timestamp desc, limit=1)"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/audit?action=block_user&sort=timestamp&order=desc&limit=1&offset=0">>, Token),
Records = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(length(Records) =< 1),
[?assertEqual(<<"block_user">>, maps:get(<<"action">>, R)) || R <- Records],
Total = list_to_integer(
proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: got ~p records (total ~p)", [length(Records), Total]).