%%%------------------------------------------------------------------- %%% @doc Тесты клиентского API для бронирований. %%% %%% Покрывает эндпоинты: %%% POST /v1/events/:id/bookings %%% PUT /v1/bookings/:id %%% DELETE /v1/bookings/:id %%% %%% Проверяет: %%% - создание бронирования участником %%% - подтверждение бронирования владельцем календаря %%% - отмену бронирования участником %%% - ошибку при повторном бронировании %%% - ошибку 401 без токена %%% @end %%%------------------------------------------------------------------- -module(user_bookings_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). %%%=================================================================== %%% Главная тестовая функция %%%=================================================================== -spec test() -> ok. test() -> ct:pal("=== User Bookings Tests ==="), OwnerToken = api_test_runner:get_user_token(), ParticipantEmail = api_test_runner:unique_email(<<"participant">>), ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>), % Создаём календарь и событие CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"BookingTest">>}), #{<<"id">> := EventId} = api_test_runner:client_post( <<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken, #{title => <<"Event to book">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}), test_create_booking(ParticipantToken, EventId), test_confirm_booking(OwnerToken, EventId), test_cancel_booking(ParticipantToken, EventId), test_duplicate_booking(ParticipantToken, EventId), test_booking_unauthorized(EventId), ct:pal("=== All user bookings tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== %% @doc Успешное создание бронирования: 201 Created. -spec test_create_booking(binary(), binary()) -> ok. test_create_booking(Token, EventId) -> ct:pal(" TEST: Create booking"), Path = <<"/v1/events/", EventId/binary, "/bookings">>, Resp = api_test_runner:client_request(post, Path, Token, <<"{}">>), {ok, 201, _, Body} = Resp, #{<<"id">> := BookingId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]), ?assert(is_binary(BookingId)), ?assertEqual(<<"pending">>, Status), ct:pal(" OK: booking ~s created", [BookingId]). %% @doc Подтверждение бронирования владельцем: 200 OK. -spec test_confirm_booking(binary(), binary()) -> ok. test_confirm_booking(OwnerToken, EventId) -> ct:pal(" TEST: Confirm booking as owner"), % Создаём новое бронирование, которое ещё не подтверждено Participant2 = api_test_runner:register_and_login( api_test_runner:unique_email(<<"part2">>), <<"pass">>), Path = <<"/v1/events/", EventId/binary, "/bookings">>, #{<<"id">> := BookingId} = api_test_runner:client_post(Path, Participant2, #{}), % Подтверждаем ConfirmPath = <<"/v1/bookings/", BookingId/binary>>, Updated = api_test_runner:client_put(ConfirmPath, OwnerToken, #{action => <<"confirm">>}), ?assertEqual(<<"confirmed">>, maps:get(<<"status">>, Updated)), ct:pal(" OK: booking ~s confirmed", [BookingId]). %% @doc Отмена бронирования участником: 200 OK. -spec test_cancel_booking(binary(), binary()) -> ok. test_cancel_booking(OwnerToken, EventId) -> ct:pal(" TEST: Cancel booking as participant"), % Создаём нового участника, у которого нет бронирований CancelUserEmail = api_test_runner:unique_email(<<"canceluser">>), CancelUserToken = api_test_runner:register_and_login(CancelUserEmail, <<"pass">>), Path = <<"/v1/events/", EventId/binary, "/bookings">>, #{<<"id">> := BookingId} = api_test_runner:client_post(Path, CancelUserToken, #{}), CancelPath = <<"/v1/bookings/", BookingId/binary>>, Resp = api_test_runner:client_request(delete, CancelPath, CancelUserToken), ?assertMatch({ok, 200, _, _}, Resp), ct:pal(" OK: booking ~s cancelled", [BookingId]). %% @doc Повторное бронирование того же события: 409 Conflict. -spec test_duplicate_booking(binary(), binary()) -> ok. test_duplicate_booking(ParticipantToken, EventId) -> ct:pal(" TEST: Duplicate booking"), Path = <<"/v1/events/", EventId/binary, "/bookings">>, % Первый раз должно быть 201 (или 200, если уже есть pending) _ = api_test_runner:client_request(post, Path, ParticipantToken, <<"{}">>), % Второй раз – уже booked Resp2 = api_test_runner:client_request(post, Path, ParticipantToken, <<"{}">>), {ok, 409, _, _} = Resp2, ct:pal(" OK: got 409 conflict"). %% @doc Запрос без токена: 401 Unauthorized. -spec test_booking_unauthorized(binary()) -> ok. test_booking_unauthorized(EventId) -> ct:pal(" TEST: Booking without token"), Path = <<"/v1/events/", EventId/binary, "/bookings">>, Resp = api_test_runner:client_request(post, Path, <<>>, <<"{}">>), ?assertMatch({ok, 401, _, _}, Resp), ct:pal(" OK: got 401").