mail_room 0.9.1 → 0.10.0
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 +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +5 -3
- data/CHANGELOG.md +21 -0
- data/README.md +38 -1
- data/lib/mail_room.rb +1 -1
- data/lib/mail_room/arbitration/redis.rb +8 -15
- data/lib/mail_room/connection.rb +28 -8
- data/lib/mail_room/delivery/postback.rb +36 -6
- data/lib/mail_room/delivery/que.rb +4 -2
- data/lib/mail_room/delivery/sidekiq.rb +4 -3
- data/lib/mail_room/logger/structured.rb +21 -0
- data/lib/mail_room/mailbox.rb +53 -14
- data/lib/mail_room/mailbox_watcher.rb +2 -0
- data/lib/mail_room/version.rb +1 -1
- data/logfile.log +1 -0
- data/mail_room.gemspec +1 -1
- data/spec/fixtures/test_config.yml +2 -0
- data/spec/lib/arbitration/redis_spec.rb +42 -23
- data/spec/lib/configuration_spec.rb +13 -9
- data/spec/lib/connection_spec.rb +4 -2
- data/spec/lib/delivery/letter_opener_spec.rb +1 -1
- data/spec/lib/delivery/logger_spec.rb +3 -3
- data/spec/lib/delivery/postback_spec.rb +57 -17
- data/spec/lib/delivery/que_spec.rb +1 -1
- data/spec/lib/delivery/sidekiq_spec.rb +5 -5
- data/spec/lib/logger/structured_spec.rb +55 -0
- data/spec/lib/mailbox_spec.rb +38 -13
- data/spec/lib/mailbox_watcher_spec.rb +1 -1
- data/spec/spec_helper.rb +10 -1
- metadata +15 -13
- data/lib/mail_room/backports/imap.rb +0 -33
@@ -23,6 +23,7 @@ module MailRoom
|
|
23
23
|
|
24
24
|
# run the mailbox watcher
|
25
25
|
def run
|
26
|
+
@mailbox.logger.info({ context: @mailbox.context, action: "Setting up watcher" })
|
26
27
|
@running = true
|
27
28
|
|
28
29
|
connection.on_new_message do |message|
|
@@ -40,6 +41,7 @@ module MailRoom
|
|
40
41
|
|
41
42
|
# stop running, cleanup connection
|
42
43
|
def quit
|
44
|
+
@mailbox.logger.info({ context: @mailbox.context, action: "Quitting connection..." })
|
43
45
|
@running = false
|
44
46
|
|
45
47
|
if @connection
|
data/lib/mail_room/version.rb
CHANGED
data/logfile.log
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Logfile created on 2019-09-26 08:59:35 -0500 by logger.rb/66358
|
data/mail_room.gemspec
CHANGED
@@ -22,12 +22,12 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.add_development_dependency "mocha"
|
23
23
|
gem.add_development_dependency "bourne"
|
24
24
|
gem.add_development_dependency "simplecov"
|
25
|
-
gem.add_development_dependency "fakeredis"
|
26
25
|
|
27
26
|
# for testing delivery methods
|
28
27
|
gem.add_development_dependency "faraday"
|
29
28
|
gem.add_development_dependency "mail"
|
30
29
|
gem.add_development_dependency "letter_opener"
|
30
|
+
gem.add_development_dependency "redis", "~> 3.3.1"
|
31
31
|
gem.add_development_dependency "redis-namespace"
|
32
32
|
gem.add_development_dependency "pg"
|
33
33
|
gem.add_development_dependency "charlock_holmes"
|
@@ -3,7 +3,7 @@ require 'mail_room/arbitration/redis'
|
|
3
3
|
|
4
4
|
describe MailRoom::Arbitration::Redis do
|
5
5
|
let(:mailbox) {
|
6
|
-
|
6
|
+
build_mailbox(
|
7
7
|
arbitration_options: {
|
8
8
|
namespace: "mail_room"
|
9
9
|
}
|
@@ -13,10 +13,14 @@ describe MailRoom::Arbitration::Redis do
|
|
13
13
|
subject { described_class.new(options) }
|
14
14
|
|
15
15
|
# Private, but we don't care.
|
16
|
-
let(:
|
16
|
+
let(:redis) { subject.send(:client) }
|
17
17
|
|
18
18
|
describe '#deliver?' do
|
19
19
|
context "when called the first time" do
|
20
|
+
after do
|
21
|
+
redis.del("delivered:123")
|
22
|
+
end
|
23
|
+
|
20
24
|
it "returns true" do
|
21
25
|
expect(subject.deliver?(123)).to be_truthy
|
22
26
|
end
|
@@ -24,29 +28,35 @@ describe MailRoom::Arbitration::Redis do
|
|
24
28
|
it "increments the delivered flag" do
|
25
29
|
subject.deliver?(123)
|
26
30
|
|
27
|
-
expect(
|
31
|
+
expect(redis.get("delivered:123")).to eq("1")
|
28
32
|
end
|
29
33
|
|
30
34
|
it "sets an expiration on the delivered flag" do
|
31
35
|
subject.deliver?(123)
|
32
36
|
|
33
|
-
expect(
|
37
|
+
expect(redis.ttl("delivered:123")).to be > 0
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
41
|
context "when called the second time" do
|
38
42
|
before do
|
39
|
-
|
43
|
+
#Short expiration, 1 second, for testing
|
44
|
+
subject.deliver?(123, 1)
|
40
45
|
end
|
41
46
|
|
42
|
-
|
43
|
-
|
47
|
+
after do
|
48
|
+
redis.del("delivered:123")
|
44
49
|
end
|
45
50
|
|
46
|
-
it "
|
47
|
-
subject.deliver?(123)
|
51
|
+
it "returns false" do
|
52
|
+
expect(subject.deliver?(123, 1)).to be_falsey
|
53
|
+
end
|
48
54
|
|
49
|
-
|
55
|
+
it "after expiration returns true" do
|
56
|
+
# Fails locally because fakeredis returns 0, not false
|
57
|
+
expect(subject.deliver?(123, 1)).to be_falsey
|
58
|
+
sleep(redis.ttl("delivered:123")+1)
|
59
|
+
expect(subject.deliver?(123, 1)).to be_truthy
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
@@ -55,38 +65,47 @@ describe MailRoom::Arbitration::Redis do
|
|
55
65
|
subject.deliver?(123)
|
56
66
|
end
|
57
67
|
|
68
|
+
after do
|
69
|
+
redis.del("delivered:123")
|
70
|
+
redis.del("delivered:124")
|
71
|
+
end
|
72
|
+
|
58
73
|
it "returns true" do
|
59
|
-
expect(subject.deliver?(
|
74
|
+
expect(subject.deliver?(124)).to be_truthy
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
63
78
|
|
64
79
|
context 'redis client connection params' do
|
65
80
|
context 'when only url is present' do
|
66
|
-
let(:redis_url) { "redis://
|
81
|
+
let(:redis_url) { "redis://localhost:6379" }
|
67
82
|
let(:mailbox) {
|
68
|
-
|
83
|
+
build_mailbox(
|
69
84
|
arbitration_options: {
|
70
85
|
redis_url: redis_url
|
71
86
|
}
|
72
87
|
)
|
73
88
|
}
|
74
89
|
|
90
|
+
after do
|
91
|
+
redis.del("delivered:123")
|
92
|
+
end
|
93
|
+
|
75
94
|
it 'client has same specified url' do
|
76
95
|
subject.deliver?(123)
|
77
96
|
|
78
|
-
expect(client.options[:url]).to eq redis_url
|
97
|
+
expect(redis.client.options[:url]).to eq redis_url
|
79
98
|
end
|
80
99
|
|
81
100
|
it 'client is a instance of Redis class' do
|
82
|
-
expect(
|
101
|
+
expect(redis).to be_a Redis
|
83
102
|
end
|
84
103
|
end
|
85
104
|
|
86
105
|
context 'when namespace is present' do
|
87
106
|
let(:namespace) { 'mail_room' }
|
88
107
|
let(:mailbox) {
|
89
|
-
|
108
|
+
build_mailbox(
|
90
109
|
arbitration_options: {
|
91
110
|
namespace: namespace
|
92
111
|
}
|
@@ -94,11 +113,11 @@ describe MailRoom::Arbitration::Redis do
|
|
94
113
|
}
|
95
114
|
|
96
115
|
it 'client has same specified namespace' do
|
97
|
-
expect(
|
116
|
+
expect(redis.namespace).to eq(namespace)
|
98
117
|
end
|
99
118
|
|
100
119
|
it 'client is a instance of RedisNamespace class' do
|
101
|
-
expect(
|
120
|
+
expect(redis).to be_a ::Redis::Namespace
|
102
121
|
end
|
103
122
|
end
|
104
123
|
|
@@ -106,7 +125,7 @@ describe MailRoom::Arbitration::Redis do
|
|
106
125
|
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
107
126
|
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
108
127
|
let(:mailbox) {
|
109
|
-
|
128
|
+
build_mailbox(
|
110
129
|
arbitration_options: {
|
111
130
|
redis_url: redis_url,
|
112
131
|
sentinels: sentinels
|
@@ -117,10 +136,10 @@ describe MailRoom::Arbitration::Redis do
|
|
117
136
|
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
118
137
|
|
119
138
|
it 'client has same specified sentinel params' do
|
120
|
-
expect(
|
121
|
-
expect(
|
122
|
-
expect(
|
123
|
-
expect(
|
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)
|
124
143
|
end
|
125
144
|
end
|
126
145
|
end
|
@@ -4,22 +4,26 @@ describe MailRoom::Configuration do
|
|
4
4
|
let(:config_path) {File.expand_path('../fixtures/test_config.yml', File.dirname(__FILE__))}
|
5
5
|
|
6
6
|
describe 'set_mailboxes' do
|
7
|
-
|
8
|
-
MailRoom::
|
7
|
+
context 'with config_path' do
|
8
|
+
let(:configuration) { MailRoom::Configuration.new(:config_path => config_path) }
|
9
9
|
|
10
|
-
|
10
|
+
it 'parses yaml into mailbox objects' do
|
11
|
+
MailRoom::Mailbox.stubs(:new).returns('mailbox1', 'mailbox2')
|
11
12
|
|
12
|
-
|
13
|
+
expect(configuration.mailboxes).to eq(['mailbox1', 'mailbox2'])
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
MailRoom::
|
17
|
+
context 'without config_path' do
|
18
|
+
let(:configuration) { MailRoom::Configuration.new }
|
17
19
|
|
18
|
-
|
20
|
+
it 'sets mailboxes to an empty set' do
|
21
|
+
MailRoom::Mailbox.stubs(:new)
|
19
22
|
|
20
|
-
|
23
|
+
expect(configuration.mailboxes).to eq([])
|
21
24
|
|
22
|
-
|
25
|
+
expect(MailRoom::Mailbox).to have_received(:new).never
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
data/spec/lib/connection_spec.rb
CHANGED
@@ -2,10 +2,10 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe MailRoom::Connection do
|
4
4
|
let(:imap) {stub}
|
5
|
-
let(:mailbox) {
|
5
|
+
let(:mailbox) {build_mailbox(delete_after_delivery: true, expunge_deleted: true)}
|
6
6
|
|
7
7
|
before :each do
|
8
|
-
|
8
|
+
Net::IMAP.stubs(:new).returns(imap)
|
9
9
|
end
|
10
10
|
|
11
11
|
context "with imap set up" do
|
@@ -50,6 +50,7 @@ describe MailRoom::Connection do
|
|
50
50
|
imap.stubs(:uid_search).returns([]).then.returns([1])
|
51
51
|
imap.stubs(:uid_fetch).returns([new_message])
|
52
52
|
imap.stubs(:store)
|
53
|
+
imap.stubs(:expunge)
|
53
54
|
|
54
55
|
connection.wait
|
55
56
|
|
@@ -58,6 +59,7 @@ describe MailRoom::Connection do
|
|
58
59
|
expect(imap).to have_received(:uid_fetch).with([1], "RFC822")
|
59
60
|
expect(mailbox).to have_received(:deliver?).with(1)
|
60
61
|
expect(imap).to have_received(:store).with(8, "+FLAGS", [Net::IMAP::DELETED])
|
62
|
+
expect(imap).to have_received(:expunge).once
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -3,7 +3,7 @@ require 'mail_room/delivery/letter_opener'
|
|
3
3
|
|
4
4
|
describe MailRoom::Delivery::LetterOpener do
|
5
5
|
describe '#deliver' do
|
6
|
-
let(:mailbox) {
|
6
|
+
let(:mailbox) {build_mailbox(:location => '/tmp/somewhere')}
|
7
7
|
let(:delivery_method) {stub(:deliver!)}
|
8
8
|
let(:mail) {stub}
|
9
9
|
|
@@ -4,7 +4,7 @@ require 'mail_room/delivery/logger'
|
|
4
4
|
describe MailRoom::Delivery::Logger do
|
5
5
|
describe '#initialize' do
|
6
6
|
context "without a log path" do
|
7
|
-
let(:mailbox) {
|
7
|
+
let(:mailbox) {build_mailbox}
|
8
8
|
|
9
9
|
it 'creates a new ruby logger' do
|
10
10
|
::Logger.stubs(:new)
|
@@ -16,7 +16,7 @@ describe MailRoom::Delivery::Logger do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
context "with a log path" do
|
19
|
-
let(:mailbox) {
|
19
|
+
let(:mailbox) {build_mailbox(:log_path => '/var/log/mail-room.log')}
|
20
20
|
|
21
21
|
it 'creates a new file to append to' do
|
22
22
|
::Logger.stubs(:new)
|
@@ -32,7 +32,7 @@ describe MailRoom::Delivery::Logger do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
describe '#deliver' do
|
35
|
-
let(:mailbox) {
|
35
|
+
let(:mailbox) {build_mailbox}
|
36
36
|
|
37
37
|
it 'writes the message to info' do
|
38
38
|
logger = stub(:info)
|
@@ -3,29 +3,69 @@ require 'mail_room/delivery/postback'
|
|
3
3
|
|
4
4
|
describe MailRoom::Delivery::Postback do
|
5
5
|
describe '#deliver' do
|
6
|
-
|
7
|
-
:
|
8
|
-
|
9
|
-
|
6
|
+
context 'with token auth delivery' do
|
7
|
+
let(:mailbox) {build_mailbox({
|
8
|
+
:delivery_url => 'http://localhost/inbox',
|
9
|
+
:delivery_token => 'abcdefg'
|
10
|
+
})}
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Faraday.stubs(:new).returns(connection)
|
12
|
+
let(:delivery_options) {
|
13
|
+
MailRoom::Delivery::Postback::Options.new(mailbox)
|
14
|
+
}
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
it 'posts the message with faraday' do
|
17
|
+
connection = stub
|
18
|
+
request = stub
|
19
|
+
Faraday.stubs(:new).returns(connection)
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
+
connection.stubs(:token_auth)
|
22
|
+
connection.stubs(:post).yields(request)
|
21
23
|
|
22
|
-
|
24
|
+
request.stubs(:url)
|
25
|
+
request.stubs(:body=)
|
23
26
|
|
24
|
-
|
25
|
-
expect(connection).to have_received(:post)
|
27
|
+
MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
+
expect(connection).to have_received(:token_auth).with('abcdefg')
|
30
|
+
expect(connection).to have_received(:post)
|
31
|
+
|
32
|
+
expect(request).to have_received(:url).with('http://localhost/inbox')
|
33
|
+
expect(request).to have_received(:body=).with('a message')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with basic auth delivery options' do
|
38
|
+
let(:mailbox) {build_mailbox({
|
39
|
+
:delivery_options => {
|
40
|
+
:url => 'http://localhost/inbox',
|
41
|
+
:username => 'user1',
|
42
|
+
:password => 'password123abc'
|
43
|
+
}
|
44
|
+
})}
|
45
|
+
|
46
|
+
let(:delivery_options) {
|
47
|
+
MailRoom::Delivery::Postback::Options.new(mailbox)
|
48
|
+
}
|
49
|
+
|
50
|
+
it 'posts the message with faraday' do
|
51
|
+
connection = stub
|
52
|
+
request = stub
|
53
|
+
Faraday.stubs(:new).returns(connection)
|
54
|
+
|
55
|
+
connection.stubs(:basic_auth)
|
56
|
+
connection.stubs(:post).yields(request)
|
57
|
+
|
58
|
+
request.stubs(:url)
|
59
|
+
request.stubs(:body=)
|
60
|
+
|
61
|
+
MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
|
62
|
+
|
63
|
+
expect(connection).to have_received(:basic_auth).with('user1', 'password123abc')
|
64
|
+
expect(connection).to have_received(:post)
|
65
|
+
|
66
|
+
expect(request).to have_received(:url).with('http://localhost/inbox')
|
67
|
+
expect(request).to have_received(:body=).with('a message')
|
68
|
+
end
|
29
69
|
end
|
30
70
|
end
|
31
71
|
end
|
@@ -7,11 +7,11 @@ describe MailRoom::Delivery::Sidekiq do
|
|
7
7
|
let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
|
8
8
|
|
9
9
|
describe '#options' do
|
10
|
-
let(:redis_url) { 'redis://
|
10
|
+
let(:redis_url) { 'redis://localhost' }
|
11
11
|
|
12
12
|
context 'when only redis_url is specified' do
|
13
13
|
let(:mailbox) {
|
14
|
-
|
14
|
+
build_mailbox(
|
15
15
|
delivery_method: :sidekiq,
|
16
16
|
delivery_options: {
|
17
17
|
redis_url: redis_url
|
@@ -20,7 +20,7 @@ describe MailRoom::Delivery::Sidekiq do
|
|
20
20
|
}
|
21
21
|
|
22
22
|
it 'client has same specified redis_url' do
|
23
|
-
expect(redis.options[:url]).to eq(redis_url)
|
23
|
+
expect(redis.client.options[:url]).to eq(redis_url)
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'client is a instance of RedisNamespace class' do
|
@@ -31,7 +31,7 @@ describe MailRoom::Delivery::Sidekiq do
|
|
31
31
|
context 'when namespace is specified' do
|
32
32
|
let(:namespace) { 'sidekiq_mailman' }
|
33
33
|
let(:mailbox) {
|
34
|
-
|
34
|
+
build_mailbox(
|
35
35
|
delivery_method: :sidekiq,
|
36
36
|
delivery_options: {
|
37
37
|
redis_url: redis_url,
|
@@ -53,7 +53,7 @@ describe MailRoom::Delivery::Sidekiq do
|
|
53
53
|
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
54
54
|
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
55
55
|
let(:mailbox) {
|
56
|
-
|
56
|
+
build_mailbox(
|
57
57
|
delivery_method: :sidekiq,
|
58
58
|
delivery_options: {
|
59
59
|
redis_url: redis_url,
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MailRoom::Logger::Structured do
|
4
|
+
|
5
|
+
subject { described_class.new $stdout }
|
6
|
+
|
7
|
+
let!(:now) { Time.now }
|
8
|
+
let(:message) { { action: 'exciting development', message: 'testing 123' } }
|
9
|
+
|
10
|
+
before do
|
11
|
+
Time.stubs(:now).returns(now)
|
12
|
+
end
|
13
|
+
|
14
|
+
[:debug, :info, :warn, :error, :fatal].each do |level|
|
15
|
+
it "logs #{level}" do
|
16
|
+
expect { subject.send(level, message) }.to output(json_matching(level.to_s.upcase, message)).to_stdout_from_any_process
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'logs unknown' do
|
21
|
+
expect { subject.unknown(message) }.to output(json_matching("ANY", message)).to_stdout_from_any_process
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'only accepts strings' do
|
25
|
+
expect { subject.unknown("just a string!") }.to raise_error(ArgumentError, /must be a Hash/)
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'logging a hash as a message' do
|
29
|
+
it 'merges the contents' do
|
30
|
+
input = {
|
31
|
+
additional_field: "some value"
|
32
|
+
}
|
33
|
+
expected = {
|
34
|
+
severity: 'DEBUG',
|
35
|
+
time: now,
|
36
|
+
additional_field: "some value"
|
37
|
+
}
|
38
|
+
|
39
|
+
expect { subject.debug(input) }.to output(as_regex(expected)).to_stdout_from_any_process
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def json_matching(level, message)
|
44
|
+
contents = {
|
45
|
+
severity: level,
|
46
|
+
time: now
|
47
|
+
}.merge(message)
|
48
|
+
|
49
|
+
as_regex(contents)
|
50
|
+
end
|
51
|
+
|
52
|
+
def as_regex(contents)
|
53
|
+
/#{Regexp.quote(contents.to_json)}/
|
54
|
+
end
|
55
|
+
end
|