fix(refactor): Cleanup the specs and the logic for FetchIMAP job (#8766)

This commit is contained in:
Pranav Raj S
2024-02-10 14:03:50 -08:00
committed by GitHub
parent 6a630bc489
commit eb379e1849
9 changed files with 384 additions and 273 deletions

View File

@@ -7,6 +7,7 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
return unless should_fetch_email?(channel) return unless should_fetch_email?(channel)
key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id) key = format(::Redis::Alfred::EMAIL_MESSAGE_MUTEX, inbox_id: channel.inbox.id)
with_lock(key, 5.minutes) do with_lock(key, 5.minutes) do
process_email_for_channel(channel) process_email_for_channel(channel)
end end
@@ -28,126 +29,14 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
end end
def process_email_for_channel(channel) def process_email_for_channel(channel)
if channel.microsoft? inbound_emails = if channel.microsoft?
fetch_mail_for_ms_provider(channel) Imap::MicrosoftFetchEmailService.new(channel: channel).perform
else else
fetch_mail_for_channel(channel) Imap::FetchEmailService.new(channel: channel).perform
end end
# clearing old failures like timeouts since the mail is now successfully processed inbound_emails.map do |inbound_mail|
channel.reauthorized!
end
def fetch_mail_for_channel(channel)
imap_client = build_imap_client(channel, channel.imap_password, 'PLAIN')
message_ids_with_seq = fetch_message_ids_with_sequence(imap_client, channel)
message_ids_with_seq.each do |message_id_with_seq|
process_message_id(channel, imap_client, message_id_with_seq)
end
end
def process_message_id(channel, imap_client, message_id_with_seq)
seq_no, message_id = message_id_with_seq
return if email_already_present?(channel, message_id)
if message_id.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Empty message id for #{channel.email} with seq no. <#{seq_no}>."
return
end
# Fetch the original mail content using the sequence no
mail_str = imap_client.fetch(seq_no, 'RFC822')[0].attr['RFC822']
if mail_str.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetch failed for #{channel.email} with message-id <#{message_id}>."
return
end
inbound_mail = build_mail_from_string(mail_str)
mail_info_logger(channel, inbound_mail, seq_no)
process_mail(inbound_mail, channel) process_mail(inbound_mail, channel)
end end
# Sends a FETCH command to retrieve data associated with a message in the mailbox.
# You can send batches of message sequence number in `.fetch` method.
def fetch_message_ids_with_sequence(imap_client, channel)
seq_nums = fetch_available_mail_sequence_numbers(imap_client)
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetching mails from #{channel.email}, found #{seq_nums.length}."
message_ids_with_seq = []
seq_nums.each_slice(10).each do |batch|
# Fetch only message-id only without mail body or contents.
batch_message_ids = imap_client.fetch(batch, 'BODY.PEEK[HEADER]')
# .fetch returns an array of Net::IMAP::FetchData or nil
# (instead of an empty array) if there is no matching message.
# Check
if batch_message_ids.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetching the batch failed for #{channel.email}."
next
end
batch_message_ids.each do |data|
message_id = build_mail_from_string(data.attr['BODY[HEADER]']).message_id
message_ids_with_seq.push([data.seqno, message_id])
end
end
message_ids_with_seq
end
# Sends a SEARCH command to search the mailbox for messages that were
# created between yesterday and today and returns message sequence numbers.
# Return <message set>
def fetch_available_mail_sequence_numbers(imap_client)
imap_client.search(['SINCE', yesterday])
end
def fetch_mail_for_ms_provider(channel)
return if channel.provider_config['access_token'].blank?
access_token = valid_access_token channel
return unless access_token
imap_client = build_imap_client(channel, access_token, 'XOAUTH2')
process_mails(imap_client, channel)
end
def process_mails(imap_client, channel)
fetch_available_mail_sequence_numbers(imap_client).each do |seq_no|
inbound_mail = Mail.read_from_string imap_client.fetch(seq_no, 'RFC822')[0].attr['RFC822']
mail_info_logger(channel, inbound_mail, seq_no)
next if channel.inbox.messages.find_by(source_id: inbound_mail.message_id).present?
process_mail(inbound_mail, channel)
end
end
def mail_info_logger(channel, inbound_mail, uid)
return if Rails.env.test?
Rails.logger.info("
#{channel.provider} Email id: #{inbound_mail.from} - message_source_id: #{inbound_mail.message_id} - sequence id: #{uid}")
end
def build_imap_client(channel, access_token, auth_method)
imap = Net::IMAP.new(channel.imap_address, channel.imap_port, true)
imap.authenticate(auth_method, channel.imap_login, access_token)
imap.select('INBOX')
imap
end
def email_already_present?(channel, message_id)
channel.inbox.messages.find_by(source_id: message_id).present?
end
def build_mail_from_string(raw_email_content)
Mail.read_from_string(raw_email_content)
end end
def process_mail(inbound_mail, channel) def process_mail(inbound_mail, channel)
@@ -157,13 +46,4 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
Rails.logger.error(" Rails.logger.error("
#{channel.provider} Email dropped: #{inbound_mail.from} and message_source_id: #{inbound_mail.message_id}") #{channel.provider} Email dropped: #{inbound_mail.from} and message_source_id: #{inbound_mail.message_id}")
end end
# Making sure the access token is valid for microsoft provider
def valid_access_token(channel)
Microsoft::RefreshOauthTokenService.new(channel: channel).access_token
end
def yesterday
(Time.zone.today - 1).strftime('%d-%b-%Y')
end
end end

View File

@@ -0,0 +1,115 @@
require 'net/imap'
class Imap::BaseFetchEmailService
pattr_initialize [:channel!]
def perform
# Override this method
end
private
def authentication_type
# Override this method
end
def imap_password
# Override this method
end
def imap_client
@imap_client ||= build_imap_client
end
def mail_info_logger(inbound_mail, seq_no)
return if Rails.env.test?
Rails.logger.info("
#{channel.provider} Email id: #{inbound_mail.from} - message_source_id: #{inbound_mail.message_id} - sequence id: #{seq_no}")
end
def email_already_present?(channel, message_id)
channel.inbox.messages.find_by(source_id: message_id).present?
end
def fetch_mail_for_channel
message_ids_with_seq = fetch_message_ids_with_sequence
message_ids_with_seq.filter_map do |message_id_with_seq|
process_message_id(message_id_with_seq)
end
end
def process_message_id(message_id_with_seq)
seq_no, message_id = message_id_with_seq
if message_id.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Empty message id for #{channel.email} with seq no. <#{seq_no}>."
return
end
return if email_already_present?(channel, message_id)
# Fetch the original mail content using the sequence no
mail_str = imap_client.fetch(seq_no, 'RFC822')[0].attr['RFC822']
if mail_str.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetch failed for #{channel.email} with message-id <#{message_id}>."
return
end
inbound_mail = build_mail_from_string(mail_str)
mail_info_logger(inbound_mail, seq_no)
inbound_mail
end
# Sends a FETCH command to retrieve data associated with a message in the mailbox.
# You can send batches of message sequence number in `.fetch` method.
def fetch_message_ids_with_sequence
seq_nums = fetch_available_mail_sequence_numbers
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetching mails from #{channel.email}, found #{seq_nums.length}."
message_ids_with_seq = []
seq_nums.each_slice(10).each do |batch|
# Fetch only message-id only without mail body or contents.
batch_message_ids = imap_client.fetch(batch, 'BODY.PEEK[HEADER]')
# .fetch returns an array of Net::IMAP::FetchData or nil
# (instead of an empty array) if there is no matching message.
# Check
if batch_message_ids.blank?
Rails.logger.info "[IMAP::FETCH_EMAIL_SERVICE] Fetching the batch failed for #{channel.email}."
next
end
batch_message_ids.each do |data|
message_id = build_mail_from_string(data.attr['BODY[HEADER]']).message_id
message_ids_with_seq.push([data.seqno, message_id])
end
end
message_ids_with_seq
end
# Sends a SEARCH command to search the mailbox for messages that were
# created between yesterday and today and returns message sequence numbers.
# Return <message set>
def fetch_available_mail_sequence_numbers
imap_client.search(['SINCE', yesterday])
end
def build_imap_client
imap = Net::IMAP.new(channel.imap_address, port: channel.imap_port, ssl: true)
imap.authenticate(authentication_type, channel.imap_login, imap_password)
imap.select('INBOX')
imap
end
def build_mail_from_string(raw_email_content)
Mail.read_from_string(raw_email_content)
end
def yesterday
(Time.zone.today - 1).strftime('%d-%b-%Y')
end
end

View File

@@ -0,0 +1,15 @@
class Imap::FetchEmailService < Imap::BaseFetchEmailService
def perform
fetch_mail_for_channel
end
private
def authentication_type
'PLAIN'
end
def imap_password
channel.imap_password
end
end

View File

@@ -0,0 +1,17 @@
class Imap::MicrosoftFetchEmailService < Imap::BaseFetchEmailService
def perform
return if channel.provider_config['access_token'].blank?
fetch_mail_for_channel
end
private
def authentication_type
'XOAUTH2'
end
def imap_password
Microsoft::RefreshOauthTokenService.new(channel: channel).access_token
end
end

View File

@@ -25,5 +25,14 @@ FactoryBot.define do
end end
provider { 'microsoft' } provider { 'microsoft' }
end end
trait :imap_email do
imap_enabled { true }
imap_address { 'imap.gmail.com' }
imap_port { 993 }
imap_login { 'email@example.com' }
imap_password { 'random-password' }
imap_enable_ssl { true }
end
end end
end end

View File

@@ -1,161 +1,106 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Inboxes::FetchImapEmailsJob do RSpec.describe Inboxes::FetchImapEmailsJob do
include ActiveJob::TestHelper
include ActionMailbox::TestHelper include ActionMailbox::TestHelper
let(:account) { create(:account) } let(:account) { create(:account) }
let(:imap_email_channel) do let(:imap_email_channel) { create(:channel_email, :imap_email, account: account) }
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com', imap_port: 993, imap_login: 'imap@gmail.com', let(:channel_with_imap_disabled) { create(:channel_email, :imap_email, imap_enabled: false, account: account) }
imap_password: 'password', account: account)
end
let(:microsoft_imap_email_channel) { create(:channel_email, :microsoft_email) } let(:microsoft_imap_email_channel) { create(:channel_email, :microsoft_email) }
let(:ms_email_inbox) { create(:inbox, channel: microsoft_imap_email_channel, account: account) }
let!(:conversation) { create(:conversation, inbox: imap_email_channel.inbox, account: account) }
let(:inbound_mail) { create_inbound_email_from_mail(from: 'testemail@gmail.com', to: 'imap@outlook.com', subject: 'Hello!') }
let(:inbound_mail_with_attachments) { create_inbound_email_from_fixture('multiple_attachments.eml') }
describe '#perform' do
it 'enqueues the job' do it 'enqueues the job' do
expect { described_class.perform_later }.to have_enqueued_job(described_class) expect do
.on_queue('scheduled_jobs') described_class.perform_later
end.to have_enqueued_job(described_class).on_queue('scheduled_jobs')
end end
context 'when imap fetch new emails' do context 'when IMAP is disabled' do
it 'process the email' do it 'does not fetch emails' do
email = Mail.new do expect(Imap::FetchEmailService).not_to receive(:new)
to 'test@outlook.com' expect(Imap::MicrosoftFetchEmailService).not_to receive(:new)
from 'test@gmail.com' described_class.perform_now(channel_with_imap_disabled)
subject :test.to_s end
body 'hello'
end end
imap_fetch_mail = Net::IMAP::FetchData.new context 'when IMAP reauthorization is required' do
imap_fetch_mail.attr = { seqno: 1, RFC822: email }.with_indifferent_access it 'does not fetch emails' do
10.times do
imap = double imap_email_channel.authorization_error!
allow(Net::IMAP).to receive(:new).and_return(imap)
allow(imap).to receive(:authenticate)
allow(imap).to receive(:select)
allow(imap).to receive(:search).and_return([1])
allow(imap).to receive(:fetch).and_return([imap_fetch_mail])
read_mail = Mail::Message.new(date: DateTime.now, from: 'testemail@gmail.com', to: 'imap@outlook.com', subject: 'Hello!')
allow(Mail).to receive(:read_from_string).and_return(inbound_mail.mail)
imap_mailbox = double
allow(Imap::ImapMailbox).to receive(:new).and_return(imap_mailbox)
expect(imap_mailbox).to receive(:process).with(read_mail, imap_email_channel).once
described_class.perform_now(imap_email_channel)
end end
it 'process the email with no date' do expect(Imap::FetchEmailService).not_to receive(:new)
fixture_path = Rails.root.join('spec/fixtures/files/mail_with_no_date.eml') # Confirm the imap_enabled flag is true to avoid false positives.
eml_content = File.read(fixture_path) expect(imap_email_channel.imap_enabled?).to be true
inbound_mail_with_no_date = create_inbound_email_from_fixture('mail_with_no_date.eml')
email_header = Net::IMAP::FetchData.new(1, 'BODY[HEADER]' => eml_content)
imap_fetch_mail = Net::IMAP::FetchData.new(1, 'RFC822' => eml_content)
imap = double
allow(Net::IMAP).to receive(:new).and_return(imap)
allow(imap).to receive(:authenticate)
allow(imap).to receive(:select)
allow(imap).to receive(:search).and_return([1])
allow(imap).to receive(:fetch).with([1], 'BODY.PEEK[HEADER]').and_return([email_header])
allow(imap).to receive(:fetch).with(1, 'RFC822').and_return([imap_fetch_mail])
imap_mailbox = double
allow(Imap::ImapMailbox).to receive(:new).and_return(imap_mailbox)
expect(imap_mailbox).to receive(:process).with(inbound_mail_with_no_date.mail, imap_email_channel).once
described_class.perform_now(imap_email_channel) described_class.perform_now(imap_email_channel)
end end
end end
context 'when imap fetch new emails with more than 15 attachments' do context 'when the channel is regular imap' do
it 'process the email' do it 'calls the imap fetch service' do
email = Mail.new do fetch_service = double
to 'test@outlook.com' allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel).and_return(fetch_service)
from 'test@gmail.com' allow(fetch_service).to receive(:perform).and_return([])
subject :test.to_s
body 'hello'
end
imap_fetch_mail = Net::IMAP::FetchData.new
imap_fetch_mail.attr = { seqno: 1, RFC822: email }.with_indifferent_access
imap = double
allow(Net::IMAP).to receive(:new).and_return(imap)
allow(imap).to receive(:authenticate)
allow(imap).to receive(:select)
allow(imap).to receive(:search).and_return([1])
allow(imap).to receive(:fetch).and_return([imap_fetch_mail])
inbound_mail_with_attachments.mail.date = DateTime.now
allow(Mail).to receive(:read_from_string).and_return(inbound_mail_with_attachments.mail)
imap_mailbox = Imap::ImapMailbox.new
allow(Imap::ImapMailbox).to receive(:new).and_return(imap_mailbox)
described_class.perform_now(imap_email_channel) described_class.perform_now(imap_email_channel)
expect(Message.last.attachments.count).to eq(15) expect(fetch_service).to have_received(:perform)
end end
end end
context 'when imap fetch new emails for microsoft mailer' do context 'when the channel is Microsoft' do
it 'fetch and process all emails' do it 'calls the Microsoft fetch service' do
email = Mail.new do fetch_service = double
to 'test@outlook.com' allow(Imap::MicrosoftFetchEmailService).to receive(:new).with(channel: microsoft_imap_email_channel).and_return(fetch_service)
from 'test@gmail.com' allow(fetch_service).to receive(:perform).and_return([])
subject :test.to_s
body 'hello'
end
imap_fetch_mail = Net::IMAP::FetchData.new
imap_fetch_mail.attr = { RFC822: email }.with_indifferent_access
ms_imap = double
allow(Net::IMAP).to receive(:new).and_return(ms_imap)
allow(ms_imap).to receive(:authenticate)
allow(ms_imap).to receive(:select)
allow(ms_imap).to receive(:search).and_return([1])
allow(ms_imap).to receive(:fetch).and_return([imap_fetch_mail])
allow(Mail).to receive(:read_from_string).and_return(inbound_mail)
ms_imap_email_inbox = double
allow(Imap::ImapMailbox).to receive(:new).and_return(ms_imap_email_inbox)
expect(ms_imap_email_inbox).to receive(:process).with(inbound_mail, microsoft_imap_email_channel).once
described_class.perform_now(microsoft_imap_email_channel) described_class.perform_now(microsoft_imap_email_channel)
expect(fetch_service).to have_received(:perform)
end end
end end
context 'when imap fetch existing emails' do context 'when IMAP connection errors out' do
it 'does not process the email' do it 'mark the connection for authorization required' do
email = Mail.new do allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel).and_raise(Errno::ECONNREFUSED)
to 'test@outlook.com' allow(Redis::Alfred).to receive(:incr)
from 'test@gmail.com'
subject :test.to_s expect(Redis::Alfred).to receive(:incr).with("AUTHORIZATION_ERROR_COUNT:channel_email:#{imap_email_channel.id}")
body 'hello' described_class.perform_now(imap_email_channel)
message_id '<messageId@example.com>' end
end end
create(:message, message_type: 'incoming', source_id: email.message_id, account: account, inbox: imap_email_channel.inbox, context 'when the fetch service returns the email objects' do
conversation: conversation) let(:inbound_mail) { create_inbound_email_from_fixture('welcome.eml').mail }
let(:mailbox) { double }
let(:exception_tracker) { double }
let(:fetch_service) { double }
allow(Mail).to receive(:find).and_return([email]) before do
imap_mailbox = double allow(Imap::ImapMailbox).to receive(:new).and_return(mailbox)
allow(Imap::ImapMailbox).to receive(:new).and_return(imap_mailbox) allow(ChatwootExceptionTracker).to receive(:new).and_return(exception_tracker)
expect(imap_mailbox).not_to receive(:process).with(email, imap_email_channel)
allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel).and_return(fetch_service)
allow(fetch_service).to receive(:perform).and_return([inbound_mail])
end
it 'calls the mailbox to create emails' do
allow(mailbox).to receive(:process)
expect(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel).and_return(fetch_service)
expect(fetch_service).to receive(:perform).and_return([inbound_mail])
expect(mailbox).to receive(:process).with(inbound_mail, imap_email_channel)
described_class.perform_now(imap_email_channel) described_class.perform_now(imap_email_channel)
end end
it 'logs errors if mailbox returns errors' do
allow(mailbox).to receive(:process).and_raise(StandardError)
expect(exception_tracker).to receive(:capture_exception)
described_class.perform_now(imap_email_channel)
end
end
end end
end end

View File

@@ -3,14 +3,10 @@ require 'rails_helper'
RSpec.describe Imap::ImapMailbox do RSpec.describe Imap::ImapMailbox do
include ActionMailbox::TestHelper include ActionMailbox::TestHelper
describe 'add mail as a new conversation in the email inbox' do describe '#process' do
let(:account) { create(:account) } let(:account) { create(:account) }
let(:agent) { create(:user, email: 'agent@example.com', account: account) } let(:agent) { create(:user, email: 'agent@example.com', account: account) }
let(:channel) do let(:channel) { create(:channel_email, :imap_email) }
create(:channel_email, imap_enabled: true, imap_address: 'imap.gmail.com',
imap_port: 993, imap_login: 'imap@gmail.com', imap_password: 'password',
account: account)
end
let(:inbox) { channel.inbox } let(:inbox) { channel.inbox }
let!(:contact) { create(:contact, email: 'email@gmail.com', phone_number: '+919584546666', account: account, identifier: '123') } let!(:contact) { create(:contact, email: 'email@gmail.com', phone_number: '+919584546666', account: account, identifier: '123') }
let(:conversation) { Conversation.where(inbox_id: channel.inbox).last } let(:conversation) { Conversation.where(inbox_id: channel.inbox).last }
@@ -20,17 +16,33 @@ RSpec.describe Imap::ImapMailbox do
create(:contact_inbox, contact_id: contact.id, inbox_id: channel.inbox.id) create(:contact_inbox, contact_id: contact.id, inbox_id: channel.inbox.id)
end end
context 'when a new email from non existing contact' do context 'when the email is from a new contact' do
let(:inbound_mail) { create_inbound_email_from_mail(from: 'testemail@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') } let(:inbound_mail) { create_inbound_email_from_mail(from: 'testemail@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') }
it 'creates the contact and conversation with message' do it 'creates the contact and conversation with message' do
expect do
class_instance.process(inbound_mail.mail, channel) class_instance.process(inbound_mail.mail, channel)
end.to change(Conversation, :count).by(1)
expect(conversation.contact.email).to eq(inbound_mail.mail.from.first) expect(conversation.contact.email).to eq(inbound_mail.mail.from.first)
expect(conversation.additional_attributes['source']).to eq('email') expect(conversation.additional_attributes['source']).to eq('email')
expect(conversation.messages.empty?).to be false expect(conversation.messages.empty?).to be false
end end
end end
context 'when the email has 15 or more attachments' do
let(:inbound_mail) { create_inbound_email_from_fixture('multiple_attachments.eml') }
it 'creates a converstation and a message properly' do
expect do
class_instance.process(inbound_mail.mail, channel)
end.to change(Conversation, :count).by(1)
expect(conversation.contact.email).to eq(inbound_mail.mail.from.first)
expect(conversation.messages.last.attachments.count).to be 15
end
end
context 'when a new email from existing contact' do context 'when a new email from existing contact' do
let(:inbound_mail) { create_inbound_email_from_mail(from: 'email@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') } let(:inbound_mail) { create_inbound_email_from_mail(from: 'email@gmail.com', to: 'imap@gmail.com', subject: 'Hello!') }

View File

@@ -0,0 +1,65 @@
require 'rails_helper'
RSpec.describe Imap::FetchEmailService do
include ActionMailbox::TestHelper
let(:logger) { instance_double(ActiveSupport::Logger, info: true, error: true) }
let(:account) { create(:account) }
let(:imap_email_channel) { create(:channel_email, :imap_email, account: account) }
let(:imap) { instance_double(Net::IMAP) }
let(:eml_content_with_message_id) { Rails.root.join('spec/fixtures/files/only_text.eml').read }
describe '#perform' do
before do
allow(Rails).to receive(:logger).and_return(logger)
allow(Net::IMAP).to receive(:new).with(
imap_email_channel.imap_address, port: imap_email_channel.imap_port, ssl: true
).and_return(imap)
allow(imap).to receive(:authenticate).with(
'PLAIN', imap_email_channel.imap_login, imap_email_channel.imap_password
)
allow(imap).to receive(:select).with('INBOX')
end
context 'when new emails are available in the mailbox' do
it 'fetches the emails and returns the emails that are not present in the db' do
travel_to '26.10.2020 10:00'.to_datetime do
email_object = create_inbound_email_from_fixture('only_text.eml')
email_header = Net::IMAP::FetchData.new(1, 'BODY[HEADER]' => eml_content_with_message_id)
imap_fetch_mail = Net::IMAP::FetchData.new(1, 'RFC822' => eml_content_with_message_id)
allow(imap).to receive(:search).with(%w[SINCE 25-Oct-2020]).and_return([1])
allow(imap).to receive(:fetch).with([1], 'BODY.PEEK[HEADER]').and_return([email_header])
allow(imap).to receive(:fetch).with(1, 'RFC822').and_return([imap_fetch_mail])
result = described_class.new(channel: imap_email_channel).perform
expect(result.length).to eq 1
expect(result[0].message_id).to eq email_object.message_id
expect(imap).to have_received(:search).with(%w[SINCE 25-Oct-2020])
expect(imap).to have_received(:fetch).with([1], 'BODY.PEEK[HEADER]')
expect(imap).to have_received(:fetch).with(1, 'RFC822')
expect(logger).to have_received(:info).with("[IMAP::FETCH_EMAIL_SERVICE] Fetching mails from #{imap_email_channel.email}, found 1.")
end
end
it 'fetches the emails and returns the mail objects that are not present in the db' do
travel_to '26.10.2020 10:00'.to_datetime do
email_object = create_inbound_email_from_fixture('only_text.eml')
create(:message, source_id: email_object.message_id, account: account, inbox: imap_email_channel.inbox)
email_header = Net::IMAP::FetchData.new(1, 'BODY[HEADER]' => eml_content_with_message_id)
allow(imap).to receive(:search).with(%w[SINCE 25-Oct-2020]).and_return([1])
allow(imap).to receive(:fetch).with([1], 'BODY.PEEK[HEADER]').and_return([email_header])
result = described_class.new(channel: imap_email_channel).perform
expect(result.length).to eq 0
expect(imap).to have_received(:search).with(%w[SINCE 25-Oct-2020])
expect(imap).to have_received(:fetch).with([1], 'BODY.PEEK[HEADER]')
expect(imap).not_to have_received(:fetch).with(1, 'RFC822')
end
end
end
end
end

View File

@@ -0,0 +1,53 @@
require 'rails_helper'
RSpec.describe Imap::MicrosoftFetchEmailService do
include ActionMailbox::TestHelper
let(:logger) { instance_double(ActiveSupport::Logger, info: true, error: true) }
let(:account) { create(:account) }
let(:microsoft_channel) { create(:channel_email, :microsoft_email, account: account) }
let(:imap) { instance_double(Net::IMAP) }
let(:refresh_token_service) { double }
let(:eml_content_with_message_id) { Rails.root.join('spec/fixtures/files/only_text.eml').read }
describe '#perform' do
before do
allow(Rails).to receive(:logger).and_return(logger)
allow(Net::IMAP).to receive(:new).with(
microsoft_channel.imap_address, port: microsoft_channel.imap_port, ssl: true
).and_return(imap)
allow(imap).to receive(:authenticate).with(
'XOAUTH2', microsoft_channel.imap_login, microsoft_channel.provider_config['access_token']
)
allow(imap).to receive(:select).with('INBOX')
allow(Microsoft::RefreshOauthTokenService).to receive(:new).and_return(refresh_token_service)
allow(refresh_token_service).to receive(:access_token).and_return(microsoft_channel.provider_config['access_token'])
end
context 'when new emails are available in the mailbox' do
it 'fetches the emails and returns the emails that are not present in the db' do
travel_to '26.10.2020 10:00'.to_datetime do
email_object = create_inbound_email_from_fixture('only_text.eml')
email_header = Net::IMAP::FetchData.new(1, 'BODY[HEADER]' => eml_content_with_message_id)
imap_fetch_mail = Net::IMAP::FetchData.new(1, 'RFC822' => eml_content_with_message_id)
allow(imap).to receive(:search).with(%w[SINCE 25-Oct-2020]).and_return([1])
allow(imap).to receive(:fetch).with([1], 'BODY.PEEK[HEADER]').and_return([email_header])
allow(imap).to receive(:fetch).with(1, 'RFC822').and_return([imap_fetch_mail])
result = described_class.new(channel: microsoft_channel).perform
expect(refresh_token_service).to have_received(:access_token)
expect(result.length).to eq 1
expect(result[0].message_id).to eq email_object.message_id
expect(imap).to have_received(:search).with(%w[SINCE 25-Oct-2020])
expect(imap).to have_received(:fetch).with([1], 'BODY.PEEK[HEADER]')
expect(imap).to have_received(:fetch).with(1, 'RFC822')
expect(logger).to have_received(:info).with("[IMAP::FETCH_EMAIL_SERVICE] Fetching mails from #{microsoft_channel.email}, found 1.")
end
end
end
end
end