gitlab-mail_room 0.0.7 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +7 -7
  3. data/.rubocop.yml +5 -0
  4. data/.rubocop_todo.yml +501 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +12 -5
  7. data/README.md +104 -10
  8. data/Rakefile +1 -1
  9. data/lib/mail_room.rb +2 -0
  10. data/lib/mail_room/arbitration/redis.rb +1 -1
  11. data/lib/mail_room/cli.rb +1 -1
  12. data/lib/mail_room/configuration.rb +11 -1
  13. data/lib/mail_room/connection.rb +6 -182
  14. data/lib/mail_room/coordinator.rb +8 -4
  15. data/lib/mail_room/crash_handler.rb +2 -2
  16. data/lib/mail_room/delivery/letter_opener.rb +1 -1
  17. data/lib/mail_room/health_check.rb +60 -0
  18. data/lib/mail_room/imap.rb +8 -0
  19. data/lib/mail_room/imap/connection.rb +200 -0
  20. data/lib/mail_room/imap/message.rb +19 -0
  21. data/lib/mail_room/logger/structured.rb +15 -1
  22. data/lib/mail_room/mailbox.rb +61 -21
  23. data/lib/mail_room/mailbox_watcher.rb +15 -2
  24. data/lib/mail_room/message.rb +16 -0
  25. data/lib/mail_room/microsoft_graph.rb +7 -0
  26. data/lib/mail_room/microsoft_graph/connection.rb +217 -0
  27. data/lib/mail_room/version.rb +1 -1
  28. data/mail_room.gemspec +6 -0
  29. data/spec/fixtures/test_config.yml +3 -0
  30. data/spec/lib/cli_spec.rb +6 -6
  31. data/spec/lib/configuration_spec.rb +10 -2
  32. data/spec/lib/coordinator_spec.rb +16 -2
  33. data/spec/lib/delivery/letter_opener_spec.rb +2 -2
  34. data/spec/lib/delivery/logger_spec.rb +1 -1
  35. data/spec/lib/delivery/postback_spec.rb +11 -11
  36. data/spec/lib/health_check_spec.rb +57 -0
  37. data/spec/lib/{connection_spec.rb → imap/connection_spec.rb} +12 -8
  38. data/spec/lib/imap/message_spec.rb +36 -0
  39. data/spec/lib/logger/structured_spec.rb +34 -2
  40. data/spec/lib/mailbox_spec.rb +75 -27
  41. data/spec/lib/mailbox_watcher_spec.rb +54 -38
  42. data/spec/lib/message_spec.rb +35 -0
  43. data/spec/lib/microsoft_graph/connection_spec.rb +190 -0
  44. data/spec/spec_helper.rb +14 -3
  45. metadata +92 -5
@@ -5,8 +5,8 @@ describe MailRoom::Delivery::Postback do
5
5
  describe '#deliver' do
6
6
  context 'with token auth delivery' do
7
7
  let(:mailbox) {build_mailbox({
8
- :delivery_url => 'http://localhost/inbox',
9
- :delivery_token => 'abcdefg'
8
+ delivery_url: 'http://localhost/inbox',
9
+ delivery_token: 'abcdefg'
10
10
  })}
11
11
 
12
12
  let(:delivery_options) {
@@ -30,10 +30,10 @@ describe MailRoom::Delivery::Postback do
30
30
 
31
31
  context 'with basic auth delivery options' do
32
32
  let(:mailbox) {build_mailbox({
33
- :delivery_options => {
34
- :url => 'http://localhost/inbox',
35
- :username => 'user1',
36
- :password => 'password123abc'
33
+ delivery_options: {
34
+ url: 'http://localhost/inbox',
35
+ username: 'user1',
36
+ password: 'password123abc'
37
37
  }
38
38
  })}
39
39
 
@@ -57,11 +57,11 @@ describe MailRoom::Delivery::Postback do
57
57
 
58
58
  context 'with content type in the delivery options' do
59
59
  let(:mailbox) {build_mailbox({
60
- :delivery_options => {
61
- :url => 'http://localhost/inbox',
62
- :username => 'user1',
63
- :password => 'password123abc',
64
- :content_type => 'text/plain'
60
+ delivery_options: {
61
+ url: 'http://localhost/inbox',
62
+ username: 'user1',
63
+ password: 'password123abc',
64
+ content_type: 'text/plain'
65
65
  }
66
66
  })}
67
67
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe MailRoom::HealthCheck do
6
+ let(:address) { '127.0.0.1' }
7
+ let(:port) { 8000 }
8
+ let(:params) { { address: address, port: port } }
9
+ subject { described_class.new(params) }
10
+
11
+ describe '#initialize' do
12
+ context 'with valid parameters' do
13
+ it 'validates successfully' do
14
+ expect(subject).to be_a(described_class)
15
+ end
16
+ end
17
+
18
+ context 'with invalid address' do
19
+ let(:address) { nil }
20
+
21
+ it 'raises an error' do
22
+ expect { subject }.to raise_error('No health check address specified')
23
+ end
24
+ end
25
+
26
+ context 'with invalid port' do
27
+ let(:port) { nil }
28
+
29
+ it 'raises an error' do
30
+ expect { subject }.to raise_error('Health check port 0 is invalid')
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#run' do
36
+ it 'sets running to true' do
37
+ server = stub(start: true)
38
+ subject.stubs(:create_server).returns(server)
39
+
40
+ subject.run
41
+
42
+ expect(subject.running).to be true
43
+ end
44
+ end
45
+
46
+ describe '#quit' do
47
+ it 'sets running to false' do
48
+ server = stub(start: true, shutdown: true)
49
+ subject.stubs(:create_server).returns(server)
50
+
51
+ subject.run
52
+ subject.quit
53
+
54
+ expect(subject.running).to be false
55
+ end
56
+ end
57
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MailRoom::Connection do
3
+ describe MailRoom::IMAP::Connection do
4
4
  let(:imap) {stub}
5
5
  let(:mailbox) {build_mailbox(delete_after_delivery: true, expunge_deleted: true)}
6
6
 
@@ -9,7 +9,9 @@ describe MailRoom::Connection do
9
9
  end
10
10
 
11
11
  context "with imap set up" do
12
- let(:connection) {MailRoom::Connection.new(mailbox)}
12
+ let(:connection) {MailRoom::IMAP::Connection.new(mailbox)}
13
+ let(:uid) { 1 }
14
+ let(:seqno) { 8 }
13
15
 
14
16
  before :each do
15
17
  imap.stubs(:starttls)
@@ -36,19 +38,21 @@ describe MailRoom::Connection do
36
38
  end
37
39
 
38
40
  it "waits for a message to process" do
39
- new_message = 'a message'
40
- new_message.stubs(:seqno).returns(8)
41
+ new_message = MailRoom::IMAP::Message.new(uid: uid, body: 'a message', seqno: seqno)
41
42
 
42
43
  connection.on_new_message do |message|
43
44
  expect(message).to eq(new_message)
44
45
  true
45
46
  end
46
47
 
48
+ attr = { 'UID' => uid, 'RFC822' => new_message.body }
49
+ fetch_data = Net::IMAP::FetchData.new(seqno, attr)
50
+
47
51
  imap.expects(:idle)
48
- imap.stubs(:uid_search).with(mailbox.search_command).returns([], [1])
49
- imap.expects(:uid_fetch).with([1], "RFC822").returns([new_message])
50
- mailbox.expects(:deliver?).with(1).returns(true)
51
- imap.expects(:store).with(8, "+FLAGS", [Net::IMAP::DELETED])
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])
52
56
  imap.expects(:expunge).once
53
57
 
54
58
  connection.wait
@@ -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
@@ -5,6 +5,7 @@ describe MailRoom::Logger::Structured do
5
5
  subject { described_class.new $stdout }
6
6
 
7
7
  let!(:now) { Time.now }
8
+ let(:timestamp) { now.to_datetime.iso8601(3) }
8
9
  let(:message) { { action: 'exciting development', message: 'testing 123' } }
9
10
 
10
11
  before do
@@ -32,7 +33,7 @@ describe MailRoom::Logger::Structured do
32
33
  }
33
34
  expected = {
34
35
  severity: 'DEBUG',
35
- time: now,
36
+ time: timestamp,
36
37
  additional_field: "some value"
37
38
  }
38
39
 
@@ -40,10 +41,41 @@ describe MailRoom::Logger::Structured do
40
41
  end
41
42
  end
42
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
+
43
75
  def json_matching(level, message)
44
76
  contents = {
45
77
  severity: level,
46
- time: now
78
+ time: timestamp
47
79
  }.merge(message)
48
80
 
49
81
  as_regex(contents)
@@ -1,14 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MailRoom::Mailbox do
4
- let(:sample_message) { {'RFC822' => 'a message', 'UID' => 123} }
4
+ let(:sample_message) { MailRoom::Message.new(uid: 123, body: 'a message') }
5
+
6
+ context 'with IMAP configuration' do
7
+ subject { build_mailbox }
8
+
9
+ describe '#imap?' do
10
+ it 'configured as an IMAP inbox' do
11
+ expect(subject.imap?).to be true
12
+ expect(subject.microsoft_graph?).to be false
13
+ end
14
+ end
15
+ end
5
16
 
6
17
  describe "#deliver" do
7
18
  context "with arbitration_method of noop" do
8
19
  it 'arbitrates with a Noop instance' do
9
- mailbox = build_mailbox({:arbitration_method => 'noop'})
20
+ mailbox = build_mailbox({arbitration_method: 'noop'})
10
21
  noop = stub(:deliver?)
11
- MailRoom::Arbitration['noop'].stubs(:new => noop)
22
+ MailRoom::Arbitration['noop'].stubs(new: noop)
12
23
 
13
24
  uid = 123
14
25
 
@@ -20,9 +31,9 @@ describe MailRoom::Mailbox do
20
31
 
21
32
  context "with arbitration_method of redis" do
22
33
  it 'arbitrates with a Redis instance' do
23
- mailbox = build_mailbox({:arbitration_method => 'redis'})
34
+ mailbox = build_mailbox({arbitration_method: 'redis'})
24
35
  redis = stub(:deliver?)
25
- MailRoom::Arbitration['redis'].stubs(:new => redis)
36
+ MailRoom::Arbitration['redis'].stubs(new: redis)
26
37
  uid = 123
27
38
  redis.expects(:deliver?).with(uid)
28
39
 
@@ -32,49 +43,49 @@ describe MailRoom::Mailbox do
32
43
 
33
44
  context "with delivery_method of noop" do
34
45
  it 'delivers with a Noop instance' do
35
- mailbox = build_mailbox({:delivery_method => 'noop'})
46
+ mailbox = build_mailbox({delivery_method: 'noop'})
36
47
  noop = stub(:deliver)
37
- MailRoom::Delivery['noop'].stubs(:new => noop)
48
+ MailRoom::Delivery['noop'].stubs(new: noop)
38
49
 
39
- noop.expects(:deliver).with('a message')
50
+ noop.expects(:deliver).with(sample_message.body)
40
51
 
41
- mailbox.deliver(stub(:attr => sample_message))
52
+ mailbox.deliver(sample_message)
42
53
  end
43
54
  end
44
55
 
45
56
  context "with delivery_method of logger" do
46
57
  it 'delivers with a Logger instance' do
47
- mailbox = build_mailbox({:delivery_method => 'logger'})
58
+ mailbox = build_mailbox({delivery_method: 'logger'})
48
59
  logger = stub(:deliver)
49
- MailRoom::Delivery['logger'].stubs(:new => logger)
60
+ MailRoom::Delivery['logger'].stubs(new: logger)
50
61
 
51
- logger.expects(:deliver).with('a message')
62
+ logger.expects(:deliver).with(sample_message.body)
52
63
 
53
- mailbox.deliver(stub(:attr => sample_message))
64
+ mailbox.deliver(sample_message)
54
65
  end
55
66
  end
56
67
 
57
68
  context "with delivery_method of postback" do
58
69
  it 'delivers with a Postback instance' do
59
- mailbox = build_mailbox({:delivery_method => 'postback'})
70
+ mailbox = build_mailbox({delivery_method: 'postback'})
60
71
  postback = stub(:deliver)
61
- MailRoom::Delivery['postback'].stubs(:new => postback)
72
+ MailRoom::Delivery['postback'].stubs(new: postback)
62
73
 
63
- postback.expects(:deliver).with('a message')
74
+ postback.expects(:deliver).with(sample_message.body)
64
75
 
65
- mailbox.deliver(stub(:attr => sample_message))
76
+ mailbox.deliver(sample_message)
66
77
  end
67
78
  end
68
79
 
69
80
  context "with delivery_method of letter_opener" do
70
81
  it 'delivers with a LetterOpener instance' do
71
- mailbox = build_mailbox({:delivery_method => 'letter_opener'})
82
+ mailbox = build_mailbox({delivery_method: 'letter_opener'})
72
83
  letter_opener = stub(:deliver)
73
- MailRoom::Delivery['letter_opener'].stubs(:new => letter_opener)
84
+ MailRoom::Delivery['letter_opener'].stubs(new: letter_opener)
74
85
 
75
- letter_opener.expects(:deliver).with('a message')
86
+ letter_opener.expects(:deliver).with(sample_message.body)
76
87
 
77
- mailbox.deliver(stub(:attr => sample_message))
88
+ mailbox.deliver(sample_message)
78
89
  end
79
90
  end
80
91
 
@@ -82,18 +93,18 @@ describe MailRoom::Mailbox do
82
93
  it "doesn't deliver the message" do
83
94
  mailbox = build_mailbox({ name: "magic mailbox", delivery_method: 'noop' })
84
95
  noop = stub(:deliver)
85
- MailRoom::Delivery['noop'].stubs(:new => noop)
96
+ MailRoom::Delivery['noop'].stubs(new: noop)
86
97
  noop.expects(:deliver).never
87
98
 
88
- mailbox.deliver(stub(:attr => {'FLAGS' => [:Seen, :Recent]}))
99
+ mailbox.deliver(MailRoom::Message.new(uid: 1234, body: nil))
89
100
  end
90
101
  end
91
102
 
92
103
  context "with ssl options hash" do
93
104
  it 'replaces verify mode with constant' do
94
- mailbox = build_mailbox({:ssl => {:verify_mode => :none}})
105
+ mailbox = build_mailbox({ssl: {verify_mode: :none}})
95
106
 
96
- expect(mailbox.ssl_options).to eq({:verify_mode => OpenSSL::SSL::VERIFY_NONE})
107
+ expect(mailbox.ssl_options).to eq({verify_mode: OpenSSL::SSL::VERIFY_NONE})
97
108
  end
98
109
  end
99
110
 
@@ -121,8 +132,45 @@ describe MailRoom::Mailbox do
121
132
  describe "#validate!" do
122
133
  context "with missing configuration" do
123
134
  it 'raises an error' do
124
- expect { build_mailbox({:name => nil}) }.to raise_error(MailRoom::ConfigurationError)
125
- expect { build_mailbox({:host => nil}) }.to raise_error(MailRoom::ConfigurationError)
135
+ expect { build_mailbox({name: nil}) }.to raise_error(MailRoom::ConfigurationError)
136
+ expect { build_mailbox({host: nil}) }.to raise_error(MailRoom::ConfigurationError)
137
+ end
138
+ end
139
+
140
+ context "with Microsoft Graph configuration" do
141
+ let(:options) do
142
+ {
143
+ arbitration_method: 'redis',
144
+ }.merge(REQUIRED_MICROSOFT_GRAPH_DEFAULTS)
145
+ end
146
+
147
+ subject { build_mailbox(options) }
148
+
149
+ def delete_inbox_option(key)
150
+ options[:inbox_options] = options[:inbox_options].dup.delete(key)
151
+ end
152
+
153
+ it 'allows password omission' do
154
+ expect { subject }.not_to raise_error
155
+ end
156
+
157
+ it 'configured as a Microsoft Graph inbox' do
158
+ expect(subject.imap?).to be false
159
+ expect(subject.microsoft_graph?).to be true
160
+ end
161
+
162
+ it 'raises an error when the inbox options are not present' do
163
+ options.delete(:inbox_options)
164
+
165
+ expect { subject }.to raise_error(MailRoom::ConfigurationError)
166
+ end
167
+
168
+ %i[tenant_id client_id client_secret].each do |item|
169
+ it "raises an error when the #{item} is not present" do
170
+ delete_inbox_option(item)
171
+
172
+ expect { subject }.to raise_error(MailRoom::ConfigurationError)
173
+ end
126
174
  end
127
175
  end
128
176
  end
@@ -1,61 +1,77 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MailRoom::MailboxWatcher do
4
- let(:mailbox) {build_mailbox}
5
-
6
- describe '#running?' do
7
- it 'is false by default' do
8
- watcher = MailRoom::MailboxWatcher.new(mailbox)
9
- expect(watcher.running?).to eq(false)
4
+ context 'with IMAP configured' do
5
+ let(:mailbox) {build_mailbox}
6
+
7
+ describe '#running?' do
8
+ it 'is false by default' do
9
+ watcher = MailRoom::MailboxWatcher.new(mailbox)
10
+ expect(watcher.running?).to eq(false)
11
+ end
10
12
  end
11
- end
12
13
 
13
- describe '#run' do
14
- let(:imap) {stub(:login => true, :select => true)}
15
- let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
14
+ describe '#run' do
15
+ let(:imap) {stub(login: true, select: true)}
16
+ let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
16
17
 
17
- before :each do
18
- Net::IMAP.stubs(:new).returns(imap) # prevent connection
19
- end
18
+ before :each do
19
+ Net::IMAP.stubs(:new).returns(imap) # prevent connection
20
+ end
20
21
 
21
- it 'loops over wait while running' do
22
- connection = MailRoom::Connection.new(mailbox)
22
+ it 'loops over wait while running' do
23
+ connection = MailRoom::IMAP::Connection.new(mailbox)
23
24
 
24
- MailRoom::Connection.stubs(:new).returns(connection)
25
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
25
26
 
26
- watcher.expects(:running?).twice.returns(true, false)
27
- connection.expects(:wait).once
28
- connection.expects(:on_new_message).once
27
+ watcher.expects(:running?).twice.returns(true, false)
28
+ connection.expects(:wait).once
29
+ connection.expects(:on_new_message).once
29
30
 
30
- watcher.run
31
- watcher.watching_thread.join # wait for finishing run
31
+ watcher.run
32
+ watcher.watching_thread.join # wait for finishing run
33
+ end
32
34
  end
33
- end
34
35
 
35
- describe '#quit' do
36
- let(:imap) {stub(:login => true, :select => true)}
37
- let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
36
+ describe '#quit' do
37
+ let(:imap) {stub(login: true, select: true)}
38
+ let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
38
39
 
39
- before :each do
40
- Net::IMAP.stubs(:new).returns(imap) # prevent connection
41
- end
40
+ before :each do
41
+ Net::IMAP.stubs(:new).returns(imap) # prevent connection
42
+ end
43
+
44
+ it 'closes and waits for the connection' do
45
+ connection = MailRoom::IMAP::Connection.new(mailbox)
46
+ connection.stubs(:wait)
47
+ connection.stubs(:quit)
42
48
 
43
- it 'closes and waits for the connection' do
44
- connection = MailRoom::Connection.new(mailbox)
45
- connection.stubs(:wait)
46
- connection.stubs(:quit)
49
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
47
50
 
48
- MailRoom::Connection.stubs(:new).returns(connection)
51
+ watcher.run
52
+
53
+ expect(watcher.running?).to eq(true)
54
+
55
+ connection.expects(:quit)
56
+
57
+ watcher.quit
58
+
59
+ expect(watcher.running?).to eq(false)
60
+ end
61
+ end
62
+ end
49
63
 
50
- watcher.run
64
+ context 'with Microsoft Graph configured' do
65
+ let(:mailbox) { build_mailbox(REQUIRED_MICROSOFT_GRAPH_DEFAULTS) }
51
66
 
52
- expect(watcher.running?).to eq(true)
67
+ subject { described_class.new(mailbox) }
53
68
 
54
- connection.expects(:quit)
69
+ it 'initializes a Microsoft Graph connection' do
70
+ connection = stub(on_new_message: nil)
55
71
 
56
- watcher.quit
72
+ MailRoom::MicrosoftGraph::Connection.stubs(:new).returns(connection)
57
73
 
58
- expect(watcher.running?).to eq(false)
74
+ expect(subject.send(:connection)).to eq(connection)
59
75
  end
60
76
  end
61
77
  end