Files
EventHubBack/test/api/admins/admin_users_tests.erl
T

311 lines
14 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/users
%%% GET /v1/admin/users/:id
%%% PUT /v1/admin/users/:id
%%% DELETE /v1/admin/users/:id
%%%
%%% Проверяет:
%%% - получение списка пользователей
%%% - получение пользователя по ID
%%% - обновление пользователя (роль, статус)
%%% - мягкое удаление пользователя
%%% - **фильтрацию** по роли, статусу и поиск (q)
%%% - **комбинации фильтров** (role + status)
%%% - **пагинацию** (limit/offset, xtotalcount)
%%% - **сортировку** по email, роли и дате создания
%%% - **комбинации сортировки с фильтрацией и пагинацией**
%%% @end
%%%-------------------------------------------------------------------
-module(admin_users_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
%%%===================================================================
%%% Главная тестовая функция
%%%===================================================================
-spec test() -> ok.
test() ->
ct:pal("=== Admin Users Tests ==="),
Token = api_test_runner:get_admin_token(),
% Создаём несколько тестовых пользователей с разными ролями
User1Email = api_test_runner:unique_email(<<"usertest1">>),
User1Token = api_test_runner:register_and_login(User1Email, <<"testpass">>),
Me1 = api_test_runner:client_get(<<"/v1/user/me">>, User1Token),
User1Id = maps:get(<<"id">>, Me1),
User2Email = api_test_runner:unique_email(<<"usertest2">>),
User2Token = api_test_runner:register_and_login(User2Email, <<"testpass">>),
Me2 = api_test_runner:client_get(<<"/v1/user/me">>, User2Token),
User2Id = maps:get(<<"id">>, Me2),
User3Email = api_test_runner:unique_email(<<"usertest3">>),
User3Token = api_test_runner:register_and_login(User3Email, <<"testpass">>),
Me3 = api_test_runner:client_get(<<"/v1/user/me">>, User3Token),
_User3Id = maps:get(<<"id">>, Me3),
% Обновляем пользователей, чтобы получить разные роли и статусы
% User1: роль bot, статус frozen (для тестов фильтрации)
Path1 = <<"/v1/admin/users/", User1Id/binary>>,
api_test_runner:admin_put(Path1, Token, #{
<<"role">> => <<"bot">>,
<<"status">> => <<"frozen">>,
<<"reason">> => <<"Test freeze">>
}),
% User2: роль user, статус active
Path2 = <<"/v1/admin/users/", User2Id/binary>>,
api_test_runner:admin_put(Path2, Token, #{
<<"role">> => <<"user">>,
<<"status">> => <<"active">>
}),
% User3: оставляем как есть (user / active) для тестов поиска
% ── Основные CRUD-тесты ──
test_list_users(Token),
test_get_user(Token, User1Id),
test_update_user(Token, User1Id),
test_delete_user(Token, User1Id),
% ── Фильтрация ──
test_filter_users_by_role(Token),
test_filter_users_by_status(Token),
test_filter_combined_role_status(Token),
test_filter_users_by_search(Token, User2Email),
% ── Сортировка ──
test_sort_by_email_asc(Token),
test_sort_by_email_desc(Token),
test_sort_by_role_desc(Token),
test_sort_by_created_at_asc(Token),
% ── Комбинации фильтрации и сортировки ──
test_filter_status_sort_email(Token),
test_filter_role_sort_created_at(Token),
% ── Пагинация ──
test_user_pagination(Token),
test_pagination_headers(Token),
% ── Комбинации с пагинацией ──
test_filter_status_with_pagination(Token),
test_filter_sort_pagination_combined(Token),
ct:pal("=== All admin users tests passed ==="),
ok.
%%%===================================================================
%%% Тестовые функции
%%%===================================================================
%% @doc GET /v1/admin/users – список пользователей.
-spec test_list_users(binary()) -> ok.
test_list_users(Token) ->
ct:pal(" TEST: List all users"),
Users = api_test_runner:admin_get(<<"/v1/admin/users">>, Token),
?assert(is_list(Users)),
?assert(length(Users) >= 3),
ct:pal(" OK: ~p users", [length(Users)]).
%% @doc GET /v1/admin/users/:id получение пользователя по ID.
-spec test_get_user(binary(), binary()) -> ok.
test_get_user(Token, UserId) ->
ct:pal(" TEST: Get user by ID"),
Path = <<"/v1/admin/users/", UserId/binary>>,
User = api_test_runner:admin_get(Path, Token),
?assertEqual(UserId, maps:get(<<"id">>, User)),
ct:pal(" OK: ~s", [maps:get(<<"email">>, User)]).
%% @doc PUT /v1/admin/users/:id обновление пользователя.
-spec test_update_user(binary(), binary()) -> ok.
test_update_user(Token, UserId) ->
ct:pal(" TEST: Update user (change nickname)"),
Path = <<"/v1/admin/users/", UserId/binary>>,
Updated = api_test_runner:admin_put(Path, Token, #{
<<"nickname">> => <<"TestBot">>
}),
?assertEqual(<<"TestBot">>, maps:get(<<"nickname">>, Updated)),
ct:pal(" OK").
%% @doc DELETE /v1/admin/users/:id мягкое удаление.
-spec test_delete_user(binary(), binary()) -> ok.
test_delete_user(Token, UserId) ->
ct:pal(" TEST: Delete (soft-delete) user"),
Path = <<"/v1/admin/users/", UserId/binary>>,
Deleted = api_test_runner:admin_delete(Path, Token),
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
ct:pal(" OK").
%% ── Фильтрация ──
%% @doc Фильтрация по роли.
-spec test_filter_users_by_role(binary()) -> ok.
test_filter_users_by_role(Token) ->
ct:pal(" TEST: Filter users by role=bot"),
Users = api_test_runner:admin_get(<<"/v1/admin/users?role=bot">>, Token),
?assert(is_list(Users)),
[?assertEqual(<<"bot">>, maps:get(<<"role">>, U)) || U <- Users],
ct:pal(" OK: ~p bot users", [length(Users)]).
%% @doc Фильтрация по статусу.
-spec test_filter_users_by_status(binary()) -> ok.
test_filter_users_by_status(Token) ->
ct:pal(" TEST: Filter users by status=active"),
Users = api_test_runner:admin_get(<<"/v1/admin/users?status=active">>, Token),
?assert(is_list(Users)),
[?assertEqual(<<"active">>, maps:get(<<"status">>, U)) || U <- Users],
ct:pal(" OK: ~p active users", [length(Users)]).
%% @doc Комбинация фильтров: роль + статус.
-spec test_filter_combined_role_status(binary()) -> ok.
test_filter_combined_role_status(Token) ->
ct:pal(" TEST: Combined filter (role=user & status=active)"),
Users = api_test_runner:admin_get(
<<"/v1/admin/users?role=user&status=active">>, Token),
?assert(is_list(Users)),
[?assertEqual(<<"user">>, maps:get(<<"role">>, U)) || U <- Users],
[?assertEqual(<<"active">>, maps:get(<<"status">>, U)) || U <- Users],
ct:pal(" OK: ~p users match both criteria", [length(Users)]).
%% @doc Поиск по email.
-spec test_filter_users_by_search(binary(), binary()) -> ok.
test_filter_users_by_search(Token, Email) ->
ct:pal(" TEST: Search users by email prefix"),
Prefix = binary:part(Email, 0, 10),
Path = <<"/v1/admin/users?q=", Prefix/binary>>,
Users = api_test_runner:admin_get(Path, Token),
?assert(is_list(Users)),
?assert(length(Users) >= 1),
ct:pal(" OK: ~p users match search", [length(Users)]).
%% ── Сортировка ──
%% @doc Сортировка по email по возрастанию.
-spec test_sort_by_email_asc(binary()) -> ok.
test_sort_by_email_asc(Token) ->
ct:pal(" TEST: Sort users by email asc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?sort=email&order=asc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
Emails = [maps:get(<<"email">>, U) || U <- Users],
?assertEqual(lists:sort(Emails), Emails),
ct:pal(" OK: sorted by email asc").
%% @doc Сортировка по email по убыванию.
-spec test_sort_by_email_desc(binary()) -> ok.
test_sort_by_email_desc(Token) ->
ct:pal(" TEST: Sort users by email desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?sort=email&order=desc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
Emails = [maps:get(<<"email">>, U) || U <- Users],
?assertEqual(lists:reverse(lists:sort(Emails)), Emails),
ct:pal(" OK: sorted by email desc").
%% @doc Сортировка по роли по убыванию.
-spec test_sort_by_role_desc(binary()) -> ok.
test_sort_by_role_desc(Token) ->
ct:pal(" TEST: Sort users by role desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?sort=role&order=desc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
Roles = [maps:get(<<"role">>, U) || U <- Users],
?assertEqual(lists:reverse(lists:sort(Roles)), Roles),
ct:pal(" OK: sorted by role desc").
%% @doc Сортировка по created_at по возрастанию.
-spec test_sort_by_created_at_asc(binary()) -> ok.
test_sort_by_created_at_asc(Token) ->
ct:pal(" TEST: Sort users by created_at asc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?sort=created_at&order=asc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
Dates = [maps:get(<<"created_at">>, U) || U <- Users],
?assertEqual(lists:sort(Dates), Dates),
ct:pal(" OK: sorted by created_at asc").
%% ── Комбинации фильтрации и сортировки ──
%% @doc Фильтр status=active + сортировка по email asc.
-spec test_filter_status_sort_email(binary()) -> ok.
test_filter_status_sort_email(Token) ->
ct:pal(" TEST: Filter status=active + sort by email asc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?status=active&sort=email&order=asc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, U)) || U <- Users],
Emails = [maps:get(<<"email">>, U) || U <- Users],
?assertEqual(lists:sort(Emails), Emails),
ct:pal(" OK: ~p users sorted", [length(Users)]).
%% @doc Фильтр role=user + сортировка по created_at desc.
-spec test_filter_role_sort_created_at(binary()) -> ok.
test_filter_role_sort_created_at(Token) ->
ct:pal(" TEST: Filter role=user + sort by created_at desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?role=user&sort=created_at&order=desc">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"user">>, maps:get(<<"role">>, U)) || U <- Users],
Dates = [maps:get(<<"created_at">>, U) || U <- Users],
Sorted = lists:reverse(lists:sort(Dates)),
?assertEqual(Sorted, Dates),
ct:pal(" OK: ~p users sorted", [length(Users)]).
%% ── Пагинация ──
%% @doc GET /v1/admin/users?limit=...&offset=... пагинация.
-spec test_user_pagination(binary()) -> ok.
test_user_pagination(Token) ->
ct:pal(" TEST: User pagination"),
Page1 = api_test_runner:admin_get(<<"/v1/admin/users?limit=1&offset=0">>, Token),
?assert(length(Page1) >= 1),
Page2 = api_test_runner:admin_get(<<"/v1/admin/users?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 Проверка заголовков пагинации (x-total-count).
-spec test_pagination_headers(binary()) -> ok.
test_pagination_headers(Token) ->
ct:pal(" TEST: Pagination headers (x-total-count)"),
{ok, 200, Headers, _} = api_test_runner:admin_request(get,
<<"/v1/admin/users?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 Фильтр status=active + пагинация limit=1.
-spec test_filter_status_with_pagination(binary()) -> ok.
test_filter_status_with_pagination(Token) ->
ct:pal(" TEST: Filter status=active + pagination (limit=1)"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?status=active&limit=1&offset=0">>, Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(length(Users) =< 1),
[?assertEqual(<<"active">>, maps:get(<<"status">>, U)) || U <- Users],
Total = list_to_integer(
proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).
%% @doc Фильтр + сортировка + пагинация: status=active + created_at desc + limit=1.
-spec test_filter_sort_pagination_combined(binary()) -> ok.
test_filter_sort_pagination_combined(Token) ->
ct:pal(" TEST: Filter + sort + pagination (status=active, sort=created_at desc, limit=1)"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/users?status=active&sort=created_at&order=desc&limit=1&offset=0">>,
Token, <<"">>),
Users = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(length(Users) =< 1),
[?assertEqual(<<"active">>, maps:get(<<"status">>, U)) || U <- Users],
Total = list_to_integer(
proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).