gitlab-mail_room 0.0.9 → 0.0.23

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab/issue_templates/Default.md +9 -0
  3. data/.gitlab/issue_templates/Release.md +1 -0
  4. data/.gitlab-ci.yml +14 -24
  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 +125 -14
  12. data/Rakefile +1 -1
  13. data/lib/mail_room/arbitration/redis.rb +1 -1
  14. data/lib/mail_room/connection.rb +6 -1
  15. data/lib/mail_room/crash_handler.rb +2 -1
  16. data/lib/mail_room/delivery/letter_opener.rb +1 -1
  17. data/lib/mail_room/delivery/postback.rb +42 -6
  18. data/lib/mail_room/delivery/que.rb +15 -1
  19. data/lib/mail_room/delivery/sidekiq.rb +4 -3
  20. data/lib/mail_room/jwt.rb +39 -0
  21. data/lib/mail_room/logger/structured.rb +15 -1
  22. data/lib/mail_room/mailbox.rb +56 -17
  23. data/lib/mail_room/mailbox_watcher.rb +7 -1
  24. data/lib/mail_room/microsoft_graph/connection.rb +243 -0
  25. data/lib/mail_room/microsoft_graph.rb +7 -0
  26. data/lib/mail_room/version.rb +1 -1
  27. data/mail_room.gemspec +7 -1
  28. data/spec/fixtures/jwt_secret +1 -0
  29. data/spec/lib/arbitration/redis_spec.rb +6 -5
  30. data/spec/lib/cli_spec.rb +3 -3
  31. data/spec/lib/configuration_spec.rb +1 -1
  32. data/spec/lib/delivery/letter_opener_spec.rb +4 -3
  33. data/spec/lib/delivery/logger_spec.rb +3 -2
  34. data/spec/lib/delivery/postback_spec.rb +62 -14
  35. data/spec/lib/delivery/sidekiq_spec.rb +33 -11
  36. data/spec/lib/jwt_spec.rb +80 -0
  37. data/spec/lib/logger/structured_spec.rb +34 -2
  38. data/spec/lib/mailbox_spec.rb +65 -17
  39. data/spec/lib/mailbox_watcher_spec.rb +54 -38
  40. data/spec/lib/microsoft_graph/connection_spec.rb +252 -0
  41. data/spec/spec_helper.rb +14 -3
  42. metadata +97 -8
@@ -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
2
  # Current version of gitlab-mail_room gem
3
- VERSION = "0.0.9"
3
+ VERSION = "0.0.23"
4
4
  end
data/mail_room.gemspec CHANGED
@@ -17,8 +17,13 @@ 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", "< 3"]
22
+ gem.add_dependency "jwt", ">= 2.0"
23
+
20
24
  gem.add_development_dependency "rake"
21
25
  gem.add_development_dependency "rspec", "~> 3.9"
26
+ gem.add_development_dependency "rubocop", "~> 1.11"
22
27
  gem.add_development_dependency "mocha", "~> 1.11"
23
28
  gem.add_development_dependency "simplecov"
24
29
  gem.add_development_dependency "webrick", "~> 1.6"
@@ -27,8 +32,9 @@ Gem::Specification.new do |gem|
27
32
  gem.add_development_dependency "faraday"
28
33
  gem.add_development_dependency "mail"
29
34
  gem.add_development_dependency "letter_opener"
30
- gem.add_development_dependency "redis", "~> 3.3.1"
35
+ gem.add_development_dependency "redis", "~> 4"
31
36
  gem.add_development_dependency "redis-namespace"
32
37
  gem.add_development_dependency "pg"
33
38
  gem.add_development_dependency "charlock_holmes"
39
+ gem.add_development_dependency "webmock"
34
40
  end
@@ -0,0 +1 @@
1
+ aGVsbG93b3JsZA==
@@ -15,6 +15,7 @@ describe MailRoom::Arbitration::Redis do
15
15
 
16
16
  # Private, but we don't care.
17
17
  let(:redis) { subject.send(:client) }
18
+ let(:raw_client) { redis._client }
18
19
 
19
20
  describe '#deliver?' do
20
21
  context "when called the first time" do
@@ -95,7 +96,7 @@ describe MailRoom::Arbitration::Redis do
95
96
  it 'client has same specified url' do
96
97
  subject.deliver?(123)
97
98
 
98
- expect(redis.client.options[:url]).to eq redis_url
99
+ expect(raw_client.options[:url]).to eq redis_url
99
100
  end
100
101
 
101
102
  it 'client is a instance of Redis class' do
@@ -137,10 +138,10 @@ describe MailRoom::Arbitration::Redis do
137
138
  before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
138
139
 
139
140
  it 'client has same specified sentinel params' do
140
- expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
141
- expect(redis.client.options[:host]).to eq('sentinel-master')
142
- expect(redis.client.options[:password]).to eq('mypassword')
143
- 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)
144
145
  end
145
146
  end
146
147
  end
data/spec/lib/cli_spec.rb CHANGED
@@ -2,8 +2,8 @@ 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
7
  let(:configuration_args) { anything }
8
8
  let(:coordinator_args) { [anything, anything] }
9
9
 
@@ -17,7 +17,7 @@ describe MailRoom::CLI do
17
17
 
18
18
  context 'with configuration args' do
19
19
  let(:configuration_args) do
20
- {:config_path => 'a path'}
20
+ {config_path: 'a path'}
21
21
  end
22
22
 
23
23
  it 'parses arguments into configuration' do
@@ -5,7 +5,7 @@ describe MailRoom::Configuration do
5
5
 
6
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')
@@ -3,17 +3,18 @@ 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')}
7
- let(:delivery_method) {stub(:deliver!)}
6
+ let(:mailbox) {build_mailbox(location: '/tmp/somewhere')}
7
+ let(:delivery_method) {stub}
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
+ delivery_method.stubs(:deliver!)
13
14
  end
14
15
 
15
16
  it 'creates a new LetterOpener::DeliveryMethod' do
16
- ::LetterOpener::DeliveryMethod.expects(:new).with(:location => '/tmp/somewhere').returns(delivery_method)
17
+ ::LetterOpener::DeliveryMethod.expects(:new).with(location: '/tmp/somewhere').returns(delivery_method)
17
18
 
18
19
  MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
19
20
  end
@@ -16,10 +16,11 @@ describe MailRoom::Delivery::Logger do
16
16
  end
17
17
 
18
18
  context "with a log path" do
19
- let(:mailbox) {build_mailbox(:log_path => '/var/log/mail-room.log')}
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
- file = stub(:sync=)
22
+ file = stub
23
+ file.stubs(:sync=)
23
24
 
24
25
  File.expects(:open).with('/var/log/mail-room.log', 'a').returns(file)
25
26
  ::Logger.stubs(:new).with(file)
@@ -5,8 +5,8 @@ describe MailRoom::Delivery::Postback do
5
5
  describe '#deliver' do
6
6
  context 'with token auth delivery' do
7
7
  let(:mailbox) {build_mailbox({
8
- :delivery_url => 'http://localhost/inbox',
9
- :delivery_token => 'abcdefg'
8
+ delivery_url: 'http://localhost/inbox',
9
+ delivery_token: 'abcdefg'
10
10
  })}
11
11
 
12
12
  let(:delivery_options) {
@@ -30,10 +30,10 @@ describe MailRoom::Delivery::Postback do
30
30
 
31
31
  context 'with basic auth delivery options' do
32
32
  let(:mailbox) {build_mailbox({
33
- :delivery_options => {
34
- :url => 'http://localhost/inbox',
35
- :username => 'user1',
36
- :password => 'password123abc'
33
+ delivery_options: {
34
+ url: 'http://localhost/inbox',
35
+ username: 'user1',
36
+ password: 'password123abc'
37
37
  }
38
38
  })}
39
39
 
@@ -57,19 +57,18 @@ describe MailRoom::Delivery::Postback do
57
57
 
58
58
  context 'with content type in the delivery options' do
59
59
  let(:mailbox) {build_mailbox({
60
- :delivery_options => {
61
- :url => 'http://localhost/inbox',
62
- :username => 'user1',
63
- :password => 'password123abc',
64
- :content_type => 'text/plain'
60
+ delivery_options: {
61
+ url: 'http://localhost/inbox',
62
+ username: 'user1',
63
+ password: 'password123abc',
64
+ content_type: 'text/plain'
65
65
  }
66
66
  })}
67
67
 
68
-
69
68
  let(:delivery_options) {
70
69
  MailRoom::Delivery::Postback::Options.new(mailbox)
71
70
  }
72
-
71
+
73
72
  it 'posts the message with faraday' do
74
73
  connection = stub
75
74
  request = stub
@@ -82,10 +81,59 @@ describe MailRoom::Delivery::Postback do
82
81
  connection.expects(:basic_auth).with('user1', 'password123abc')
83
82
 
84
83
  MailRoom::Delivery::Postback.new(delivery_options).deliver('a message')
85
-
84
+
86
85
  expect(request.headers['Content-Type']).to eq('text/plain')
87
86
  end
88
87
  end
88
+
89
+ context 'with jwt token in the delivery options' do
90
+ let(:mailbox) {build_mailbox({
91
+ delivery_options: {
92
+ url: 'http://localhost/inbox',
93
+ jwt_auth_header: "Mailroom-Api-Request",
94
+ jwt_issuer: "mailroom",
95
+ jwt_algorithm: "HS256",
96
+ jwt_secret_path: "secret_path"
97
+ }
98
+ })}
99
+
100
+ let(:delivery_options) {
101
+ MailRoom::Delivery::Postback::Options.new(mailbox)
102
+ }
103
+
104
+ it 'posts the message with faraday' do
105
+ connection = stub
106
+ request = stub
107
+ Faraday.stubs(:new).returns(connection)
108
+
109
+ connection.expects(:post).yields(request).twice
110
+ request.stubs(:url)
111
+ request.stubs(:body=)
112
+ request.stubs(:headers).returns({})
113
+
114
+ jwt = stub
115
+ MailRoom::JWT.expects(:new).with(
116
+ header: 'Mailroom-Api-Request',
117
+ issuer: 'mailroom',
118
+ algorithm: 'HS256',
119
+ secret_path: 'secret_path'
120
+ ).returns(jwt)
121
+ jwt.stubs(:valid?).returns(true)
122
+ jwt.stubs(:header).returns('Mailroom-Api-Request')
123
+ jwt.stubs(:token).returns('a_jwt_token')
124
+
125
+ delivery = MailRoom::Delivery::Postback.new(delivery_options)
126
+
127
+ delivery.deliver('a message')
128
+ expect(request.headers['Mailroom-Api-Request']).to eql('a_jwt_token')
129
+
130
+ # A different jwt token for the second time
131
+ jwt.stubs(:token).returns('another_jwt_token')
132
+
133
+ delivery.deliver('another message')
134
+ expect(request.headers['Mailroom-Api-Request']).to eql('another_jwt_token')
135
+ end
136
+ end
89
137
  end
90
138
  end
91
139
  end
@@ -4,27 +4,49 @@ require 'mail_room/delivery/sidekiq'
4
4
  describe MailRoom::Delivery::Sidekiq do
5
5
  subject { described_class.new(options) }
6
6
  let(:redis) { subject.send(:client) }
7
+ let(:raw_client) { redis._client }
7
8
  let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
8
9
 
9
10
  describe '#options' do
10
11
  let(:redis_url) { 'redis://localhost' }
12
+ let(:redis_options) { { redis_url: redis_url } }
11
13
 
12
14
  context 'when only redis_url is specified' do
13
15
  let(:mailbox) {
14
16
  build_mailbox(
15
17
  delivery_method: :sidekiq,
16
- delivery_options: {
17
- redis_url: redis_url
18
- }
18
+ delivery_options: redis_options
19
19
  )
20
20
  }
21
21
 
22
- it 'client has same specified redis_url' do
23
- expect(redis.client.options[:url]).to eq(redis_url)
22
+ context 'with simple redis url' do
23
+ it 'client has same specified redis_url' do
24
+ expect(raw_client.options[:url]).to eq(redis_url)
25
+ end
26
+
27
+ it 'client is a instance of RedisNamespace class' do
28
+ expect(redis).to be_a ::Redis
29
+ end
30
+
31
+ it 'connection has correct values' do
32
+ expect(redis.connection[:host]).to eq('localhost')
33
+ expect(redis.connection[:db]).to eq(0)
34
+ end
24
35
  end
25
36
 
26
- it 'client is a instance of RedisNamespace class' do
27
- expect(redis).to be_a ::Redis
37
+ context 'with redis_db specified in options' do
38
+ before do
39
+ redis_options[:redis_db] = 4
40
+ end
41
+
42
+ it 'client has correct redis_url' do
43
+ expect(raw_client.options[:url]).to eq(redis_url)
44
+ end
45
+
46
+ it 'connection has correct values' do
47
+ expect(redis.connection[:host]).to eq('localhost')
48
+ expect(redis.connection[:db]).to eq(4)
49
+ end
28
50
  end
29
51
  end
30
52
 
@@ -65,10 +87,10 @@ describe MailRoom::Delivery::Sidekiq do
65
87
  before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
66
88
 
67
89
  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)
90
+ expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
91
+ expect(raw_client.options[:host]).to eq('sentinel-master')
92
+ expect(raw_client.options[:password]).to eq('mypassword')
93
+ expect(raw_client.options[:sentinels]).to eq(sentinels)
72
94
  end
73
95
  end
74
96
 
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ require 'mail_room/jwt'
4
+
5
+ describe MailRoom::JWT do
6
+ let(:secret_path) { File.expand_path('../fixtures/jwt_secret', File.dirname(__FILE__)) }
7
+ let(:secret) { Base64.strict_decode64(File.read(secret_path).chomp) }
8
+
9
+ let(:standard_config) do
10
+ {
11
+ secret_path: secret_path,
12
+ issuer: 'mailroom',
13
+ header: 'Mailroom-Api-Request',
14
+ algorithm: 'HS256'
15
+ }
16
+ end
17
+
18
+ describe '#token' do
19
+ let(:jwt) { described_class.new(**standard_config) }
20
+
21
+ it 'generates a valid jwt token' do
22
+ token = jwt.token
23
+ expect(token).not_to be_empty
24
+
25
+ payload = nil
26
+ expect do
27
+ payload = JWT.decode(token, secret, true, iss: 'mailroom', verify_iat: true, verify_iss: true, algorithm: 'HS256')
28
+ end.not_to raise_error
29
+ expect(payload).to be_an(Array)
30
+ expect(payload).to match(
31
+ [
32
+ a_hash_including(
33
+ 'iss' => 'mailroom',
34
+ 'nonce' => be_a(String),
35
+ 'iat' => be_a(Integer)
36
+ ),
37
+ { 'alg' => 'HS256' }
38
+ ]
39
+ )
40
+ end
41
+
42
+ it 'generates a different token for each invocation' do
43
+ expect(jwt.token).not_to eql(jwt.token)
44
+ end
45
+ end
46
+
47
+ describe '#valid?' do
48
+ it 'returns true if all essential components are present' do
49
+ jwt = described_class.new(**standard_config)
50
+ expect(jwt.valid?).to eql(true)
51
+ end
52
+
53
+ it 'returns true if header and secret path are present' do
54
+ jwt = described_class.new(
55
+ secret_path: secret_path,
56
+ header: 'Mailroom-Api-Request',
57
+ issuer: nil,
58
+ algorithm: nil
59
+ )
60
+ expect(jwt.valid?).to eql(true)
61
+ expect(jwt.issuer).to eql(described_class::DEFAULT_ISSUER)
62
+ expect(jwt.algorithm).to eql(described_class::DEFAULT_ALGORITHM)
63
+ end
64
+
65
+ it 'returns false if either header or secret_path are missing' do
66
+ expect(described_class.new(
67
+ secret_path: nil,
68
+ header: 'Mailroom-Api-Request',
69
+ issuer: nil,
70
+ algorithm: nil
71
+ ).valid?).to eql(false)
72
+ expect(described_class.new(
73
+ secret_path: secret_path,
74
+ header: nil,
75
+ issuer: nil,
76
+ algorithm: nil
77
+ ).valid?).to eql(false)
78
+ end
79
+ end
80
+ end