-module(logic_email_tests). -include_lib("eunit/include/eunit.hrl"). logic_email_test_() -> {foreach, fun setup/0, fun cleanup/1, [ {"log-only without host", fun test_log_only/0}, {"mock deliver gets CalenTIQ + verify url", fun test_verify_deliver/0}, {"invite and reset urls", fun test_invite_reset/0}, {"deliver error still ok", fun test_deliver_error/0}, {"html template placeholders filled", fun test_template_fill/0}, {"booking reminder deliver", fun test_reminder_deliver/0}, {"http_api mock deliver", fun test_http_api_deliver/0}, {"http_api error still ok", fun test_http_api_error/0} ]}. setup() -> application:unset_env(eventhub, smtp_host), application:unset_env(eventhub, smtp_deliver), application:unset_env(eventhub, email_transport), application:unset_env(eventhub, email_api_key), application:unset_env(eventhub, email_http_deliver), ok. cleanup(_) -> application:unset_env(eventhub, smtp_host), application:unset_env(eventhub, smtp_deliver), application:unset_env(eventhub, email_transport), application:unset_env(eventhub, email_api_key), application:unset_env(eventhub, email_http_deliver), ok. test_log_only() -> application:unset_env(eventhub, smtp_host), application:set_env(eventhub, email_transport, log), ?assertEqual(ok, logic_email:send_verification_email(<<"a@b.c">>, <<"tok">>)). test_verify_deliver() -> Self = self(), application:set_env(eventhub, email_transport, smtp), application:set_env(eventhub, smtp_host, "smtp.test"), application:set_env(eventhub, smtp_deliver, fun(From, To, Raw, Opts) -> Self ! {smtp, From, To, Raw, Opts}, {ok, mock} end), ?assertEqual(ok, logic_email:send_verification_email(<<"user@ex.com">>, <<"abc123">>)), receive {smtp, From, To, Raw, Opts} -> ?assertEqual(<<"noreply@calentiq.com">>, From), ?assertEqual(<<"user@ex.com">>, To), ?assertEqual("smtp.test", proplists:get_value(relay, Opts)), ?assert(binary:match(Raw, <<"multipart/alternative">>) =/= nomatch), ?assert(binary:match(Raw, <<"text/html">>) =/= nomatch), ?assert(binary:match(Raw, <<"CalenTIQ">>) =/= nomatch), ?assert(binary:match(Raw, <<"/verify?token=abc123">>) =/= nomatch), ?assert(binary:match(Raw, <<"#FF6A3D">>) =/= nomatch) after 1000 -> error(timeout) end. test_invite_reset() -> Self = self(), application:set_env(eventhub, email_transport, smtp), application:set_env(eventhub, smtp_host, <<"smtp.test">>), application:set_env(eventhub, smtp_deliver, fun(_From, To, Raw, _Opts) -> Self ! {smtp, To, Raw}, ok end), ok = logic_email:send_specialist_invite(<<"i@ex.com">>, <<"invtok">>), receive {smtp, <<"i@ex.com">>, Raw1} -> ?assert(binary:match(Raw1, <<"/invites?token=invtok">>) =/= nomatch) after 1000 -> error(timeout) end, ok = logic_email:send_password_reset(<<"r@ex.com">>, <<"rst">>), receive {smtp, <<"r@ex.com">>, Raw2} -> ?assert(binary:match(Raw2, <<"/reset-password?token=rst">>) =/= nomatch) after 1000 -> error(timeout) end. test_deliver_error() -> application:set_env(eventhub, email_transport, smtp), application:set_env(eventhub, smtp_host, "smtp.test"), application:set_env(eventhub, smtp_deliver, fun(_, _, _, _) -> {error, boom} end), ?assertEqual(ok, logic_email:send_verification_email(<<"a@b.c">>, <<"t">>)), application:set_env(eventhub, smtp_deliver, fun(_, _, _, _) -> error(crash) end), ?assertEqual(ok, logic_email:send_password_reset(<<"a@b.c">>, <<"t">>)). test_template_fill() -> {Sub, Plain, Html} = logic_email_templates:render(verify, <<"https://x/verify?token=z">>), ?assert(binary:match(Sub, <<"CalenTIQ">>) =/= nomatch), %% OTP truncates Cyrillic in <<"...">> without /utf8 — guard real UTF-8 text ?assert(binary:match(Sub, <<"подтвердите"/utf8>>) =/= nomatch), ?assert(binary:match(Plain, <<"Подтвердите"/utf8>>) =/= nomatch), ?assert(binary:match(Plain, <<"https://x/verify?token=z">>) =/= nomatch), ?assert(binary:match(Html, <<"{{">>) =:= nomatch), ?assert(binary:match(Html, <<"https://x/verify?token=z">>) =/= nomatch), ?assert(binary:match(Html, <<"Подтвердите"/utf8>>) =/= nomatch). test_reminder_deliver() -> Self = self(), application:set_env(eventhub, email_transport, smtp), 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), Start = {{2026, 8, 15}, {10, 30, 0}}, ?assertEqual(ok, logic_email:send_booking_reminder( <<"u@ex.com">>, <<"Yoga hour">>, Start, <<"/c/cal1/e/ev1">>)), receive {smtp, <<"u@ex.com">>, Raw} -> ?assert(binary:match(Raw, <<"/c/cal1/e/ev1">>) =/= nomatch), ?assert(binary:match(Raw, <<"Yoga hour">>) =/= nomatch), ?assert(binary:match(Raw, <<"2026-08-15 10:30">>) =/= nomatch) after 1000 -> error(timeout) end. test_http_api_deliver() -> Self = self(), application:set_env(eventhub, email_transport, http_api), application:set_env(eventhub, email_api_key, <<"test-key">>), application:set_env(eventhub, email_http_deliver, fun(Key, From, To, Subject, Plain, Html) -> Self ! {http, Key, From, To, Subject, Plain, Html}, ok end), ?assertEqual(ok, logic_email:send_verification_email(<<"user@ex.com">>, <<"abc123">>)), receive {http, Key, From, To, Subject, Plain, Html} -> ?assertEqual(<<"test-key">>, Key), ?assertEqual(<<"noreply@calentiq.com">>, From), ?assertEqual(<<"user@ex.com">>, To), ?assert(binary:match(Subject, <<"CalenTIQ">>) =/= nomatch), ?assert(binary:match(Plain, <<"/verify?token=abc123">>) =/= nomatch), ?assert(binary:match(Html, <<"/verify?token=abc123">>) =/= nomatch) after 1000 -> error(timeout) end. test_http_api_error() -> application:set_env(eventhub, email_transport, http_api), application:set_env(eventhub, email_api_key, <<"k">>), application:set_env(eventhub, email_http_deliver, fun(_, _, _, _, _, _) -> {error, boom} end), ?assertEqual(ok, logic_email:send_verification_email(<<"a@b.c">>, <<"t">>)), application:set_env(eventhub, email_http_deliver, fun(_, _, _, _, _, _) -> error(crash) end), ?assertEqual(ok, logic_email:send_password_reset(<<"a@b.c">>, <<"t">>)).