mail_room 0.9.1 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/.gitlab-ci.yml +14 -12
  3. data/.rubocop.yml +5 -0
  4. data/.rubocop_todo.yml +501 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +15 -6
  7. data/CHANGELOG.md +25 -0
  8. data/README.md +137 -14
  9. data/Rakefile +1 -1
  10. data/lib/mail_room/arbitration/redis.rb +8 -15
  11. data/lib/mail_room/cli.rb +9 -2
  12. data/lib/mail_room/connection.rb +6 -160
  13. data/lib/mail_room/crash_handler.rb +26 -0
  14. data/lib/mail_room/delivery/letter_opener.rb +1 -1
  15. data/lib/mail_room/delivery/postback.rb +39 -7
  16. data/lib/mail_room/delivery/que.rb +4 -2
  17. data/lib/mail_room/delivery/sidekiq.rb +6 -4
  18. data/lib/mail_room/imap/connection.rb +200 -0
  19. data/lib/mail_room/imap/message.rb +19 -0
  20. data/lib/mail_room/imap.rb +8 -0
  21. data/lib/mail_room/logger/structured.rb +35 -0
  22. data/lib/mail_room/mailbox.rb +123 -28
  23. data/lib/mail_room/mailbox_watcher.rb +11 -1
  24. data/lib/mail_room/message.rb +16 -0
  25. data/lib/mail_room/microsoft_graph/connection.rb +217 -0
  26. data/lib/mail_room/microsoft_graph.rb +7 -0
  27. data/lib/mail_room/version.rb +1 -1
  28. data/lib/mail_room.rb +3 -1
  29. data/logfile.log +1 -0
  30. data/mail_room.gemspec +8 -4
  31. data/spec/fixtures/test_config.yml +2 -0
  32. data/spec/lib/arbitration/redis_spec.rb +44 -24
  33. data/spec/lib/cli_spec.rb +46 -11
  34. data/spec/lib/configuration_spec.rb +13 -10
  35. data/spec/lib/coordinator_spec.rb +11 -9
  36. data/spec/lib/crash_handler_spec.rb +42 -0
  37. data/spec/lib/delivery/letter_opener_spec.rb +10 -6
  38. data/spec/lib/delivery/logger_spec.rb +10 -12
  39. data/spec/lib/delivery/postback_spec.rb +77 -17
  40. data/spec/lib/delivery/que_spec.rb +6 -9
  41. data/spec/lib/delivery/sidekiq_spec.rb +33 -11
  42. data/spec/lib/imap/connection_spec.rb +61 -0
  43. data/spec/lib/imap/message_spec.rb +36 -0
  44. data/spec/lib/logger/structured_spec.rb +87 -0
  45. data/spec/lib/mailbox_spec.rb +109 -33
  46. data/spec/lib/mailbox_watcher_spec.rb +54 -41
  47. data/spec/lib/message_spec.rb +35 -0
  48. data/spec/lib/microsoft_graph/connection_spec.rb +190 -0
  49. data/spec/spec_helper.rb +21 -2
  50. metadata +92 -31
  51. data/lib/mail_room/backports/imap.rb +0 -33
  52. data/spec/lib/connection_spec.rb +0 -63
@@ -3,27 +3,31 @@ require 'mail_room/delivery/letter_opener'
3
3
 
4
4
  describe MailRoom::Delivery::LetterOpener do
5
5
  describe '#deliver' do
6
- let(:mailbox) {MailRoom::Mailbox.new(:location => '/tmp/somewhere')}
6
+ let(:mailbox) {build_mailbox(location: '/tmp/somewhere')}
7
7
  let(:delivery_method) {stub(:deliver!)}
8
8
  let(:mail) {stub}
9
9
 
10
10
  before :each do
11
11
  Mail.stubs(:read_from_string).returns(mail)
12
12
  ::LetterOpener::DeliveryMethod.stubs(:new).returns(delivery_method)
13
-
14
- MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
15
13
  end
16
14
 
17
15
  it 'creates a new LetterOpener::DeliveryMethod' do
18
- expect(::LetterOpener::DeliveryMethod).to have_received(:new).with(:location => '/tmp/somewhere')
16
+ ::LetterOpener::DeliveryMethod.expects(:new).with(location: '/tmp/somewhere').returns(delivery_method)
17
+
18
+ MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
19
19
  end
20
20
 
21
21
  it 'parses the message string with Mail' do
22
- expect(::Mail).to have_received(:read_from_string).with('a message')
22
+ ::Mail.expects(:read_from_string).with('a message')
23
+
24
+ MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
23
25
  end
24
26
 
25
27
  it 'delivers the mail message' do
26
- expect(delivery_method).to have_received(:deliver!).with(mail)
28
+ delivery_method.expects(:deliver!).with(mail)
29
+
30
+ MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
27
31
  end
28
32
  end
29
33
  end
@@ -4,43 +4,41 @@ require 'mail_room/delivery/logger'
4
4
  describe MailRoom::Delivery::Logger do
5
5
  describe '#initialize' do
6
6
  context "without a log path" do
7
- let(:mailbox) {MailRoom::Mailbox.new}
7
+ let(:mailbox) {build_mailbox}
8
8
 
9
9
  it 'creates a new ruby logger' do
10
10
  ::Logger.stubs(:new)
11
11
 
12
- MailRoom::Delivery::Logger.new(mailbox)
12
+ ::Logger.expects(:new).with(STDOUT)
13
13
 
14
- expect(::Logger).to have_received(:new).with(STDOUT)
14
+ MailRoom::Delivery::Logger.new(mailbox)
15
15
  end
16
16
  end
17
17
 
18
18
  context "with a log path" do
19
- let(:mailbox) {MailRoom::Mailbox.new(:log_path => '/var/log/mail-room.log')}
19
+ let(:mailbox) {build_mailbox(log_path: '/var/log/mail-room.log')}
20
20
 
21
21
  it 'creates a new file to append to' do
22
- ::Logger.stubs(:new)
23
22
  file = stub(:sync=)
24
- ::File.stubs(:open).returns(file)
25
23
 
26
- MailRoom::Delivery::Logger.new(mailbox)
24
+ File.expects(:open).with('/var/log/mail-room.log', 'a').returns(file)
25
+ ::Logger.stubs(:new).with(file)
27
26
 
28
- expect(File).to have_received(:open).with('/var/log/mail-room.log', 'a')
29
- expect(::Logger).to have_received(:new).with(file)
27
+ MailRoom::Delivery::Logger.new(mailbox)
30
28
  end
31
29
  end
32
30
  end
33
31
 
34
32
  describe '#deliver' do
35
- let(:mailbox) {MailRoom::Mailbox.new}
33
+ let(:mailbox) {build_mailbox}
36
34
 
37
35
  it 'writes the message to info' do
38
36
  logger = stub(:info)
39
37
  ::Logger.stubs(:new).returns(logger)
40
38
 
41
- MailRoom::Delivery::Logger.new(mailbox).deliver('a message')
39
+ logger.expects(:info).with('a message')
42
40
 
43
- expect(logger).to have_received(:info).with('a message')
41
+ MailRoom::Delivery::Logger.new(mailbox).deliver('a message')
44
42
  end
45
43
  end
46
44
  end
@@ -3,29 +3,89 @@ require 'mail_room/delivery/postback'
3
3
 
4
4
  describe MailRoom::Delivery::Postback do
5
5
  describe '#deliver' do
6
- let(:mailbox) {MailRoom::Mailbox.new({
7
- :delivery_url => 'http://localhost/inbox',
8
- :delivery_token => 'abcdefg'
9
- })}
6
+ context 'with token auth delivery' do
7
+ let(:mailbox) {build_mailbox({
8
+ delivery_url: 'http://localhost/inbox',
9
+ delivery_token: 'abcdefg'
10
+ })}
10
11
 
11
- it 'posts the message with faraday' do
12
- connection = stub
13
- request = stub
14
- Faraday.stubs(:new).returns(connection)
12
+ let(:delivery_options) {
13
+ MailRoom::Delivery::Postback::Options.new(mailbox)
14
+ }
15
15
 
16
- connection.stubs(:token_auth)
17
- connection.stubs(:post).yields(request)
16
+ it 'posts the message with faraday' do
17
+ connection = stub
18
+ request = stub
19
+ Faraday.stubs(:new).returns(connection)
18
20
 
19
- request.stubs(:url)
20
- request.stubs(:body=)
21
+ connection.expects(:token_auth).with('abcdefg')
22
+ connection.expects(:post).yields(request)
21
23
 
22
- MailRoom::Delivery::Postback.new(mailbox).deliver('a message')
24
+ request.expects(:url).with('http://localhost/inbox')
25
+ request.expects(:body=).with('a message')
23
26
 
24
- expect(connection).to have_received(:token_auth).with('abcdefg')
25
- expect(connection).to have_received(:post)
27
+ MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
28
+ end
29
+ end
30
+
31
+ context 'with basic auth delivery options' do
32
+ let(:mailbox) {build_mailbox({
33
+ delivery_options: {
34
+ url: 'http://localhost/inbox',
35
+ username: 'user1',
36
+ password: 'password123abc'
37
+ }
38
+ })}
39
+
40
+ let(:delivery_options) {
41
+ MailRoom::Delivery::Postback::Options.new(mailbox)
42
+ }
43
+
44
+ it 'posts the message with faraday' do
45
+ connection = stub
46
+ request = stub
47
+ Faraday.stubs(:new).returns(connection)
48
+
49
+ connection.expects(:basic_auth).with('user1', 'password123abc')
50
+ connection.expects(:post).yields(request)
51
+
52
+ request.expects(:url).with('http://localhost/inbox')
53
+ request.expects(:body=).with('a message')
54
+
55
+ MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
56
+ end
57
+
58
+ context 'with content type in the delivery options' do
59
+ let(:mailbox) {build_mailbox({
60
+ delivery_options: {
61
+ url: 'http://localhost/inbox',
62
+ username: 'user1',
63
+ password: 'password123abc',
64
+ content_type: 'text/plain'
65
+ }
66
+ })}
67
+
68
+
69
+ let(:delivery_options) {
70
+ MailRoom::Delivery::Postback::Options.new(mailbox)
71
+ }
72
+
73
+ it 'posts the message with faraday' do
74
+ connection = stub
75
+ request = stub
76
+ Faraday.stubs(:new).returns(connection)
77
+
78
+ connection.expects(:post).yields(request)
79
+ request.stubs(:url)
80
+ request.stubs(:body=)
81
+ request.stubs(:headers).returns({})
82
+ connection.expects(:basic_auth).with('user1', 'password123abc')
26
83
 
27
- expect(request).to have_received(:url).with('http://localhost/inbox')
28
- expect(request).to have_received(:body=).with('a message')
84
+ MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
85
+
86
+ expect(request.headers['Content-Type']).to eq('text/plain')
87
+ end
88
+ end
29
89
  end
30
90
  end
31
91
  end
@@ -3,7 +3,7 @@ require 'mail_room/delivery/que'
3
3
 
4
4
  describe MailRoom::Delivery::Que do
5
5
  describe '#deliver' do
6
- let(:mailbox) {MailRoom::Mailbox.new({
6
+ let(:mailbox) {build_mailbox({
7
7
  delivery_options: {
8
8
  database: 'delivery_test',
9
9
  username: 'postgres',
@@ -18,20 +18,15 @@ describe MailRoom::Delivery::Que do
18
18
  let(:options) {MailRoom::Delivery::Que::Options.new(mailbox)}
19
19
 
20
20
  it 'stores the message in que_jobs table' do
21
- PG.stubs(:connect).returns(connection)
22
- connection.stubs(:exec)
23
-
24
- MailRoom::Delivery::Que.new(options).deliver('email')
25
-
26
- expect(PG).to have_received(:connect).with({
21
+ PG.expects(:connect).with({
27
22
  host: 'localhost',
28
23
  port: 5432,
29
24
  dbname: 'delivery_test',
30
25
  user: 'postgres',
31
26
  password: ''
32
- })
27
+ }).returns(connection)
33
28
 
34
- expect(connection).to have_received(:exec).with(
29
+ connection.expects(:exec).with(
35
30
  "INSERT INTO que_jobs (priority, job_class, queue, args) VALUES ($1, $2, $3, $4)",
36
31
  [
37
32
  5,
@@ -40,6 +35,8 @@ describe MailRoom::Delivery::Que do
40
35
  JSON.dump(['email'])
41
36
  ]
42
37
  )
38
+
39
+ MailRoom::Delivery::Que.new(options).deliver('email')
43
40
  end
44
41
  end
45
42
  end
@@ -7,31 +7,53 @@ describe MailRoom::Delivery::Sidekiq do
7
7
  let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
8
8
 
9
9
  describe '#options' do
10
- let(:redis_url) { 'redis://redis.example.com' }
10
+ let(:redis_url) { 'redis://localhost' }
11
+ let(:redis_options) { { redis_url: redis_url } }
11
12
 
12
13
  context 'when only redis_url is specified' do
13
14
  let(:mailbox) {
14
- MailRoom::Mailbox.new(
15
+ build_mailbox(
15
16
  delivery_method: :sidekiq,
16
- delivery_options: {
17
- redis_url: redis_url
18
- }
17
+ delivery_options: redis_options
19
18
  )
20
19
  }
21
20
 
22
- it 'client has same specified redis_url' do
23
- expect(redis.options[:url]).to eq(redis_url)
21
+ context 'with simple redis url' do
22
+ it 'client has same specified redis_url' do
23
+ expect(redis.client.options[:url]).to eq(redis_url)
24
+ end
25
+
26
+ it 'client is a instance of RedisNamespace class' do
27
+ expect(redis).to be_a ::Redis
28
+ end
29
+
30
+ it 'connection has correct values' do
31
+ expect(redis.connection[:host]).to eq('localhost')
32
+ expect(redis.connection[:db]).to eq(0)
33
+ end
24
34
  end
25
35
 
26
- it 'client is a instance of RedisNamespace class' do
27
- expect(redis).to be_a ::Redis
36
+ context 'with redis_db specified in options' do
37
+ before do
38
+ redis_options[:redis_db] = 4
39
+ end
40
+
41
+ it 'client has correct redis_url' do
42
+ expect(redis.client.options[:url]).to eq(redis_url)
43
+ end
44
+
45
+
46
+ it 'connection has correct values' do
47
+ expect(redis.connection[:host]).to eq('localhost')
48
+ expect(redis.connection[:db]).to eq(4)
49
+ end
28
50
  end
29
51
  end
30
52
 
31
53
  context 'when namespace is specified' do
32
54
  let(:namespace) { 'sidekiq_mailman' }
33
55
  let(:mailbox) {
34
- MailRoom::Mailbox.new(
56
+ build_mailbox(
35
57
  delivery_method: :sidekiq,
36
58
  delivery_options: {
37
59
  redis_url: redis_url,
@@ -53,7 +75,7 @@ describe MailRoom::Delivery::Sidekiq do
53
75
  let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
54
76
  let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
55
77
  let(:mailbox) {
56
- MailRoom::Mailbox.new(
78
+ build_mailbox(
57
79
  delivery_method: :sidekiq,
58
80
  delivery_options: {
59
81
  redis_url: redis_url,
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailRoom::IMAP::Connection do
4
+ let(:imap) {stub}
5
+ let(:mailbox) {build_mailbox(delete_after_delivery: true, expunge_deleted: true)}
6
+
7
+ before :each do
8
+ Net::IMAP.stubs(:new).returns(imap)
9
+ end
10
+
11
+ context "with imap set up" do
12
+ let(:connection) {MailRoom::IMAP::Connection.new(mailbox)}
13
+ let(:uid) { 1 }
14
+ let(:seqno) { 8 }
15
+
16
+ before :each do
17
+ imap.stubs(:starttls)
18
+ imap.stubs(:login)
19
+ imap.stubs(:select)
20
+ end
21
+
22
+ it "is logged in" do
23
+ expect(connection.logged_in?).to eq(true)
24
+ end
25
+
26
+ it "is not idling" do
27
+ expect(connection.idling?).to eq(false)
28
+ end
29
+
30
+ it "is not disconnected" do
31
+ imap.stubs(:disconnected?).returns(false)
32
+
33
+ expect(connection.disconnected?).to eq(false)
34
+ end
35
+
36
+ it "is ready to idle" do
37
+ expect(connection.ready_to_idle?).to eq(true)
38
+ end
39
+
40
+ it "waits for a message to process" do
41
+ new_message = MailRoom::IMAP::Message.new(uid: uid, body: 'a message', seqno: seqno)
42
+
43
+ connection.on_new_message do |message|
44
+ expect(message).to eq(new_message)
45
+ true
46
+ end
47
+
48
+ attr = { 'UID' => uid, 'RFC822' => new_message.body }
49
+ fetch_data = Net::IMAP::FetchData.new(seqno, attr)
50
+
51
+ imap.expects(:idle)
52
+ imap.stubs(:uid_search).with(mailbox.search_command).returns([], [uid])
53
+ imap.expects(:uid_fetch).with([uid], "RFC822").returns([fetch_data])
54
+ mailbox.expects(:deliver?).with(uid).returns(true)
55
+ imap.expects(:store).with(seqno, "+FLAGS", [Net::IMAP::DELETED])
56
+ imap.expects(:expunge).once
57
+
58
+ connection.wait
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal:true
2
+
3
+ require 'spec_helper'
4
+ require 'securerandom'
5
+
6
+ describe MailRoom::IMAP::Message do
7
+ let(:uid) { SecureRandom.hex }
8
+ let(:body) { 'hello world' }
9
+ let(:seqno) { 5 }
10
+
11
+ subject { described_class.new(uid: uid, body: body, seqno: seqno) }
12
+
13
+ describe '#initalize' do
14
+ it 'initializes with required parameters' do
15
+ subject
16
+
17
+ expect(subject.uid).to eq(uid)
18
+ expect(subject.body).to eq(body)
19
+ expect(subject.seqno).to eq(seqno)
20
+ end
21
+ end
22
+
23
+ describe '#==' do
24
+ let(:dup) { described_class.new(uid: uid, body: body, seqno: seqno) }
25
+ let(:base_msg) { MailRoom::Message.new(uid: uid, body: body) }
26
+
27
+ it 'matches an equivalent message' do
28
+ expect(dup == subject).to be true
29
+ end
30
+
31
+ it 'does not match a base message' do
32
+ expect(subject == base_msg).to be false
33
+ expect(base_msg == subject).to be false
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailRoom::Logger::Structured do
4
+
5
+ subject { described_class.new $stdout }
6
+
7
+ let!(:now) { Time.now }
8
+ let(:timestamp) { now.to_datetime.iso8601(3) }
9
+ let(:message) { { action: 'exciting development', message: 'testing 123' } }
10
+
11
+ before do
12
+ Time.stubs(:now).returns(now)
13
+ end
14
+
15
+ [:debug, :info, :warn, :error, :fatal].each do |level|
16
+ it "logs #{level}" do
17
+ expect { subject.send(level, message) }.to output(json_matching(level.to_s.upcase, message)).to_stdout_from_any_process
18
+ end
19
+ end
20
+
21
+ it 'logs unknown' do
22
+ expect { subject.unknown(message) }.to output(json_matching("ANY", message)).to_stdout_from_any_process
23
+ end
24
+
25
+ it 'only accepts hashes' do
26
+ expect { subject.unknown("just a string!") }.to raise_error(ArgumentError, /must be a Hash/)
27
+ end
28
+
29
+ context 'logging a hash as a message' do
30
+ it 'merges the contents' do
31
+ input = {
32
+ additional_field: "some value"
33
+ }
34
+ expected = {
35
+ severity: 'DEBUG',
36
+ time: timestamp,
37
+ additional_field: "some value"
38
+ }
39
+
40
+ expect { subject.debug(input) }.to output(as_regex(expected)).to_stdout_from_any_process
41
+ end
42
+ end
43
+
44
+ describe '#format_message' do
45
+ shared_examples 'timestamp formatting' do
46
+ it 'outputs ISO8601 timestamps' do
47
+ data = JSON.parse(subject.format_message('debug', input_timestamp, 'test', { message: 'hello' } ))
48
+
49
+ expect(data['time']).to eq(expected_timestamp)
50
+ end
51
+ end
52
+
53
+ context 'with no timestamp' do
54
+ let(:input_timestamp) { nil }
55
+ let(:expected_timestamp) { timestamp }
56
+
57
+ it_behaves_like 'timestamp formatting'
58
+ end
59
+
60
+ context 'with DateTime' do
61
+ let(:input_timestamp) { now.to_datetime }
62
+ let(:expected_timestamp) { timestamp }
63
+
64
+ it_behaves_like 'timestamp formatting'
65
+ end
66
+
67
+ context 'with string' do
68
+ let(:input_timestamp) { now.to_s }
69
+ let(:expected_timestamp) { input_timestamp }
70
+
71
+ it_behaves_like 'timestamp formatting'
72
+ end
73
+ end
74
+
75
+ def json_matching(level, message)
76
+ contents = {
77
+ severity: level,
78
+ time: timestamp
79
+ }.merge(message)
80
+
81
+ as_regex(contents)
82
+ end
83
+
84
+ def as_regex(contents)
85
+ /#{Regexp.quote(contents.to_json)}/
86
+ end
87
+ end