fix(backend): IP в аудите, closed_at тикетов и admin_activity в статистике. Refs EventHub/EventHubBack#27 #28 #29
This commit is contained in:
@@ -42,6 +42,7 @@ test() ->
|
||||
% ── Основные тесты ──
|
||||
test_list_audit(SuperToken),
|
||||
test_list_admin_forbidden(AdminToken),
|
||||
test_login_audit_ip(SuperToken),
|
||||
|
||||
% ── Фильтрация ──
|
||||
test_filter_audit_by_admin_id(SuperToken, AdminId),
|
||||
@@ -85,6 +86,23 @@ test_list_admin_forbidden(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.
|
||||
|
||||
@@ -70,6 +70,8 @@ test() ->
|
||||
|
||||
test_stats_with_dates(SuperToken),
|
||||
test_node_metrics_history(SuperToken),
|
||||
test_avg_ticket_resolution_after_close(SuperToken, UserToken),
|
||||
test_admin_activity(SuperToken),
|
||||
|
||||
% Детальная статистика
|
||||
test_user_stats(SuperToken),
|
||||
@@ -116,6 +118,7 @@ test_stats_for_role(RoleName, Token, Strictness) ->
|
||||
?assert(maps:is_key(<<"tickets_by_day">>, Stats)),
|
||||
?assert(maps:is_key(<<"subscriptions_by_day">>, Stats)),
|
||||
?assert(maps:is_key(<<"pending_users_by_day">>, Stats)),
|
||||
?assert(maps:is_key(<<"avg_ticket_resolution_h">>, Stats)),
|
||||
% Проверка, что они действительно списки
|
||||
?assert(is_list(maps:get(<<"registrations_by_day">>, Stats))),
|
||||
?assert(is_list(maps:get(<<"events_by_day">>, Stats))),
|
||||
@@ -244,4 +247,35 @@ test_node_metrics_history(Token) ->
|
||||
?assert(maps:is_key(<<"memory_total">>, First)),
|
||||
?assert(maps:is_key(<<"memory_available">>, First)),
|
||||
?assert(maps:is_key(<<"cpu_utilization">>, First)),
|
||||
ct:pal(" OK: ~p metrics received", [length(Metrics)]).
|
||||
ct:pal(" OK: ~p metrics received", [length(Metrics)]).
|
||||
|
||||
test_avg_ticket_resolution_after_close(Token, UserToken) ->
|
||||
ct:pal(" TEST: avg_ticket_resolution_h after closing ticket"),
|
||||
Ticket = api_test_runner:client_post(<<"/v1/tickets">>, UserToken,
|
||||
#{<<"error_message">> => <<"Resolution metric">>, <<"stacktrace">> => <<"trace">>}),
|
||||
#{<<"id">> := TicketId} = Ticket,
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
timer:sleep(20000),
|
||||
Closed = api_test_runner:admin_put(Path, Token, #{<<"status">> => <<"closed">>}),
|
||||
?assertEqual(<<"closed">>, maps:get(<<"status">>, Closed)),
|
||||
?assertNotEqual(<<"1970-01-01T00:00:00Z">>, maps:get(<<"closed_at">>, Closed)),
|
||||
Stats = api_test_runner:admin_get(<<"/v1/admin/stats">>, Token),
|
||||
Avg = maps:get(<<"avg_ticket_resolution_h">>, Stats),
|
||||
?assert(is_number(Avg)),
|
||||
?assert(Avg > 0.0),
|
||||
ct:pal(" OK: avg_ticket_resolution_h=~p", [Avg]).
|
||||
|
||||
test_admin_activity(Token) ->
|
||||
ct:pal(" TEST: admin_activity in dashboard stats"),
|
||||
Stats = api_test_runner:admin_get(<<"/v1/admin/stats">>, Token),
|
||||
Activity = maps:get(<<"admin_activity">>, Stats),
|
||||
?assert(is_list(Activity)),
|
||||
?assert(length(Activity) >= 1),
|
||||
First = hd(Activity),
|
||||
?assert(maps:is_key(<<"admin_id">>, First)),
|
||||
?assert(maps:is_key(<<"email">>, First)),
|
||||
?assert(maps:is_key(<<"role">>, First)),
|
||||
?assert(maps:is_key(<<"actions">>, First)),
|
||||
?assert(maps:is_key(<<"last_login">>, First)),
|
||||
?assert(maps:get(<<"actions">>, First) >= 1),
|
||||
ct:pal(" OK: ~p admin activity rows", [length(Activity)]).
|
||||
@@ -23,10 +23,13 @@
|
||||
register_and_login/2,
|
||||
create_calendar/2,
|
||||
create_event/3
|
||||
, get_admin_refresh_token/0]).
|
||||
, get_admin_refresh_token/0
|
||||
, admin_super_email/0
|
||||
, admin_super_password/0]).
|
||||
-export([
|
||||
admin_request/3,
|
||||
admin_request/4,
|
||||
admin_request/5,
|
||||
client_request/3,
|
||||
client_request/4
|
||||
]).
|
||||
@@ -175,7 +178,12 @@ admin_request(Method, Path, Token) ->
|
||||
|
||||
-spec admin_request(atom(), binary(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
admin_request(Method, Path, Token, Body) ->
|
||||
request(get_admin_url(), Method, Path, Token, Body, "ADMIN").
|
||||
admin_request(Method, Path, Token, Body, []).
|
||||
|
||||
-spec admin_request(atom(), binary(), binary(), binary(), [{string(), string()}]) ->
|
||||
{ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
admin_request(Method, Path, Token, Body, ExtraHeaders) ->
|
||||
request(get_admin_url(), Method, Path, Token, Body, "ADMIN", ExtraHeaders).
|
||||
|
||||
-spec client_request(atom(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
client_request(Method, Path, Token) ->
|
||||
@@ -183,7 +191,7 @@ client_request(Method, Path, Token) ->
|
||||
|
||||
-spec client_request(atom(), binary(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
client_request(Method, Path, Token, Body) ->
|
||||
request(get_base_url(), Method, Path, Token, Body, "CLIENT").
|
||||
request(get_base_url(), Method, Path, Token, Body, "CLIENT", []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Внутренняя реализация HTTP-запроса
|
||||
@@ -191,12 +199,17 @@ client_request(Method, Path, Token, Body) ->
|
||||
|
||||
-spec request(string(), atom(), binary(), binary(), binary(), string()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
request(BaseUrl, Method, Path, Token, Body, Prefix) ->
|
||||
request(BaseUrl, Method, Path, Token, Body, Prefix, []).
|
||||
|
||||
-spec request(string(), atom(), binary(), binary(), binary(), string(), [{string(), string()}]) ->
|
||||
{ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
request(BaseUrl, Method, Path, Token, Body, Prefix, ExtraHeaders) ->
|
||||
URL = BaseUrl ++ binary_to_list(Path),
|
||||
Headers0 = [],
|
||||
Headers = case Token of
|
||||
<<>> -> Headers0; % пустой токен – не добавляем Authorization
|
||||
_ -> [{"Authorization", "Bearer " ++ binary_to_list(Token)}]
|
||||
end,
|
||||
AuthHeaders = case Token of
|
||||
<<>> -> [];
|
||||
_ -> [{"Authorization", "Bearer " ++ binary_to_list(Token)}]
|
||||
end,
|
||||
Headers = ExtraHeaders ++ AuthHeaders,
|
||||
ct:pal("~s REQUEST: ~s ~s", [Prefix, Method, URL]),
|
||||
RequestArg = case Method of
|
||||
get -> {URL, Headers};
|
||||
|
||||
Reference in New Issue
Block a user