Рефакторинг админских обработчиков - пагинация, сортировка, фильтрация. Финал
This commit is contained in:
@@ -1,26 +1,33 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления отзывами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/reviews
|
||||
%%% PATCH /v1/admin/reviews
|
||||
%%% GET /v1/admin/reviews/:id
|
||||
%%% PUT /v1/admin/reviews/:id
|
||||
%%% GET /v1/admin/reviews
|
||||
%%% PATCH /v1/admin/reviews
|
||||
%%% GET /v1/admin/reviews/:id
|
||||
%%% PUT /v1/admin/reviews/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка отзывов
|
||||
%%% - фильтрацию по цели, статусу, пользователю
|
||||
%%% - комбинации фильтров
|
||||
%%% - сортировку по created_at, rating
|
||||
%%% - комбинации фильтрации с сортировкой
|
||||
%%% - пагинацию
|
||||
%%% - комбинации фильтрации, сортировки и пагинации
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_reviews_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% EUnit test generator
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
test() ->
|
||||
ct:pal("=== Admin Reviews Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
%% Создаём тестовые данные: календарь, событие
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ReviewsTestCal">>}),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
@@ -28,46 +35,54 @@ test() ->
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
|
||||
%% Создаём двух пользователей для отзывов
|
||||
User1Email = api_test_runner:unique_email(<<"rev1">>),
|
||||
User1Token = api_test_runner:register_and_login(User1Email, <<"pass1">>),
|
||||
User2Email = api_test_runner:unique_email(<<"rev2">>),
|
||||
User2Token = api_test_runner:register_and_login(User2Email, <<"pass2">>),
|
||||
|
||||
%% Записываем пользователей на событие и подтверждаем бронирования
|
||||
#{<<"id">> := Booking1Id} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, User1Token, #{}),
|
||||
#{<<"id">> := Booking2Id} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, User2Token, #{}),
|
||||
|
||||
ct:pal(" Confirming bookings as calendar owner..."),
|
||||
ct:pal(" Confirming bookings as calendar owner..."),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", Booking1Id/binary>>, UserToken,
|
||||
#{action => <<"confirm">>}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", Booking2Id/binary>>, UserToken,
|
||||
#{action => <<"confirm">>}),
|
||||
|
||||
%% Оставляем отзывы от имени участников
|
||||
#{<<"id">> := Review1Id} = api_test_runner:client_post(<<"/v1/reviews">>, User1Token, #{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 5,
|
||||
comment => <<"Great!">>
|
||||
}),
|
||||
#{<<"id">> := Review2Id} = api_test_runner:client_post(<<"/v1/reviews">>, User2Token, #{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 4,
|
||||
comment => <<"Good">>
|
||||
}),
|
||||
#{<<"id">> := Review1Id} = api_test_runner:client_post(<<"/v1/reviews">>, User1Token,
|
||||
#{ target_type => <<"event">>, target_id => EventId, rating => 5,
|
||||
comment => <<"Great!">> }),
|
||||
#{<<"id">> := Review2Id} = api_test_runner:client_post(<<"/v1/reviews">>, User2Token,
|
||||
#{ target_type => <<"event">>, target_id => EventId, rating => 4,
|
||||
comment => <<"Good">> }),
|
||||
%% Получаем ID первого пользователя для теста фильтрации по user_id
|
||||
#{<<"id">> := User1Id} = api_test_runner:client_get(<<"/v1/user/me">>, User1Token),
|
||||
|
||||
%% Запускаем тесты
|
||||
test_list_reviews(Token),
|
||||
test_filter_reviews(Token, EventId),
|
||||
test_filter_by_user(Token, User1Id),
|
||||
|
||||
% Сначала меняем статусы, чтобы потом проверить фильтрацию по статусам
|
||||
test_bulk_update_status(Token, Review1Id, Review2Id),
|
||||
|
||||
test_filter_by_status(Token),
|
||||
test_filter_combined_status_target(Token, EventId),
|
||||
|
||||
test_get_review(Token, Review1Id),
|
||||
test_update_review(Token, Review1Id),
|
||||
|
||||
test_sort_by_created_at_desc(Token),
|
||||
test_sort_by_created_at_asc(Token),
|
||||
test_sort_by_rating_asc(Token),
|
||||
|
||||
test_filter_status_sort_created_at(Token),
|
||||
test_filter_sort_pagination_combined(Token, EventId),
|
||||
|
||||
test_review_pagination(Token),
|
||||
test_pagination_headers(Token),
|
||||
|
||||
ct:pal("=== All admin reviews tests passed ==="),
|
||||
ok.
|
||||
@@ -78,54 +93,147 @@ test() ->
|
||||
|
||||
%% @doc GET /v1/admin/reviews – список отзывов.
|
||||
test_list_reviews(Token) ->
|
||||
ct:pal(" TEST: List all reviews"),
|
||||
ct:pal(" TEST: List all reviews"),
|
||||
Reviews = api_test_runner:admin_get(<<"/v1/admin/reviews">>, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 2),
|
||||
ct:pal(" OK: ~p reviews", [length(Reviews)]).
|
||||
ct:pal(" OK: ~p reviews", [length(Reviews)]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews?target_type=event&target_id=... – фильтрация по цели.
|
||||
test_filter_reviews(Token, EventId) ->
|
||||
ct:pal(" TEST: Filter reviews by target"),
|
||||
ct:pal(" TEST: Filter reviews by target"),
|
||||
Path = <<"/v1/admin/reviews?target_type=event&target_id=", EventId/binary>>,
|
||||
Reviews = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 2),
|
||||
[?assertEqual(EventId, maps:get(<<"target_id">>, R)) || R <- Reviews],
|
||||
ct:pal(" OK: ~p reviews for event", [length(Reviews)]).
|
||||
ct:pal(" OK: ~p reviews for event", [length(Reviews)]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews?user_id=... – фильтрация по пользователю.
|
||||
test_filter_by_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Filter reviews by user_id"),
|
||||
Path = <<"/v1/admin/reviews?user_id=", UserId/binary>>,
|
||||
Reviews = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
[?assertEqual(UserId, maps:get(<<"user_id">>, R)) || R <- Reviews],
|
||||
ct:pal(" OK: ~p reviews by user", [length(Reviews)]).
|
||||
|
||||
%% @doc PATCH /v1/admin/reviews – массовое обновление статусов.
|
||||
test_bulk_update_status(Token, Review1Id, Review2Id) ->
|
||||
ct:pal(" TEST: Bulk update review statuses"),
|
||||
ct:pal(" TEST: Bulk update review statuses"),
|
||||
Body = [
|
||||
#{<<"id">> => Review1Id, <<"status">> => <<"visible">>},
|
||||
#{<<"id">> => Review2Id, <<"status">> => <<"hidden">>}
|
||||
],
|
||||
#{<<"updated_count">> := Count} = api_test_runner:admin_patch(<<"/v1/admin/reviews">>, Token, Body),
|
||||
#{<<"updated_count">> := Count} = api_test_runner:admin_patch(
|
||||
<<"/v1/admin/reviews">>, Token, Body),
|
||||
?assertEqual(2, Count),
|
||||
ct:pal(" OK: updated ~p reviews", [Count]).
|
||||
ct:pal(" OK: updated ~p reviews", [Count]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews?status=visible – фильтрация по статусу.
|
||||
test_filter_by_status(Token) ->
|
||||
ct:pal(" TEST: Filter reviews by status=visible"),
|
||||
Reviews = api_test_runner:admin_get(<<"/v1/admin/reviews?status=visible">>, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
[?assertEqual(<<"visible">>, maps:get(<<"status">>, R)) || R <- Reviews],
|
||||
ct:pal(" OK: ~p visible reviews", [length(Reviews)]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews?status=hidden&target_type=event – комбинация фильтров.
|
||||
test_filter_combined_status_target(Token, EventId) ->
|
||||
ct:pal(" TEST: Combined filter (status=hidden & target_type=event)"),
|
||||
Path = <<"/v1/admin/reviews?status=hidden&target_type=event&target_id=", EventId/binary>>,
|
||||
Reviews = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
[?assertEqual(<<"hidden">>, maps:get(<<"status">>, R)) || R <- Reviews],
|
||||
[?assertEqual(EventId, maps:get(<<"target_id">>, R)) || R <- Reviews],
|
||||
ct:pal(" OK: ~p reviews match both criteria", [length(Reviews)]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews/:id – получение отзыва по ID.
|
||||
test_get_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Get review by ID"),
|
||||
ct:pal(" TEST: Get review by ID"),
|
||||
Path = <<"/v1/admin/reviews/", ReviewId/binary>>,
|
||||
Review = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(ReviewId, maps:get(<<"id">>, Review)),
|
||||
?assertEqual(<<"visible">>, maps:get(<<"status">>, Review)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"comment">>, Review)]).
|
||||
ct:pal(" OK: ~s", [maps:get(<<"comment">>, Review)]).
|
||||
|
||||
%% @doc PUT /v1/admin/reviews/:id – обновление отзыва.
|
||||
test_update_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Update review"),
|
||||
ct:pal(" TEST: Update review"),
|
||||
Path = <<"/v1/admin/reviews/", ReviewId/binary>>,
|
||||
Updates = #{<<"comment">> => <<"Updated by admin">>},
|
||||
Updated = api_test_runner:admin_put(Path, Token, Updates),
|
||||
?assertEqual(<<"Updated by admin">>, maps:get(<<"comment">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
ct:pal(" OK").
|
||||
|
||||
%% ── Сортировка ──
|
||||
|
||||
%% @doc Сортировка по created_at по убыванию.
|
||||
test_sort_by_created_at_desc(Token) ->
|
||||
ct:pal(" TEST: Sort by created_at desc"),
|
||||
{ok, 200, _, Body} = api_test_runner:admin_request(get,
|
||||
<<"/v1/admin/reviews?sort=created_at&order=desc">>, Token, <<"">>),
|
||||
Reviews = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
Dates = [maps:get(<<"created_at">>, R) || R <- Reviews],
|
||||
Sorted = lists:reverse(lists:sort(Dates)),
|
||||
?assertEqual(Sorted, Dates),
|
||||
ct:pal(" OK: sorted by created_at desc").
|
||||
|
||||
%% @doc Сортировка по created_at по возрастанию.
|
||||
test_sort_by_created_at_asc(Token) ->
|
||||
ct:pal(" TEST: Sort by created_at asc"),
|
||||
{ok, 200, _, Body} = api_test_runner:admin_request(get,
|
||||
<<"/v1/admin/reviews?sort=created_at&order=asc">>, Token, <<"">>),
|
||||
Reviews = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
Dates = [maps:get(<<"created_at">>, R) || R <- Reviews],
|
||||
?assertEqual(lists:sort(Dates), Dates),
|
||||
ct:pal(" OK: sorted by created_at asc").
|
||||
|
||||
%% @doc Сортировка по rating по возрастанию.
|
||||
test_sort_by_rating_asc(Token) ->
|
||||
ct:pal(" TEST: Sort by rating asc"),
|
||||
{ok, 200, _, Body} = api_test_runner:admin_request(get,
|
||||
<<"/v1/admin/reviews?sort=rating&order=asc">>, Token, <<"">>),
|
||||
Reviews = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
Ratings = [maps:get(<<"rating">>, R) || R <- Reviews],
|
||||
?assertEqual(lists:sort(Ratings), Ratings),
|
||||
ct:pal(" OK: sorted by rating asc").
|
||||
|
||||
%% ── Фильтр + сортировка ──
|
||||
|
||||
%% @doc Фильтр status=visible + сортировка по created_at desc.
|
||||
test_filter_status_sort_created_at(Token) ->
|
||||
ct:pal(" TEST: Filter status=visible + sort by created_at desc"),
|
||||
{ok, 200, _, Body} = api_test_runner:admin_request(get,
|
||||
<<"/v1/admin/reviews?status=visible&sort=created_at&order=desc">>, Token, <<"">>),
|
||||
Reviews = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
[?assertEqual(<<"visible">>, maps:get(<<"status">>, R)) || R <- Reviews],
|
||||
Dates = [maps:get(<<"created_at">>, R) || R <- Reviews],
|
||||
Sorted = lists:reverse(lists:sort(Dates)),
|
||||
?assertEqual(Sorted, Dates),
|
||||
ct:pal(" OK: ~p reviews sorted", [length(Reviews)]).
|
||||
|
||||
%% ── Фильтр + сортировка + пагинация ──
|
||||
|
||||
%% @doc Фильтр target_type=event + сортировка по rating desc + пагинация limit=1.
|
||||
test_filter_sort_pagination_combined(Token, EventId) ->
|
||||
ct:pal(" TEST: Filter + sort + pagination (target_type=event, sort=rating desc, limit=1)"),
|
||||
Path = <<"/v1/admin/reviews?target_type=event&target_id=", EventId/binary,
|
||||
"&sort=rating&order=desc&limit=1&offset=0">>,
|
||||
{ok, 200, Headers, Body} = api_test_runner:admin_request(get, Path, Token, <<"">>),
|
||||
Reviews = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(length(Reviews) =< 1),
|
||||
[?assertEqual(EventId, maps:get(<<"target_id">>, R)) || R <- Reviews],
|
||||
Total = list_to_integer(
|
||||
proplists:get_value("x-total-count", Headers, "0")),
|
||||
?assert(Total >= 1),
|
||||
ct:pal(" OK: total count = ~p", [Total]).
|
||||
|
||||
%% ── Пагинация ──
|
||||
|
||||
%% @doc GET /v1/admin/reviews?limit=...&offset=... – пагинация.
|
||||
test_review_pagination(Token) ->
|
||||
ct:pal(" TEST: Review pagination"),
|
||||
ct:pal(" TEST: Review pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/reviews?limit=1&offset=0">>, Token),
|
||||
?assertEqual(1, length(Page1)),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/reviews?limit=1&offset=1">>, Token),
|
||||
@@ -133,4 +241,14 @@ test_review_pagination(Token) ->
|
||||
Id1 = maps:get(<<"id">>, hd(Page1)),
|
||||
Id2 = maps:get(<<"id">>, hd(Page2)),
|
||||
?assertNotEqual(Id1, Id2),
|
||||
ct:pal(" OK").
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc Проверка заголовков пагинации (x-total-count).
|
||||
test_pagination_headers(Token) ->
|
||||
ct:pal(" TEST: Pagination headers (x-total-count)"),
|
||||
{ok, 200, Headers, _} = api_test_runner:admin_request(get,
|
||||
<<"/v1/admin/reviews?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]).
|
||||
Reference in New Issue
Block a user