Files
EventHubBack/test/api/users/user_bookings_tests.erl
T

146 lines
6.9 KiB
Erlang
Executable File

%%%-------------------------------------------------------------------
%%% @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">>,
type => <<"commercial">>,
confirmation => <<"manual">>
}),
#{<<"id">> := EventId} = api_test_runner:client_post(
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
#{title => <<"Event to book">>,
start_time => api_test_runner:future_date_iso8601(),
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),
test_recurring_booking(OwnerToken, ParticipantToken),
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").
%% @doc Серия: без occurrence_start — 400; с датой вхождения — 201 на instance.
-spec test_recurring_booking(binary(), binary()) -> ok.
test_recurring_booking(OwnerToken, ParticipantToken) ->
ct:pal(" TEST: Recurring booking occurrence_start"),
CalId = api_test_runner:create_calendar(OwnerToken, #{
title => <<"SeriesBooking">>,
type => <<"commercial">>,
confirmation => <<"auto">>
}),
Start = api_test_runner:future_date(),
StartIso = api_test_runner:iso8601_utc(Start),
#{<<"id">> := MasterId} = api_test_runner:client_post(
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
#{title => <<"Weekly slot">>,
start_time => StartIso,
duration => 60,
recurrence => #{freq => <<"WEEKLY">>, interval => 1}}),
Path = <<"/v1/events/", MasterId/binary, "/bookings">>,
{ok, 400, _, _} = api_test_runner:client_request(post, Path, ParticipantToken, <<"{}">>),
OccSec = calendar:datetime_to_gregorian_seconds(Start) + 7 * 86400,
OccIso = api_test_runner:iso8601_utc(calendar:gregorian_seconds_to_datetime(OccSec)),
#{<<"id">> := _Bid, <<"event_id">> := InstId} =
api_test_runner:client_post(Path, ParticipantToken, #{occurrence_start => OccIso}),
?assertNotEqual(MasterId, InstId),
ct:pal(" OK: booked instance ~s", [InstId]).