%%%------------------------------------------------------------------- %%% @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]).