%%%------------------------------------------------------------------- %%% @doc Тесты клиентского API для отмены вхождения повторяющегося события. %%% %%% Покрывает эндпоинты: %%% DELETE /v1/events/:id/occurrences/:start_time %%% %%% Проверяет: %%% - успешную отмену конкретного вхождения %%% - ошибку 400 для не-recurring события %%% - ошибку 403 при попытке отмены чужим пользователем %%% - ошибку 401 без токена %%% @end %%%------------------------------------------------------------------- -module(user_occurrence_cancel_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). %%%=================================================================== %%% Главная тестовая функция %%%=================================================================== -spec test() -> ok. test() -> ct:pal("=== User Occurrence Cancel Tests ==="), OwnerToken = api_test_runner:get_user_token(), OtherToken = api_test_runner:register_and_login( api_test_runner:unique_email(<<"other">>), <<"pass">>), % Создаём календарь и повторяющееся событие CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"OccCancel">>}), #{<<"id">> := RecurringId} = api_test_runner:client_post( <<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken, #{title => <<"Weekly Standup">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 30, recurrence => #{freq => <<"WEEKLY">>, interval => 1}}), % Получаем вхождения (ответ – список карт) OccPath = <<"/v1/events/", RecurringId/binary, "/occurrences?from=2026-06-01T00:00:00Z&to=2026-06-30T00:00:00Z">>, Occurrences = api_test_runner:client_get(OccPath, OwnerToken), ?assert(is_list(Occurrences)), ?assert(length(Occurrences) >= 1), #{<<"start_time">> := FirstStart} = hd(Occurrences), test_cancel_occurrence(OwnerToken, RecurringId, FirstStart), test_cancel_occurrence_on_single_event(OwnerToken, CalId), test_cancel_occurrence_forbidden(OtherToken, RecurringId, FirstStart), test_cancel_occurrence_unauthorized(RecurringId, FirstStart), ct:pal("=== All user occurrence cancel tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== %% @doc Успешная отмена вхождения: 200 OK. -spec test_cancel_occurrence(binary(), binary(), binary()) -> ok. test_cancel_occurrence(Token, EventId, StartTime) -> ct:pal(" TEST: Cancel occurrence"), Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>, Resp = api_test_runner:client_request(delete, Path, Token), {ok, 200, _, Body} = Resp, #{<<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]), ?assertEqual(<<"cancelled">>, Status), ct:pal(" OK: occurrence cancelled"). %% @doc Попытка отменить вхождение для одиночного события: 400. -spec test_cancel_occurrence_on_single_event(binary(), binary()) -> ok. test_cancel_occurrence_on_single_event(Token, CalId) -> ct:pal(" TEST: Cancel occurrence on non-recurring event"), #{<<"id">> := SingleId} = api_test_runner:client_post( <<"/v1/calendars/", CalId/binary, "/events">>, Token, #{title => <<"Single">>, start_time => <<"2026-06-02T10:00:00Z">>, duration => 30}), Path = <<"/v1/events/", SingleId/binary, "/occurrences/2026-06-02T10:00:00Z">>, Resp = api_test_runner:client_request(delete, Path, Token), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK: got 400"). %% @doc Попытка отмены чужим пользователем: 403. -spec test_cancel_occurrence_forbidden(binary(), binary(), binary()) -> ok. test_cancel_occurrence_forbidden(OtherToken, EventId, StartTime) -> ct:pal(" TEST: Cancel occurrence by non-owner"), Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>, Resp = api_test_runner:client_request(delete, Path, OtherToken), ?assertMatch({ok, 403, _, _}, Resp), ct:pal(" OK: got 403"). %% @doc Запрос без токена: 401. -spec test_cancel_occurrence_unauthorized(binary(), binary()) -> ok. test_cancel_occurrence_unauthorized(EventId, StartTime) -> ct:pal(" TEST: Cancel occurrence without token"), Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>, Resp = api_test_runner:client_request(delete, Path, <<>>), ?assertMatch({ok, 401, _, _}, Resp), ct:pal(" OK: got 401").