123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- require 'rails_helper'
- require 'time'
- describe Agents::ImapFolderAgent do
- module MessageMixin
- def folder
- 'INBOX'
- end
- def uidvalidity
- 100
- end
- def has_attachment?
- false
- end
- def body_parts(mime_types = %[text/plain text/enriched text/html])
- mime_types.map { |type|
- all_parts.find { |part|
- part.mime_type == type
- }
- }.compact.map! { |part|
- part.extend(Agents::ImapFolderAgent::Message::Scrubbed)
- }
- end
- include Agents::ImapFolderAgent::Message::Scrubbed
- end
- describe 'checking IMAP' do
- let(:valid_options) {
- {
- 'expected_update_period_in_days' => 1,
- 'host' => 'mail.example.net',
- 'ssl' => true,
- 'username' => 'foo',
- 'password' => 'bar',
- 'folders' => ['INBOX'],
- 'conditions' => {
- }
- }
- }
- let(:mails) {
- [
- Mail.read(Rails.root.join('spec/data_fixtures/imap1.eml')).tap { |mail|
- mail.extend(MessageMixin)
- stub(mail).uid.returns(1)
- stub(mail).raw_mail.returns(mail.encoded)
- },
- Mail.read(Rails.root.join('spec/data_fixtures/imap2.eml')).tap { |mail|
- mail.extend(MessageMixin)
- stub(mail).uid.returns(2)
- stub(mail).has_attachment?.returns(true)
- stub(mail).raw_mail.returns(mail.encoded)
- },
- ]
- }
- let(:expected_payloads) {
- [
- {
- 'message_id' => 'foo.123@mail.example.jp',
- 'folder' => 'INBOX',
- 'from' => 'nanashi.gombeh@example.jp',
- 'to' => ['jane.doe@example.com', 'john.doe@example.com'],
- 'cc' => [],
- 'date' => '2014-05-09T16:00:00+09:00',
- 'subject' => 'some subject',
- 'body' => "Some plain text\nSome second line\n",
- 'has_attachment' => false,
- 'matches' => {},
- 'mime_type' => 'text/plain',
- },
- {
- 'message_id' => 'bar.456@mail.example.com',
- 'folder' => 'INBOX',
- 'from' => 'john.doe@example.com',
- 'to' => ['jane.doe@example.com', 'nanashi.gombeh@example.jp'],
- 'cc' => [],
- 'subject' => 'Re: some subject',
- 'body' => "Some reply\n",
- 'date' => '2014-05-09T17:00:00+09:00',
- 'has_attachment' => true,
- 'matches' => {},
- 'mime_type' => 'text/plain',
- }
- ]
- }
- before do
- @checker = Agents::ImapFolderAgent.new(name: 'Example', options: valid_options, keep_events_for: 2.days)
- @checker.user = users(:bob)
- @checker.save!
- stub(@checker).each_unread_mail.returns { |yielder|
- seen = @checker.lastseen
- notified = @checker.notified
- mails.each_with_object(notified) { |mail|
- yielder[mail, notified]
- seen[mail.uidvalidity] = mail.uid
- }
- @checker.lastseen = seen
- @checker.notified = notified
- nil
- }
- end
- describe 'validations' do
- before do
- expect(@checker).to be_valid
- end
- it 'should validate the integer fields' do
- @checker.options['expected_update_period_in_days'] = 'nonsense'
- expect(@checker).not_to be_valid
- @checker.options['expected_update_period_in_days'] = '2'
- expect(@checker).to be_valid
- @checker.options['port'] = -1
- expect(@checker).not_to be_valid
- @checker.options['port'] = 'imap'
- expect(@checker).not_to be_valid
- @checker.options['port'] = '143'
- expect(@checker).to be_valid
- @checker.options['port'] = 993
- expect(@checker).to be_valid
- end
- it 'should validate the boolean fields' do
- %w[ssl mark_as_read].each do |key|
- @checker.options[key] = 1
- expect(@checker).not_to be_valid
- @checker.options[key] = false
- expect(@checker).to be_valid
- @checker.options[key] = 'true'
- expect(@checker).to be_valid
- @checker.options[key] = ''
- expect(@checker).to be_valid
- end
- end
- it 'should validate regexp conditions' do
- @checker.options['conditions'] = {
- 'subject' => '(foo'
- }
- expect(@checker).not_to be_valid
- @checker.options['conditions'] = {
- 'body' => '***'
- }
- expect(@checker).not_to be_valid
- @checker.options['conditions'] = {
- 'subject' => '\ARe:',
- 'body' => '(?<foo>http://\S+)'
- }
- expect(@checker).to be_valid
- end
- end
- describe '#check' do
- it 'should check for mails and save memory' do
- expect { @checker.check }.to change { Event.count }.by(2)
- expect(@checker.notified.sort).to eq(mails.map(&:message_id).sort)
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- expect(Event.last(2).map(&:payload)).to eq expected_payloads
- expect { @checker.check }.not_to change { Event.count }
- end
- it 'should narrow mails by To' do
- @checker.options['conditions']['to'] = 'John.Doe@*'
- expect { @checker.check }.to change { Event.count }.by(1)
- expect(@checker.notified.sort).to eq([mails.first.message_id])
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- expect(Event.last.payload).to eq(expected_payloads.first)
- expect { @checker.check }.not_to change { Event.count }
- end
- it 'should not fail when a condition on Cc is given and a mail does not have the field' do
- @checker.options['conditions']['cc'] = 'John.Doe@*'
- expect {
- expect { @checker.check }.not_to change { Event.count }
- }.not_to raise_exception
- end
- it 'should perform regexp matching and save named captures' do
- @checker.options['conditions'].update(
- 'subject' => '\ARe: (?<a>.+)',
- 'body' => 'Some (?<b>.+) reply',
- )
- expect { @checker.check }.to change { Event.count }.by(1)
- expect(@checker.notified.sort).to eq([mails.last.message_id])
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- expect(Event.last.payload).to eq(expected_payloads.last.update(
- 'body' => "<div dir=\"ltr\">Some HTML reply<br></div>\r\n",
- 'matches' => { 'a' => 'some subject', 'b' => 'HTML' },
- 'mime_type' => 'text/html',
- ))
- expect { @checker.check }.not_to change { Event.count }
- end
- it 'should narrow mails by has_attachment (true)' do
- @checker.options['conditions']['has_attachment'] = true
- expect { @checker.check }.to change { Event.count }.by(1)
- expect(Event.last.payload['subject']).to eq('Re: some subject')
- end
- it 'should narrow mails by has_attachment (false)' do
- @checker.options['conditions']['has_attachment'] = false
- expect { @checker.check }.to change { Event.count }.by(1)
- expect(Event.last.payload['subject']).to eq('some subject')
- end
- it 'should narrow mail parts by MIME types' do
- @checker.options['mime_types'] = %w[text/plain]
- @checker.options['conditions'].update(
- 'subject' => '\ARe: (?<a>.+)',
- 'body' => 'Some (?<b>.+) reply',
- )
- expect { @checker.check }.not_to change { Event.count }
- expect(@checker.notified.sort).to eq([])
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- end
- it 'should never mark mails as read unless mark_as_read is true' do
- mails.each { |mail|
- stub(mail).mark_as_read.never
- }
- expect { @checker.check }.to change { Event.count }.by(2)
- end
- it 'should mark mails as read if mark_as_read is true' do
- @checker.options['mark_as_read'] = true
- mails.each { |mail|
- stub(mail).mark_as_read.once
- }
- expect { @checker.check }.to change { Event.count }.by(2)
- end
- it 'should create just one event for multiple mails with the same Message-Id' do
- mails.first.message_id = mails.last.message_id
- @checker.options['mark_as_read'] = true
- mails.each { |mail|
- stub(mail).mark_as_read.once
- }
- expect { @checker.check }.to change { Event.count }.by(1)
- end
- it 'should delete mails if delete is true' do
- @checker.options['delete'] = true
- mails.each { |mail|
- stub(mail).delete.once
- }
- expect { @checker.check }.to change { Event.count }.by(2)
- end
- describe 'processing mails with a broken From header value' do
- before do
- # "from" patterns work against mail addresses and not
- # against text parts, so these mails should be skipped if a
- # "from" condition is given.
- mails.first.header['from'] = '.'
- mails.last.header['from'] = '@'
- end
- it 'should ignore them without failing if a "from" condition is given' do
- @checker.options['conditions']['from'] = '*'
- expect {
- expect { @checker.check }.not_to change { Event.count }
- }.not_to raise_exception
- end
- end
- describe 'with event_headers' do
- let(:expected_headers) {
- [
- {
- 'mime_version' => '1.0',
- 'x_foo' => "test1-1\ntest1-2"
- },
- {
- 'mime_version' => '1.0',
- 'x_foo' => "test2-1\ntest2-2"
- }
- ]
- }
- before do
- expected_payloads.zip(expected_headers) do |payload, headers|
- payload['headers'] = headers
- end
- @checker.options['event_headers'] = %w[mime-version x-foo]
- @checker.options['event_headers_style'] = 'snakecased'
- @checker.save!
- end
- it 'should check for mails and emit events with headers' do
- expect { @checker.check }.to change { Event.count }.by(2)
- expect(@checker.notified.sort).to eq(mails.map(&:message_id).sort)
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- expect(Event.last(2).map(&:payload)).to match expected_payloads
- expect { @checker.check }.not_to change { Event.count }
- end
- end
- describe 'with include_raw_mail' do
- before do
- @checker.options['include_raw_mail'] = true
- @checker.save!
- end
- it 'should check for mails and emit events with raw_mail' do
- expect { @checker.check }.to change { Event.count }.by(2)
- expect(@checker.notified.sort).to eq(mails.map(&:message_id).sort)
- expect(@checker.lastseen).to eq(mails.each_with_object(@checker.make_seen) { |mail, seen|
- seen[mail.uidvalidity] = mail.uid
- })
- expect(Event.last(2).map(&:payload)).to match expected_payloads.map.with_index { |payload, i|
- payload.merge(
- 'raw_mail' => satisfy { |d| Base64.decode64(d) == mails[i].encoded }
- )
- }
- expect { @checker.check }.not_to change { Event.count }
- end
- end
- end
- end
- describe 'Agents::ImapFolderAgent::Message::Scrubbed' do
- before do
- @class = Class.new do
- def subject
- "broken\xB7subject\xB6"
- end
- def body
- "broken\xB7body\xB6"
- end
- include Agents::ImapFolderAgent::Message::Scrubbed
- end
- @object = @class.new
- end
- describe '#scrubbed' do
- it 'should return a scrubbed string' do
- expect(@object.scrubbed(:subject)).to eq("broken<b7>subject<b6>")
- expect(@object.scrubbed(:body)).to eq("broken<b7>body<b6>")
- end
- end
- end
- end
|