gitlab-mail_room 0.0.4 → 0.0.20
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.
- checksums.yaml +4 -4
- data/.gitlab/issue_templates/Default.md +9 -0
- data/.gitlab/issue_templates/Release.md +8 -0
- data/.gitlab-ci.yml +18 -16
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +494 -0
- data/.ruby-version +1 -1
- data/.travis.yml +12 -5
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +40 -0
- data/README.md +136 -10
- data/Rakefile +1 -1
- data/lib/mail_room/arbitration/redis.rb +1 -1
- data/lib/mail_room/cli.rb +2 -2
- data/lib/mail_room/configuration.rb +11 -1
- data/lib/mail_room/connection.rb +10 -177
- data/lib/mail_room/coordinator.rb +8 -4
- data/lib/mail_room/crash_handler.rb +8 -12
- data/lib/mail_room/delivery/letter_opener.rb +1 -1
- data/lib/mail_room/delivery/postback.rb +36 -4
- data/lib/mail_room/delivery/sidekiq.rb +4 -3
- data/lib/mail_room/health_check.rb +60 -0
- data/lib/mail_room/imap/connection.rb +200 -0
- data/lib/mail_room/imap/message.rb +19 -0
- data/lib/mail_room/imap.rb +8 -0
- data/lib/mail_room/jwt.rb +39 -0
- data/lib/mail_room/logger/structured.rb +15 -1
- data/lib/mail_room/mailbox.rb +62 -20
- data/lib/mail_room/mailbox_watcher.rb +15 -2
- data/lib/mail_room/message.rb +16 -0
- data/lib/mail_room/microsoft_graph/connection.rb +243 -0
- data/lib/mail_room/microsoft_graph.rb +7 -0
- data/lib/mail_room/version.rb +2 -2
- data/lib/mail_room.rb +2 -0
- data/mail_room.gemspec +13 -4
- data/spec/fixtures/jwt_secret +1 -0
- data/spec/fixtures/test_config.yml +3 -0
- data/spec/lib/arbitration/redis_spec.rb +9 -7
- data/spec/lib/cli_spec.rb +32 -17
- data/spec/lib/configuration_spec.rb +10 -3
- data/spec/lib/coordinator_spec.rb +27 -11
- data/spec/lib/crash_handler_spec.rb +10 -9
- data/spec/lib/delivery/letter_opener_spec.rb +10 -6
- data/spec/lib/delivery/logger_spec.rb +8 -10
- data/spec/lib/delivery/postback_spec.rb +73 -41
- data/spec/lib/delivery/que_spec.rb +5 -8
- data/spec/lib/delivery/sidekiq_spec.rb +33 -11
- data/spec/lib/health_check_spec.rb +57 -0
- data/spec/lib/{connection_spec.rb → imap/connection_spec.rb} +13 -17
- data/spec/lib/imap/message_spec.rb +36 -0
- data/spec/lib/jwt_spec.rb +80 -0
- data/spec/lib/logger/structured_spec.rb +34 -2
- data/spec/lib/mailbox_spec.rb +79 -34
- data/spec/lib/mailbox_watcher_spec.rb +54 -41
- data/spec/lib/message_spec.rb +35 -0
- data/spec/lib/microsoft_graph/connection_spec.rb +252 -0
- data/spec/spec_helper.rb +14 -4
- metadata +130 -21
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'oauth2'
|
5
|
+
|
6
|
+
module MailRoom
|
7
|
+
module MicrosoftGraph
|
8
|
+
class Connection < MailRoom::Connection
|
9
|
+
NEXT_PAGE_KEY = '@odata.nextLink'
|
10
|
+
DEFAULT_POLL_INTERVAL_S = 60
|
11
|
+
|
12
|
+
TooManyRequestsError = Class.new(RuntimeError)
|
13
|
+
|
14
|
+
attr_accessor :token, :throttled_count
|
15
|
+
|
16
|
+
def initialize(mailbox)
|
17
|
+
super
|
18
|
+
|
19
|
+
reset
|
20
|
+
setup
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait
|
24
|
+
return if stopped?
|
25
|
+
|
26
|
+
process_mailbox
|
27
|
+
|
28
|
+
@throttled_count = 0
|
29
|
+
wait_for_new_messages
|
30
|
+
rescue TooManyRequestsError => e
|
31
|
+
@throttled_count += 1
|
32
|
+
|
33
|
+
@mailbox.logger.warn({ context: @mailbox.context, action: 'Too many requests, backing off...', backoff_s: backoff_secs, error: e.message, error_backtrace: e.backtrace })
|
34
|
+
|
35
|
+
backoff
|
36
|
+
rescue IOError => e
|
37
|
+
@mailbox.logger.warn({ context: @mailbox.context, action: 'Disconnected. Resetting...', error: e.message, error_backtrace: e.backtrace })
|
38
|
+
|
39
|
+
reset
|
40
|
+
setup
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def wait_for_new_messages
|
46
|
+
sleep_while_running(poll_interval)
|
47
|
+
end
|
48
|
+
|
49
|
+
def backoff
|
50
|
+
sleep_while_running(backoff_secs)
|
51
|
+
end
|
52
|
+
|
53
|
+
def backoff_secs
|
54
|
+
[60 * 10, 2**throttled_count].min
|
55
|
+
end
|
56
|
+
|
57
|
+
# Unless wake up periodically, we won't notice that the thread was stopped
|
58
|
+
# if we sleep the entire interval.
|
59
|
+
def sleep_while_running(sleep_interval)
|
60
|
+
sleep_interval.times do
|
61
|
+
do_sleep(1)
|
62
|
+
return if stopped?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def do_sleep(interval)
|
67
|
+
sleep(interval)
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset
|
71
|
+
@token = nil
|
72
|
+
@throttled_count = 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup
|
76
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Retrieving OAuth2 token...' })
|
77
|
+
|
78
|
+
@token = client.client_credentials.get_token({ scope: scope })
|
79
|
+
end
|
80
|
+
|
81
|
+
def client
|
82
|
+
@client ||= OAuth2::Client.new(client_id, client_secret,
|
83
|
+
site: azure_ad_endpoint,
|
84
|
+
authorize_url: "/#{tenant_id}/oauth2/v2.0/authorize",
|
85
|
+
token_url: "/#{tenant_id}/oauth2/v2.0/token",
|
86
|
+
auth_scheme: :basic_auth)
|
87
|
+
end
|
88
|
+
|
89
|
+
def inbox_options
|
90
|
+
mailbox.inbox_options
|
91
|
+
end
|
92
|
+
|
93
|
+
def tenant_id
|
94
|
+
inbox_options[:tenant_id]
|
95
|
+
end
|
96
|
+
|
97
|
+
def client_id
|
98
|
+
inbox_options[:client_id]
|
99
|
+
end
|
100
|
+
|
101
|
+
def client_secret
|
102
|
+
inbox_options[:client_secret]
|
103
|
+
end
|
104
|
+
|
105
|
+
def poll_interval
|
106
|
+
@poll_interval ||= begin
|
107
|
+
interval = inbox_options[:poll_interval].to_i
|
108
|
+
|
109
|
+
if interval.positive?
|
110
|
+
interval
|
111
|
+
else
|
112
|
+
DEFAULT_POLL_INTERVAL_S
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_mailbox
|
118
|
+
return unless @new_message_handler
|
119
|
+
|
120
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Processing started' })
|
121
|
+
|
122
|
+
new_messages.each do |msg|
|
123
|
+
success = @new_message_handler.call(msg)
|
124
|
+
handle_delivered(msg) if success
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_delivered(msg)
|
129
|
+
mark_as_read(msg)
|
130
|
+
delete_message(msg) if @mailbox.delete_after_delivery
|
131
|
+
end
|
132
|
+
|
133
|
+
def delete_message(msg)
|
134
|
+
token.delete(msg_url(msg.uid))
|
135
|
+
end
|
136
|
+
|
137
|
+
def mark_as_read(msg)
|
138
|
+
token.patch(msg_url(msg.uid),
|
139
|
+
headers: { 'Content-Type' => 'application/json' },
|
140
|
+
body: { isRead: true }.to_json)
|
141
|
+
end
|
142
|
+
|
143
|
+
def new_messages
|
144
|
+
messages_for_ids(new_message_ids)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Yields a page of message IDs at a time
|
148
|
+
def new_message_ids
|
149
|
+
url = unread_messages_url
|
150
|
+
|
151
|
+
Enumerator.new do |block|
|
152
|
+
loop do
|
153
|
+
messages, next_page_url = unread_messages(url: url)
|
154
|
+
messages.each { |msg| block.yield msg }
|
155
|
+
|
156
|
+
break unless next_page_url
|
157
|
+
|
158
|
+
url = next_page_url
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def unread_messages(url:)
|
164
|
+
body = get(url)
|
165
|
+
|
166
|
+
return [[], nil] unless body
|
167
|
+
|
168
|
+
all_unread = body['value'].map { |msg| msg['id'] }
|
169
|
+
to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
|
170
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Getting new messages',
|
171
|
+
unread: { count: all_unread.count, ids: all_unread },
|
172
|
+
to_be_delivered: { count: to_deliver.count, ids: to_deliver } })
|
173
|
+
[to_deliver, body[NEXT_PAGE_KEY]]
|
174
|
+
rescue TypeError, JSON::ParserError => e
|
175
|
+
log_exception('Error parsing JSON response', e)
|
176
|
+
[[], nil]
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns the JSON response
|
180
|
+
def get(url)
|
181
|
+
response = token.get(url, { raise_errors: false })
|
182
|
+
|
183
|
+
# https://docs.microsoft.com/en-us/graph/errors
|
184
|
+
case response.status
|
185
|
+
when 509, 429
|
186
|
+
raise TooManyRequestsError
|
187
|
+
when 400..599
|
188
|
+
raise OAuth2::Error, response
|
189
|
+
end
|
190
|
+
|
191
|
+
return unless response.body
|
192
|
+
|
193
|
+
body = JSON.parse(response.body)
|
194
|
+
|
195
|
+
raise TypeError, 'Response did not contain value hash' unless body.is_a?(Hash) && body.key?('value')
|
196
|
+
|
197
|
+
body
|
198
|
+
end
|
199
|
+
|
200
|
+
def messages_for_ids(message_ids)
|
201
|
+
message_ids.each_with_object([]) do |id, arr|
|
202
|
+
response = token.get(rfc822_msg_url(id))
|
203
|
+
|
204
|
+
arr << ::MailRoom::Message.new(uid: id, body: response.body)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def base_url
|
209
|
+
"#{graph_endpoint}/v1.0/users/#{mailbox.email}/mailFolders/#{mailbox.name}/messages"
|
210
|
+
end
|
211
|
+
|
212
|
+
def unread_messages_url
|
213
|
+
"#{base_url}?$filter=isRead eq false"
|
214
|
+
end
|
215
|
+
|
216
|
+
def msg_url(id)
|
217
|
+
# Attempting to use the base_url fails with "The OData request is not supported"
|
218
|
+
"#{graph_endpoint}/v1.0/users/#{mailbox.email}/messages/#{id}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def rfc822_msg_url(id)
|
222
|
+
# Attempting to use the base_url fails with "The OData request is not supported"
|
223
|
+
"#{msg_url(id)}/$value"
|
224
|
+
end
|
225
|
+
|
226
|
+
def log_exception(message, exception)
|
227
|
+
@mailbox.logger.warn({ context: @mailbox.context, message: message, exception: exception.to_s })
|
228
|
+
end
|
229
|
+
|
230
|
+
def scope
|
231
|
+
"#{graph_endpoint}/.default"
|
232
|
+
end
|
233
|
+
|
234
|
+
def graph_endpoint
|
235
|
+
inbox_options[:graph_endpoint] || 'https://graph.microsoft.com'
|
236
|
+
end
|
237
|
+
|
238
|
+
def azure_ad_endpoint
|
239
|
+
inbox_options[:azure_ad_endpoint] || 'https://login.microsoftonline.com'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
data/lib/mail_room/version.rb
CHANGED
data/lib/mail_room.rb
CHANGED
@@ -7,8 +7,10 @@ end
|
|
7
7
|
|
8
8
|
require "mail_room/version"
|
9
9
|
require "mail_room/configuration"
|
10
|
+
require "mail_room/health_check"
|
10
11
|
require "mail_room/mailbox"
|
11
12
|
require "mail_room/mailbox_watcher"
|
13
|
+
require "mail_room/message"
|
12
14
|
require "mail_room/connection"
|
13
15
|
require "mail_room/coordinator"
|
14
16
|
require "mail_room/cli"
|
data/mail_room.gemspec
CHANGED
@@ -17,18 +17,27 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
+
gem.add_dependency "net-imap", ">= 0.2.1"
|
21
|
+
gem.add_dependency "oauth2", "~> 1.4.4"
|
22
|
+
gem.add_dependency "jwt", ">= 2.0"
|
23
|
+
|
24
|
+
# Pinning io-wait to 0.1.0, which is the last version to support Ruby < 3
|
25
|
+
gem.add_dependency "io-wait", "~> 0.1.0"
|
26
|
+
|
20
27
|
gem.add_development_dependency "rake"
|
21
|
-
gem.add_development_dependency "rspec"
|
22
|
-
gem.add_development_dependency "
|
23
|
-
gem.add_development_dependency "
|
28
|
+
gem.add_development_dependency "rspec", "~> 3.9"
|
29
|
+
gem.add_development_dependency "rubocop", "~> 1.11"
|
30
|
+
gem.add_development_dependency "mocha", "~> 1.11"
|
24
31
|
gem.add_development_dependency "simplecov"
|
32
|
+
gem.add_development_dependency "webrick", "~> 1.6"
|
25
33
|
|
26
34
|
# for testing delivery methods
|
27
35
|
gem.add_development_dependency "faraday"
|
28
36
|
gem.add_development_dependency "mail"
|
29
37
|
gem.add_development_dependency "letter_opener"
|
30
|
-
gem.add_development_dependency "redis", "~>
|
38
|
+
gem.add_development_dependency "redis", "~> 4"
|
31
39
|
gem.add_development_dependency "redis-namespace"
|
32
40
|
gem.add_development_dependency "pg"
|
33
41
|
gem.add_development_dependency "charlock_holmes"
|
42
|
+
gem.add_development_dependency "webmock"
|
34
43
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
aGVsbG93b3JsZA==
|
@@ -5,7 +5,8 @@ describe MailRoom::Arbitration::Redis do
|
|
5
5
|
let(:mailbox) {
|
6
6
|
build_mailbox(
|
7
7
|
arbitration_options: {
|
8
|
-
namespace: "mail_room"
|
8
|
+
namespace: "mail_room",
|
9
|
+
redis_url: ENV['REDIS_URL']
|
9
10
|
}
|
10
11
|
)
|
11
12
|
}
|
@@ -14,6 +15,7 @@ describe MailRoom::Arbitration::Redis do
|
|
14
15
|
|
15
16
|
# Private, but we don't care.
|
16
17
|
let(:redis) { subject.send(:client) }
|
18
|
+
let(:raw_client) { redis._client }
|
17
19
|
|
18
20
|
describe '#deliver?' do
|
19
21
|
context "when called the first time" do
|
@@ -78,7 +80,7 @@ describe MailRoom::Arbitration::Redis do
|
|
78
80
|
|
79
81
|
context 'redis client connection params' do
|
80
82
|
context 'when only url is present' do
|
81
|
-
let(:redis_url) {
|
83
|
+
let(:redis_url) { ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
|
82
84
|
let(:mailbox) {
|
83
85
|
build_mailbox(
|
84
86
|
arbitration_options: {
|
@@ -94,7 +96,7 @@ describe MailRoom::Arbitration::Redis do
|
|
94
96
|
it 'client has same specified url' do
|
95
97
|
subject.deliver?(123)
|
96
98
|
|
97
|
-
expect(
|
99
|
+
expect(raw_client.options[:url]).to eq redis_url
|
98
100
|
end
|
99
101
|
|
100
102
|
it 'client is a instance of Redis class' do
|
@@ -136,10 +138,10 @@ describe MailRoom::Arbitration::Redis do
|
|
136
138
|
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
137
139
|
|
138
140
|
it 'client has same specified sentinel params' do
|
139
|
-
expect(
|
140
|
-
expect(
|
141
|
-
expect(
|
142
|
-
expect(
|
141
|
+
expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
142
|
+
expect(raw_client.options[:host]).to eq('sentinel-master')
|
143
|
+
expect(raw_client.options[:password]).to eq('mypassword')
|
144
|
+
expect(raw_client.options[:sentinels]).to eq(sentinels)
|
143
145
|
end
|
144
146
|
end
|
145
147
|
end
|
data/spec/lib/cli_spec.rb
CHANGED
@@ -2,25 +2,37 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe MailRoom::CLI do
|
4
4
|
let(:config_path) {File.expand_path('../fixtures/test_config.yml', File.dirname(__FILE__))}
|
5
|
-
let!(:configuration) {MailRoom::Configuration.new({:
|
6
|
-
let(:coordinator) {stub(:
|
5
|
+
let!(:configuration) {MailRoom::Configuration.new({config_path: config_path})}
|
6
|
+
let(:coordinator) {stub(run: true, quit: true)}
|
7
|
+
let(:configuration_args) { anything }
|
8
|
+
let(:coordinator_args) { [anything, anything] }
|
7
9
|
|
8
10
|
describe '.new' do
|
9
11
|
let(:args) {["-c", "a path"]}
|
10
12
|
|
11
13
|
before :each do
|
12
|
-
MailRoom::Configuration.
|
13
|
-
MailRoom::Coordinator.stubs(:new).returns(coordinator)
|
14
|
+
MailRoom::Configuration.expects(:new).with(configuration_args).returns(configuration)
|
15
|
+
MailRoom::Coordinator.stubs(:new).with(*coordinator_args).returns(coordinator)
|
14
16
|
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
context 'with configuration args' do
|
19
|
+
let(:configuration_args) do
|
20
|
+
{config_path: 'a path'}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'parses arguments into configuration' do
|
24
|
+
expect(MailRoom::CLI.new(args).configuration).to eq configuration
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
28
|
+
context 'with coordinator args' do
|
29
|
+
let(:coordinator_args) do
|
30
|
+
[configuration.mailboxes, anything]
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'creates a new coordinator with configuration' do
|
34
|
+
expect(MailRoom::CLI.new(args).coordinator).to eq(coordinator)
|
35
|
+
end
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
@@ -30,30 +42,33 @@ describe MailRoom::CLI do
|
|
30
42
|
before :each do
|
31
43
|
cli.configuration = configuration
|
32
44
|
cli.coordinator = coordinator
|
45
|
+
cli.stubs(:exit)
|
33
46
|
end
|
34
47
|
|
35
48
|
it 'starts running the coordinator' do
|
36
|
-
|
49
|
+
coordinator.expects(:run)
|
37
50
|
|
38
|
-
|
51
|
+
cli.start
|
39
52
|
end
|
40
53
|
|
41
54
|
context 'on error' do
|
42
|
-
let(:
|
43
|
-
let(:coordinator) {
|
55
|
+
let(:error) { RuntimeError.new("oh noes!") }
|
56
|
+
let(:coordinator) { stub(run: true, quit: true) }
|
57
|
+
let(:crash_handler) { stub(handle: nil) }
|
44
58
|
|
45
59
|
before do
|
46
60
|
cli.instance_variable_set(:@options, {exit_error_format: error_format})
|
47
|
-
coordinator.stubs(:run).raises(
|
61
|
+
coordinator.stubs(:run).raises(error)
|
62
|
+
MailRoom::CrashHandler.stubs(:new).returns(crash_handler)
|
48
63
|
end
|
49
64
|
|
50
65
|
context 'json format provided' do
|
51
66
|
let(:error_format) { 'json' }
|
52
67
|
|
53
68
|
it 'passes onto CrashHandler' do
|
54
|
-
|
69
|
+
crash_handler.expects(:handle).with(error, error_format)
|
55
70
|
|
56
|
-
|
71
|
+
cli.start
|
57
72
|
end
|
58
73
|
end
|
59
74
|
end
|
@@ -3,15 +3,19 @@ require 'spec_helper'
|
|
3
3
|
describe MailRoom::Configuration do
|
4
4
|
let(:config_path) {File.expand_path('../fixtures/test_config.yml', File.dirname(__FILE__))}
|
5
5
|
|
6
|
-
describe '
|
6
|
+
describe '#initalize' do
|
7
7
|
context 'with config_path' do
|
8
|
-
let(:configuration) { MailRoom::Configuration.new(:
|
8
|
+
let(:configuration) { MailRoom::Configuration.new(config_path: config_path) }
|
9
9
|
|
10
10
|
it 'parses yaml into mailbox objects' do
|
11
11
|
MailRoom::Mailbox.stubs(:new).returns('mailbox1', 'mailbox2')
|
12
12
|
|
13
13
|
expect(configuration.mailboxes).to eq(['mailbox1', 'mailbox2'])
|
14
14
|
end
|
15
|
+
|
16
|
+
it 'parses health check' do
|
17
|
+
expect(configuration.health_check).to be_a(MailRoom::HealthCheck)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
context 'without config_path' do
|
@@ -19,10 +23,13 @@ describe MailRoom::Configuration do
|
|
19
23
|
|
20
24
|
it 'sets mailboxes to an empty set' do
|
21
25
|
MailRoom::Mailbox.stubs(:new)
|
26
|
+
MailRoom::Mailbox.expects(:new).never
|
22
27
|
|
23
28
|
expect(configuration.mailboxes).to eq([])
|
29
|
+
end
|
24
30
|
|
25
|
-
|
31
|
+
it 'sets the health check to nil' do
|
32
|
+
expect(configuration.health_check).to be_nil
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -3,20 +3,25 @@ require 'spec_helper'
|
|
3
3
|
describe MailRoom::Coordinator do
|
4
4
|
describe '#initialize' do
|
5
5
|
it 'builds a watcher for each mailbox' do
|
6
|
-
MailRoom::MailboxWatcher.
|
6
|
+
MailRoom::MailboxWatcher.expects(:new).with('mailbox1').returns('watcher1')
|
7
|
+
MailRoom::MailboxWatcher.expects(:new).with('mailbox2').returns('watcher2')
|
7
8
|
|
8
9
|
coordinator = MailRoom::Coordinator.new(['mailbox1', 'mailbox2'])
|
9
10
|
|
10
11
|
expect(coordinator.watchers).to eq(['watcher1', 'watcher2'])
|
11
|
-
|
12
|
-
expect(MailRoom::MailboxWatcher).to have_received(:new).with('mailbox1')
|
13
|
-
expect(MailRoom::MailboxWatcher).to have_received(:new).with('mailbox2')
|
14
12
|
end
|
15
13
|
|
16
14
|
it 'makes no watchers when mailboxes is empty' do
|
17
15
|
coordinator = MailRoom::Coordinator.new([])
|
18
16
|
expect(coordinator.watchers).to eq([])
|
19
17
|
end
|
18
|
+
|
19
|
+
it 'sets the health check' do
|
20
|
+
health_check = MailRoom::HealthCheck.new({ address: '127.0.0.1', port: 8080})
|
21
|
+
coordinator = MailRoom::Coordinator.new([], health_check)
|
22
|
+
|
23
|
+
expect(coordinator.health_check).to eq(health_check)
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
describe '#run' do
|
@@ -24,27 +29,37 @@ describe MailRoom::Coordinator do
|
|
24
29
|
watcher = stub
|
25
30
|
watcher.stubs(:run)
|
26
31
|
watcher.stubs(:quit)
|
32
|
+
|
33
|
+
health_check = stub
|
34
|
+
health_check.stubs(:run)
|
35
|
+
health_check.stubs(:quit)
|
36
|
+
|
27
37
|
MailRoom::MailboxWatcher.stubs(:new).returns(watcher)
|
28
|
-
coordinator = MailRoom::Coordinator.new(['mailbox1'])
|
38
|
+
coordinator = MailRoom::Coordinator.new(['mailbox1'], health_check)
|
29
39
|
coordinator.stubs(:sleep_while_running)
|
40
|
+
watcher.expects(:run)
|
41
|
+
watcher.expects(:quit)
|
42
|
+
health_check.expects(:run)
|
43
|
+
health_check.expects(:quit)
|
44
|
+
|
30
45
|
coordinator.run
|
31
|
-
expect(watcher).to have_received(:run)
|
32
|
-
expect(watcher).to have_received(:quit)
|
33
46
|
end
|
34
|
-
|
47
|
+
|
35
48
|
it 'should go to sleep after running watchers' do
|
36
49
|
coordinator = MailRoom::Coordinator.new([])
|
37
50
|
coordinator.stubs(:running=)
|
38
51
|
coordinator.stubs(:running?).returns(false)
|
52
|
+
coordinator.expects(:running=).with(true)
|
53
|
+
coordinator.expects(:running?)
|
54
|
+
|
39
55
|
coordinator.run
|
40
|
-
expect(coordinator).to have_received(:running=).with(true)
|
41
|
-
expect(coordinator).to have_received(:running?)
|
42
56
|
end
|
43
57
|
|
44
58
|
it 'should set attribute running to true' do
|
45
59
|
coordinator = MailRoom::Coordinator.new([])
|
46
60
|
coordinator.stubs(:sleep_while_running)
|
47
61
|
coordinator.run
|
62
|
+
|
48
63
|
expect(coordinator.running).to eq(true)
|
49
64
|
end
|
50
65
|
end
|
@@ -54,8 +69,9 @@ describe MailRoom::Coordinator do
|
|
54
69
|
watcher = stub(:quit)
|
55
70
|
MailRoom::MailboxWatcher.stubs(:new).returns(watcher)
|
56
71
|
coordinator = MailRoom::Coordinator.new(['mailbox1'])
|
72
|
+
watcher.expects(:quit)
|
73
|
+
|
57
74
|
coordinator.quit
|
58
|
-
expect(watcher).to have_received(:quit)
|
59
75
|
end
|
60
76
|
end
|
61
77
|
end
|
@@ -4,21 +4,22 @@ describe MailRoom::CrashHandler do
|
|
4
4
|
|
5
5
|
let(:error_message) { "oh noes!" }
|
6
6
|
let(:error) { RuntimeError.new(error_message) }
|
7
|
+
let(:stdout) { StringIO.new }
|
7
8
|
|
8
9
|
describe '#handle' do
|
9
10
|
|
10
|
-
subject{ described_class.new(error
|
11
|
+
subject{ described_class.new(stdout).handle(error, format) }
|
11
12
|
|
12
13
|
context 'when given a json format' do
|
13
14
|
let(:format) { 'json' }
|
14
|
-
let(:fake_json) do
|
15
|
-
{ message: error_message }.to_json
|
16
|
-
end
|
17
15
|
|
18
|
-
it '
|
19
|
-
subject
|
16
|
+
it 'writes a json message to stdout' do
|
17
|
+
subject
|
18
|
+
stdout.rewind
|
19
|
+
output = stdout.read
|
20
20
|
|
21
|
-
expect
|
21
|
+
expect(output).to end_with("\n")
|
22
|
+
expect(JSON.parse(output)['message']).to eq(error_message)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
@@ -26,7 +27,7 @@ describe MailRoom::CrashHandler do
|
|
26
27
|
let(:format) { "" }
|
27
28
|
|
28
29
|
it 'raises an error as designed' do
|
29
|
-
expect{ subject
|
30
|
+
expect{ subject }.to raise_error(error.class, error_message)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
@@ -34,7 +35,7 @@ describe MailRoom::CrashHandler do
|
|
34
35
|
let(:format) { "nonsense" }
|
35
36
|
|
36
37
|
it 'raises an error as designed' do
|
37
|
-
expect{ subject
|
38
|
+
expect{ subject }.to raise_error(error.class, error_message)
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
@@ -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) {build_mailbox(:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|