%%%------------------------------------------------------------------- %%% @doc Тесты клиентского API для отзывов. %%% %%% Покрывает эндпоинты: %%% POST /v1/reviews %%% GET /v1/reviews %%% %%% Проверяет: %%% - создание отзыва участником подтверждённого бронирования %%% - ошибку при повторном отзыве на ту же цель %%% - ошибку при отзыве без участия (403) %%% - получение списка отзывов для цели %%% @end %%%------------------------------------------------------------------- -module(user_reviews_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). %%%=================================================================== %%% Главная тестовая функция %%%=================================================================== -spec test() -> ok. test() -> ct:pal("=== User Reviews Tests ==="), OwnerToken = api_test_runner:get_user_token(), ParticipantEmail = api_test_runner:unique_email(<<"reviewer">>), ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>), StrangerEmail = api_test_runner:unique_email(<<"stranger">>), StrangerToken = api_test_runner:register_and_login(StrangerEmail, <<"pass">>), % Создаём календарь и событие CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"ReviewTest">>}), #{<<"id">> := EventId} = api_test_runner:client_post( <<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken, #{title => <<"Event for review">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}), % Бронируем и подтверждаем участие #{<<"id">> := BookingId} = api_test_runner:client_post( <<"/v1/events/", EventId/binary, "/bookings">>, ParticipantToken, #{}), api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, OwnerToken, #{action => <<"confirm">>}), test_create_review(ParticipantToken, EventId), test_duplicate_review(ParticipantToken, EventId), test_review_without_booking(StrangerToken, EventId), test_list_reviews(OwnerToken, EventId), ct:pal("=== All user reviews tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== %% @doc Успешное создание отзыва: 201 Created. -spec test_create_review(binary(), binary()) -> ok. test_create_review(Token, EventId) -> ct:pal(" TEST: Create a review"), Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, Token, jsx:encode(#{ target_type => <<"event">>, target_id => EventId, rating => 5, comment => <<"Excellent!">> })), {ok, 201, _, Body} = Resp, #{<<"id">> := ReviewId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]), ?assert(is_binary(ReviewId)), ?assertEqual(<<"visible">>, Status), ct:pal(" OK: review ~s created", [ReviewId]). %% @doc Повторный отзыв на ту же цель: 409 Conflict. -spec test_duplicate_review(binary(), binary()) -> ok. test_duplicate_review(Token, EventId) -> ct:pal(" TEST: Duplicate review"), Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, Token, jsx:encode(#{ target_type => <<"event">>, target_id => EventId, rating => 3, comment => <<"Second try">> })), {ok, 409, _, _} = Resp, ct:pal(" OK: got 409 conflict"). %% @doc Попытка оставить отзыв без бронирования: 403 Forbidden. -spec test_review_without_booking(binary(), binary()) -> ok. test_review_without_booking(StrangerToken, EventId) -> ct:pal(" TEST: Review without booking"), Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, StrangerToken, jsx:encode(#{ target_type => <<"event">>, target_id => EventId, rating => 1, comment => <<"Not allowed">> })), {ok, 403, _, _} = Resp, ct:pal(" OK: got 403 forbidden"). %% @doc GET /v1/reviews?target_type=event&target_id=... – список отзывов. -spec test_list_reviews(binary(), binary()) -> ok. test_list_reviews(_, EventId) -> ct:pal(" TEST: List reviews for event"), Path = <<"/v1/reviews?target_type=event&target_id=", EventId/binary>>, Reviews = api_test_runner:client_get(Path, api_test_runner:get_user_token()), ?assert(is_list(Reviews)), ?assert(length(Reviews) >= 1), First = hd(Reviews), ?assertEqual(<<"Excellent!">>, maps:get(<<"comment">>, First)), ct:pal(" OK: ~p reviews found", [length(Reviews)]).