feat: calendar follow API (POST/DELETE follow, GET following). Refs EventHub/EventHubFront#9
CI / test (push) Failing after 6m39s
CI / deploy-ift (push) Has been skipped
CI / e2e-ift (push) Has been skipped
CI / deploy-stage (push) Has been skipped
CI / e2e-stage (push) Has been skipped

This commit is contained in:
2026-07-20 22:10:48 +03:00
parent 4932f9abae
commit 59220b955e
16 changed files with 599 additions and 4 deletions
+113
View File
@@ -0,0 +1,113 @@
%%%-------------------------------------------------------------------
%%% @doc Тесты follow / unfollow чужого календаря.
%%%
%%% POST /v1/calendars/:id/follow
%%% DELETE /v1/calendars/:id/follow
%%% GET /v1/user/following
%%% GET /v1/calendars/:id (поле following)
%%% @end
%%%-------------------------------------------------------------------
-module(user_calendar_follow_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
-spec test() -> ok.
test() ->
ct:pal("=== User Calendar Follow Tests ==="),
OwnerEmail = api_test_runner:unique_email(<<"flowner">>),
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"pass">>),
FollowerEmail = api_test_runner:unique_email(<<"fluser">>),
FollowerToken = api_test_runner:register_and_login(FollowerEmail, <<"pass">>),
%% commercial calendar requires subscription
{ok, 201, _, _} = api_test_runner:client_request(
post, <<"/v1/subscription">>, OwnerToken,
jsx:encode(#{action => <<"start_trial">>})),
CalId = api_test_runner:create_calendar(OwnerToken, #{
title => <<"FollowMe">>,
type => <<"commercial">>
}),
FollowPath = <<"/v1/calendars/", CalId/binary, "/follow">>,
test_follow(FollowerToken, FollowPath, CalId),
test_follow_idempotent(FollowerToken, FollowPath, CalId),
test_get_calendar_following(FollowerToken, CalId, true),
test_list_following(FollowerToken, CalId),
test_own_forbidden(OwnerToken, FollowPath),
test_unfollow(FollowerToken, FollowPath, CalId),
test_unfollow_idempotent(FollowerToken, FollowPath, CalId),
test_get_calendar_following(FollowerToken, CalId, false),
test_list_following_empty(FollowerToken),
test_unauthorized(FollowPath),
ct:pal("=== All user calendar follow tests passed ==="),
ok.
%%%===================================================================
%%% Cases
%%%===================================================================
test_follow(Token, Path, CalId) ->
ct:pal(" TEST: POST follow"),
{ok, 200, _, Body} = api_test_runner:client_request(post, Path, Token, <<"{}">>),
Resp = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(CalId, maps:get(<<"calendar_id">>, Resp)),
?assertEqual(true, maps:get(<<"following">>, Resp)),
ct:pal(" OK").
test_follow_idempotent(Token, Path, CalId) ->
ct:pal(" TEST: POST follow idempotent"),
{ok, 200, _, Body} = api_test_runner:client_request(post, Path, Token, <<"{}">>),
Resp = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(CalId, maps:get(<<"calendar_id">>, Resp)),
?assertEqual(true, maps:get(<<"following">>, Resp)),
ct:pal(" OK").
test_get_calendar_following(Token, CalId, Expected) ->
ct:pal(" TEST: GET calendar following=~p", [Expected]),
Cal = api_test_runner:client_get(<<"/v1/calendars/", CalId/binary>>, Token),
?assertEqual(Expected, maps:get(<<"following">>, Cal)),
ct:pal(" OK").
test_list_following(Token, CalId) ->
ct:pal(" TEST: GET /v1/user/following"),
List = api_test_runner:client_get(<<"/v1/user/following">>, Token),
?assert(is_list(List)),
Match = [C || C <- List, maps:get(<<"id">>, C) =:= CalId],
?assertMatch([_], Match),
[C] = Match,
?assertEqual(true, maps:get(<<"following">>, C)),
ct:pal(" OK").
test_own_forbidden(Token, Path) ->
ct:pal(" TEST: follow own calendar forbidden"),
Resp = api_test_runner:client_request(post, Path, Token, <<"{}">>),
?assertMatch({ok, 403, _, _}, Resp),
ct:pal(" OK").
test_unfollow(Token, Path, CalId) ->
ct:pal(" TEST: DELETE unfollow"),
Resp = api_test_runner:client_delete(Path, Token),
?assertEqual(CalId, maps:get(<<"calendar_id">>, Resp)),
?assertEqual(false, maps:get(<<"following">>, Resp)),
ct:pal(" OK").
test_unfollow_idempotent(Token, Path, CalId) ->
ct:pal(" TEST: DELETE unfollow idempotent"),
Resp = api_test_runner:client_delete(Path, Token),
?assertEqual(CalId, maps:get(<<"calendar_id">>, Resp)),
?assertEqual(false, maps:get(<<"following">>, Resp)),
ct:pal(" OK").
test_list_following_empty(Token) ->
ct:pal(" TEST: GET following empty"),
List = api_test_runner:client_get(<<"/v1/user/following">>, Token),
?assertEqual([], List),
ct:pal(" OK").
test_unauthorized(Path) ->
ct:pal(" TEST: unauthorized"),
Resp = api_test_runner:client_request(post, Path, <<>>, <<"{}">>),
?assertMatch({ok, 401, _, _}, Resp),
ct:pal(" OK").