Feature/1899 send daily email summaries (#9430)
* email digests * use time for notification digest setting * safeguard against empty digestpull/9456/head
parent
2ee3a06c63
commit
75bb809ad6
@ -0,0 +1,48 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module MailDigestHelper |
||||
def digest_timespan_text |
||||
end_time = Time.parse(Setting.notification_email_digest_time) |
||||
|
||||
I18n.t(:"mail.digests.time_frame", |
||||
start: format_time(end_time - 1.day), |
||||
end: format_time(end_time)) |
||||
end |
||||
|
||||
def digest_notification_timestamp_text(notification, html: true) |
||||
journal = notification.journal |
||||
user = html ? link_to_user(journal.user, only_path: false) : journal.user.name |
||||
|
||||
raw(I18n.t(:"mail.digests.work_packages.#{journal.initial? ? 'created_at' : 'updated_at'}", |
||||
user: user, |
||||
timestamp: format_time(journal.created_at))) |
||||
end |
||||
end |
@ -0,0 +1,74 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
# Sends digest mails. Digest mails contain the combined information of multiple updates to |
||||
# resources. |
||||
# Currently, this is limited to work packages |
||||
|
||||
class DigestMailer < ApplicationMailer |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
include OpenProject::TextFormatting |
||||
include Redmine::I18n |
||||
|
||||
helper :mail_digest |
||||
|
||||
class << self |
||||
def generate_message_id(_, user) |
||||
hash = "openproject.digest-#{user.id}-#{Time.current.strftime('%Y%m%d%H%M%S')}" |
||||
host = Setting.mail_from.to_s.gsub(%r{\A.*@}, '') |
||||
host = "#{::Socket.gethostname}.openproject" if host.empty? |
||||
"#{hash}@#{host}" |
||||
end |
||||
end |
||||
|
||||
def work_packages(recipient_id, notification_ids) |
||||
recipient = User.find(recipient_id) |
||||
|
||||
open_project_headers User: recipient.name |
||||
message_id nil, recipient |
||||
|
||||
@notifications_by_project = load_notifications(notification_ids) |
||||
.group_by(&:project) |
||||
.transform_values { |of_project| of_project.group_by(&:resource) } |
||||
|
||||
mail to: recipient.mail, |
||||
subject: I18n.t('mail.digests.work_packages.subject', |
||||
date: format_time_as_date(Time.current), |
||||
number: notification_ids.count) |
||||
end |
||||
|
||||
protected |
||||
|
||||
def load_notifications(notification_ids) |
||||
Notification |
||||
.where(id: notification_ids) |
||||
.includes(:project, :resource, journal: %i[data attachable_journals customizable_journals]) |
||||
end |
||||
end |
@ -0,0 +1,45 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Notifications::Scopes |
||||
module MailDigestBefore |
||||
extend ActiveSupport::Concern |
||||
|
||||
class_methods do |
||||
# Return notifications of the user for which mail digest is to be sent and that is created before |
||||
# the specified time. |
||||
def mail_digest_before(recipient:, time:) |
||||
where(Notification.arel_table[:created_at].lteq(time)) |
||||
.where(recipient: recipient) |
||||
.where(read_mail_digest: false) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,56 @@ |
||||
<div style="color: #777; font-weight: bold"> |
||||
<%= digest_timespan_text %> |
||||
</div> |
||||
|
||||
<% @notifications_by_project.each do |project, notifications_by_work_package| %> |
||||
<section style="margin-bottom: 3em; margin-top: 5em"> |
||||
<h1 style="font-size: 2em; margin-bottom: 1.5em"><%= link_to_project(project, only_path: false) %></h1> |
||||
|
||||
<% notifications_by_work_package.each do |work_package, notifications| %> |
||||
<section style="margin-bottom: 3em;"> |
||||
<h2 style="margin-bottom: 1em; font-size: 1.5em;"><%= link_to_work_package work_package, only_path: false %></h2> |
||||
|
||||
<% notifications.sort_by(&:created_at).each do |notification| %> |
||||
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0"> |
||||
<tr> |
||||
<td width="20px"></td> |
||||
<td style="font-weight: normal; font-size: 1.1em;"> |
||||
<%= digest_notification_timestamp_text(notification) %> |
||||
</td> |
||||
<td style="text-align: right"> |
||||
<%= I18n.t(:"mail.digests.work_packages.reason.#{notification.reason_mail_digest}") %> |
||||
</td> |
||||
<td width="20px"></td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<% journal = notification.journal %> |
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0"> |
||||
<tr> |
||||
<td width="20px"></td> |
||||
<td> |
||||
<%= format_text(journal.notes, |
||||
only_path: false, |
||||
object: notification.resource, |
||||
project: notification.project) %> |
||||
|
||||
<% if journal.notes.present? && journal.details.any? %> |
||||
<div style="margin-bottom: 2em"></div> |
||||
<% end %> |
||||
|
||||
<ul> |
||||
<% journal.details.each do |detail| %> |
||||
<li><%= journal.render_detail(detail, only_path: false) %></li> |
||||
<% end %> |
||||
</ul> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<div style="margin-bottom: 3em"></div> |
||||
<% end %> |
||||
</section> |
||||
<% end %> |
||||
</section> |
||||
<% end %> |
@ -0,0 +1,36 @@ |
||||
<%= digest_timespan_text %> |
||||
|
||||
<% @notifications_by_project.each do |project, notifications_by_work_package| %> |
||||
|
||||
<%= "=" * (project.name.length + 4) %> |
||||
= <%= project.name %> = |
||||
<%= "=" * (project.name.length + 4) %> |
||||
|
||||
<% notifications_by_work_package.each do |work_package, notifications| %> |
||||
|
||||
<%= "*" * (work_package.to_s.length + 4) %> |
||||
* <%= work_package.to_s %> * |
||||
<%= "*" * (work_package.to_s.length + 4) %> |
||||
|
||||
<% notifications.sort_by(&:created_at).each do |notification| %> |
||||
|
||||
<%= "-" * 20 %> |
||||
|
||||
<%= digest_notification_timestamp_text(notification, html: false) %> (<%= I18n.t('mail.digests.work_packages.reason.prefix', |
||||
reason: I18n.t(:"mail.digests.work_packages.reason.#{notification.reason_mail_digest}")) %>) |
||||
|
||||
<% journal = notification.journal %> |
||||
<% if journal.notes.present? %> |
||||
<%= journal.notes %> |
||||
|
||||
<% end %> |
||||
<% journal.details.each do |detail| %> |
||||
* <%= journal.render_detail(detail, only_path: false, no_html: true) %> |
||||
<% end %> |
||||
<% end %> |
||||
|
||||
<%= "-" * 20 %> |
||||
|
||||
|
||||
<% end %> |
||||
<% end %> |
@ -0,0 +1,83 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class Mails::DigestJob < Mails::DeliverJob |
||||
class << self |
||||
def execution_time(user) |
||||
zone = (user.time_zone || ActiveSupport::TimeZone.new('UTC')) |
||||
|
||||
zone.parse(Setting.notification_email_digest_time) + 1.day |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def render_mail |
||||
# Have to cast to array since the update in the subsequent block |
||||
# will result in the notification to not be found via the .mail_digest_before scope. |
||||
notification_ids = Notification.mail_digest_before(recipient: recipient, time: Time.current).pluck(:id) |
||||
|
||||
return nil if notification_ids.empty? |
||||
|
||||
with_marked_notifications(notification_ids) do |
||||
DigestMailer |
||||
.work_packages(recipient.id, notification_ids) |
||||
end |
||||
end |
||||
|
||||
# Running the digest job will take some time to complete. |
||||
# Within this timeframe, new notifications might come in. Upon notification creation |
||||
# a job is scheduled unless there is no prior digest notification that is not yet read (read_mail_digest: true). |
||||
# If we were to only set the read_mail_digest state at the end of the mail rendering an edge case of the following |
||||
# would lead to digest not being sent or at least sent unduly late: |
||||
# * Job starts and fetches the notifications for rendering. We need to fetch all notifications to be rendered to |
||||
# order them as desired. |
||||
# * Notification is created. Because there are unhandled digest notifications no job is scheduled. |
||||
# * The above can happen repeatedly. |
||||
# * Job ends. |
||||
# * No new notification is generated. |
||||
# |
||||
# A new job would then only be scheduled upon the creation of a new digest notification which (as unlikely as that is) |
||||
# might only happen after some days have gone by. |
||||
# |
||||
# Because we mark the notifications as read even though they in fact aren't, we do it in a transaction |
||||
# so that the change is rolled back in case of an error. |
||||
def with_marked_notifications(notification_ids) |
||||
Notification.transaction do |
||||
mark_notifications_read(notification_ids) |
||||
|
||||
yield |
||||
end |
||||
end |
||||
|
||||
def mark_notifications_read(notification_ids) |
||||
Notification.where(id: notification_ids).update_all(read_mail_digest: true, updated_at: Time.current) |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Mails::WithSender |
||||
def perform(recipient_id, sender_id) |
||||
self.sender_id = sender_id |
||||
|
||||
super(recipient_id) |
||||
end |
||||
|
||||
private |
||||
|
||||
attr_accessor :sender_id |
||||
|
||||
def sender |
||||
@sender ||= if sender_id.is_a?(User) |
||||
sender_id |
||||
else |
||||
User.find_by(id: sender_id) || DeletedUser.first |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,43 @@ |
||||
class AddDigestSetting < ActiveRecord::Migration[6.1] |
||||
def up |
||||
insert_default_digest_channel |
||||
end |
||||
|
||||
def down |
||||
remove_digest_channels |
||||
end |
||||
|
||||
def insert_default_digest_channel |
||||
execute <<~SQL |
||||
INSERT INTO |
||||
notification_settings |
||||
(user_id, |
||||
channel, |
||||
involved, |
||||
mentioned, |
||||
watched) |
||||
SELECT |
||||
id, |
||||
2, |
||||
true, |
||||
true, |
||||
true |
||||
FROM |
||||
users |
||||
WHERE |
||||
type = 'User' |
||||
SQL |
||||
end |
||||
|
||||
# Removes all digest channels. Includes non default channels as those might |
||||
# also have been added not by the migration but in the cause of the functionality |
||||
# the migration was added for. |
||||
def remove_digest_channels |
||||
execute <<~SQL |
||||
DELETE FROM |
||||
notification_settings |
||||
WHERE |
||||
channel = 2 |
||||
SQL |
||||
end |
||||
end |
@ -0,0 +1,12 @@ |
||||
class AddDigestToNotification < ActiveRecord::Migration[6.1] |
||||
def change |
||||
change_table :notifications, bulk: true do |t| |
||||
t.column :read_mail_digest, :boolean, default: false, index: true |
||||
t.column :reason_mail, :integer, limit: 1 |
||||
t.column :reason_mail_digest, :integer, limit: 1 |
||||
end |
||||
|
||||
rename_column :notifications, :reason, :reason_ian |
||||
rename_column :notifications, :read_email, :read_mail |
||||
end |
||||
end |
@ -0,0 +1,109 @@ |
||||
require 'spec_helper' |
||||
require 'support/pages/my/notifications' |
||||
|
||||
describe "Digest email", type: :feature, js: true do |
||||
let!(:project) { FactoryBot.create :project, members: { current_user => role } } |
||||
let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } } |
||||
let(:notification_settings_page) { Pages::My::Notifications.new(current_user) } |
||||
let(:role) { FactoryBot.create(:role, permissions: %i[view_work_packages]) } |
||||
let(:other_user) { FactoryBot.create(:user) } |
||||
let(:work_package) { FactoryBot.create(:work_package, project: project) } |
||||
let(:watched_work_package) { FactoryBot.create(:work_package, project: project, watcher_users: [current_user]) } |
||||
let(:involved_work_package) { FactoryBot.create(:work_package, project: project, assigned_to: current_user) } |
||||
|
||||
current_user do |
||||
FactoryBot.create :user, |
||||
notification_settings: [ |
||||
FactoryBot.build(:mail_notification_setting, |
||||
involved: false, |
||||
watched: false, |
||||
mentioned: false, |
||||
all: false), |
||||
FactoryBot.build(:in_app_notification_setting, |
||||
involved: false, |
||||
watched: false, |
||||
mentioned: false, |
||||
all: false), |
||||
FactoryBot.build(:mail_digest_notification_setting, |
||||
involved: true, |
||||
watched: true, |
||||
mentioned: true, |
||||
all: false) |
||||
] |
||||
end |
||||
|
||||
before do |
||||
watched_work_package |
||||
work_package |
||||
involved_work_package |
||||
|
||||
ActiveJob::Base.queue_adapter.enqueued_jobs.clear |
||||
end |
||||
|
||||
it 'sends a digest mail based on the configuration', with_settings: { journal_aggregation_time_minutes: 0 } do |
||||
# Configure the digest |
||||
notification_settings_page.visit! |
||||
|
||||
notification_settings_page.expect_setting channel: :mail_digest, |
||||
project: nil, |
||||
involved: true, |
||||
mentioned: true, |
||||
watched: true, |
||||
all: false |
||||
|
||||
notification_settings_page.configure_channel :mail_digest, |
||||
project: nil, |
||||
involved: false, |
||||
mentioned: true, |
||||
watched: true, |
||||
all: false |
||||
|
||||
notification_settings_page.add_row(mute_project) |
||||
|
||||
notification_settings_page.configure_channel :mail_digest, |
||||
project: mute_project, |
||||
involved: false, |
||||
mentioned: false, |
||||
watched: false, |
||||
all: false |
||||
|
||||
notification_settings_page.save |
||||
|
||||
# Perform some actions the user listens to |
||||
User.execute_as other_user do |
||||
note = <<~NOTE |
||||
Hey <mention class=\"mention\" |
||||
data-id=\"#{current_user.id}\" |
||||
data-type=\"user\" |
||||
data-text=\"@#{current_user.name}\"> |
||||
@#{current_user.name} |
||||
</mention> |
||||
NOTE |
||||
|
||||
work_package.add_journal(other_user, note) |
||||
work_package.save! |
||||
|
||||
watched_work_package.subject = 'New watched work package subject' |
||||
watched_work_package.save! |
||||
|
||||
involved_work_package.description = 'New involved work package description' |
||||
involved_work_package.save! |
||||
end |
||||
|
||||
# Have to explicitly execute the delayed jobs. If we were to execute all |
||||
# by wrapping the above, work package altering code, inside a |
||||
# perform_enqueued_jobs block, the digest job would be executed right away |
||||
# so that the second update would trigger a new digest. But we want to test |
||||
# that only one digest is sent out |
||||
perform_enqueued_jobs |
||||
perform_enqueued_jobs |
||||
|
||||
expect(ActionMailer::Base.deliveries.length) |
||||
.to eql 1 |
||||
|
||||
expect(ActionMailer::Base.deliveries.first.subject) |
||||
.to eql I18n.t(:'mail.digests.work_packages.subject', |
||||
date: Time.current.strftime('%m/%d/%Y'), |
||||
number: 2) |
||||
end |
||||
end |
@ -1,11 +1,14 @@ |
||||
require 'rails_helper' |
||||
require_relative '../users/notifications/shared_examples' |
||||
require 'support/pages/my/notifications' |
||||
|
||||
describe "My notifications settings", type: :feature, js: true do |
||||
current_user { FactoryBot.create :user } |
||||
|
||||
let(:settings_page) { Pages::My::Notifications.new(current_user) } |
||||
|
||||
before do |
||||
visit my_notifications_path |
||||
settings_page.visit! |
||||
end |
||||
|
||||
it_behaves_like 'notification settings workflow' do |
@ -0,0 +1,121 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe DigestMailer, type: :mailer do |
||||
include OpenProject::ObjectLinking |
||||
include ActionView::Helpers::UrlHelper |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
include Redmine::I18n |
||||
|
||||
let(:recipient) do |
||||
FactoryBot.build_stubbed(:user).tap do |u| |
||||
allow(User) |
||||
.to receive(:find) |
||||
.with(u.id) |
||||
.and_return(u) |
||||
end |
||||
end |
||||
let(:project1) { FactoryBot.build_stubbed(:project) } |
||||
|
||||
let(:work_package) do |
||||
FactoryBot.build_stubbed(:work_package, |
||||
type: FactoryBot.build_stubbed(:type)) |
||||
end |
||||
let(:journal) do |
||||
FactoryBot.build_stubbed(:work_package_journal, |
||||
notes: 'Some notes').tap do |j| |
||||
allow(j) |
||||
.to receive(:details) |
||||
.and_return({ "subject" => ["old subject", "new subject"] }) |
||||
end |
||||
end |
||||
let(:notifications) do |
||||
[FactoryBot.build_stubbed(:notification, |
||||
resource: work_package, |
||||
journal: journal, |
||||
project: project1)].tap do |notifications| |
||||
allow(Notification) |
||||
.to receive(:where) |
||||
.and_return(notifications) |
||||
|
||||
allow(notifications) |
||||
.to receive(:includes) |
||||
.and_return(notifications) |
||||
end |
||||
end |
||||
|
||||
describe '#work_packages' do |
||||
subject(:mail) { described_class.work_packages(recipient.id, notifications.map(&:id)) } |
||||
let(:mail_body) { mail.body.parts.detect { |part| part['Content-Type'].value == 'text/html' }.body.to_s } |
||||
|
||||
it 'notes the day and the number of notifications in the subject' do |
||||
expect(mail.subject) |
||||
.to eql I18n.t('mail.digests.work_packages.subject', |
||||
date: format_time_as_date(Time.current), |
||||
number: 1) |
||||
end |
||||
|
||||
it 'sends to the recipient' do |
||||
expect(mail.to) |
||||
.to match_array [recipient.mail] |
||||
end |
||||
|
||||
it 'sets the expected message_id header' do |
||||
allow(Time) |
||||
.to receive(:current) |
||||
.and_return(Time.current) |
||||
|
||||
expect(mail['Message-ID']&.value) |
||||
.to eql "<openproject.digest-#{recipient.id}-#{Time.current.strftime('%Y%m%d%H%M%S')}@example.net>" |
||||
end |
||||
|
||||
it 'sets the expected openproject headers' do |
||||
expect(mail['X-OpenProject-User']&.value) |
||||
.to eql recipient.name |
||||
end |
||||
|
||||
it 'includes the notifications grouped by project and work package' do |
||||
expect(mail_body) |
||||
.to have_selector('body section h1', text: project1.name) |
||||
|
||||
expect(mail_body) |
||||
.to have_selector('body section section h2', text: work_package.to_s) |
||||
|
||||
expect(mail_body) |
||||
.to have_selector('body section section p.op-uc-p', text: journal.notes) |
||||
|
||||
expect(mail_body) |
||||
.to have_selector('body section section li', |
||||
text: "Subject changed from old subject to new subject") |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,92 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Notifications::Scopes::MailDigestBefore, type: :model do |
||||
describe '.mail_digest_before' do |
||||
subject(:scope) { ::Notification.mail_digest_before(recipient: recipient, time: time) } |
||||
|
||||
let(:recipient) do |
||||
FactoryBot.create(:user) |
||||
end |
||||
let(:time) do |
||||
Time.current |
||||
end |
||||
|
||||
let(:notification) do |
||||
FactoryBot.create(:notification, |
||||
recipient: notification_recipient, |
||||
read_mail_digest: notification_read_mail_digest, |
||||
created_at: notification_created_at) |
||||
end |
||||
let(:notification_read_mail_digest) { false } |
||||
let(:notification_created_at) { Time.current - 10.minutes } |
||||
let(:notification_recipient) { recipient } |
||||
|
||||
let!(:notifications) { notification } |
||||
|
||||
shared_examples_for 'is empty' do |
||||
it 'is empty' do |
||||
expect(scope) |
||||
.to be_empty |
||||
end |
||||
end |
||||
|
||||
context 'with a notification of the user for mail digests before the time' do |
||||
it 'returns the notification' do |
||||
expect(scope) |
||||
.to match_array([notification]) |
||||
end |
||||
end |
||||
|
||||
context 'with a notification of the user for mail digests after the time' do |
||||
let(:notification_created_at) { Time.current + 10.minutes } |
||||
|
||||
it_behaves_like 'is empty' |
||||
end |
||||
|
||||
context 'with a notification of a different user for mail digests before the time' do |
||||
let(:notification_recipient) { FactoryBot.create(:user) } |
||||
|
||||
it_behaves_like 'is empty' |
||||
end |
||||
|
||||
context 'with a notification of a different user not for mail digests before the time' do |
||||
let(:notification_read_mail_digest) { nil } |
||||
|
||||
it_behaves_like 'is empty' |
||||
end |
||||
|
||||
context 'with a notification of a different user for already covered mail digests before the time' do |
||||
let(:notification_read_mail_digest) { true } |
||||
|
||||
it_behaves_like 'is empty' |
||||
end |
||||
end |
||||
end |
@ -1,116 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe ::API::V3::Notifications::NotificationsAPI, |
||||
'bulk set read status', |
||||
type: :request, |
||||
content_type: :json do |
||||
include API::V3::Utilities::PathHelper |
||||
|
||||
shared_let(:recipient) { FactoryBot.create :user } |
||||
shared_let(:other_recipient) { FactoryBot.create :user } |
||||
shared_let(:notification1) { FactoryBot.create :notification, recipient: recipient } |
||||
shared_let(:notification2) { FactoryBot.create :notification, recipient: recipient } |
||||
shared_let(:notification3) { FactoryBot.create :notification, recipient: recipient } |
||||
shared_let(:other_user_notification) { FactoryBot.create :notification, recipient: other_recipient } |
||||
|
||||
let(:filters) { nil } |
||||
|
||||
let(:read_path) do |
||||
api_v3_paths.path_for :notification_bulk_read_email, filters: filters |
||||
end |
||||
|
||||
let(:parsed_response) { JSON.parse(last_response.body) } |
||||
|
||||
before do |
||||
login_as current_user |
||||
|
||||
post read_path |
||||
end |
||||
|
||||
describe 'POST /api/v3/notifications/read_email' do |
||||
let(:current_user) { recipient } |
||||
|
||||
it 'returns 204' do |
||||
expect(last_response.status) |
||||
.to eql(204) |
||||
end |
||||
|
||||
it 'sets all the current users`s notifications to read' do |
||||
expect(::Notification.where(id: [notification1.id, notification2.id, notification3.id]).pluck(:read_email)) |
||||
.to all(be_truthy) |
||||
|
||||
expect(::Notification.where(id: [other_user_notification]).pluck(:read_email)) |
||||
.to all(be_falsey) |
||||
end |
||||
|
||||
context 'with a filter for id' do |
||||
let(:filters) do |
||||
[ |
||||
{ |
||||
'id' => { |
||||
'operator' => '=', |
||||
'values' => [notification1.id.to_s, notification2.id.to_s] |
||||
|
||||
} |
||||
} |
||||
] |
||||
end |
||||
|
||||
it 'sets the current users`s notifications matching the filter to read' do |
||||
expect(::Notification.where(id: [notification1.id, notification2.id]).pluck(:read_email)) |
||||
.to all(be_truthy) |
||||
|
||||
expect(::Notification.where(id: [other_user_notification, notification3.id]).pluck(:read_email)) |
||||
.to all(be_falsey) |
||||
end |
||||
end |
||||
|
||||
context 'with an invalid filter' do |
||||
let(:filters) do |
||||
[ |
||||
{ |
||||
'bogus' => { |
||||
'operator' => '=', |
||||
'values' => [] |
||||
|
||||
} |
||||
} |
||||
] |
||||
end |
||||
|
||||
it 'returns 400' do |
||||
expect(last_response.status) |
||||
.to eql(400) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,116 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe ::API::V3::Notifications::NotificationsAPI, |
||||
'bulk unset read status', |
||||
type: :request, |
||||
content_type: :json do |
||||
include API::V3::Utilities::PathHelper |
||||
|
||||
shared_let(:recipient) { FactoryBot.create :user } |
||||
shared_let(:other_recipient) { FactoryBot.create :user } |
||||
shared_let(:notification1) { FactoryBot.create :notification, recipient: recipient, read_email: true } |
||||
shared_let(:notification2) { FactoryBot.create :notification, recipient: recipient, read_email: true } |
||||
shared_let(:notification3) { FactoryBot.create :notification, recipient: recipient, read_email: true } |
||||
shared_let(:other_user_notification) { FactoryBot.create :notification, recipient: other_recipient, read_email: true } |
||||
|
||||
let(:filters) { nil } |
||||
|
||||
let(:read_path) do |
||||
api_v3_paths.path_for :notification_bulk_unread_email, filters: filters |
||||
end |
||||
|
||||
let(:parsed_response) { JSON.parse(last_response.body) } |
||||
|
||||
before do |
||||
login_as current_user |
||||
|
||||
post read_path |
||||
end |
||||
|
||||
describe 'POST /api/v3/notifications/unread_email' do |
||||
let(:current_user) { recipient } |
||||
|
||||
it 'returns 204' do |
||||
expect(last_response.status) |
||||
.to eql(204) |
||||
end |
||||
|
||||
it 'sets all the current users`s notifications to read' do |
||||
expect(::Notification.where(id: [notification1.id, notification2.id, notification3.id]).pluck(:read_email)) |
||||
.to all(be_falsey) |
||||
|
||||
expect(::Notification.where(id: [other_user_notification]).pluck(:read_email)) |
||||
.to all(be_truthy) |
||||
end |
||||
|
||||
context 'with a filter for id' do |
||||
let(:filters) do |
||||
[ |
||||
{ |
||||
'id' => { |
||||
'operator' => '=', |
||||
'values' => [notification1.id.to_s, notification2.id.to_s] |
||||
|
||||
} |
||||
} |
||||
] |
||||
end |
||||
|
||||
it 'sets the current users`s notifications matching the filter to read' do |
||||
expect(::Notification.where(id: [notification1.id, notification2.id]).pluck(:read_email)) |
||||
.to all(be_falsey) |
||||
|
||||
expect(::Notification.where(id: [other_user_notification, notification3.id]).pluck(:read_email)) |
||||
.to all(be_truthy) |
||||
end |
||||
end |
||||
|
||||
context 'with an invalid filter' do |
||||
let(:filters) do |
||||
[ |
||||
{ |
||||
'bogus' => { |
||||
'operator' => '=', |
||||
'values' => [] |
||||
|
||||
} |
||||
} |
||||
] |
||||
end |
||||
|
||||
it 'returns 400' do |
||||
expect(last_response.status) |
||||
.to eql(400) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,82 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe ::API::V3::Notifications::NotificationsAPI, |
||||
'update email read status', |
||||
type: :request, |
||||
content_type: :json do |
||||
include API::V3::Utilities::PathHelper |
||||
|
||||
shared_let(:recipient) { FactoryBot.create :user } |
||||
shared_let(:notification) { FactoryBot.create :notification, recipient: recipient } |
||||
|
||||
let(:send_read) do |
||||
post api_v3_paths.notification_read_email(notification.id) |
||||
end |
||||
|
||||
let(:send_unread) do |
||||
post api_v3_paths.notification_unread_email(notification.id) |
||||
end |
||||
|
||||
let(:parsed_response) { JSON.parse(last_response.body) } |
||||
|
||||
before do |
||||
login_as current_user |
||||
end |
||||
|
||||
describe 'recipient user' do |
||||
let(:current_user) { recipient } |
||||
|
||||
it 'can read and unread' do |
||||
send_read |
||||
expect(last_response.status).to eq(204) |
||||
expect(notification.reload.read_email).to be_truthy |
||||
|
||||
send_unread |
||||
expect(last_response.status).to eq(204) |
||||
expect(notification.reload.read_email).to be_falsey |
||||
end |
||||
end |
||||
|
||||
describe 'admin user' do |
||||
let(:current_user) { FactoryBot.build(:admin) } |
||||
|
||||
it 'returns a 404 response' do |
||||
send_read |
||||
expect(last_response.status).to eq(404) |
||||
expect(notification.reload.read_email).to be_falsey |
||||
|
||||
send_unread |
||||
expect(last_response.status).to eq(404) |
||||
expect(notification.reload.read_email).to be_falsey |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,39 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'support/pages/notifications/settings' |
||||
|
||||
module Pages |
||||
module My |
||||
class Notifications < ::Pages::Notifications::Settings |
||||
def path |
||||
my_notifications_path |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,152 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Mails::DigestJob, type: :model do |
||||
subject(:job) { described_class.perform_now(recipient) } |
||||
|
||||
let(:recipient) do |
||||
FactoryBot.build_stubbed(:user) |
||||
end |
||||
|
||||
let(:notification_ids) { [1, 2, 3] } |
||||
|
||||
let!(:notifications) do |
||||
instance_double(ActiveRecord::Relation).tap do |notifications| |
||||
allow(Time) |
||||
.to receive(:current) |
||||
.and_return(Time.current) |
||||
|
||||
allow(Notification) |
||||
.to receive(:mail_digest_before) |
||||
.with(recipient: recipient, time: Time.current) |
||||
.and_return(notifications) |
||||
|
||||
allow(notifications) |
||||
.to receive(:pluck) |
||||
.with(:id) |
||||
.and_return(notification_ids) |
||||
|
||||
allow(Notification) |
||||
.to receive(:where) |
||||
.with(id: notification_ids) |
||||
.and_return(notifications) |
||||
|
||||
allow(notifications) |
||||
.to receive(:update_all) |
||||
end |
||||
end |
||||
|
||||
let(:mail) { instance_double(ActionMailer::MessageDelivery, deliver_now: nil) } |
||||
|
||||
before do |
||||
# make sure no actual calls make it into the DigestMailer |
||||
allow(DigestMailer) |
||||
.to receive(:work_packages) |
||||
.with(recipient&.id, notification_ids) |
||||
.and_return(mail) |
||||
end |
||||
|
||||
describe '#perform' do |
||||
context 'with successful mail sending' do |
||||
it 'sends a mail' do |
||||
job |
||||
expect(DigestMailer) |
||||
.to have_received(:work_packages) |
||||
.with(recipient.id, notification_ids) |
||||
end |
||||
|
||||
it 'marks the notifications as read' do |
||||
job |
||||
|
||||
expect(notifications) |
||||
.to have_received(:update_all) |
||||
.with(read_mail_digest: true, updated_at: Time.current) |
||||
end |
||||
|
||||
it 'impersonates the recipient' do |
||||
allow(DigestMailer).to receive(:work_packages) do |
||||
|
||||
expect(User.current) |
||||
.eql receiver |
||||
end |
||||
|
||||
job |
||||
end |
||||
end |
||||
|
||||
context 'without a recipient' do |
||||
let(:recipient) { nil } |
||||
|
||||
it 'sends no mail' do |
||||
job |
||||
expect(DigestMailer) |
||||
.not_to have_received(:work_packages) |
||||
end |
||||
end |
||||
|
||||
context 'with an error on mail rendering' do |
||||
before do |
||||
allow(DigestMailer) |
||||
.to receive(:work_packages) |
||||
.and_raise('error') |
||||
end |
||||
|
||||
it 'swallows the error' do |
||||
expect { job } |
||||
.not_to raise_error |
||||
end |
||||
end |
||||
|
||||
context 'with an error on mail sending' do |
||||
before do |
||||
allow(mail) |
||||
.to receive(:deliver_now) |
||||
.and_raise(SocketError) |
||||
end |
||||
|
||||
it 'raises the error' do |
||||
expect { job } |
||||
.to raise_error(SocketError) |
||||
end |
||||
end |
||||
|
||||
context 'with an empty list of notification ids' do |
||||
let(:notification_ids) { [] } |
||||
|
||||
it 'sends no mail' do |
||||
job |
||||
expect(DigestMailer) |
||||
.not_to have_received(:work_packages) |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue