gitlab-mail_room 0.0.2

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