-module(core_user_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). -define(TABLES, [user]). setup() -> eh_test_support:start_mnesia(), eh_test_support:ensure_tables(?TABLES), case mnesia:add_table_index(user, email) of {atomic, ok} -> ok; {aborted, {already_exists, user, email}} -> ok; {aborted, Reason} -> error({add_index_failed, Reason}) end, ok. cleanup(_) -> eh_test_support:clear_tables(?TABLES), eh_test_support:delete_tables(?TABLES), eh_test_support:stop_mnesia(), ok. core_user_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"Create user", fun test_create/0}, {"Create user – email exists", fun test_create_email_exists/0}, {"Get by id", fun test_get_by_id/0}, {"Get by email / email_exists", fun test_get_by_email/0}, {"List all and list_users", fun test_list/0}, {"Update fields", fun test_update/0}, {"Update status", fun test_update_status/0}, {"Update last login", fun test_update_last_login/0}, {"Block and unblock", fun test_block_unblock/0}, {"Soft delete", fun test_delete/0}, {"Count helpers", fun test_counts/0}, {"Create and delete bot", fun test_bot/0} ]}. test_create() -> Email = <<"user1@test.local">>, {ok, User} = core_user:create(Email, <<"secret123">>), ?assertEqual(Email, User#user.email), ?assertEqual(user, User#user.role), ?assertEqual(pending, User#user.status), ?assertEqual(<<"user1">>, User#user.nickname), ?assert(is_binary(User#user.id)), ?assert(is_binary(User#user.password_hash)), ?assert(User#user.password_hash =/= <<"secret123">>). test_create_email_exists() -> Email = <<"dup@test.local">>, {ok, _} = core_user:create(Email, <<"pass1">>), ?assertEqual({error, email_exists}, core_user:create(Email, <<"pass2">>)). test_get_by_id() -> {ok, User} = core_user:create(<<"byid@test.local">>, <<"pass">>), {ok, Found} = core_user:get_by_id(User#user.id), ?assertEqual(User#user.id, Found#user.id), ?assertEqual({error, not_found}, core_user:get_by_id(<<"missing">>)). test_get_by_email() -> Email = <<"byemail@test.local">>, {ok, User} = core_user:create(Email, <<"pass">>), {ok, Found} = core_user:get_by_email(Email), ?assertEqual(User#user.id, Found#user.id), ?assertEqual(true, core_user:email_exists(Email)), ?assertEqual(false, core_user:email_exists(<<"nope@test.local">>)), ?assertEqual({error, not_found}, core_user:get_by_email(<<"nope@test.local">>)). test_list() -> ?assertEqual([], core_user:list_all()), {ok, U1} = core_user:create(<<"a@test.local">>, <<"pass">>), {ok, U2} = core_user:create(<<"b@test.local">>, <<"pass">>), ?assertEqual(2, length(core_user:list_all())), {ok, _} = core_user:delete(U1#user.id), All = core_user:list_all(), ?assertEqual(2, length(All)), {ok, ActiveMaps} = core_user:list_users(), ?assertEqual(1, length(ActiveMaps)), [Map] = ActiveMaps, ?assertEqual(U2#user.id, maps:get(id, Map)), ?assertEqual(<<"b@test.local">>, maps:get(email, Map)). test_update() -> {ok, User} = core_user:create(<<"upd@test.local">>, <<"pass">>), {ok, Updated} = core_user:update(User#user.id, [ {nickname, <<"nick">>}, {phone, <<"+7000">>}, {timezone, <<"UTC">>}, {status, active} ]), ?assertEqual(<<"nick">>, Updated#user.nickname), ?assertEqual(<<"+7000">>, Updated#user.phone), ?assertEqual(<<"UTC">>, Updated#user.timezone), ?assertEqual(active, Updated#user.status), ?assertEqual({error, not_found}, core_user:update(<<"missing">>, [{nickname, <<"x">>}])). test_update_status() -> {ok, User} = core_user:create(<<"st@test.local">>, <<"pass">>), {ok, Updated} = core_user:update_status(User#user.id, frozen, <<"review">>), ?assertEqual(frozen, Updated#user.status), ?assertEqual(<<"review">>, Updated#user.reason), ?assertEqual({error, not_found}, core_user:update_status(<<"missing">>, active, <<>>)). test_update_last_login() -> {ok, User} = core_user:create(<<"login@test.local">>, <<"pass">>), OldLogin = User#user.last_login, {ok, Updated} = core_user:update_last_login(User#user.id), ?assert(Updated#user.last_login >= OldLogin), ?assertEqual({error, not_found}, core_user:update_last_login(<<"missing">>)). test_block_unblock() -> {ok, User} = core_user:create(<<"block@test.local">>, <<"pass">>), {ok, Blocked} = core_user:block(User#user.id, <<"spam">>), ?assertEqual(blocked, Blocked#user.status), ?assertEqual(<<"spam">>, Blocked#user.reason), {ok, Active} = core_user:unblock(User#user.id, <<"ok">>), ?assertEqual(active, Active#user.status), ?assertEqual(<<"ok">>, Active#user.reason), ?assertEqual({error, not_found}, core_user:block(<<"missing">>, <<"x">>)), ?assertEqual({error, not_found}, core_user:unblock(<<"missing">>, <<"x">>)). test_delete() -> {ok, User} = core_user:create(<<"del@test.local">>, <<"pass">>), {ok, Deleted} = core_user:delete(User#user.id), ?assertEqual(deleted, Deleted#user.status), {ok, StillThere} = core_user:get_by_id(User#user.id), ?assertEqual(deleted, StillThere#user.status), ?assertEqual({error, not_found}, core_user:delete(<<"missing">>)). test_counts() -> {ok, U1} = core_user:create(<<"c1@test.local">>, <<"pass">>), {ok, _} = core_user:create(<<"c2@test.local">>, <<"pass">>), {ok, _} = core_user:update_status(U1#user.id, active, <<>>), ?assertEqual(2, core_user:count_users()), ?assertEqual(1, core_user:count_pending_users()), ByRole = core_user:count_users_by_role(), ?assertEqual(2, proplists:get_value(user, ByRole)), ByStatus = core_user:count_users_by_status(), ?assertEqual(1, proplists:get_value(pending, ByStatus)), ?assertEqual(1, proplists:get_value(active, ByStatus)), Now = calendar:universal_time(), {{Y, M, D}, _} = Now, From = {{Y, M, D}, {0, 0, 0}}, To = {{Y, M, D}, {23, 59, 59}}, ByDate = core_user:count_users_by_date(From, To), ?assert(lists:keyfind({Y, M, D}, 1, ByDate) =/= false), PendingByDate = core_user:count_pending_users_by_date(From, To), {_, PendingCount} = lists:keyfind({Y, M, D}, 1, PendingByDate), ?assertEqual(1, PendingCount). test_bot() -> Email = <<"bot@test.local">>, {ok, Bot} = core_user:create_bot(Email, <<"botpass">>), ?assertEqual(bot, Bot#user.role), ?assertEqual(active, Bot#user.status), ?assertEqual({error, email_exists}, core_user:create_bot(Email, <<"other">>)), ?assertEqual(ok, core_user:delete_bot(Bot#user.id)), ?assertEqual({error, not_found}, core_user:get_by_id(Bot#user.id)), ?assertEqual({error, not_found}, core_user:delete_bot(<<"missing">>)).