gitlab-mail_room 0.0.6 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 +1,18 @@
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
@@ -36,9 +47,9 @@ describe MailRoom::Mailbox do
36
47
  noop = stub(:deliver)
37
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
 
@@ -48,9 +59,9 @@ describe MailRoom::Mailbox do
48
59
  logger = stub(:deliver)
49
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
 
@@ -60,9 +71,9 @@ describe MailRoom::Mailbox do
60
71
  postback = stub(:deliver)
61
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
 
@@ -72,9 +83,9 @@ describe MailRoom::Mailbox do
72
83
  letter_opener = stub(:deliver)
73
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
 
@@ -85,7 +96,7 @@ describe MailRoom::Mailbox do
85
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
 
@@ -125,5 +136,42 @@ describe MailRoom::Mailbox do
125
136
  expect { build_mailbox({:host => nil}) }.to raise_error(MailRoom::ConfigurationError)
126
137
  end
127
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
174
+ end
175
+ end
128
176
  end
129
177
  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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal:true
2
+
3
+ require 'spec_helper'
4
+ require 'securerandom'
5
+
6
+ describe MailRoom::Message do
7
+ let(:uid) { SecureRandom.hex }
8
+ let(:body) { 'hello world' }
9
+
10
+ subject { described_class.new(uid: uid, body: body) }
11
+
12
+ describe '#initalize' do
13
+ it 'initializes with required parameters' do
14
+ subject
15
+
16
+ expect(subject.uid).to eq(uid)
17
+ expect(subject.body).to eq(body)
18
+ end
19
+ end
20
+
21
+ describe '#==' do
22
+ let(:dup) { described_class.new(uid: uid, body: body) }
23
+
24
+ it 'matches an equivalent message' do
25
+ expect(dup == subject).to be true
26
+ end
27
+
28
+ it 'does not match a message with a different UID' do
29
+ msg = described_class.new(uid: '12345', body: body)
30
+
31
+ expect(subject == msg).to be false
32
+ expect(msg == subject).to be false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'spec_helper'
5
+ require 'json'
6
+ require 'webmock/rspec'
7
+
8
+ describe MailRoom::MicrosoftGraph::Connection do
9
+ let(:tenant_id) { options[:inbox_options][:tenant_id] }
10
+ let(:options) do
11
+ {
12
+ delete_after_delivery: true,
13
+ expunge_deleted: true
14
+ }.merge(REQUIRED_MICROSOFT_GRAPH_DEFAULTS)
15
+ end
16
+ let(:mailbox) { build_mailbox(options) }
17
+ let(:base_url) { 'https://graph.microsoft.com/v1.0/users/user@example.com/mailFolders/inbox/messages' }
18
+ let(:message_base_url) { 'https://graph.microsoft.com/v1.0/users/user@example.com/messages' }
19
+
20
+ before do
21
+ WebMock.enable!
22
+ end
23
+
24
+ context '#wait' do
25
+ let(:connection) { described_class.new(mailbox) }
26
+ let(:uid) { 1 }
27
+ let(:access_token) { SecureRandom.hex }
28
+ let(:refresh_token) { SecureRandom.hex }
29
+ let(:expires_in) { Time.now + 3600 }
30
+ let(:unread_messages_body) { '' }
31
+ let(:status) { 200 }
32
+ let!(:stub_token) do
33
+ stub_request(:post, "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token").to_return(
34
+ body: { 'access_token' => access_token, 'refresh_token' => refresh_token, 'expires_in' => expires_in }.to_json,
35
+ headers: { 'Content-Type' => 'application/json' }
36
+ )
37
+ end
38
+ let!(:stub_unread_messages_request) do
39
+ stub_request(:get, "#{base_url}?$filter=isRead%20eq%20false").to_return(
40
+ status: status,
41
+ body: unread_messages_body.to_json,
42
+ headers: { 'Content-Type' => 'application/json' }
43
+ )
44
+ end
45
+
46
+ before do
47
+ connection.stubs(:wait_for_new_messages)
48
+ end
49
+
50
+ describe 'poll interval' do
51
+ it 'defaults to 60 seconds' do
52
+ expect(connection.send(:poll_interval)).to eq(60)
53
+ end
54
+
55
+ context 'interval set to 10' do
56
+ let(:options) do
57
+ {
58
+ inbox_method: :microsoft_graph,
59
+ inbox_options: {
60
+ tenant_id: '98776',
61
+ client_id: '12345',
62
+ client_secret: 'MY-SECRET',
63
+ poll_interval: '10'
64
+ }
65
+ }
66
+ end
67
+
68
+ it 'sets the poll interval to 10' do
69
+ expect(connection.send(:poll_interval)).to eq(10)
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'with a single message' do
75
+ let(:message_id) { SecureRandom.hex }
76
+ let(:unread_messages_body) { { value: ['id' => message_id] } }
77
+ let(:message_url) { "#{message_base_url}/#{message_id}" }
78
+ let(:message_body) { 'hello world' }
79
+
80
+ it 'requests message ID' do
81
+ stub_get = stub_request(:get, "#{message_url}/$value").to_return(
82
+ status: 200,
83
+ body: message_body
84
+ )
85
+ stub_patch = stub_request(:patch, message_url).with(body: { "isRead": true }.to_json)
86
+ stub_delete = stub_request(:delete, message_url)
87
+ message_count = 0
88
+
89
+ connection.on_new_message do |message|
90
+ message_count += 1
91
+ expect(message.uid).to eq(message_id)
92
+ expect(message.body).to eq(message_body)
93
+ end
94
+
95
+ connection.wait
96
+
97
+ assert_requested(stub_token)
98
+ assert_requested(stub_unread_messages_request)
99
+ assert_requested(stub_get)
100
+ assert_requested(stub_patch)
101
+ assert_requested(stub_delete)
102
+ expect(message_count).to eq(1)
103
+ end
104
+ end
105
+
106
+ context 'with multiple pages of messages' do
107
+ let(:message_ids) { [SecureRandom.hex, SecureRandom.hex] }
108
+ let(:next_page_url) { 'https://graph.microsoft.com/v1.0/nextPage' }
109
+ let(:unread_messages_body) { { value: ['id' => message_ids.first], '@odata.nextLink' => next_page_url } }
110
+ let(:message_body) { 'hello world' }
111
+
112
+ it 'requests message ID' do
113
+ stub_request(:get, next_page_url).to_return(
114
+ status: 200,
115
+ body: { value: ['id' => message_ids[1]] }.to_json
116
+ )
117
+
118
+ stubs = []
119
+ message_ids.each do |message_id|
120
+ rfc822_msg_url = "#{message_base_url}/#{message_id}/$value"
121
+ stubs << stub_request(:get, rfc822_msg_url).to_return(
122
+ status: 200,
123
+ body: message_body
124
+ )
125
+
126
+ msg_url = "#{message_base_url}/#{message_id}"
127
+ stubs << stub_request(:patch, msg_url).with(body: { "isRead": true }.to_json)
128
+ stubs << stub_request(:delete, msg_url)
129
+ end
130
+
131
+ message_count = 0
132
+
133
+ connection.on_new_message do |message|
134
+ expect(message.uid).to eq(message_ids[message_count])
135
+ expect(message.body).to eq(message_body)
136
+ message_count += 1
137
+ end
138
+
139
+ connection.wait
140
+
141
+ stubs.each { |stub| assert_requested(stub) }
142
+ expect(message_count).to eq(2)
143
+ end
144
+ end
145
+
146
+ context 'too many requests' do
147
+ let(:status) { 429 }
148
+
149
+ it 'backs off' do
150
+ connection.expects(:backoff)
151
+
152
+ connection.on_new_message {}
153
+ connection.wait
154
+
155
+ expect(connection.throttled_count).to eq(1)
156
+ end
157
+ end
158
+
159
+ context 'invalid JSON response' do
160
+ let(:body) { 'this is something' }
161
+
162
+ it 'ignores the message and logs a warning' do
163
+ mailbox.logger.expects(:warn)
164
+
165
+ connection.on_new_message {}
166
+ connection.wait
167
+ end
168
+ end
169
+
170
+ context '500 error' do
171
+ let(:status) { 500 }
172
+
173
+ it 'resets the state and logs a warning' do
174
+ connection.expects(:reset)
175
+ connection.expects(:setup)
176
+ mailbox.logger.expects(:warn)
177
+
178
+ connection.on_new_message {}
179
+ connection.wait
180
+ end
181
+ end
182
+ end
183
+ end