From 5d7e12b72de90d139b91359406a4849680b993c0 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 12 Sep 2024 17:18:40 +0200 Subject: [PATCH] Move time zone logic into mailers --- app/mailers/admin_mailer.rb | 15 +++--- app/mailers/application_mailer.rb | 6 ++- app/mailers/notification_mailer.rb | 10 ++-- app/mailers/user_mailer.rb | 48 ++++++++++--------- .../notification_mailer/_status.html.haml | 2 +- .../user_mailer/appeal_approved.html.haml | 4 +- .../user_mailer/appeal_approved.text.erb | 2 +- .../user_mailer/appeal_rejected.html.haml | 4 +- .../user_mailer/appeal_rejected.text.erb | 2 +- app/views/user_mailer/failed_2fa.html.haml | 2 +- app/views/user_mailer/failed_2fa.text.erb | 2 +- .../user_mailer/suspicious_sign_in.html.haml | 2 +- .../user_mailer/suspicious_sign_in.text.erb | 2 +- config/application.rb | 3 +- config/initializers/time_zone.rb | 4 ++ spec/mailers/notification_mailer_spec.rb | 7 ++- spec/mailers/user_mailer_spec.rb | 32 +++++-------- spec/support/examples/mailers.rb | 27 +++++++++++ 18 files changed, 102 insertions(+), 72 deletions(-) create mode 100644 config/initializers/time_zone.rb diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index 8dd7b6e59f..7df546ef5f 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -14,7 +14,7 @@ class AdminMailer < ApplicationMailer def new_report(report) @report = report - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance, id: @report.id) end end @@ -22,7 +22,7 @@ class AdminMailer < ApplicationMailer def new_appeal(appeal) @appeal = appeal - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance, username: @appeal.account.username) end end @@ -30,7 +30,7 @@ class AdminMailer < ApplicationMailer def new_pending_account(user) @account = user.account - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance, username: @account.username) end end @@ -40,7 +40,7 @@ class AdminMailer < ApplicationMailer @tags = tags @statuses = statuses - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance) end end @@ -48,7 +48,7 @@ class AdminMailer < ApplicationMailer def new_software_updates @software_updates = SoftwareUpdate.all.to_a.sort_by(&:gem_version) - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance) end end @@ -60,13 +60,13 @@ class AdminMailer < ApplicationMailer headers['X-Priority'] = '1' headers['Importance'] = 'high' - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance) end end def auto_close_registrations - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(instance: @instance) end end @@ -75,6 +75,7 @@ class AdminMailer < ApplicationMailer def process_params @me = params[:recipient] + @user = @me.user end def set_instance diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 35f0b5fee1..ba3009cda7 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -11,8 +11,10 @@ class ApplicationMailer < ActionMailer::Base protected - def locale_for_account(account, &block) - I18n.with_locale(account.user_locale || I18n.default_locale, &block) + def with_user(user, &block) + I18n.with_locale(user.locale || I18n.default_locale) do + Time.use_zone(user.time_zone || ENV['DEFAULT_TIME_ZONE'] || 'UTC', &block) + end end def set_autoreply_headers! diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 4eb38ec340..a9050210be 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -17,7 +17,7 @@ class NotificationMailer < ApplicationMailer def mention return unless @user.functional? && @status.present? - locale_for_account(@me) do + with_user(@user) do thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @status.account.acct) end @@ -26,7 +26,7 @@ class NotificationMailer < ApplicationMailer def follow return unless @user.functional? - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(name: @account.acct) end end @@ -34,7 +34,7 @@ class NotificationMailer < ApplicationMailer def favourite return unless @user.functional? && @status.present? - locale_for_account(@me) do + with_user(@user) do thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end @@ -43,7 +43,7 @@ class NotificationMailer < ApplicationMailer def reblog return unless @user.functional? && @status.present? - locale_for_account(@me) do + with_user(@user) do thread_by_conversation(@status.conversation) mail subject: default_i18n_subject(name: @account.acct) end @@ -52,7 +52,7 @@ class NotificationMailer < ApplicationMailer def follow_request return unless @user.functional? - locale_for_account(@me) do + with_user(@user) do mail subject: default_i18n_subject(name: @account.acct) end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 5c9e5c96d9..e81fee7b9e 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -20,7 +20,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale) do + with_user(@resource) do mail to: @resource.unconfirmed_email.presence || @resource.email, subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance), template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions' @@ -33,7 +33,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -43,7 +43,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -53,7 +53,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -63,7 +63,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -73,7 +73,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -83,7 +83,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -93,7 +93,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -103,7 +103,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: default_devise_subject end end @@ -114,7 +114,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: I18n.t('devise.mailer.webauthn_credential.added.subject') end end @@ -125,7 +125,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale(use_current_locale: true)) do + with_user(@resource, use_current_locale: true) do mail subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject') end end @@ -141,7 +141,7 @@ class UserMailer < Devise::Mailer @has_active_relationships = @resource.account.active_relationships.exists? @has_statuses = @resource.account.statuses.exists? - I18n.with_locale(locale) do + with_user(@resource) do mail subject: default_i18n_subject end end @@ -152,7 +152,7 @@ class UserMailer < Devise::Mailer return unless @resource.active_for_authentication? - I18n.with_locale(locale) do + with_user(@resource) do mail subject: default_i18n_subject end end @@ -162,7 +162,7 @@ class UserMailer < Devise::Mailer @warning = warning @statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account]) - I18n.with_locale(locale) do + with_user(@resource) do mail subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}") end end @@ -171,7 +171,7 @@ class UserMailer < Devise::Mailer @resource = user @appeal = appeal - I18n.with_locale(locale) do + with_user(@resource) do mail subject: default_i18n_subject(date: l(@appeal.created_at)) end end @@ -180,7 +180,7 @@ class UserMailer < Devise::Mailer @resource = user @appeal = appeal - I18n.with_locale(locale) do + with_user(@resource) do mail subject: default_i18n_subject(date: l(@appeal.created_at)) end end @@ -190,9 +190,10 @@ class UserMailer < Devise::Mailer @remote_ip = remote_ip @user_agent = user_agent @detection = Browser.new(user_agent) - @timestamp = timestamp.to_time.utc - I18n.with_locale(locale) do + with_user(@resource) do + @timestamp = timestamp.in_time_zone + mail subject: default_i18n_subject end end @@ -202,9 +203,10 @@ class UserMailer < Devise::Mailer @remote_ip = remote_ip @user_agent = user_agent @detection = Browser.new(user_agent) - @timestamp = timestamp.to_time.utc - I18n.with_locale(locale) do + with_user(@resource) do + @timestamp = timestamp.in_time_zone + mail subject: default_i18n_subject end end @@ -219,7 +221,9 @@ class UserMailer < Devise::Mailer @instance = Rails.configuration.x.local_domain end - def locale(use_current_locale: false) - @resource.locale.presence || (use_current_locale && I18n.locale) || I18n.default_locale + def with_user(user, use_current_locale: false, &block) + I18n.with_locale(user.locale || (use_current_locale && I18n.locale) || I18n.default_locale) do + Time.use_zone(user.time_zone || ENV['DEFAULT_TIME_ZONE'] || 'UTC', &block) + end end end diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index bf38dc9aa2..9def9b22aa 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -28,4 +28,4 @@ = link_to a.remote_url, a.remote_url %p.email-status-footer - = link_to l(status.created_at.in_time_zone(time_zone.presence), format: :with_time_zone), web_url("@#{status.account.pretty_acct}/#{status.id}") + = link_to l(status.created_at, format: :with_time_zone), web_url("@#{status.account.pretty_acct}/#{status.id}") diff --git a/app/views/user_mailer/appeal_approved.html.haml b/app/views/user_mailer/appeal_approved.html.haml index 54e9a94a54..e4fdd586a3 100644 --- a/app/views/user_mailer/appeal_approved.html.haml +++ b/app/views/user_mailer/appeal_approved.html.haml @@ -7,6 +7,6 @@ %tr %td.email-inner-card-td.email-prose %p= t 'user_mailer.appeal_approved.explanation', - appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone), - strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) + appeal_date: l(@appeal.created_at, format: :with_time_zone), + strike_date: l(@appeal.strike.created_at, format: :with_time_zone) = render 'application/mailer/button', text: t('user_mailer.appeal_approved.action'), url: root_url diff --git a/app/views/user_mailer/appeal_approved.text.erb b/app/views/user_mailer/appeal_approved.text.erb index 48fc4b4f75..501e839152 100644 --- a/app/views/user_mailer/appeal_approved.text.erb +++ b/app/views/user_mailer/appeal_approved.text.erb @@ -2,6 +2,6 @@ === -<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) %> +<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at, format: :with_time_zone), strike_date: l(@appeal.strike.created_at, format: :with_time_zone) %> => <%= root_url %> diff --git a/app/views/user_mailer/appeal_rejected.html.haml b/app/views/user_mailer/appeal_rejected.html.haml index b493712b05..764358a799 100644 --- a/app/views/user_mailer/appeal_rejected.html.haml +++ b/app/views/user_mailer/appeal_rejected.html.haml @@ -7,6 +7,6 @@ %tr %td.email-inner-card-td.email-prose %p= t 'user_mailer.appeal_rejected.explanation', - appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone), - strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) + appeal_date: l(@appeal.created_at, format: :with_time_zone), + strike_date: l(@appeal.strike.created_at, format: :with_time_zone) = render 'application/mailer/button', text: t('user_mailer.appeal_approved.action'), url: root_url diff --git a/app/views/user_mailer/appeal_rejected.text.erb b/app/views/user_mailer/appeal_rejected.text.erb index 8b8408e91a..a763a35227 100644 --- a/app/views/user_mailer/appeal_rejected.text.erb +++ b/app/views/user_mailer/appeal_rejected.text.erb @@ -2,6 +2,6 @@ === -<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) %> +<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at, format: :with_time_zone), strike_date: l(@appeal.strike.created_at, format: :with_time_zone) %> => <%= root_url %> diff --git a/app/views/user_mailer/failed_2fa.html.haml b/app/views/user_mailer/failed_2fa.html.haml index e1da35ce06..62b124649e 100644 --- a/app/views/user_mailer/failed_2fa.html.haml +++ b/app/views/user_mailer/failed_2fa.html.haml @@ -18,7 +18,7 @@ platform: t("sessions.platforms.#{@detection.platform.id}", default: @detection.platform.id.to_s) %br/ %strong #{t('sessions.date')}: - = l(@timestamp.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) + = l(@timestamp, format: :with_time_zone) = render 'application/mailer/button', text: t('settings.account_settings'), url: edit_user_registration_url %p= t 'user_mailer.failed_2fa.further_actions_html', action: link_to(t('user_mailer.suspicious_sign_in.change_password'), edit_user_registration_url) diff --git a/app/views/user_mailer/failed_2fa.text.erb b/app/views/user_mailer/failed_2fa.text.erb index c1dbf7d929..dcf450f08d 100644 --- a/app/views/user_mailer/failed_2fa.text.erb +++ b/app/views/user_mailer/failed_2fa.text.erb @@ -8,7 +8,7 @@ <%= t('sessions.ip') %>: <%= @remote_ip %> <%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %> -<%= l(@timestamp.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) %> +<%= l(@timestamp, format: :with_time_zone) %> <%= t 'user_mailer.failed_2fa.further_actions_html', action: t('user_mailer.suspicious_sign_in.change_password') %> diff --git a/app/views/user_mailer/suspicious_sign_in.html.haml b/app/views/user_mailer/suspicious_sign_in.html.haml index deee7a1ce1..c3769b46db 100644 --- a/app/views/user_mailer/suspicious_sign_in.html.haml +++ b/app/views/user_mailer/suspicious_sign_in.html.haml @@ -18,7 +18,7 @@ platform: t("sessions.platforms.#{@detection.platform.id}", default: @detection.platform.id.to_s) %br/ %strong #{t('sessions.date')}: - = l(@timestamp.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) + = l(@timestamp, format: :with_time_zone) = render 'application/mailer/button', text: t('settings.account_settings'), url: edit_user_registration_url %p= t 'user_mailer.suspicious_sign_in.further_actions_html', action: link_to(t('user_mailer.suspicious_sign_in.change_password'), edit_user_registration_url) diff --git a/app/views/user_mailer/suspicious_sign_in.text.erb b/app/views/user_mailer/suspicious_sign_in.text.erb index 0aa4d227d1..c06936c4e6 100644 --- a/app/views/user_mailer/suspicious_sign_in.text.erb +++ b/app/views/user_mailer/suspicious_sign_in.text.erb @@ -8,7 +8,7 @@ <%= t('sessions.ip') %>: <%= @remote_ip %> <%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %> -<%= l(@timestamp.in_time_zone(@resource.time_zone.presence), format: :with_time_zone) %> +<%= l(@timestamp, format: :with_time_zone) %> <%= t 'user_mailer.suspicious_sign_in.further_actions_html', action: t('user_mailer.suspicious_sign_in.change_password') %> diff --git a/config/application.rb b/config/application.rb index 41d33198cc..0013c78858 100644 --- a/config/application.rb +++ b/config/application.rb @@ -78,13 +78,12 @@ module Mastodon # These settings can be overridden in specific environments using the files # in config/environments, which are processed later. # + # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") # config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') # config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] - config.time_zone = ENV['DEFAULT_TIME_ZONE'] || 'UTC' - config.active_job.queue_adapter = :sidekiq config.action_mailer.deliver_later_queue_name = 'mailers' diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb new file mode 100644 index 0000000000..5a96c1fd21 --- /dev/null +++ b/config/initializers/time_zone.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Raise if invalid zone is specified +Time.find_zone!(ENV.fetch('DEFAULT_TIME_ZONE', nil)) diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index eab196166d..6569bc10ce 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -5,8 +5,8 @@ require 'rails_helper' RSpec.describe NotificationMailer do let(:receiver) { Fabricate(:user, account_attributes: { username: 'alice' }) } let(:sender) { Fabricate(:account, username: 'bob') } - let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') } - let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') } + let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status', created_at: '2024-01-01 12:01Z') } + let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status', created_at: '2024-01-01 13:02Z') } shared_examples 'standard headers' do |type| it 'renders the email' do @@ -38,6 +38,7 @@ RSpec.describe NotificationMailer do let(:mail) { prepared_mailer_for(receiver.account).mention } include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob' + include_examples 'timestamp in time zone', '2024-01-01 12:01Z'.to_datetime include_examples 'standard headers', 'mention' include_examples 'thread headers' @@ -72,6 +73,7 @@ RSpec.describe NotificationMailer do let(:mail) { prepared_mailer_for(own_status.account).favourite } include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob' + include_examples 'timestamp in time zone', '2024-01-01 13:02Z'.to_datetime include_examples 'standard headers', 'favourite' include_examples 'thread headers' @@ -90,6 +92,7 @@ RSpec.describe NotificationMailer do let(:mail) { prepared_mailer_for(own_status.account).reblog } include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob' + include_examples 'timestamp in time zone', '2024-01-01 13:02Z'.to_datetime include_examples 'standard headers', 'reblog' include_examples 'thread headers' diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 0257465817..c2b2cd91cb 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -3,14 +3,12 @@ require 'rails_helper' RSpec.describe UserMailer do - let(:receiver) { Fabricate(:user) } + let(:receiver) { Fabricate(:user, locale: nil) } describe '#confirmation_instructions' do let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } it 'renders confirmation instructions' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('devise.mailer.confirmation_instructions.title'))) @@ -45,8 +43,6 @@ RSpec.describe UserMailer do let(:mail) { described_class.reset_password_instructions(receiver, 'spec') } it 'renders reset password instructions' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('devise.mailer.reset_password_instructions.title'))) @@ -61,8 +57,6 @@ RSpec.describe UserMailer do let(:mail) { described_class.password_change(receiver) } it 'renders password change notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('devise.mailer.password_change.title'))) @@ -76,8 +70,6 @@ RSpec.describe UserMailer do let(:mail) { described_class.email_changed(receiver) } it 'renders email change notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('devise.mailer.email_changed.title'))) @@ -92,8 +84,6 @@ RSpec.describe UserMailer do let(:mail) { described_class.warning(receiver, strike) } it 'renders warning notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct))) @@ -106,8 +96,6 @@ RSpec.describe UserMailer do let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) } it 'renders webauthn credential deleted notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('devise.mailer.webauthn_credential.deleted.title'))) @@ -120,12 +108,10 @@ RSpec.describe UserMailer do describe '#suspicious_sign_in' do let(:ip) { '192.168.0.1' } let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } - let(:timestamp) { Time.now.utc } + let(:timestamp) { '2024-01-01 12:34Z'.to_datetime } let(:mail) { described_class.suspicious_sign_in(receiver, ip, agent, timestamp) } it 'renders suspicious sign in notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('user_mailer.suspicious_sign_in.explanation'))) @@ -133,17 +119,16 @@ RSpec.describe UserMailer do include_examples 'localized subject', 'user_mailer.suspicious_sign_in.subject' + include_examples 'timestamp in time zone', '2024-01-01 12:34Z'.to_datetime end describe '#failed_2fa' do let(:ip) { '192.168.0.1' } let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } - let(:timestamp) { Time.now.utc } + let(:timestamp) { '2024-01-01 12:34Z'.to_datetime } let(:mail) { described_class.failed_2fa(receiver, ip, agent, timestamp) } it 'renders failed 2FA notification' do - receiver.update!(locale: nil) - expect(mail) .to be_present .and(have_body_text(I18n.t('user_mailer.failed_2fa.explanation'))) @@ -151,10 +136,11 @@ RSpec.describe UserMailer do include_examples 'localized subject', 'user_mailer.failed_2fa.subject' + include_examples 'timestamp in time zone', '2024-01-01 12:34Z'.to_datetime end describe '#appeal_approved' do - let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) } + let(:appeal) { Fabricate(:appeal, account: receiver.account, created_at: '2024-01-01 12:34Z', approved_at: Time.now.utc) } let(:mail) { described_class.appeal_approved(receiver, appeal) } it 'renders appeal_approved notification' do @@ -163,10 +149,12 @@ RSpec.describe UserMailer do .and(have_subject(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)))) .and(have_body_text(I18n.t('user_mailer.appeal_approved.title'))) end + + include_examples 'timestamp in time zone', '2024-01-01 12:34Z'.to_datetime end describe '#appeal_rejected' do - let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) } + let(:appeal) { Fabricate(:appeal, account: receiver.account, created_at: '2024-01-01 12:34Z', rejected_at: Time.now.utc) } let(:mail) { described_class.appeal_rejected(receiver, appeal) } it 'renders appeal_rejected notification' do @@ -175,6 +163,8 @@ RSpec.describe UserMailer do .and(have_subject(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)))) .and(have_body_text(I18n.t('user_mailer.appeal_rejected.title'))) end + + include_examples 'timestamp in time zone', '2024-01-01 12:34Z'.to_datetime end describe '#two_factor_enabled' do diff --git a/spec/support/examples/mailers.rb b/spec/support/examples/mailers.rb index a8469f1964..f4e8bc463c 100644 --- a/spec/support/examples/mailers.rb +++ b/spec/support/examples/mailers.rb @@ -12,3 +12,30 @@ RSpec.shared_examples 'localized subject' do |*args, **kwrest| expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: I18n.default_locale)) end end + +RSpec.shared_examples 'timestamp in time zone' do |at| + context 'when default time zone is defined' do + around do |example| + ClimateControl.modify DEFAULT_TIME_ZONE: 'Europe/Athens' do + example.run + end + end + + it 'displays timestamp in time zone of the receiver' do + time_zone = 'Europe/Berlin' + receiver.update!(time_zone: time_zone) + expect(mail) + .to have_body_text(at.in_time_zone(time_zone).strftime(I18n.t('time.formats.with_time_zone'))) + end + + it 'displays timestamp in default time zone if the time zone of the receiver is unavailable' do + receiver.update!(time_zone: nil) + expect(mail).to have_body_text(at.in_time_zone('Europe/Athens').strftime(I18n.t('time.formats.with_time_zone'))) + end + end + + it 'formats timestamp in UTC' do + receiver.update!(time_zone: nil) + expect(mail).to have_body_text(at.in_time_zone('UTC').strftime(I18n.t('time.formats.with_time_zone'))) + end +end