gitlab-mail_room 0.0.2

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.gitlab-ci.yml +27 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +125 -0
  7. data/CODE_OF_CONDUCT.md +24 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +361 -0
  11. data/Rakefile +6 -0
  12. data/bin/mail_room +5 -0
  13. data/lib/mail_room.rb +16 -0
  14. data/lib/mail_room/arbitration.rb +16 -0
  15. data/lib/mail_room/arbitration/noop.rb +18 -0
  16. data/lib/mail_room/arbitration/redis.rb +58 -0
  17. data/lib/mail_room/cli.rb +62 -0
  18. data/lib/mail_room/configuration.rb +36 -0
  19. data/lib/mail_room/connection.rb +195 -0
  20. data/lib/mail_room/coordinator.rb +41 -0
  21. data/lib/mail_room/crash_handler.rb +29 -0
  22. data/lib/mail_room/delivery.rb +24 -0
  23. data/lib/mail_room/delivery/letter_opener.rb +34 -0
  24. data/lib/mail_room/delivery/logger.rb +37 -0
  25. data/lib/mail_room/delivery/noop.rb +22 -0
  26. data/lib/mail_room/delivery/postback.rb +72 -0
  27. data/lib/mail_room/delivery/que.rb +63 -0
  28. data/lib/mail_room/delivery/sidekiq.rb +88 -0
  29. data/lib/mail_room/logger/structured.rb +21 -0
  30. data/lib/mail_room/mailbox.rb +182 -0
  31. data/lib/mail_room/mailbox_watcher.rb +62 -0
  32. data/lib/mail_room/version.rb +4 -0
  33. data/logfile.log +1 -0
  34. data/mail_room.gemspec +34 -0
  35. data/spec/fixtures/test_config.yml +16 -0
  36. data/spec/lib/arbitration/redis_spec.rb +146 -0
  37. data/spec/lib/cli_spec.rb +61 -0
  38. data/spec/lib/configuration_spec.rb +29 -0
  39. data/spec/lib/connection_spec.rb +65 -0
  40. data/spec/lib/coordinator_spec.rb +61 -0
  41. data/spec/lib/crash_handler_spec.rb +41 -0
  42. data/spec/lib/delivery/letter_opener_spec.rb +29 -0
  43. data/spec/lib/delivery/logger_spec.rb +46 -0
  44. data/spec/lib/delivery/postback_spec.rb +107 -0
  45. data/spec/lib/delivery/que_spec.rb +45 -0
  46. data/spec/lib/delivery/sidekiq_spec.rb +76 -0
  47. data/spec/lib/logger/structured_spec.rb +55 -0
  48. data/spec/lib/mailbox_spec.rb +132 -0
  49. data/spec/lib/mailbox_watcher_spec.rb +64 -0
  50. data/spec/spec_helper.rb +32 -0
  51. metadata +277 -0
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailRoom::CrashHandler do
4
+
5
+ let(:error_message) { "oh noes!" }
6
+ let(:error) { RuntimeError.new(error_message) }
7
+
8
+ describe '#handle' do
9
+
10
+ subject{ described_class.new(error: error, format: format) }
11
+
12
+ context 'when given a json format' do
13
+ let(:format) { 'json' }
14
+ let(:fake_json) do
15
+ { message: error_message }.to_json
16
+ end
17
+
18
+ it 'outputs the result of json to stdout' do
19
+ subject.stubs(:json).returns(fake_json)
20
+
21
+ expect{ subject.handle }.to output(/\"message\":\"#{error_message}\"/).to_stdout
22
+ end
23
+ end
24
+
25
+ context 'when given a blank format' do
26
+ let(:format) { "" }
27
+
28
+ it 'raises an error as designed' do
29
+ expect{ subject.handle }.to raise_error(error.class, error_message)
30
+ end
31
+ end
32
+
33
+ context 'when given a nonexistent format' do
34
+ let(:format) { "nonsense" }
35
+
36
+ it 'raises an error as designed' do
37
+ expect{ subject.handle }.to raise_error(error.class, error_message)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'mail_room/delivery/letter_opener'
3
+
4
+ describe MailRoom::Delivery::LetterOpener do
5
+ describe '#deliver' do
6
+ let(:mailbox) {build_mailbox(:location => '/tmp/somewhere')}
7
+ let(:delivery_method) {stub(:deliver!)}
8
+ let(:mail) {stub}
9
+
10
+ before :each do
11
+ Mail.stubs(:read_from_string).returns(mail)
12
+ ::LetterOpener::DeliveryMethod.stubs(:new).returns(delivery_method)
13
+
14
+ MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
15
+ end
16
+
17
+ it 'creates a new LetterOpener::DeliveryMethod' do
18
+ expect(::LetterOpener::DeliveryMethod).to have_received(:new).with(:location => '/tmp/somewhere')
19
+ end
20
+
21
+ it 'parses the message string with Mail' do
22
+ expect(::Mail).to have_received(:read_from_string).with('a message')
23
+ end
24
+
25
+ it 'delivers the mail message' do
26
+ expect(delivery_method).to have_received(:deliver!).with(mail)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'mail_room/delivery/logger'
3
+
4
+ describe MailRoom::Delivery::Logger do
5
+ describe '#initialize' do
6
+ context "without a log path" do
7
+ let(:mailbox) {build_mailbox}
8
+
9
+ it 'creates a new ruby logger' do
10
+ ::Logger.stubs(:new)
11
+
12
+ MailRoom::Delivery::Logger.new(mailbox)
13
+
14
+ expect(::Logger).to have_received(:new).with(STDOUT)
15
+ end
16
+ end
17
+
18
+ context "with a log path" do
19
+ let(:mailbox) {build_mailbox(:log_path => '/var/log/mail-room.log')}
20
+
21
+ it 'creates a new file to append to' do
22
+ ::Logger.stubs(:new)
23
+ file = stub(:sync=)
24
+ ::File.stubs(:open).returns(file)
25
+
26
+ MailRoom::Delivery::Logger.new(mailbox)
27
+
28
+ expect(File).to have_received(:open).with('/var/log/mail-room.log', 'a')
29
+ expect(::Logger).to have_received(:new).with(file)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#deliver' do
35
+ let(:mailbox) {build_mailbox}
36
+
37
+ it 'writes the message to info' do
38
+ logger = stub(:info)
39
+ ::Logger.stubs(:new).returns(logger)
40
+
41
+ MailRoom::Delivery::Logger.new(mailbox).deliver('a message')
42
+
43
+ expect(logger).to have_received(:info).with('a message')
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+ require 'mail_room/delivery/postback'
3
+
4
+ describe MailRoom::Delivery::Postback do
5
+ describe '#deliver' do
6
+ context 'with token auth delivery' do
7
+ let(:mailbox) {build_mailbox({
8
+ :delivery_url => 'http://localhost/inbox',
9
+ :delivery_token => 'abcdefg'
10
+ })}
11
+
12
+ let(:delivery_options) {
13
+ MailRoom::Delivery::Postback::Options.new(mailbox)
14
+ }
15
+
16
+ it 'posts the message with faraday' do
17
+ connection = stub
18
+ request = stub
19
+ Faraday.stubs(:new).returns(connection)
20
+
21
+ connection.stubs(:token_auth)
22
+ connection.stubs(:post).yields(request)
23
+
24
+ request.stubs(:url)
25
+ request.stubs(:body=)
26
+
27
+ MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
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
69
+
70
+ context 'with content type in the delivery options' do
71
+ let(:mailbox) {build_mailbox({
72
+ :delivery_options => {
73
+ :url => 'http://localhost/inbox',
74
+ :username => 'user1',
75
+ :password => 'password123abc',
76
+ :content_type => 'text/plain'
77
+ }
78
+ })}
79
+
80
+
81
+ let(:delivery_options) {
82
+ MailRoom::Delivery::Postback::Options.new(mailbox)
83
+ }
84
+
85
+ it 'posts the message with faraday' do
86
+ connection = stub
87
+ request = stub
88
+ Faraday.stubs(:new).returns(connection)
89
+
90
+ connection.stubs(:basic_auth)
91
+ connection.stubs(:post).yields(request)
92
+
93
+ request.stubs(:url)
94
+ request.stubs(:body=)
95
+ request.stubs(:headers).returns({})
96
+
97
+ MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
98
+
99
+ expect(connection).to have_received(:basic_auth).with('user1', 'password123abc')
100
+ expect(connection).to have_received(:post)
101
+
102
+ expect(request.headers['Content-Type']).to eq('text/plain')
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'mail_room/delivery/que'
3
+
4
+ describe MailRoom::Delivery::Que do
5
+ describe '#deliver' do
6
+ let(:mailbox) {build_mailbox({
7
+ delivery_options: {
8
+ database: 'delivery_test',
9
+ username: 'postgres',
10
+ password: '',
11
+ queue: 'default',
12
+ priority: 5,
13
+ job_class: 'ParseMailJob'
14
+ }
15
+ })}
16
+
17
+ let(:connection) {stub}
18
+ let(:options) {MailRoom::Delivery::Que::Options.new(mailbox)}
19
+
20
+ it 'stores the message in que_jobs table' do
21
+ PG.stubs(:connect).returns(connection)
22
+ connection.stubs(:exec)
23
+
24
+ MailRoom::Delivery::Que.new(options).deliver('email')
25
+
26
+ expect(PG).to have_received(:connect).with({
27
+ host: 'localhost',
28
+ port: 5432,
29
+ dbname: 'delivery_test',
30
+ user: 'postgres',
31
+ password: ''
32
+ })
33
+
34
+ expect(connection).to have_received(:exec).with(
35
+ "INSERT INTO que_jobs (priority, job_class, queue, args) VALUES ($1, $2, $3, $4)",
36
+ [
37
+ 5,
38
+ 'ParseMailJob',
39
+ 'default',
40
+ JSON.dump(['email'])
41
+ ]
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'mail_room/delivery/sidekiq'
3
+
4
+ describe MailRoom::Delivery::Sidekiq do
5
+ subject { described_class.new(options) }
6
+ let(:redis) { subject.send(:client) }
7
+ let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
8
+
9
+ describe '#options' do
10
+ let(:redis_url) { 'redis://localhost' }
11
+
12
+ context 'when only redis_url is specified' do
13
+ let(:mailbox) {
14
+ build_mailbox(
15
+ delivery_method: :sidekiq,
16
+ delivery_options: {
17
+ redis_url: redis_url
18
+ }
19
+ )
20
+ }
21
+
22
+ it 'client has same specified redis_url' do
23
+ expect(redis.client.options[:url]).to eq(redis_url)
24
+ end
25
+
26
+ it 'client is a instance of RedisNamespace class' do
27
+ expect(redis).to be_a ::Redis
28
+ end
29
+ end
30
+
31
+ context 'when namespace is specified' do
32
+ let(:namespace) { 'sidekiq_mailman' }
33
+ let(:mailbox) {
34
+ build_mailbox(
35
+ delivery_method: :sidekiq,
36
+ delivery_options: {
37
+ redis_url: redis_url,
38
+ namespace: namespace
39
+ }
40
+ )
41
+ }
42
+
43
+ it 'client has same specified namespace' do
44
+ expect(redis.namespace).to eq(namespace)
45
+ end
46
+
47
+ it 'client is a instance of RedisNamespace class' do
48
+ expect(redis).to be_a ::Redis::Namespace
49
+ end
50
+ end
51
+
52
+ context 'when sentinel is specified' do
53
+ let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
54
+ let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
55
+ let(:mailbox) {
56
+ build_mailbox(
57
+ delivery_method: :sidekiq,
58
+ delivery_options: {
59
+ redis_url: redis_url,
60
+ sentinels: sentinels
61
+ }
62
+ )
63
+ }
64
+
65
+ before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
66
+
67
+ it 'client has same specified sentinel params' do
68
+ expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
69
+ expect(redis.client.options[:host]).to eq('sentinel-master')
70
+ expect(redis.client.options[:password]).to eq('mypassword')
71
+ expect(redis.client.options[:sentinels]).to eq(sentinels)
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -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 hashes' 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
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailRoom::Mailbox do
4
+ let(:sample_message) { {'RFC822' => 'a message', 'UID' => 123} }
5
+
6
+ describe "#deliver" do
7
+ context "with arbitration_method of noop" do
8
+ it 'arbitrates with a Noop instance' do
9
+ mailbox = build_mailbox({:arbitration_method => 'noop'})
10
+ noop = stub(:deliver?)
11
+ MailRoom::Arbitration['noop'].stubs(:new => noop)
12
+
13
+ uid = 123
14
+
15
+ mailbox.deliver?(uid)
16
+
17
+ expect(noop).to have_received(:deliver?).with(uid)
18
+ end
19
+ end
20
+
21
+ context "with arbitration_method of redis" do
22
+ it 'arbitrates with a Redis instance' do
23
+ mailbox = build_mailbox({:arbitration_method => 'redis'})
24
+ redis = stub(:deliver?)
25
+ MailRoom::Arbitration['redis'].stubs(:new => redis)
26
+
27
+ uid = 123
28
+
29
+ mailbox.deliver?(uid)
30
+
31
+ expect(redis).to have_received(:deliver?).with(uid)
32
+ end
33
+ end
34
+
35
+ context "with delivery_method of noop" do
36
+ it 'delivers with a Noop instance' do
37
+ mailbox = build_mailbox({:delivery_method => 'noop'})
38
+ noop = stub(:deliver)
39
+ MailRoom::Delivery['noop'].stubs(:new => noop)
40
+
41
+ mailbox.deliver(stub(:attr => sample_message))
42
+
43
+ expect(noop).to have_received(:deliver).with('a message')
44
+ end
45
+ end
46
+
47
+ context "with delivery_method of logger" do
48
+ it 'delivers with a Logger instance' do
49
+ mailbox = build_mailbox({:delivery_method => 'logger'})
50
+ logger = stub(:deliver)
51
+ MailRoom::Delivery['logger'].stubs(:new => logger)
52
+
53
+ mailbox.deliver(stub(:attr => sample_message))
54
+
55
+ expect(logger).to have_received(:deliver).with('a message')
56
+ end
57
+ end
58
+
59
+ context "with delivery_method of postback" do
60
+ it 'delivers with a Postback instance' do
61
+ mailbox = build_mailbox({:delivery_method => 'postback'})
62
+ postback = stub(:deliver)
63
+ MailRoom::Delivery['postback'].stubs(:new => postback)
64
+
65
+ mailbox.deliver(stub(:attr => sample_message))
66
+
67
+ expect(postback).to have_received(:deliver).with('a message')
68
+ end
69
+ end
70
+
71
+ context "with delivery_method of letter_opener" do
72
+ it 'delivers with a LetterOpener instance' do
73
+ mailbox = build_mailbox({:delivery_method => 'letter_opener'})
74
+ letter_opener = stub(:deliver)
75
+ MailRoom::Delivery['letter_opener'].stubs(:new => letter_opener)
76
+
77
+ mailbox.deliver(stub(:attr => sample_message))
78
+
79
+ expect(letter_opener).to have_received(:deliver).with('a message')
80
+ end
81
+ end
82
+
83
+ context "without an RFC822 attribute" do
84
+ it "doesn't deliver the message" do
85
+ mailbox = build_mailbox({ name: "magic mailbox", delivery_method: 'noop' })
86
+ noop = stub(:deliver)
87
+ MailRoom::Delivery['noop'].stubs(:new => noop)
88
+
89
+ mailbox.deliver(stub(:attr => {'FLAGS' => [:Seen, :Recent]}))
90
+
91
+ expect(noop).to have_received(:deliver).never
92
+ end
93
+ end
94
+
95
+ context "with ssl options hash" do
96
+ it 'replaces verify mode with constant' do
97
+ mailbox = build_mailbox({:ssl => {:verify_mode => :none}})
98
+
99
+ expect(mailbox.ssl_options).to eq({:verify_mode => OpenSSL::SSL::VERIFY_NONE})
100
+ end
101
+ end
102
+
103
+ context 'structured logger setup' do
104
+ it 'sets up the logger correctly and does not error' do
105
+ mailbox = build_mailbox({ name: "magic mailbox", logger: { log_path: '/dev/null' } })
106
+
107
+ expect{ mailbox.logger.info(message: "asdf") }.not_to raise_error
108
+ end
109
+
110
+ it 'accepts stdout symbol to mean STDOUT' do
111
+ mailbox = build_mailbox({ name: "magic mailbox", logger: { log_path: :stdout } })
112
+
113
+ expect{ mailbox.logger.info(message: "asdf") }.not_to raise_error
114
+ end
115
+
116
+ it 'sets up the noop logger correctly and does not error' do
117
+ mailbox = build_mailbox({ name: "magic mailbox" })
118
+
119
+ expect{ mailbox.logger.info(message: "asdf") }.not_to raise_error
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#validate!" do
125
+ context "with missing configuration" do
126
+ it 'raises an error' do
127
+ expect { build_mailbox({:name => nil}) }.to raise_error(MailRoom::ConfigurationError)
128
+ expect { build_mailbox({:host => nil}) }.to raise_error(MailRoom::ConfigurationError)
129
+ end
130
+ end
131
+ end
132
+ end