%%%-------------------------------------------------------------------
%%% @doc Тесты административного API для управления событиями.
%%% Покрывает эндпоинты:
%%% GET /v1/admin/events
%%% GET /v1/admin/events/:id
%%% PUT /v1/admin/events/:id
%%% DELETE /v1/admin/events/:id
%%% Проверяет фильтрацию, сортировку, пагинацию и их комбинации.
%%% @end
%%%-------------------------------------------------------------------
-module(admin_events_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
test() ->
ct:pal("=== Admin Events Tests ==="),
Token = api_test_runner:get_admin_token(),
UserToken = api_test_runner:get_user_token(),
%% Создаём тестовый календарь и два события
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"EventsTestCal">>}),
Event1Id = api_test_runner:create_event(UserToken, CalId, #{
title => <<"Test Event Alpha">>,
start_time => api_test_runner:future_date(),
duration => 60
}),
Event2Id = api_test_runner:create_event(UserToken, CalId, #{
title => <<"Beta Event">>,
start_time => api_test_runner:future_date(),
duration => 60
}),
%% Выполняем тесты
test_list_events(Token, CalId, Event1Id, Event2Id),
test_get_event(Token, Event1Id),
test_update_event(Token, Event1Id),
test_delete_event(Token, Event2Id),
test_filter_events(Token, CalId),
test_search_events(Token, CalId),
test_event_pagination(Token, CalId),
%% Комбинации фильтров
test_filter_combined_status_calendar(Token, CalId),
test_filter_combined_title_status(Token, CalId),
%% Сортировка
test_sort_by_title(Token, CalId),
test_sort_by_start_time(Token, CalId),
%% Фильтр + сортировка
test_filter_status_sort_title(Token, CalId),
test_search_sort_created_at(Token, CalId),
%% Фильтр + сортировка + пагинация
test_filter_sort_pagination_combined(Token, CalId),
%% Проверка заголовка x-total-count
test_pagination_headers(Token, CalId),
test_delete_event(Token, Event1Id),
ct:pal("=== All admin events tests passed ==="),
ok.
%% @doc GET /v1/admin/events – список событий (базовая проверка).
test_list_events(Token, _CalId, _Event1Id, _Event2Id) ->
ct:pal(" TEST: List all events"),
Events = api_test_runner:admin_get(<<"/v1/admin/events">>, Token),
?assert(is_list(Events)),
?assert(length(Events) >= 2),
ct:pal(" OK: ~p events found", [length(Events)]).
%% @doc GET /v1/admin/events/:id – получение события по ID.
test_get_event(Token, EventId) ->
ct:pal(" TEST: Get event by ID"),
Path = <<"/v1/admin/events/", EventId/binary>>,
Event = api_test_runner:admin_get(Path, Token),
?assertEqual(EventId, maps:get(<<"id">>, Event)),
?assertEqual(<<"active">>, maps:get(<<"status">>, Event)),
ct:pal(" OK: ~s", [maps:get(<<"title">>, Event)]).
%% @doc PUT /v1/admin/events/:id – обновление события.
test_update_event(Token, EventId) ->
ct:pal(" TEST: Update event"),
Path = <<"/v1/admin/events/", EventId/binary>>,
Updates = #{<<"title">> => <<"Updated by admin">>, <<"description">> => <<"Test update">>},
Updated = api_test_runner:admin_put(Path, Token, Updates),
?assertEqual(<<"Updated by admin">>, maps:get(<<"title">>, Updated)),
?assertEqual(<<"Test update">>, maps:get(<<"description">>, Updated)),
ct:pal(" OK").
%% @doc DELETE /v1/admin/events/:id – удаление события.
test_delete_event(Token, EventId) ->
ct:pal(" TEST: Delete event"),
Path = <<"/v1/admin/events/", EventId/binary>>,
Deleted = api_test_runner:admin_delete(Path, Token),
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
ct:pal(" OK").
%% @doc GET /v1/admin/events?status=active – фильтрация по статусу.
test_filter_events(Token, CalId) ->
ct:pal(" TEST: Filter events by status=active"),
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&status=active">>,
Events = api_test_runner:admin_get(Path, Token),
?assert(is_list(Events)),
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
ct:pal(" OK: ~p events", [length(Events)]).
%% @doc GET /v1/admin/events?q=... – поиск по подстроке.
test_search_events(Token, CalId) ->
ct:pal(" TEST: Search events by title substring"),
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&q=Updated">>,
Events = api_test_runner:admin_get(Path, Token),
?assert(is_list(Events)),
?assert(length(Events) >= 1),
[?assertNotEqual(nomatch, binary:match(maps:get(<<"title">>, E), <<"Updated">>)) || E <- Events],
ct:pal(" OK: ~p results", [length(Events)]).
%% @doc GET /v1/admin/events?limit=...&offset=... – пагинация.
test_event_pagination(Token, CalId) ->
ct:pal(" TEST: Event pagination"),
Base = <<"/v1/admin/events?calendar_id=", CalId/binary, "&status=all&sort=title&order=asc">>,
Page1 = api_test_runner:admin_get(<>, Token),
?assertEqual(1, length(Page1)),
Page2 = api_test_runner:admin_get(<>, Token),
?assertEqual(1, length(Page2)),
Id1 = maps:get(<<"id">>, hd(Page1)),
Id2 = maps:get(<<"id">>, hd(Page2)),
?assertNotEqual(Id1, Id2),
ct:pal(" OK").
%% ── Комбинации фильтров ──
test_filter_combined_status_calendar(Token, CalId) ->
ct:pal(" TEST: Filter by status=active + calendar_id"),
Path = <<"/v1/admin/events?status=active&calendar_id=", CalId/binary>>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
[?assertEqual(CalId, maps:get(<<"calendar_id">>, E)) || E <- Events],
ct:pal(" OK: ~p events", [length(Events)]).
test_filter_combined_title_status(Token, CalId) ->
ct:pal(" TEST: Filter by title=Updated + status=active"),
Path = <<"/v1/admin/events?title=Updated%20by%20admin&status=active&calendar_id=", CalId/binary>>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"Updated by admin">>, maps:get(<<"title">>, E)) || E <- Events],
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
ct:pal(" OK: ~p events", [length(Events)]).
%% ── Сортировка ──
test_sort_by_title(Token, CalId) ->
ct:pal(" TEST: Sort by title asc"),
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&sort=title&order=asc">>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
Titles = [maps:get(<<"title">>, E) || E <- Events],
?assertEqual(lists:sort(Titles), Titles),
ct:pal(" OK: sorted by title asc").
test_sort_by_start_time(Token, CalId) ->
ct:pal(" TEST: Sort by start_time desc"),
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&sort=start_time&order=desc">>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
Dates = [maps:get(<<"start_time">>, E) || E <- Events],
Sorted = lists:reverse(lists:sort(Dates)),
?assertEqual(Sorted, Dates),
ct:pal(" OK: sorted by start_time desc").
%% ── Фильтр + сортировка ──
test_filter_status_sort_title(Token, CalId) ->
ct:pal(" TEST: Filter status=active + sort by title desc"),
Path = <<"/v1/admin/events?status=active&calendar_id=", CalId/binary, "&sort=title&order=desc">>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
Titles = [maps:get(<<"title">>, E) || E <- Events],
?assertEqual(lists:reverse(lists:sort(Titles)), Titles),
ct:pal(" OK: ~p events", [length(Events)]).
test_search_sort_created_at(Token, CalId) ->
ct:pal(" TEST: Search q=Updated + sort by created_at asc"),
Path = <<"/v1/admin/events?q=Updated&calendar_id=", CalId/binary, "&sort=created_at&order=asc">>,
{ok, 200, _, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
[?assert(binary:match(maps:get(<<"title">>, E), <<"Updated">>) =/= nomatch) || E <- Events],
Dates = [maps:get(<<"created_at">>, E) || E <- Events],
?assertEqual(lists:sort(Dates), Dates),
ct:pal(" OK: ~p events", [length(Events)]).
%% ── Фильтр + сортировка + пагинация ──
test_filter_sort_pagination_combined(Token, CalId) ->
ct:pal(" TEST: status=active + sort=start_time desc + limit=1"),
Path = <<"/v1/admin/events?status=active&calendar_id=", CalId/binary, "&sort=start_time&order=desc&limit=1&offset=0">>,
{ok, 200, Headers, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Events = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(length(Events) =< 1),
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
Total = list_to_integer(proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: ~p events (total ~p)", [length(Events), Total]).
%% ── Заголовки пагинации ──
test_pagination_headers(Token, CalId) ->
ct:pal(" TEST: Pagination headers (x-total-count)"),
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&limit=2&offset=0">>,
{ok, 200, Headers, _} = api_test_runner:admin_request(get, Path, Token, <<"">>),
Total = list_to_integer(proplists:get_value("x-total-count", Headers, "0")),
?assert(Total >= 1),
ct:pal(" OK: total count = ~p", [Total]).