discourse/spec/jobs/poll_mailbox_spec.rb
Alessio Cosenza 56718504ac
FEATURE: Add hooks for email poller plugins (#21384)
While we are unable to support OAUTH2 with pop3 (due to upstream dependency ruby/net-pop#16), we are adding the support for mail pollers plugin. Doing so, it would be possible to write a plugin which then uses other ways (microsoft graph sdk for example) to poll emails from a mailbox.

The idea is that a plugin would define a class which inherits from Email::Poller and defines a poll_mailbox static method which returns an array of strings. Then the plugin could call register_mail_poller(<class_name>) to have it registered. All the configuration (oauth2 tokens, email, etc) could be managed by sitesettings defined in the plugin.
2023-06-26 13:16:03 +08:00

221 lines
6.8 KiB
Ruby

# frozen_string_literal: true
require "email/poller"
RSpec.describe Jobs::PollMailbox do
let(:poller) { Jobs::PollMailbox.new }
describe ".execute" do
it "does no polling if pop3_polling_enabled is false" do
SiteSetting.expects(:pop3_polling_enabled).returns(false)
poller.expects(:poll_pop3).never
poller.execute({})
end
it "polls when pop3_polling_enabled is true" do
SiteSetting.expects(:pop3_polling_enabled).returns(true)
poller.expects(:poll_pop3).once
poller.execute({})
end
end
describe ".poll_pop3" do
# the date is dynamic here because there is a 1 week cutoff for
# the pop3 polling
let(:example_email) { email = <<~EMAIL }
Return-Path: <one@foo.com>
From: One <one@foo.com>
To: team@bar.com
Subject: Testing email
Date: #{1.day.ago.strftime("%a, %d %b %Y")} 03:12:43 +0100
Message-ID: <34@foo.bar.mail>
Mime-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
This is an email example.
EMAIL
context "with pop errors" do
before { Discourse.expects(:handle_job_exception).at_least_once }
after { Discourse.redis.del(Jobs::PollMailbox::POLL_MAILBOX_TIMEOUT_ERROR_KEY) }
it "add an admin dashboard message on pop authentication error" do
Net::POP3.any_instance.expects(:start).raises(Net::POPAuthenticationError.new).at_least_once
poller.poll_pop3
i18n_key = "dashboard.poll_pop3_auth_error"
expect(AdminDashboardData.problem_message_check(i18n_key)).to eq(
I18n.t(i18n_key, base_path: Discourse.base_path),
)
end
it "logs an error on pop connection timeout error" do
Net::POP3.any_instance.expects(:start).raises(Net::OpenTimeout.new).at_least_once
4.times { poller.poll_pop3 }
i18n_key = "dashboard.poll_pop3_timeout"
expect(AdminDashboardData.problem_message_check(i18n_key)).to eq(
I18n.t(i18n_key, base_path: Discourse.base_path),
)
end
it "logs an error when pop fails and continues with next message" do
mail1 = Net::POPMail.new(1, nil, nil, nil)
mail2 = Net::POPMail.new(2, nil, nil, nil)
mail3 = Net::POPMail.new(3, nil, nil, nil)
mail4 = Net::POPMail.new(4, nil, nil, nil)
Net::POP3.any_instance.stubs(:start).yields(Net::POP3.new(nil, nil))
Net::POP3.any_instance.stubs(:mails).returns([mail1, mail2, mail3, mail4])
mail1.expects(:pop).raises(Net::POPError).once
mail1.expects(:delete).never
mail2.expects(:pop).returns(example_email).once
mail2.expects(:delete).raises(Net::POPError).once
mail3.expects(:pop).returns(example_email).once
mail3.expects(:delete).never
mail4.expects(:pop).returns(example_email).once
mail4.expects(:delete).returns(example_email).once
SiteSetting.pop3_polling_delete_from_server = true
poller
.expects(:mail_too_old?)
.returns(false)
.then
.raises(RuntimeError)
.then
.returns(false)
.times(3)
poller.expects(:process_popmail).times(2)
poller.poll_pop3
end
end
it "calls enable_ssl when the setting is enabled" do
SiteSetting.pop3_polling_ssl = true
Net::POP3.any_instance.stubs(:start)
Net::POP3.any_instance.expects(:enable_ssl)
poller.poll_pop3
end
it "does not call enable_ssl when the setting is disabled" do
SiteSetting.pop3_polling_ssl = false
Net::POP3.any_instance.stubs(:start)
Net::POP3.any_instance.expects(:enable_ssl).never
poller.poll_pop3
end
context "when has emails" do
let(:oldmail) { file_from_fixtures("old_destination.eml", "emails").read }
before do
mail1 = Net::POPMail.new(1, nil, nil, nil)
mail2 = Net::POPMail.new(2, nil, nil, nil)
mail3 = Net::POPMail.new(3, nil, nil, nil)
mail4 = Net::POPMail.new(4, nil, nil, nil)
Net::POP3.any_instance.stubs(:start).yields(Net::POP3.new(nil, nil))
Net::POP3.any_instance.stubs(:mails).returns([mail1, mail2, mail3, mail4])
Net::POP3.any_instance.expects(:delete_all).never
mail1.stubs(:pop).returns(example_email)
mail2.stubs(:pop).returns(example_email)
mail3.stubs(:pop).returns(example_email)
mail4.stubs(:pop).returns(oldmail)
poller.expects(:process_popmail).times(3)
end
it "deletes emails from server when when deleting emails from server is enabled" do
Net::POPMail.any_instance.stubs(:delete).times(3)
SiteSetting.pop3_polling_delete_from_server = true
poller.poll_pop3
end
it "does not delete emails server inbox when deleting emails from server is disabled" do
Net::POPMail.any_instance.stubs(:delete).never
SiteSetting.pop3_polling_delete_from_server = false
poller.poll_pop3
end
it "does not process emails > 1 week old" do
SiteSetting.pop3_polling_delete_from_server = false
poller.poll_pop3
end
it "does not stop after an old email" do
SiteSetting.pop3_polling_delete_from_server = false
poller.expects(:mail_too_old?).returns(false, true, false, false).times(4)
poller.poll_pop3
end
end
end
describe "#process_popmail" do
def process_popmail(email_name)
pop_mail = stub("pop mail")
pop_mail.expects(:pop).returns(email(email_name))
Jobs::PollMailbox.new.process_popmail(pop_mail.pop)
end
it "does not reply to a bounced email" do
expect { process_popmail(:bounced_email) }.to_not change {
ActionMailer::Base.deliveries.count
}
incoming_email = IncomingEmail.last
expect(incoming_email.rejection_message).to eq(
I18n.t("emails.incoming.errors.bounced_email_error"),
)
end
end
describe "poller plugin" do
let(:poller_plugin) do
Class
.new(described_class) do
def set_enabled(e)
@enabled = e
end
def enabled?
@enabled
end
def poll_mailbox(process_cb)
process_cb.call(file_from_fixtures("original_message.eml", "emails"))
end
end
.new
end
let(:plugin) { Plugin::Instance.new }
before(:each) { plugin.register_email_poller(poller_plugin) }
after(:each) do
Discourse.plugins.delete plugin
DiscoursePluginRegistry.reset!
end
it "doesn't call process method when plugin is not active" do
poller_plugin.set_enabled(false)
poller.expects(:process_popmail).never
poller.execute({})
end
it "calls process method when plugin is active" do
poller_plugin.set_enabled(true)
poller.expects(:process_popmail).once
poller.execute({})
end
end
end