gitlab-mail_room 0.0.4 → 0.0.20

Sign up to get free protection for your applications and to get access to all the features.
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