diff options
Diffstat (limited to 'spec/mailers/notify_spec.rb')
-rw-r--r-- | spec/mailers/notify_spec.rb | 780 |
1 files changed, 427 insertions, 353 deletions
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 980b24370d0..83c33797bbc 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -8,349 +8,447 @@ describe Notify do include_context 'gitlab email notification' + set(:user) { create(:user) } + set(:current_user) { create(:user, email: "current@email.com") } + set(:assignee) { create(:user, email: 'assignee@example.com', name: 'John Doe') } + + set(:merge_request) do + create(:merge_request, source_project: project, + target_project: project, + author: current_user, + assignee: assignee, + description: 'Awesome description') + end + + set(:issue) do + create(:issue, author: current_user, + assignees: [assignee], + project: project, + description: 'My awesome description!') + end + def have_referable_subject(referable, reply: false) - prefix = referable.project.name if referable.project - prefix = "Re: #{prefix}" if reply + prefix = referable.project ? "#{referable.project.name} | " : '' + prefix.prepend('Re: ') if reply suffix = "#{referable.title} (#{referable.to_reference})" - have_subject [prefix, suffix].compact.join(' | ') + have_subject [prefix, suffix].compact.join end context 'for a project' do - describe 'items that are assignable, the email' do - let(:current_user) { create(:user, email: "current@email.com") } - let(:assignee) { create(:user, email: 'assignee@example.com', name: 'John Doe') } - let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + shared_examples 'an assignee email' do + it 'is sent to the assignee as the author' do + sender = subject.header[:from].addrs.first + + aggregate_failures do + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + expect(subject).to deliver_to(assignee.email) + end + end + end - shared_examples 'an assignee email' do - it 'is sent to the assignee as the author' do - sender = subject.header[:from].addrs.first + context 'for issues' do + describe 'that are new' do + subject { described_class.new_issue_email(issue.assignees.first.id, issue.id) } + it_behaves_like 'an assignee email' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'has the correct subject and body' do aggregate_failures do - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - expect(subject).to deliver_to(assignee.email) + is_expected.to have_referable_subject(issue) + is_expected.to have_body_text(project_issue_path(project, issue)) end end - end - context 'for issues' do - let(:issue) { create(:issue, author: current_user, assignees: [assignee], project: project) } - let(:issue_with_description) { create(:issue, author: current_user, assignees: [assignee], project: project, description: 'My awesome description') } + it 'contains the description' do + is_expected.to have_html_escaped_body_text issue.description + end + + it 'does not add a reason header' do + is_expected.not_to have_header('X-GitLab-NotificationReason', /.+/) + end - describe 'that are new' do - subject { described_class.new_issue_email(issue.assignees.first.id, issue.id) } + context 'when sent with a reason' do + subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) } - it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' - - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) - end + it 'includes the reason in a header' do + is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end + end - context 'when enabled email_author_in_body' do - before do - stub_application_setting(email_author_in_body: true) - end + context 'when enabled email_author_in_body' do + before do + stub_application_setting(email_author_in_body: true) + end - it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text(issue.author_name) - is_expected.to have_body_text 'created an issue:' - end + it 'contains a link to note author' do + is_expected.to have_html_escaped_body_text(issue.author_name) + is_expected.to have_body_text 'created an issue:' end end + end - describe 'that are new with a description' do - subject { described_class.new_issue_email(issue_with_description.assignees.first.id, issue_with_description.id) } + describe 'that are reassigned' do + let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id) } - it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' - it 'contains the description' do - is_expected.to have_html_escaped_body_text issue_with_description.description + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_html_escaped_body_text(assignee.name) + is_expected.to have_body_text(project_issue_path(project, issue)) end end - describe 'that have been reassigned' do - subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id) } + context 'when sent with a reason' do + subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) } - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'includes the reason in a header' do + is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + end + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + describe 'that have been relabeled' do + subject { described_class.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) } - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_html_escaped_body_text(assignee.name) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) - end - end + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'an email with a labels subscriptions link in its footer' - describe 'that have been relabeled' do - subject { described_class.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(project_issue_path(project, issue)) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'an email with a labels subscriptions link in its footer' + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) + context 'with a preferred language' do + before do + Gitlab::I18n.locale = :es end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text('foo, bar, and baz') - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) - end + after do + Gitlab::I18n.use_default_locale end - context 'with a preferred language' do - before do - Gitlab::I18n.locale = :es - end + it 'always generates the email using the default language' do + is_expected.to have_body_text('foo, bar, and baz') + end + end + end - after do - Gitlab::I18n.use_default_locale - end + describe 'status changed' do + let(:status) { 'closed' } + subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } - it 'always generates the email using the default language' do - is_expected.to have_body_text('foo, bar, and baz') - end - end + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' - describe 'status changed' do - let(:status) { 'closed' } - subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(project_issue_path project, issue) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + end + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + describe 'moved to another project' do + let(:new_issue) { create(:issue) } + subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'contains description about action taken' do + is_expected.to have_body_text 'Issue was moved to another project' + end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue) - end + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) + + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) end end + end + end - describe 'moved to another project' do - let(:new_issue) { create(:issue) } - subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } + context 'for merge requests' do + describe 'that are new' do + subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + + it_behaves_like 'an assignee email' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_body_text(merge_request.source_branch) + is_expected.to have_body_text(merge_request.target_branch) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + end - it 'contains description about action taken' do - is_expected.to have_body_text 'Issue was moved to another project' + it 'contains the description' do + is_expected.to have_html_escaped_body_text merge_request.description + end + + context 'when sent with a reason' do + subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) } + + it 'includes the reason in a header' do + is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end + end - it 'has the correct subject and body' do - new_issue_url = namespace_project_issue_path(new_issue.project.namespace, - new_issue.project, new_issue) + context 'when enabled email_author_in_body' do + before do + stub_application_setting(email_author_in_body: true) + end - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(new_issue_url) - is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) - end + it 'contains a link to note author' do + is_expected.to have_html_escaped_body_text merge_request.author_name + is_expected.to have_body_text 'created a merge request:' end end end - context 'for merge requests' do - let(:project) { create(:project, :repository) } - let(:merge_author) { create(:user) } - let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) } - let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: 'My awesome description') } + describe 'that are reassigned' do + let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } + + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like "an unsubscribeable thread" - describe 'that are new' do - subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread with reply-by-email enabled' do - let(:model) { merge_request } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_html_escaped_body_text(assignee.name) end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' - - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) - is_expected.to have_body_text(merge_request.source_branch) - is_expected.to have_body_text(merge_request.target_branch) - end + end + + context 'when sent with a reason' do + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) } + + it 'includes the reason in a header' do + is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED) end - context 'when enabled email_author_in_body' do - before do - stub_application_setting(email_author_in_body: true) - end + it 'includes the reason in the footer' do + text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED) + is_expected.to have_body_text(text) + + new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::MENTIONED) + text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED) + expect(new_subject).to have_body_text(text) - it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text merge_request.author_name - is_expected.to have_body_text 'created a merge request:' - end + new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, nil) + text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil) + expect(new_subject).to have_body_text(text) end end + end - describe 'that are new with a description' do - subject { described_class.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } + describe 'that have been relabeled' do + subject { described_class.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) } - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'an email with a labels subscriptions link in its footer' - it 'contains the description' do - is_expected.to have_html_escaped_body_text merge_request_with_description.description - end + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end - describe 'that are reassigned' do - subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } + it 'has the correct subject and body' do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + end + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + describe 'status changed' do + let(:status) { 'reopened' } + subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) - is_expected.to have_html_escaped_body_text(assignee.name) - end + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end + end - describe 'that have been relabeled' do - subject { described_class.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) } + describe 'that are merged' do + let(:merge_author) { create(:user) } + subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'an email with a labels subscriptions link in its footer' + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + it 'is sent as the merge author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(merge_author.name) + expect(sender.address).to eq(gitlab_sender) + end - it 'has the correct subject and body' do + it 'has the correct subject and body' do + aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text('foo, bar, and baz') - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text('merged') + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end + end + end - describe 'status changed' do - let(:status) { 'reopened' } - subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } + context 'for issue notes' do + let(:host) { Gitlab.config.gitlab.host } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' + context 'in discussion' do + set(:first_note) { create(:discussion_note_on_issue) } + set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note) } + set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note) } - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + subject { described_class.note_issue_email(recipient.id, third_note.id) } - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) - end - end + it 'has In-Reply-To header pointing to previous note in discussion' do + expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"]) end - describe 'that are merged' do - subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } + it 'has References header including the notes and issue of the discussion' do + expect(subject.header['References'].message_ids).to include("issue_#{first_note.noteable.id}@#{host}", + "note_#{first_note.id}@#{host}", + "note_#{second_note.id}@#{host}") + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' + it 'has X-GitLab-Discussion-ID header' do + expect(subject.header['X-GitLab-Discussion-ID'].value).to eq(third_note.discussion.id) + end + end - it 'is sent as the merge author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(merge_author.name) - expect(sender.address).to eq(gitlab_sender) - end + context 'individual issue comments' do + set(:note) { create(:note_on_issue) } - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text('merged') - is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) - end - end + subject { described_class.note_issue_email(recipient.id, note.id) } + + it 'has In-Reply-To header pointing to the issue' do + expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"]) + end + + it 'has References header including the notes and issue of the discussion' do + expect(subject.header['References'].message_ids).to include("issue_#{note.noteable.id}@#{host}") end end end + context 'for snippet notes' do + let(:project_snippet) { create(:project_snippet, project: project) } + let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) } + + subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) } + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { project_snippet } + end + it_behaves_like 'a user cannot unsubscribe through footer link' + + it 'has the correct subject and body' do + is_expected.to have_referable_subject(project_snippet, reply: true) + is_expected.to have_html_escaped_body_text project_snippet_note.note + end + end + describe 'project was moved' do - let(:project) { create(:empty_project) } - let(:user) { create(:user) } subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } it_behaves_like 'an email sent from GitLab' @@ -359,73 +457,41 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_subject("#{project.name} | Project was moved") - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text(project.ssh_url_to_repo) end end describe 'project access requested' do - context 'for a project in a user namespace' do - let(:project) do - create(:empty_project, :public, :access_requestable) do |project| - project.team << [project.owner, :master, project.owner] - end - end - - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.requesters.find_by(user_id: user.id) - end - subject { described_class.member_access_requested_email('project', project_member.id) } - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" - - it 'contains all the useful information' do - to_emails = subject.header[:to].addrs - expect(to_emails.size).to eq(1) - expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email) - - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_html_escaped_body_text project.name_with_namespace - is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project) - is_expected.to have_body_text project_member.human_access + let(:project) do + create(:project, :public, :access_requestable) do |project| + project.add_master(project.owner) end end - context 'for a project in a group' do - let(:group_owner) { create(:user) } - let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } - let(:project) { create(:empty_project, :public, :access_requestable, namespace: group) } - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.requesters.find_by(user_id: user.id) - end - subject { described_class.member_access_requested_email('project', project_member.id) } + let(:project_member) do + project.request_access(user) + project.requesters.find_by(user_id: user.id) + end + subject { described_class.member_access_requested_email('project', project_member.id, recipient.notification_email) } - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" - it 'contains all the useful information' do - to_emails = subject.header[:to].addrs - expect(to_emails.size).to eq(1) - expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email) + it 'contains all the useful information' do + to_emails = subject.header[:to].addrs.map(&:address) + expect(to_emails).to eq([recipient.notification_email]) - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_html_escaped_body_text project.name_with_namespace - is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project) - is_expected.to have_body_text project_member.human_access - end + is_expected.to have_subject "Request to join the #{project.full_name} project" + is_expected.to have_html_escaped_body_text project.full_name + is_expected.to have_body_text project_project_members_url(project) + is_expected.to have_body_text project_member.human_access end end describe 'project access denied' do - let(:project) { create(:empty_project, :public, :access_requestable) } - let(:user) { create(:user) } + let(:project) { create(:project, :public, :access_requestable) } let(:project_member) do project.request_access(user) project.requesters.find_by(user_id: user.id) @@ -437,16 +503,15 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Access to the #{project.full_name} project was denied" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url end end describe 'project access changed' do let(:owner) { create(:user, name: "Chang O'Keefe") } - let(:project) { create(:empty_project, :public, :access_requestable, namespace: owner.namespace) } - let(:user) { create(:user) } + let(:project) { create(:project, :public, :access_requestable, namespace: owner.namespace) } let(:project_member) { create(:project_member, project: project, user: user) } subject { described_class.member_access_granted_email('project', project_member.id) } @@ -455,8 +520,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Access to the #{project.full_name} project was granted" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access end @@ -475,8 +540,7 @@ describe Notify do end describe 'project invitation' do - let(:project) { create(:empty_project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) { invite_to_project(project, inviter: master) } subject { described_class.member_invited_email('project', project_member.id, project_member.invite_token) } @@ -486,8 +550,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Invitation to join the #{project.full_name} project" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access is_expected.to have_body_text project_member.invite_token @@ -495,9 +559,8 @@ describe Notify do end describe 'project invitation accepted' do - let(:project) { create(:empty_project) } let(:invited_user) { create(:user, name: 'invited user') } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) do invitee = invite_to_project(project, inviter: master) invitee.accept_invite!(invited_user) @@ -512,7 +575,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email is_expected.to have_html_escaped_body_text invited_user.name @@ -520,8 +583,7 @@ describe Notify do end describe 'project invitation declined' do - let(:project) { create(:empty_project) } - let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:master) { create(:user).tap { |u| project.add_master(u) } } let(:project_member) do invitee = invite_to_project(project, inviter: master) invitee.decline_invite! @@ -536,7 +598,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email end @@ -546,7 +608,7 @@ describe Notify do let(:note_author) { create(:user, name: 'author_name') } let(:note) { create(:note, project: project, author: note_author) } - before :each do + before do allow(Note).to receive(:find).with(note.id).and_return(note) end @@ -583,7 +645,6 @@ describe Notify do end describe 'on a commit' do - let(:project) { create(:project, :repository) } let(:commit) { project.commit } before do @@ -601,15 +662,14 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do - is_expected.to have_subject("Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})") + is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})") is_expected.to have_body_text(commit.short_id) end end end describe 'on a merge request' do - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } + let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(merge_request) @@ -633,8 +693,7 @@ describe Notify do end describe 'on an issue' do - let(:issue) { create(:issue, project: project) } - let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } + let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(issue) @@ -659,10 +718,9 @@ describe Notify do end context 'items that are noteable, the email for a discussion note' do - let(:project) { create(:project, :repository) } let(:note_author) { create(:user, name: 'author_name') } - before :each do + before do allow(Note).to receive(:find).with(note.id).and_return(note) end @@ -714,7 +772,7 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject' do - is_expected.to have_subject "Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})" + is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})" end it 'contains a link to the commit' do @@ -723,9 +781,8 @@ describe Notify do end describe 'on a merge request' do - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) } - let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } + let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(merge_request) @@ -750,9 +807,8 @@ describe Notify do end describe 'on an issue' do - let(:issue) { create(:issue, project: project) } let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) } - let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } + let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } before do allow(note).to receive(:noteable).and_return(issue) @@ -780,14 +836,32 @@ describe Notify do context 'items that are noteable, the email for a diff discussion note' do let(:note_author) { create(:user, name: 'author_name') } - before :each do + before do allow(Note).to receive(:find).with(note.id).and_return(note) end shared_examples 'an email for a note on a diff discussion' do |model| let(:note) { create(model, author: note_author) } - it "includes diffs with character-level highlighting" do + context 'when note is on image' do + before do + allow_any_instance_of(DiffDiscussion).to receive(:on_image?).and_return(true) + end + + it 'does not include diffs with character-level highlighting' do + is_expected.not_to have_body_text '<span class="p">}</span></span>' + end + + it 'ends the intro with a dot' do + is_expected.to have_body_text "#{note.diff_file.file_path}</a>." + end + end + + it 'ends the intro with a colon' do + is_expected.to have_body_text "#{note.diff_file.file_path}</a>:" + end + + it 'includes diffs with character-level highlighting' do is_expected.to have_body_text '<span class="p">}</span></span>' end @@ -836,7 +910,6 @@ describe Notify do end describe 'on a merge request' do - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:note) { create(:diff_note_on_merge_request) } subject { described_class.note_merge_request_email(recipient.id, note.id) } @@ -849,20 +922,24 @@ describe Notify do end context 'for a group' do + set(:group) { create(:group) } + describe 'group access requested' do let(:group) { create(:group, :public, :access_requestable) } - let(:user) { create(:user) } let(:group_member) do group.request_access(user) group.requesters.find_by(user_id: user.id) end - subject { described_class.member_access_requested_email('group', group_member.id) } + subject { described_class.member_access_requested_email('group', group_member.id, recipient.notification_email) } it_behaves_like 'an email sent from GitLab' it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do + to_emails = subject.header[:to].addrs.map(&:address) + expect(to_emails).to eq([recipient.notification_email]) + is_expected.to have_subject "Request to join the #{group.name} group" is_expected.to have_html_escaped_body_text group.name is_expected.to have_body_text group_group_members_url(group) @@ -871,8 +948,6 @@ describe Notify do end describe 'group access denied' do - let(:group) { create(:group) } - let(:user) { create(:user) } let(:group_member) do group.request_access(user) group.requesters.find_by(user_id: user.id) @@ -891,8 +966,6 @@ describe Notify do end describe 'group access changed' do - let(:group) { create(:group) } - let(:user) { create(:user) } let(:group_member) { create(:group_member, group: group, user: user) } subject { described_class.member_access_granted_email('group', group_member.id) } @@ -922,7 +995,6 @@ describe Notify do end describe 'group invitation' do - let(:group) { create(:group) } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:group_member) { invite_to_group(group, inviter: owner) } @@ -942,7 +1014,6 @@ describe Notify do end describe 'group invitation accepted' do - let(:group) { create(:group) } let(:invited_user) { create(:user, name: 'invited user') } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:group_member) do @@ -967,7 +1038,6 @@ describe Notify do end describe 'group invitation declined' do - let(:group) { create(:group) } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:group_member) do invitee = invite_to_group(group, inviter: owner) @@ -1021,8 +1091,7 @@ describe Notify do describe 'email on push for a created branch' do let(:example_site_path) { root_path } - let(:user) { create(:user) } - let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") } + let(:tree_path) { project_tree_path(project, "empty-branch") } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) } @@ -1047,8 +1116,7 @@ describe Notify do describe 'email on push for a created tag' do let(:example_site_path) { root_path } - let(:user) { create(:user) } - let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") } + let(:tree_path) { project_tree_path(project, "v1.0") } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } @@ -1073,7 +1141,6 @@ describe Notify do describe 'email on push for a deleted branch' do let(:example_site_path) { root_path } - let(:user) { create(:user) } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) } @@ -1095,7 +1162,6 @@ describe Notify do describe 'email on push for a deleted tag' do let(:example_site_path) { root_path } - let(:user) { create(:user) } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) } @@ -1116,13 +1182,11 @@ describe Notify do end describe 'email on push with multiple commits' do - let(:project) { create(:project, :repository) } let(:example_site_path) { root_path } - let(:user) { create(:user) } let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } let(:compare) { Compare.decorate(raw_compare, project) } let(:commits) { compare.commits } - let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } + let(:diff_path) { project_compare_path(project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } @@ -1210,13 +1274,11 @@ describe Notify do end describe 'email on push with a single commit' do - let(:project) { create(:project, :repository) } let(:example_site_path) { root_path } - let(:user) { create(:user) } let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } let(:compare) { Compare.decorate(raw_compare, project) } let(:commits) { compare.commits } - let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } + let(:diff_path) { project_commit_path(project, commits.first) } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) } @@ -1243,8 +1305,6 @@ describe Notify do end describe 'HTML emails setting' do - let(:project) { create(:empty_project) } - let(:user) { create(:user) } let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } context 'when disabled' do @@ -1271,8 +1331,22 @@ describe Notify do matcher :have_part_with do |expected| match do |actual| - actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) } + actual.body.parts.any? { |part| part.content_type.try(:match, /#{expected}/) } end end end + + context 'for personal snippet notes' do + let(:personal_snippet) { create(:personal_snippet) } + let(:personal_snippet_note) { create(:note_on_personal_snippet, noteable: personal_snippet) } + + subject { described_class.note_personal_snippet_email(personal_snippet_note.author_id, personal_snippet_note.id) } + + it_behaves_like 'a user cannot unsubscribe through footer link' + + it 'has the correct subject and body' do + is_expected.to have_referable_subject(personal_snippet, reply: true) + is_expected.to have_html_escaped_body_text personal_snippet_note.note + end + end end |