-module(logic_password_reset_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). -define(TABLES, [user, password_reset, auth_session]). setup() -> eh_test_support:start_mnesia(), eh_test_support:ensure_tables(?TABLES), catch ets:new(eventhub_counters, [named_table, public, set, {write_concurrency, true}]), ok. cleanup(_) -> catch ets:delete(eventhub_counters), eh_test_support:delete_tables(?TABLES), eh_test_support:stop_mnesia(), ok. logic_password_reset_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"request_reset – active sends token", {timeout, 60, fun test_request_active/0}}, {"request_reset – pending silent", {timeout, 60, fun test_request_pending/0}}, {"reset_password – happy path + revoke sessions", {timeout, 60, fun test_reset_ok/0}}, {"reset_password – expired", {timeout, 60, fun test_reset_expired/0}}, {"reset_password – short password", {timeout, 60, fun test_short_password/0}} ]}. seed_active() -> {ok, Hash} = logic_auth:hash_password(<<"OldPass123">>), User = eh_test_support:make_user(#{ id => <<"u_active_pw">>, email => <<"active-pw@test.local">>, status => active, password_hash => Hash }), ok = mnesia:dirty_write(User), User. seed_pending() -> User = eh_test_support:make_user(#{ id => <<"u_pending_pw">>, email => <<"pending-pw@test.local">>, status => pending }), ok = mnesia:dirty_write(User), User. test_request_active() -> User = seed_active(), ?assertEqual({ok, sent}, logic_password_reset:request_reset(User#user.email)), Matches = mnesia:dirty_match_object(#password_reset{user_id = User#user.id, _ = '_'}), ?assertMatch([_], Matches). test_request_pending() -> User = seed_pending(), ?assertEqual({ok, sent}, logic_password_reset:request_reset(User#user.email)), Matches = mnesia:dirty_match_object(#password_reset{user_id = User#user.id, _ = '_'}), ?assertEqual([], Matches). test_reset_ok() -> User = seed_active(), {ok, Session} = core_auth_session:create(User#user.id, user, <<"web">>), {ok, Token, _} = core_password_reset:create_token(User#user.id), ?assertEqual(ok, logic_password_reset:reset_password(Token, <<"NewPass456">>)), {ok, Updated} = core_user:get_by_id(User#user.id), ?assertMatch({ok, true}, logic_auth:verify_password(<<"NewPass456">>, Updated#user.password_hash)), {ok, Sess2} = core_auth_session:get(Session#auth_session.session_id), ?assertEqual(true, Sess2#auth_session.revoked), ?assertEqual({error, not_found}, core_password_reset:verify_token(Token)). test_reset_expired() -> User = seed_active(), Past = calendar:gregorian_seconds_to_datetime( calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - 10), Token = <<"expired_tok">>, mnesia:dirty_write(#password_reset{token = Token, user_id = User#user.id, expires_at = Past}), ?assertEqual({error, expired}, logic_password_reset:reset_password(Token, <<"NewPass456">>)). test_short_password() -> User = seed_active(), {ok, Token, _} = core_password_reset:create_token(User#user.id), ?assertEqual({error, invalid_password}, logic_password_reset:reset_password(Token, <<"short">>)).