216 lines
10 KiB
Erlang
216 lines
10 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_sort_by_title(AdminToken),
|
|
test_sort_by_created_at(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}),
|
|
% Проверяем через пользовательский API
|
|
{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, <<"">>),
|
|
% Проверяем, что календарь не удалён физически, а помечен как deleted (soft delete)
|
|
{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_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"). |