%%%------------------------------------------------------------------- %%% @doc Тесты клиентского API профиля текущего пользователя. %%% %%% GET /v1/user/me %%% PATCH /v1/user/me %%% @end %%%------------------------------------------------------------------- -module(user_me_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). -spec test() -> ok. test() -> ct:pal("=== User Profile (me) Tests ==="), Email = api_test_runner:unique_email(<<"meuser">>), Pass0 = <<"oldpass1">>, Token = api_test_runner:register_and_login(Email, Pass0), test_get_me_success(Token), test_get_me_unauthorized(), test_patch_language(Token), test_patch_profile_fields(Token), test_patch_avatar_and_preferences(Token), test_patch_null_clears(Token), test_patch_invalid_language(Token), test_patch_empty_body(Token), test_patch_invalid_json(Token), test_patch_unknown_field(Token), test_patch_forbid_email(Token), test_patch_unauthorized(), test_patch_password_wrong_current(Token, Pass0), test_patch_password_ok(Token, Pass0, <<"newpass2">>, Email), test_patch_password_pair_required(Token), ct:pal("=== All user me tests passed ==="), ok. %%%=================================================================== %%% GET %%%=================================================================== test_get_me_success(Token) -> ct:pal(" TEST: Get current user profile"), User = api_test_runner:client_get(<<"/v1/user/me">>, Token), ?assert(is_map(User)), ?assert(maps:is_key(<<"id">>, User)), ?assert(maps:is_key(<<"email">>, User)), ?assert(maps:is_key(<<"role">>, User)), ?assert(maps:is_key(<<"status">>, User)), ct:pal(" OK: got profile for ~s", [maps:get(<<"email">>, User)]). test_get_me_unauthorized() -> ct:pal(" TEST: Get profile without token"), Resp = api_test_runner:client_request(get, <<"/v1/user/me">>, <<>>), ?assertMatch({ok, 401, _, _}, Resp), ct:pal(" OK: got 401 unauthorized"). %%%=================================================================== %%% PATCH %%%=================================================================== test_patch_language(Token) -> ct:pal(" TEST: PATCH language"), User = api_test_runner:client_patch(<<"/v1/user/me">>, Token, #{language => <<"en">>}), ?assertEqual(<<"en">>, maps:get(<<"language">>, User)), ct:pal(" OK"). test_patch_profile_fields(Token) -> ct:pal(" TEST: PATCH nickname/timezone/phone"), User = api_test_runner:client_patch(<<"/v1/user/me">>, Token, #{nickname => <<"Nick">>, timezone => <<"Europe/Moscow">>, phone => <<"+7000">>}), ?assertEqual(<<"Nick">>, maps:get(<<"nickname">>, User)), ?assertEqual(<<"Europe/Moscow">>, maps:get(<<"timezone">>, User)), ?assertEqual(<<"+7000">>, maps:get(<<"phone">>, User)), ct:pal(" OK"). test_patch_avatar_and_preferences(Token) -> ct:pal(" TEST: PATCH avatar_url and preferences"), User = api_test_runner:client_patch(<<"/v1/user/me">>, Token, #{avatar_url => <<"https://cdn.example/a.png">>, preferences => #{<<"theme">> => <<"dark">>, <<"notify">> => true}}), ?assertEqual(<<"https://cdn.example/a.png">>, maps:get(<<"avatar_url">>, User)), Prefs = maps:get(<<"preferences">>, User), ?assertEqual(<<"dark">>, maps:get(<<"theme">>, Prefs)), ?assertEqual(true, maps:get(<<"notify">>, Prefs)), ct:pal(" OK"). test_patch_null_clears(Token) -> ct:pal(" TEST: PATCH null clears optional fields"), User = api_test_runner:client_patch(<<"/v1/user/me">>, Token, #{nickname => null, phone => null, avatar_url => null, timezone => null, preferences => null}), ?assertEqual(<<>>, maps:get(<<"nickname">>, User)), ?assertEqual(<<>>, maps:get(<<"phone">>, User)), ?assertEqual(<<>>, maps:get(<<"avatar_url">>, User)), ?assertEqual(<<>>, maps:get(<<"timezone">>, User)), ?assertEqual(#{}, maps:get(<<"preferences">>, User)), ct:pal(" OK"). test_patch_invalid_language(Token) -> ct:pal(" TEST: PATCH invalid language -> 400"), Body = jsx:encode(#{language => <<"de">>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, Body), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK"). test_patch_empty_body(Token) -> ct:pal(" TEST: PATCH empty body -> 400"), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, <<"{}">>), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK"). test_patch_invalid_json(Token) -> ct:pal(" TEST: PATCH invalid JSON -> 400"), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, <<"{not-json">>), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK"). test_patch_unknown_field(Token) -> ct:pal(" TEST: PATCH unknown field -> 400"), Body = jsx:encode(#{email => <<"hack@x.com">>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, Body), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK"). test_patch_forbid_email(Token) -> ct:pal(" TEST: PATCH role -> 400"), Body = jsx:encode(#{role => <<"bot">>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, Body), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK"). test_patch_unauthorized() -> ct:pal(" TEST: PATCH without token -> 401"), Body = jsx:encode(#{language => <<"ru">>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, <<>>, Body), ?assertMatch({ok, 401, _, _}, Resp), ct:pal(" OK"). test_patch_password_wrong_current(Token, Current) -> ct:pal(" TEST: PATCH password wrong current -> 403"), Body = jsx:encode(#{current_password => <<"nope">>, password => <<"x", Current/binary>>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, Body), ?assertMatch({ok, 403, _, _}, Resp), ct:pal(" OK"). test_patch_password_ok(Token, OldPass, NewPass, Email) -> ct:pal(" TEST: PATCH password ok"), User = api_test_runner:client_patch(<<"/v1/user/me">>, Token, #{current_password => OldPass, password => NewPass}), ?assert(maps:is_key(<<"id">>, User)), %% login with new password Login = api_test_runner:client_request(post, <<"/v1/login">>, <<>>, jsx:encode(#{email => Email, password => NewPass})), ?assertMatch({ok, 200, _, _}, Login), %% old password fails OldLogin = api_test_runner:client_request(post, <<"/v1/login">>, <<>>, jsx:encode(#{email => Email, password => OldPass})), ?assertMatch({ok, 401, _, _}, OldLogin), ct:pal(" OK"). test_patch_password_pair_required(Token) -> ct:pal(" TEST: PATCH password without current -> 400"), Body = jsx:encode(#{password => <<"alone">>}), Resp = api_test_runner:client_request(patch, <<"/v1/user/me">>, Token, Body), ?assertMatch({ok, 400, _, _}, Resp), ct:pal(" OK").