discourse/spec/requests/admin/email_controller_spec.rb
Martin Brennan c187ede3c6
FIX: Catch UndefinedConversionError for inbound emails ()
Some emails coming in via the mail receiver can still end up
with bad encoding when trying to enqueue the job. This catches
the last encoding issue and forces iso-8559-1 and encodes to
UTF-8 to circumvent the issue.
2021-05-10 14:26:23 +10:00

355 lines
11 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe Admin::EmailController do
fab!(:admin) { Fabricate(:admin) }
fab!(:email_log) { Fabricate(:email_log) }
before do
sign_in(admin)
end
it "is a subclass of AdminController" do
expect(Admin::EmailController < Admin::AdminController).to eq(true)
end
describe '#index' do
before do
Admin::EmailController.any_instance
.expects(:action_mailer_settings)
.returns(
username: 'username',
password: 'secret'
)
end
it 'does not include the password in the response' do
get "/admin/email.json"
mail_settings = response.parsed_body['settings']
expect(
mail_settings.select { |setting| setting['name'] == 'password' }
).to be_empty
end
end
describe '#sent' do
fab!(:post) { Fabricate(:post) }
fab!(:email_log) { Fabricate(:email_log, post: post) }
let(:post_reply_key) do
Fabricate(:post_reply_key, post: post, user: email_log.user)
end
it "should return the right response" do
email_log
get "/admin/email/sent.json"
expect(response.status).to eq(200)
log = response.parsed_body.first
expect(log["id"]).to eq(email_log.id)
expect(log["reply_key"]).to eq(nil)
post_reply_key
get "/admin/email/sent.json"
expect(response.status).to eq(200)
log = response.parsed_body.first
expect(log["id"]).to eq(email_log.id)
expect(log["reply_key"]).to eq(post_reply_key.reply_key)
end
it 'should be able to filter by reply key' do
email_log_2 = Fabricate(:email_log, post: post)
post_reply_key_2 = Fabricate(:post_reply_key,
post: post,
user: email_log_2.user,
reply_key: "2d447423-c625-4fb9-8717-ff04ac60eee8"
)
[
"17ff04",
"2d447423c6254fb98717ff04ac60eee8"
].each do |reply_key|
get "/admin/email/sent.json", params: {
reply_key: reply_key
}
expect(response.status).to eq(200)
logs = response.parsed_body
expect(logs.size).to eq(1)
expect(logs.first["reply_key"]).to eq(post_reply_key_2.reply_key)
end
end
end
describe '#skipped' do
fab!(:user) { Fabricate(:user) }
fab!(:log1) { Fabricate(:skipped_email_log, user: user, created_at: 20.minutes.ago) }
fab!(:log2) { Fabricate(:skipped_email_log, created_at: 10.minutes.ago) }
it "succeeds" do
get "/admin/email/skipped.json"
expect(response.status).to eq(200)
logs = response.parsed_body
expect(logs.first["id"]).to eq(log2.id)
expect(logs.last["id"]).to eq(log1.id)
end
describe 'when filtered by username' do
it 'should return the right response' do
get "/admin/email/skipped.json", params: {
user: user.username
}
expect(response.status).to eq(200)
logs = response.parsed_body
expect(logs.count).to eq(1)
expect(logs.first["id"]).to eq(log1.id)
end
end
end
describe '#test' do
it 'raises an error without the email parameter' do
post "/admin/email/test.json"
expect(response.status).to eq(400)
end
context 'with an email address' do
it 'enqueues a test email job' do
post "/admin/email/test.json", params: { email_address: 'eviltrout@test.domain' }
expect(response.status).to eq(200)
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include('eviltrout@test.domain')
end
end
context 'with SiteSetting.disable_emails' do
fab!(:eviltrout) { Fabricate(:evil_trout) }
fab!(:admin) { Fabricate(:admin) }
it 'bypasses disable when setting is "yes"' do
SiteSetting.disable_emails = 'yes'
post "/admin/email/test.json", params: { email_address: admin.email }
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
admin.email
)
incoming = response.parsed_body
expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test"))
end
it 'bypasses disable when setting is "non-staff"' do
SiteSetting.disable_emails = 'non-staff'
post "/admin/email/test.json", params: { email_address: eviltrout.email }
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
eviltrout.email
)
incoming = response.parsed_body
expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test"))
end
it 'works when setting is "no"' do
SiteSetting.disable_emails = 'no'
post "/admin/email/test.json", params: { email_address: eviltrout.email }
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
eviltrout.email
)
incoming = response.parsed_body
expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test"))
end
end
end
describe '#preview_digest' do
it 'raises an error without the last_seen_at parameter' do
get "/admin/email/preview-digest.json"
expect(response.status).to eq(400)
end
it "returns the right response when username is invalid" do
get "/admin/email/preview-digest.json", params: {
last_seen_at: 1.week.ago, username: "somerandomeusername"
}
expect(response.status).to eq(400)
end
it "previews the digest" do
get "/admin/email/preview-digest.json", params: {
last_seen_at: 1.week.ago, username: admin.username
}
expect(response.status).to eq(200)
end
end
describe '#handle_mail' do
it "returns a bad request if neither email parameter is present" do
post "/admin/email/handle_mail.json"
expect(response.status).to eq(400)
expect(response.body).to include("param is missing")
end
it 'should enqueue the right job, and show a deprecation warning (email_encoded param should be used)' do
expect_enqueued_with(
job: :process_email,
args: { mail: email('cc'), retry_on_rate_limit: true, source: :handle_mail }
) do
post "/admin/email/handle_mail.json", params: { email: email('cc') }
end
expect(response.status).to eq(200)
expect(response.body).to eq("warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded encoded_email parameter instead. email has been received and is queued for processing")
end
it 'should enqueue the right job, decoding the raw email param' do
expect_enqueued_with(
job: :process_email,
args: { mail: email('cc'), retry_on_rate_limit: true, source: :handle_mail }
) do
post "/admin/email/handle_mail.json", params: { email_encoded: Base64.strict_encode64(email('cc')) }
end
expect(response.status).to eq(200)
expect(response.body).to eq("email has been received and is queued for processing")
end
it "retries enqueueing with forced UTF-8 encoding when encountering Encoding::UndefinedConversionError" do
post "/admin/email/handle_mail.json", params: { email_encoded: Base64.strict_encode64(email('encoding_undefined_conversion')) }
expect(response.status).to eq(200)
expect(response.body).to eq("email has been received and is queued for processing")
end
end
describe '#rejected' do
it 'should provide a string for a blank error' do
Fabricate(:incoming_email, error: "")
get "/admin/email/rejected.json"
expect(response.status).to eq(200)
rejected = response.parsed_body
expect(rejected.first['error']).to eq(I18n.t("emails.incoming.unrecognized_error"))
end
end
describe '#incoming' do
it 'should provide a string for a blank error' do
incoming_email = Fabricate(:incoming_email, error: "")
get "/admin/email/incoming/#{incoming_email.id}.json"
expect(response.status).to eq(200)
incoming = response.parsed_body
expect(incoming['error']).to eq(I18n.t("emails.incoming.unrecognized_error"))
end
end
describe '#incoming_from_bounced' do
it 'raises an error when the email log entry does not exist' do
get "/admin/email/incoming_from_bounced/12345.json"
expect(response.status).to eq(404)
json = response.parsed_body
expect(json["errors"]).to include("Discourse::InvalidParameters")
end
it 'raises an error when the email log entry is not marked as bounced' do
get "/admin/email/incoming_from_bounced/#{email_log.id}.json"
expect(response.status).to eq(404)
json = response.parsed_body
expect(json["errors"]).to include("Discourse::InvalidParameters")
end
context 'bounced email log entry exists' do
fab!(:email_log) { Fabricate(:email_log, bounced: true, bounce_key: SecureRandom.hex) }
let(:error_message) { "Email::Receiver::BouncedEmailError" }
it 'returns an incoming email sent to the reply_by_email_address' do
SiteSetting.reply_by_email_address = "replies+%{reply_key}@example.com"
Fabricate(:incoming_email,
is_bounce: true,
error: error_message,
to_addresses: Email::Sender.bounce_address(email_log.bounce_key)
)
get "/admin/email/incoming_from_bounced/#{email_log.id}.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["error"]).to eq(error_message)
end
it 'returns an incoming email sent to the notification_email address' do
Fabricate(:incoming_email,
is_bounce: true,
error: error_message,
to_addresses: SiteSetting.notification_email.sub("@", "+verp-#{email_log.bounce_key}@")
)
get "/admin/email/incoming_from_bounced/#{email_log.id}.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["error"]).to eq(error_message)
end
it 'raises an error if the bounce_key is blank' do
email_log.update(bounce_key: nil)
get "/admin/email/incoming_from_bounced/#{email_log.id}.json"
expect(response.status).to eq(404)
json = response.parsed_body
expect(json["errors"]).to include("Discourse::InvalidParameters")
end
it 'raises an error if there is no incoming email' do
get "/admin/email/incoming_from_bounced/#{email_log.id}.json"
expect(response.status).to eq(404)
json = response.parsed_body
expect(json["errors"]).to include("Discourse::NotFound")
end
end
end
describe '#advanced_test' do
it 'should ...' do
email = <<~EMAIL
From: "somebody" <somebody@example.com>
To: someone@example.com
Date: Mon, 3 Dec 2018 00:00:00 -0000
Subject: This is some subject
Content-Type: text/plain; charset="UTF-8"
Hello, this is a test!
---
This part should be elided.
EMAIL
post "/admin/email/advanced-test.json", params: { email: email }
expect(response.status).to eq(200)
incoming = response.parsed_body
expect(incoming['format']).to eq(1)
expect(incoming['text']).to eq("Hello, this is a test!")
expect(incoming['elided']).to eq("---\n\nThis part should be elided.")
end
end
end