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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab/issue_templates/Default.md +9 -0
  3. data/.gitlab/issue_templates/Release.md +8 -0
  4. data/.gitlab-ci.yml +18 -16
  5. data/.rubocop.yml +5 -0
  6. data/.rubocop_todo.yml +494 -0
  7. data/.ruby-version +1 -1
  8. data/.travis.yml +12 -5
  9. data/CHANGELOG.md +4 -0
  10. data/CONTRIBUTING.md +40 -0
  11. data/README.md +136 -10
  12. data/Rakefile +1 -1
  13. data/lib/mail_room/arbitration/redis.rb +1 -1
  14. data/lib/mail_room/cli.rb +2 -2
  15. data/lib/mail_room/configuration.rb +11 -1
  16. data/lib/mail_room/connection.rb +10 -177
  17. data/lib/mail_room/coordinator.rb +8 -4
  18. data/lib/mail_room/crash_handler.rb +8 -12
  19. data/lib/mail_room/delivery/letter_opener.rb +1 -1
  20. data/lib/mail_room/delivery/postback.rb +36 -4
  21. data/lib/mail_room/delivery/sidekiq.rb +4 -3
  22. data/lib/mail_room/health_check.rb +60 -0
  23. data/lib/mail_room/imap/connection.rb +200 -0
  24. data/lib/mail_room/imap/message.rb +19 -0
  25. data/lib/mail_room/imap.rb +8 -0
  26. data/lib/mail_room/jwt.rb +39 -0
  27. data/lib/mail_room/logger/structured.rb +15 -1
  28. data/lib/mail_room/mailbox.rb +62 -20
  29. data/lib/mail_room/mailbox_watcher.rb +15 -2
  30. data/lib/mail_room/message.rb +16 -0
  31. data/lib/mail_room/microsoft_graph/connection.rb +243 -0
  32. data/lib/mail_room/microsoft_graph.rb +7 -0
  33. data/lib/mail_room/version.rb +2 -2
  34. data/lib/mail_room.rb +2 -0
  35. data/mail_room.gemspec +13 -4
  36. data/spec/fixtures/jwt_secret +1 -0
  37. data/spec/fixtures/test_config.yml +3 -0
  38. data/spec/lib/arbitration/redis_spec.rb +9 -7
  39. data/spec/lib/cli_spec.rb +32 -17
  40. data/spec/lib/configuration_spec.rb +10 -3
  41. data/spec/lib/coordinator_spec.rb +27 -11
  42. data/spec/lib/crash_handler_spec.rb +10 -9
  43. data/spec/lib/delivery/letter_opener_spec.rb +10 -6
  44. data/spec/lib/delivery/logger_spec.rb +8 -10
  45. data/spec/lib/delivery/postback_spec.rb +73 -41
  46. data/spec/lib/delivery/que_spec.rb +5 -8
  47. data/spec/lib/delivery/sidekiq_spec.rb +33 -11
  48. data/spec/lib/health_check_spec.rb +57 -0
  49. data/spec/lib/{connection_spec.rb → imap/connection_spec.rb} +13 -17
  50. data/spec/lib/imap/message_spec.rb +36 -0
  51. data/spec/lib/jwt_spec.rb +80 -0
  52. data/spec/lib/logger/structured_spec.rb +34 -2
  53. data/spec/lib/mailbox_spec.rb +79 -34
  54. data/spec/lib/mailbox_watcher_spec.rb +54 -41
  55. data/spec/lib/message_spec.rb +35 -0
  56. data/spec/lib/microsoft_graph/connection_spec.rb +252 -0
  57. data/spec/spec_helper.rb +14 -4
  58. 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MailRoom
4
+ module MicrosoftGraph
5
+ autoload :Connection, 'mail_room/microsoft_graph/connection'
6
+ end
7
+ end
@@ -1,4 +1,4 @@
1
1
  module MailRoom
2
- # Current version of MailRoom gem
3
- VERSION = "0.0.4"
2
+ # Current version of gitlab-mail_room gem
3
+ VERSION = "0.0.20"
4
4
  end
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 "mocha"
23
- gem.add_development_dependency "bourne"
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", "~> 3.3.1"
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==
@@ -1,4 +1,7 @@
1
1
  ---
2
+ :health_check:
3
+ :address: "127.0.0.1"
4
+ :port: 8080
2
5
  :mailboxes:
3
6
  -
4
7
  :email: "user1@gmail.com"
@@ -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) { "redis://localhost:6379" }
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(redis.client.options[:url]).to eq redis_url
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(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
140
- expect(redis.client.options[:host]).to eq('sentinel-master')
141
- expect(redis.client.options[:password]).to eq('mypassword')
142
- expect(redis.client.options[:sentinels]).to eq(sentinels)
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({:config_path => config_path})}
6
- let(:coordinator) {stub(:run => true, :quit => true)}
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.stubs(:new).returns(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
- it 'parses arguments into configuration' do
17
- expect(MailRoom::CLI.new(args).configuration).to eq(configuration)
18
- expect(MailRoom::Configuration).to have_received(:new).with({:config_path => 'a path'})
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
- it 'creates a new coordinator with configuration' do
22
- expect(MailRoom::CLI.new(args).coordinator).to eq(coordinator)
23
- expect(MailRoom::Coordinator).to have_received(:new).with(configuration.mailboxes)
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
- cli.start
49
+ coordinator.expects(:run)
37
50
 
38
- expect(coordinator).to have_received(:run)
51
+ cli.start
39
52
  end
40
53
 
41
54
  context 'on error' do
42
- let(:error_message) { "oh noes!" }
43
- let(:coordinator) { OpenStruct.new(run: true, quit: true) }
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(RuntimeError, error_message)
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
- cli.start
69
+ crash_handler.expects(:handle).with(error, error_format)
55
70
 
56
- expect(MailRoom::CrashHandler).to have_received(:new).with a_hash_including({format: error_format})
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 'set_mailboxes' do
6
+ describe '#initalize' do
7
7
  context 'with config_path' do
8
- let(:configuration) { MailRoom::Configuration.new(:config_path => config_path) }
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
- expect(MailRoom::Mailbox).to have_received(:new).never
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.stubs(:new).returns('watcher1', 'watcher2')
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: error, format: format) }
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 'outputs the result of json to stdout' do
19
- subject.stubs(:json).returns(fake_json)
16
+ it 'writes a json message to stdout' do
17
+ subject
18
+ stdout.rewind
19
+ output = stdout.read
20
20
 
21
- expect{ subject.handle }.to output(/\"message\":\"#{error_message}\"/).to_stdout
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.handle }.to raise_error(error.class, error_message)
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.handle }.to raise_error(error.class, error_message)
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(: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