Сделано подтверждение регистрации пользователей #22

This commit is contained in:
2026-05-27 21:44:13 +03:00
parent b78df927dd
commit 4444461ee3
17 changed files with 358 additions and 48 deletions
+25 -1
View File
@@ -40,6 +40,7 @@
client_put/3,
client_delete/2,
admin_patch/3]).
-export([verify_user/2]).
%%%===================================================================
%%% Конфигурация окружения (CT_MODE, ...)
@@ -281,10 +282,23 @@ future_date() ->
-spec register_and_login(binary(), binary()) -> binary().
register_and_login(Email, Password) ->
% 1. Регистрируем пользователя
Resp = client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
{ok, 201, _, Body} = Resp,
#{<<"token">> := Token} = jsx:decode(list_to_binary(Body), [return_maps]),
#{<<"user">> := #{<<"id">> := UserId}} = jsx:decode(list_to_binary(Body), [return_maps]),
% 2. Получаем админский токен (используется кеширование)
AdminToken = get_admin_token(),
% 3. Верифицируем пользователя
verify_user(AdminToken, UserId),
% 4. Логинимся и возвращаем свежий JWT
LoginResp = client_request(post, <<"/v1/login">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
{ok, 200, _, LoginBody} = LoginResp,
#{<<"token">> := Token} = jsx:decode(list_to_binary(LoginBody), [return_maps]),
Token.
-spec create_calendar(binary(), map()) -> binary().
@@ -298,6 +312,16 @@ create_event(Token, CalId, Params) ->
#{<<"id">> := EventId} = client_post(Path, Token, Params),
EventId.
%% @doc Подтверждает email пользователя, используя админский эндпоинт
%% для получения верификационного токена и публичный /v1/verify.
-spec verify_user(AdminToken :: binary(), UserId :: binary()) -> ok.
verify_user(AdminToken, UserId) ->
#{<<"token">> := Token} = admin_get(
<<"/v1/admin/users/", UserId/binary, "/verification-token">>, AdminToken),
{ok, 200, _, _} = client_request(post, <<"/v1/verify">>, <<>>,
jsx:encode(#{<<"token">> => Token})),
ok.
%%%===================================================================
%%% Внутренние функции
%%%===================================================================
+12 -5
View File
@@ -22,7 +22,7 @@
-spec test() -> ok.
test() ->
ct:pal("=== User Refresh Tests ==="),
Token = api_test_runner:get_user_token(),
_Token = api_test_runner:get_user_token(),
% Получаем refresh_token через логин (или регистрацию)
Email = api_test_runner:unique_email(<<"refresh">>),
@@ -76,13 +76,20 @@ test_missing_refresh_token() ->
%%% Вспомогательные функции
%%%===================================================================
%% @doc Регистрирует пользователя, выполняет логин и возвращает refresh_token.
%% @doc Регистрирует пользователя, подтверждает email через админку
%% и возвращает результат логина (с refresh_token).
-spec register_and_get_refresh(binary(), binary()) -> map().
register_and_get_refresh(Email, Password) ->
% Регистрируем
_ = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
% Регистрируем пользователя
{ok, 201, _, RegBody} = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
% Логинимся, чтобы получить refresh_token
#{<<"user">> := #{<<"id">> := UserId}} = jsx:decode(list_to_binary(RegBody), [return_maps]),
% Верифицируем аккаунт (получаем токен через админку и подтверждаем)
AdminToken = api_test_runner:get_admin_token(),
api_test_runner:verify_user(AdminToken, UserId),
% Логинимся, чтобы получить access_token и refresh_token
{ok, 200, _, Body} = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
jsx:decode(list_to_binary(Body), [return_maps]).
+25 -19
View File
@@ -2,34 +2,31 @@
%%% @doc Тесты клиентского API для регистрации пользователей.
%%%
%%% Покрывает эндпоинты:
%%% POST /v1/register
%%% POST /v1/register
%%%
%%% Проверяет:
%%% - успешную регистрацию нового пользователя
%%% - возврат JWT токена и данных пользователя
%%% - ошибку при повторной регистрации с тем же email
%%% - ошибку при отсутствии обязательных полей
%%% - успешную регистрацию нового пользователя (статус pending)
%%% - ошибку при повторной регистрации с тем же email
%%% - ошибку при отсутствии обязательных полей
%%% - что логин до подтверждения возвращает 403
%%% @end
%%%-------------------------------------------------------------------
-module(user_register_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
%%%===================================================================
%%% Главная тестовая функция
%%%===================================================================
-spec test() -> ok.
test() ->
ct:pal("=== Client Register Tests ==="),
Email = api_test_runner:unique_email(<<"register">>),
Password = <<"StrongPass1!">>,
test_successful_register(Email, Password),
test_duplicate_register(Email, Password),
test_missing_fields(),
test_login_before_verification(Email, Password),
ct:pal("=== All client register tests passed ==="),
ok.
@@ -37,40 +34,49 @@ test() ->
%%% Тестовые функции
%%%===================================================================
%% @doc Успешная регистрация: 201 Created, возвращает токен и пользователя.
%% @doc Успешная регистрация: 201 Created, возвращает пользователя со статусом pending.
-spec test_successful_register(binary(), binary()) -> ok.
test_successful_register(Email, Password) ->
ct:pal(" TEST: Successful registration"),
ct:pal(" TEST: Successful registration"),
Resp = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
{ok, 201, _, Body} = Resp,
#{<<"token">> := Token, <<"user">> := User} = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(is_binary(Token)),
#{<<"user">> := User} = jsx:decode(list_to_binary(Body), [return_maps]),
?assert(maps:is_key(<<"id">>, User)),
?assertEqual(Email, maps:get(<<"email">>, User)),
ct:pal(" OK: user ~s created", [maps:get(<<"id">>, User)]).
?assertEqual(<<"pending">>, maps:get(<<"status">>, User)),
ct:pal(" OK: user ~s created with status pending", [maps:get(<<"id">>, User)]).
%% @doc Повторная регистрация с тем же email: 409 Conflict.
-spec test_duplicate_register(binary(), binary()) -> ok.
test_duplicate_register(Email, Password) ->
ct:pal(" TEST: Duplicate registration"),
ct:pal(" TEST: Duplicate registration"),
Resp = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
{ok, 409, _, Body} = Resp,
#{<<"error">> := ErrorMsg} = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(<<"Email already exists">>, ErrorMsg),
ct:pal(" OK: got 409 conflict").
ct:pal(" OK: got 409 conflict").
%% @doc Отсутствие обязательных полей: 400 Bad Request.
-spec test_missing_fields() -> ok.
test_missing_fields() ->
ct:pal(" TEST: Missing required fields"),
ct:pal(" TEST: Missing required fields"),
Resp1 = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => <<"missing@test.local">>})),
?assertMatch({ok, 400, _, _}, Resp1),
Resp2 = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{password => <<"NoEmail1">>})),
?assertMatch({ok, 400, _, _}, Resp2),
ct:pal(" OK: 400 on missing fields").
ct:pal(" OK: 400 on missing fields").
%% @doc Попытка входа до подтверждения email: 403 Forbidden.
-spec test_login_before_verification(binary(), binary()) -> ok.
test_login_before_verification(Email, Password) ->
ct:pal(" TEST: Login before email verification (403)"),
Resp = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
{ok, 403, _, Body} = Resp,
#{<<"error">> := ErrorMsg} = jsx:decode(list_to_binary(Body), [return_maps]),
?assertEqual(<<"Account not verified">>, ErrorMsg),
ct:pal(" OK: got 403").
@@ -0,0 +1,62 @@
%%%-------------------------------------------------------------------
%%% @doc Тесты процесса подтверждения регистрации.
%%%
%%% Проверяет:
%%% - регистрация создаёт пользователя со статусом `pending`
%%% - вход до подтверждения возвращает 403
%%% - получение верификационного токена через админку
%%% - подтверждение аккаунта через публичный эндпоинт
%%% - после подтверждения вход успешен
%%% - повторное использование токена возвращает ошибку
%%% - невалидный токен возвращает 404
%%% @end
%%%-------------------------------------------------------------------
-module(user_verification_tests).
-include_lib("eunit/include/eunit.hrl").
-export([test/0]).
-export([verification_test/0]).
verification_test() -> test().
-spec test() -> ok.
test() ->
ct:pal("=== User Verification Tests ==="),
AdminToken = api_test_runner:get_admin_token(),
Email = api_test_runner:unique_email(<<"verify">>),
Password = <<"TestPass123">>,
% 1. Регистрируем пользователя (должен получить статус pending)
{ok, 201, _, RegBody} = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
#{<<"user">> := #{<<"id">> := UserId, <<"status">> := UserStatus}} =
jsx:decode(list_to_binary(RegBody), [return_maps]),
?assertEqual(<<"pending">>, UserStatus),
% 2. Попытка входа до подтверждения – должна вернуть 403
{ok, 403, _, _} = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
% 3. Получаем верификационный токен через админку
#{<<"token">> := Token} = api_test_runner:admin_get(
<<"/v1/admin/users/", UserId/binary, "/verification-token">>, AdminToken),
% 4. Подтверждаем аккаунт через публичный эндпоинт
{ok, 200, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>,
jsx:encode(#{<<"token">> => Token})),
% 5. Теперь вход должен быть успешным
{ok, 200, _, LoginBody} = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
jsx:encode(#{email => Email, password => Password})),
#{<<"token">> := AuthToken} = jsx:decode(list_to_binary(LoginBody), [return_maps]),
?assert(is_binary(AuthToken)),
% 6. Повторное использование того же токена – ошибка 404
{ok, 404, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>,
jsx:encode(#{<<"token">> => Token})),
% 7. Невалидный токен – ошибка 404
{ok, 404, _, _} = api_test_runner:client_request(post, <<"/v1/verify">>, <<>>,
jsx:encode(#{<<"token">> => <<"invalid_token">>})),
ct:pal("=== All user verification tests passed ==="),
ok.