-module(infra_cleanup_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). -define(TABLES, [notification, auth_session, admin_audit]). setup() -> eh_test_support:start_mnesia(), eh_test_support:ensure_tables(?TABLES), ok. cleanup(_) -> eh_test_support:clear_tables(?TABLES), eh_test_support:delete_tables(?TABLES), eh_test_support:stop_mnesia(), ok. infra_cleanup_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"cutoff_datetime is in the past", fun test_cutoff_datetime/0}, {"keeps unread notifications", fun test_keeps_unread_notifications/0}, {"deletes old read notifications", fun test_deletes_old_read_notifications/0}, {"keeps active non-expired sessions", fun test_keeps_active_sessions/0}, {"deletes old revoked sessions", fun test_deletes_old_revoked_sessions/0}, {"deletes old admin_audit", fun test_deletes_old_admin_audit/0} ]}. test_cutoff_datetime() -> Cutoff = infra_cleanup:cutoff_datetime(30), NowSecs = calendar:datetime_to_gregorian_seconds(calendar:universal_time()), CutoffSecs = calendar:datetime_to_gregorian_seconds(Cutoff), ?assert(CutoffSecs < NowSecs), %% ~30 days (± a few seconds for test runtime) DiffDays = (NowSecs - CutoffSecs) / 86400, ?assert(DiffDays > 29.9), ?assert(DiffDays < 30.1). test_keeps_unread_notifications() -> Old = infra_cleanup:cutoff_datetime(60), write_notification(<<"n_unread">>, false, Old), write_notification(<<"n_read_recent">>, true, calendar:universal_time()), #{notifications := N} = infra_cleanup:run_once(), ?assertEqual(0, N), ?assertMatch([_], mnesia:dirty_read(notification, <<"n_unread">>)), ?assertMatch([_], mnesia:dirty_read(notification, <<"n_read_recent">>)). test_deletes_old_read_notifications() -> Old = infra_cleanup:cutoff_datetime(60), write_notification(<<"n_old_read">>, true, Old), write_notification(<<"n_unread">>, false, Old), #{notifications := N} = infra_cleanup:run_once(), ?assertEqual(1, N), ?assertEqual([], mnesia:dirty_read(notification, <<"n_old_read">>)), ?assertMatch([_], mnesia:dirty_read(notification, <<"n_unread">>)). test_keeps_active_sessions() -> Now = calendar:universal_time(), Future = days_offset(Now, 7), Recent = days_offset(Now, -1), write_auth_session(<<"s_active">>, false, Future, Recent), #{auth_sessions := N} = infra_cleanup:run_once(), ?assertEqual(0, N), ?assertMatch([_], mnesia:dirty_read(auth_session, <<"s_active">>)). test_deletes_old_revoked_sessions() -> Now = calendar:universal_time(), Old = days_offset(Now, -14), Future = days_offset(Now, 7), write_auth_session(<<"s_revoked_old">>, true, Future, Old), write_auth_session(<<"s_active">>, false, Future, Old), #{auth_sessions := N} = infra_cleanup:run_once(), ?assert(N >= 1), ?assertEqual([], mnesia:dirty_read(auth_session, <<"s_revoked_old">>)), %% Active but old created_at with future expiry must stay ?assertMatch([_], mnesia:dirty_read(auth_session, <<"s_active">>)). test_deletes_old_admin_audit() -> Now = calendar:universal_time(), Old = days_offset(Now, -120), write_admin_audit(<<"a_old">>, Old), write_admin_audit(<<"a_new">>, Now), #{admin_audit := N} = infra_cleanup:run_once(), ?assertEqual(1, N), ?assertEqual([], mnesia:dirty_read(admin_audit, <<"a_old">>)), ?assertMatch([_], mnesia:dirty_read(admin_audit, <<"a_new">>)). %%%=================================================================== %%% Helpers %%%=================================================================== write_notification(Id, IsRead, CreatedAt) -> mnesia:dirty_write(#notification{ id = Id, user_id = <<"u1">>, type = custom, title = <<"t">>, body = <<"b">>, is_read = IsRead, created_at = CreatedAt }). write_auth_session(Id, Revoked, ExpiresAt, CreatedAt) -> mnesia:dirty_write(#auth_session{ session_id = Id, family_id = <<"fam">>, subject_id = <<"subj">>, subject_type = user, client_type = <<"web">>, current_jti = <<"jti">>, expires_at = ExpiresAt, revoked = Revoked, created_at = CreatedAt, updated_at = CreatedAt }). write_admin_audit(Id, Timestamp) -> mnesia:dirty_write(#admin_audit{ id = Id, admin_id = <<"adm">>, email = <<"a@test.local">>, role = admin, action = <<"test">>, entity_type = <<"user">>, entity_id = <<"u1">>, timestamp = Timestamp, ip = <<"127.0.0.1">>, reason = <<>> }). days_offset(Datetime, Days) -> Secs = calendar:datetime_to_gregorian_seconds(Datetime), calendar:gregorian_seconds_to_datetime(Secs + Days * 86400).