311 lines
14 KiB
Erlang
311 lines
14 KiB
Erlang
%%%-------------------------------------------------------------------
|
||
%%% @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, x‑total‑count)
|
||
%%% - **сортировку** по 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]). |