feat(email): send booking reminders before event start.
Job process_reminders in subscription_worker; REMINDER_LEAD_HOURS window; CalenTIQ email + in-app event_reminder; idempotent reminder_sent. Refs EventHub/EventHubBack#70
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, calendar, event, booking, admin, subscription, calendar_specialist,
|
||||
recurrence_exception]).
|
||||
recurrence_exception, notification]).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
@@ -54,7 +54,10 @@ logic_booking_test_() ->
|
||||
{"Past pending excluded from booking requests", fun test_past_pending_excluded_from_requests/0},
|
||||
{"Past pending confirm denied", fun test_past_pending_confirm_denied/0},
|
||||
{"Future pending still confirmable", fun test_future_pending_still_confirmable/0},
|
||||
{"Process timeout expires past pending", fun test_process_timeout_expires_past/0}
|
||||
{"Process timeout expires past pending", fun test_process_timeout_expires_past/0},
|
||||
{"Reminder sends once within lead window", fun test_reminder_within_window/0},
|
||||
{"Reminder skipped outside lead window", fun test_reminder_outside_window/0},
|
||||
{"Reminder skipped for pending", fun test_reminder_skips_pending/0}
|
||||
]}.
|
||||
|
||||
%% Вспомогательные функции
|
||||
@@ -509,3 +512,89 @@ test_process_timeout_expires_past() ->
|
||||
ok = logic_booking:process_timeout_bookings(),
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(expired, Stored#booking.status).
|
||||
|
||||
hours_from_now(Hours) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
|
||||
+ Hours * 3600,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_reminder_within_window() ->
|
||||
application:set_env(eventhub, reminder_lead_hours, 24),
|
||||
Self = self(),
|
||||
application:set_env(eventhub, smtp_host, "smtp.test"),
|
||||
application:set_env(eventhub, smtp_deliver,
|
||||
fun(_From, To, Raw, _Opts) ->
|
||||
Self ! {smtp, To, Raw},
|
||||
{ok, mock}
|
||||
end),
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, auto),
|
||||
EventId = create_test_event_at(CalendarId, hours_from_now(2)),
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
?assertEqual(confirmed, Booking#booking.status),
|
||||
?assertEqual(false, Booking#booking.reminder_sent),
|
||||
ok = logic_booking:process_reminders(),
|
||||
receive
|
||||
{smtp, To, Raw} ->
|
||||
?assertEqual(<<ParticipantId/binary, "@test.com">>, To),
|
||||
?assert(binary:match(Raw, <<"CalenTIQ">>) =/= nomatch),
|
||||
?assert(binary:match(Raw, <<"/c/", CalendarId/binary, "/e/", EventId/binary>>)
|
||||
=/= nomatch)
|
||||
after 1000 ->
|
||||
error(timeout_reminder_email)
|
||||
end,
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(true, Stored#booking.reminder_sent),
|
||||
Notifs = core_notification:list_by_user(ParticipantId),
|
||||
?assert(lists:any(fun(#notification{type = event_reminder}) -> true; (_) -> false end,
|
||||
Notifs)),
|
||||
%% second pass: no duplicate email
|
||||
ok = logic_booking:process_reminders(),
|
||||
receive
|
||||
{smtp, _, _} -> error(duplicate_reminder)
|
||||
after 200 ->
|
||||
ok
|
||||
end,
|
||||
application:unset_env(eventhub, smtp_host),
|
||||
application:unset_env(eventhub, smtp_deliver),
|
||||
application:unset_env(eventhub, reminder_lead_hours).
|
||||
|
||||
test_reminder_outside_window() ->
|
||||
application:set_env(eventhub, reminder_lead_hours, 24),
|
||||
Self = self(),
|
||||
application:set_env(eventhub, smtp_host, "smtp.test"),
|
||||
application:set_env(eventhub, smtp_deliver,
|
||||
fun(_From, To, Raw, _Opts) ->
|
||||
Self ! {smtp, To, Raw},
|
||||
{ok, mock}
|
||||
end),
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, auto),
|
||||
EventId = create_test_event_at(CalendarId, hours_from_now(48)),
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
ok = logic_booking:process_reminders(),
|
||||
receive
|
||||
{smtp, _, _} -> error(unexpected_reminder)
|
||||
after 200 ->
|
||||
ok
|
||||
end,
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(false, Stored#booking.reminder_sent),
|
||||
application:unset_env(eventhub, smtp_host),
|
||||
application:unset_env(eventhub, smtp_deliver),
|
||||
application:unset_env(eventhub, reminder_lead_hours).
|
||||
|
||||
test_reminder_skips_pending() ->
|
||||
application:set_env(eventhub, reminder_lead_hours, 24),
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event_at(CalendarId, hours_from_now(2)),
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
?assertEqual(pending, Booking#booking.status),
|
||||
ok = logic_booking:process_reminders(),
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(false, Stored#booking.reminder_sent),
|
||||
application:unset_env(eventhub, reminder_lead_hours).
|
||||
|
||||
Reference in New Issue
Block a user