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

305 lines
15 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @doc API-тесты для административного управления календарями.
%%% Покрывает эндпоинты:
%%% GET /v1/admin/calendars
%%% GET /v1/admin/calendars/:id
%%% PUT /v1/admin/calendars/:id
%%% DELETE /v1/admin/calendars/:id
%%% Проверяет пагинацию, фильтрацию по статусу/типу, поиск и сортировку,
%%% а также комбинации фильтров, сортировки и пагинации.
%%% @end
%%%-------------------------------------------------------------------
-module(admin_calendars_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
%%%===================================================================
%%% Главная тестовая функция
%%%===================================================================
-spec test() -> ok.
test() ->
ct:pal("=== Admin Calendars Tests ==="),
{AdminToken, UserToken, CalIds} = setup(),
[CalId1, CalId2, _CalId3] = CalIds,
try
%% Основные CRUD
test_get_calendar(AdminToken, CalId1),
test_get_calendar_not_found(AdminToken),
test_update_calendar(AdminToken, CalId1, UserToken),
test_update_calendar_not_found(AdminToken),
test_update_calendar_invalid_body(AdminToken, CalId1),
test_delete_calendar(AdminToken, CalId2),
test_delete_calendar_not_found(AdminToken),
%% Пагинация
test_pagination(AdminToken),
%% Фильтрация
test_filter_by_status(AdminToken),
test_filter_by_type(AdminToken),
test_search(AdminToken),
%% Комбинации фильтров
test_filter_combined_status_type(AdminToken),
%% Сортировка
test_sort_by_title(AdminToken),
test_sort_by_created_at(AdminToken),
%% Фильтр + сортировка
test_filter_status_with_sort(AdminToken),
test_filter_type_with_sort(AdminToken),
test_filter_search_with_sort(AdminToken),
%% Фильтр + пагинация
test_filter_status_with_pagination(AdminToken),
%% Фильтр + сортировка + пагинация
test_filter_type_with_sort_and_pagination(AdminToken),
test_filter_sort_pagination_combined(AdminToken)
after
cleanup(AdminToken, CalIds)
end,
ct:pal("=== All admin calendars tests passed ==="),
ok.
%%%===================================================================
%%% Подготовка и очистка
%%%===================================================================
-spec setup() -> {binary(), binary(), [binary()]}.
setup() ->
ct:pal(" Setup: register user and create calendars"),
AdminToken = api_test_runner:get_admin_token(),
Email = api_test_runner:unique_email(<<"caladmin">>),
Password = <<"testpass">>,
UserToken = api_test_runner:register_and_login(Email, Password),
CalId1 = create_calendar(UserToken, <<"Personal Active">>, <<"Desc1">>),
CalId2 = create_calendar(UserToken, <<"Commercial">>, <<"Desc2">>),
CalId3 = create_calendar(UserToken, <<"Frozen">>, <<"Desc3">>),
update_calendar_admin(AdminToken, CalId2, #{<<"type">> => <<"commercial">>}),
update_calendar_admin(AdminToken, CalId3, #{<<"status">> => <<"frozen">>}),
ct:pal(" Created calendars: ~p", [[CalId1, CalId2, CalId3]]),
{AdminToken, UserToken, [CalId1, CalId2, CalId3]}.
-spec create_calendar(binary(), binary(), binary()) -> binary().
create_calendar(UserToken, Title, Description) ->
Body = jsx:encode(#{title => Title, description => Description}),
{ok, 201, _, Resp} = api_test_runner:client_request(post, <<"/v1/calendars">>, UserToken, Body),
#{<<"id">> := Id} = jsx:decode(list_to_binary(Resp), [return_maps]),
Id.
-spec update_calendar_admin(binary(), binary(), map()) -> ok.
update_calendar_admin(AdminToken, CalId, Updates) ->
{ok, 200, _, RespBody} = api_test_runner:admin_request(get, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, <<"">>),
Cal = jsx:decode(list_to_binary(RespBody), [return_maps]),
UpdatedCal = maps:merge(Cal, Updates),
Body = jsx:encode(UpdatedCal),
{ok, 200, _, _} = api_test_runner:admin_request(put, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, Body),
ok.
-spec cleanup(binary(), [binary()]) -> ok.
cleanup(_AdminToken, []) -> ok;
cleanup(AdminToken, [Id | Rest]) ->
catch api_test_runner:admin_request(delete, <<"/v1/admin/calendars/", Id/binary>>, AdminToken, <<"">>),
cleanup(AdminToken, Rest).
%%%===================================================================
%%% CRUD тесты
%%%===================================================================
test_get_calendar(AdminToken, CalId) ->
ct:pal(" TEST: Get calendar by ID (admin)"),
{ok, 200, _, RespBody} = api_test_runner:admin_request(get, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, <<"">>),
Cal = jsx:decode(list_to_binary(RespBody), [return_maps]),
?assertEqual(CalId, maps:get(<<"id">>, Cal)),
ct:pal(" OK").
test_get_calendar_not_found(AdminToken) ->
ct:pal(" TEST: Get non-existent calendar"),
{ok, 404, _, _} = api_test_runner:admin_request(get, <<"/v1/admin/calendars/nonexistent">>, AdminToken, <<"">>),
ct:pal(" OK").
test_update_calendar(AdminToken, CalId, UserToken) ->
ct:pal(" TEST: Update calendar (admin)"),
NewTitle = <<"Updated by admin">>,
update_calendar_admin(AdminToken, CalId, #{<<"title">> => NewTitle}),
{ok, 200, _, UserResp} = api_test_runner:client_request(get, <<"/v1/calendars/", CalId/binary>>, UserToken, <<"">>),
UserCal = jsx:decode(list_to_binary(UserResp), [return_maps]),
?assertEqual(NewTitle, maps:get(<<"title">>, UserCal)),
ct:pal(" OK").
test_update_calendar_not_found(AdminToken) ->
ct:pal(" TEST: Update non-existent calendar"),
{ok, 404, _, _} = api_test_runner:admin_request(put, <<"/v1/admin/calendars/nonexistent">>, AdminToken, jsx:encode(#{title => <<"x">>})),
ct:pal(" OK").
test_update_calendar_invalid_body(AdminToken, CalId) ->
ct:pal(" TEST: Update calendar with invalid JSON"),
{ok, 400, _, _} = api_test_runner:admin_request(put, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, <<"not json">>),
ct:pal(" OK").
test_delete_calendar(AdminToken, CalId) ->
ct:pal(" TEST: Delete calendar (admin)"),
{ok, 200, _, _} = api_test_runner:admin_request(delete, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, <<"">>),
{ok, 200, _, RespBody} = api_test_runner:admin_request(get, <<"/v1/admin/calendars/", CalId/binary>>, AdminToken, <<"">>),
Cal = jsx:decode(list_to_binary(RespBody), [return_maps]),
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Cal)),
ct:pal(" OK: calendar soft deleted").
test_delete_calendar_not_found(AdminToken) ->
ct:pal(" TEST: Delete non-existent calendar"),
{ok, 404, _, _} = api_test_runner:admin_request(delete, <<"/v1/admin/calendars/nonexistent">>, AdminToken, <<"">>),
ct:pal(" OK").
%%%===================================================================
%%% Пагинация
%%%===================================================================
test_pagination(AdminToken) ->
ct:pal(" TEST: Pagination"),
{ok, 200, Headers1, Body1} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?limit=2&offset=0">>, AdminToken, <<"">>),
List1 = jsx:decode(list_to_binary(Body1), [return_maps]),
?assertEqual(2, length(List1)),
TotalCount = list_to_integer(proplists:get_value("x-total-count", Headers1, "0")),
?assert(TotalCount >= 3),
{ok, 200, _, Body2} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?limit=2&offset=2">>, AdminToken, <<"">>),
List2 = jsx:decode(list_to_binary(Body2), [return_maps]),
?assert(length(List2) >= 1),
ct:pal(" OK: got pages").
%%%===================================================================
%%% Фильтрация
%%%===================================================================
test_filter_by_status(AdminToken) ->
ct:pal(" TEST: Filter by status"),
{ok, 200, _, BodyActive} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?status=active">>, AdminToken, <<"">>),
ActiveList = jsx:decode(list_to_binary(BodyActive), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, C)) || C <- ActiveList],
{ok, 200, _, BodyFrozen} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?status=frozen">>, AdminToken, <<"">>),
FrozenList = jsx:decode(list_to_binary(BodyFrozen), [return_maps]),
[?assertEqual(<<"frozen">>, maps:get(<<"status">>, C)) || C <- FrozenList],
?assert(length(FrozenList) >= 1),
ct:pal(" OK: status filter works").
test_filter_by_type(AdminToken) ->
ct:pal(" TEST: Filter by type"),
{ok, 200, _, Body} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?type=commercial">>, AdminToken, <<"">>),
CommercialList = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"commercial">>, maps:get(<<"type">>, C)) || C <- CommercialList],
?assert(length(CommercialList) >= 1),
ct:pal(" OK: type filter works").
test_search(AdminToken) ->
ct:pal(" TEST: Search (q parameter)"),
{ok, 200, _, Body} = api_test_runner:admin_request(get, <<"/v1/admin/calendars?q=commercial">>, AdminToken, <<"">>),
Results = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(length(Results) >= 1),
HasMatch = lists:any(fun(C) ->
Title = string:lowercase(binary_to_list(maps:get(<<"title">>, C, <<>>))),
Desc = string:lowercase(binary_to_list(maps:get(<<"description">>, C, <<>>))),
string:str(Title, "commercial") > 0 orelse string:str(Desc, "commercial") > 0
end, Results),
?assert(HasMatch),
ct:pal(" OK: search works").
%%%===================================================================
%%% Комбинации фильтров
%%%===================================================================
test_filter_combined_status_type(AdminToken) ->
ct:pal(" TEST: Combined filter (status=active, type=personal)"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?status=active&type=personal">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, C)) || C <- List],
[?assertEqual(<<"personal">>, maps:get(<<"type">>, C)) || C <- List],
ct:pal(" OK: ~p calendars match both criteria", [length(List)]).
%%%===================================================================
%%% Сортировка
%%%===================================================================
test_sort_by_title(AdminToken) ->
ct:pal(" TEST: Sort by title asc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?sort_by=title&sort_order=asc">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
Titles = [maps:get(<<"title">>, C) || C <- List],
?assertEqual(lists:sort(Titles), Titles),
ct:pal(" OK: sorted by title").
test_sort_by_created_at(AdminToken) ->
ct:pal(" TEST: Sort by created_at desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?sort_by=created_at&sort_order=desc">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
Dates = [maps:get(<<"created_at">>, C) || C <- List],
Sorted = lists:reverse(lists:sort(Dates)),
?assertEqual(Sorted, Dates),
ct:pal(" OK: sorted by created_at desc").
%%%===================================================================
%%% Фильтр + сортировка
%%%===================================================================
test_filter_status_with_sort(AdminToken) ->
ct:pal(" TEST: Filter status=frozen + sort by title asc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?status=frozen&sort_by=title&sort_order=asc">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"frozen">>, maps:get(<<"status">>, C)) || C <- List],
Titles = [maps:get(<<"title">>, C) || C <- List],
?assertEqual(lists:sort(Titles), Titles),
ct:pal(" OK: ~p calendars", [length(List)]).
test_filter_type_with_sort(AdminToken) ->
ct:pal(" TEST: Filter type=commercial + sort by created_at desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?type=commercial&sort_by=created_at&sort_order=desc">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"commercial">>, maps:get(<<"type">>, C)) || C <- List],
Dates = [maps:get(<<"created_at">>, C) || C <- List],
Sorted = lists:reverse(lists:sort(Dates)),
?assertEqual(Sorted, Dates),
ct:pal(" OK: ~p calendars", [length(List)]).
test_filter_search_with_sort(AdminToken) ->
ct:pal(" TEST: Search q=Personal + sort by title desc"),
{ok, 200, _, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?q=Personal&sort_by=title&sort_order=desc">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
[?assert(string:str(string:lowercase(binary_to_list(maps:get(<<"title">>, C))), "personal") > 0
orelse string:str(string:lowercase(binary_to_list(maps:get(<<"description">>, C))), "personal") > 0)
|| C <- List],
Titles = [maps:get(<<"title">>, C) || C <- List],
?assertEqual(lists:reverse(lists:sort(Titles)), Titles),
ct:pal(" OK: ~p calendars", [length(List)]).
%%%===================================================================
%%% Фильтр + пагинация
%%%===================================================================
test_filter_status_with_pagination(AdminToken) ->
ct:pal(" TEST: Filter status=active + pagination (limit=1, offset=0)"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?status=active&limit=1&offset=0">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(1, length(List)),
[?assertEqual(<<"active">>, maps:get(<<"status">>, C)) || C <- List],
Total = list_to_integer(proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).
%%%===================================================================
%%% Фильтр + сортировка + пагинация
%%%===================================================================
test_filter_type_with_sort_and_pagination(AdminToken) ->
ct:pal(" TEST: Filter type=personal + sort by created_at asc + pagination (limit=1)"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?type=personal&sort_by=created_at&sort_order=asc&limit=1&offset=0">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(1, length(List)),
[?assertEqual(<<"personal">>, maps:get(<<"type">>, C)) || C <- List],
Total = list_to_integer(proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).
test_filter_sort_pagination_combined(AdminToken) ->
ct:pal(" TEST: status=active + sort_by=title asc + limit=2"),
{ok, 200, Headers, Body} = api_test_runner:admin_request(get,
<<"/v1/admin/calendars?status=active&sort_by=title&sort_order=asc&limit=2&offset=0">>, AdminToken, <<"">>),
List = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, C)) || C <- List],
Titles = [maps:get(<<"title">>, C) || C <- List],
?assertEqual(lists:sort(Titles), Titles),
Total = list_to_integer(proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: ~p calendars (total ~p)", [length(List), Total]).