gitlab-mail_room 0.0.10 → 0.0.19
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 +4 -4
- data/.gitlab/issue_templates/Release.md +1 -0
- data/.gitlab-ci.yml +11 -21
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +501 -0
- data/.ruby-version +1 -1
- data/.travis.yml +9 -3
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +40 -0
- data/README.md +99 -9
- data/Rakefile +1 -1
- data/lib/mail_room/arbitration/redis.rb +1 -1
- data/lib/mail_room/connection.rb +6 -1
- data/lib/mail_room/delivery/letter_opener.rb +1 -1
- data/lib/mail_room/delivery/postback.rb +36 -4
- data/lib/mail_room/delivery/sidekiq.rb +4 -3
- data/lib/mail_room/jwt.rb +39 -0
- data/lib/mail_room/mailbox.rb +56 -17
- data/lib/mail_room/mailbox_watcher.rb +7 -1
- data/lib/mail_room/microsoft_graph/connection.rb +232 -0
- data/lib/mail_room/microsoft_graph.rb +7 -0
- data/lib/mail_room/version.rb +1 -1
- data/mail_room.gemspec +8 -1
- data/spec/fixtures/jwt_secret +1 -0
- data/spec/lib/arbitration/redis_spec.rb +6 -5
- data/spec/lib/cli_spec.rb +3 -3
- data/spec/lib/configuration_spec.rb +1 -1
- data/spec/lib/delivery/letter_opener_spec.rb +2 -2
- data/spec/lib/delivery/logger_spec.rb +1 -1
- data/spec/lib/delivery/postback_spec.rb +62 -14
- data/spec/lib/delivery/sidekiq_spec.rb +34 -11
- data/spec/lib/jwt_spec.rb +80 -0
- data/spec/lib/mailbox_spec.rb +65 -17
- data/spec/lib/mailbox_watcher_spec.rb +54 -38
- data/spec/lib/microsoft_graph/connection_spec.rb +221 -0
- data/spec/spec_helper.rb +13 -3
- metadata +87 -5
@@ -0,0 +1,232 @@
|
|
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
|
+
SCOPE = 'https://graph.microsoft.com/.default'
|
10
|
+
NEXT_PAGE_KEY = '@odata.nextLink'
|
11
|
+
DEFAULT_POLL_INTERVAL_S = 60
|
12
|
+
|
13
|
+
TooManyRequestsError = Class.new(RuntimeError)
|
14
|
+
|
15
|
+
attr_accessor :token, :throttled_count
|
16
|
+
|
17
|
+
def initialize(mailbox)
|
18
|
+
super
|
19
|
+
|
20
|
+
reset
|
21
|
+
setup
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
return if stopped?
|
26
|
+
|
27
|
+
process_mailbox
|
28
|
+
|
29
|
+
@throttled_count = 0
|
30
|
+
wait_for_new_messages
|
31
|
+
rescue TooManyRequestsError => e
|
32
|
+
@throttled_count += 1
|
33
|
+
|
34
|
+
@mailbox.logger.warn({ context: @mailbox.context, action: 'Too many requests, backing off...', backoff_s: backoff_secs, error: e.message, error_backtrace: e.backtrace })
|
35
|
+
|
36
|
+
backoff
|
37
|
+
rescue IOError => e
|
38
|
+
@mailbox.logger.warn({ context: @mailbox.context, action: 'Disconnected. Resetting...', error: e.message, error_backtrace: e.backtrace })
|
39
|
+
|
40
|
+
reset
|
41
|
+
setup
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def wait_for_new_messages
|
47
|
+
sleep_while_running(poll_interval)
|
48
|
+
end
|
49
|
+
|
50
|
+
def backoff
|
51
|
+
sleep_while_running(backoff_secs)
|
52
|
+
end
|
53
|
+
|
54
|
+
def backoff_secs
|
55
|
+
[60 * 10, 2**throttled_count].min
|
56
|
+
end
|
57
|
+
|
58
|
+
# Unless wake up periodically, we won't notice that the thread was stopped
|
59
|
+
# if we sleep the entire interval.
|
60
|
+
def sleep_while_running(sleep_interval)
|
61
|
+
sleep_interval.times do
|
62
|
+
do_sleep(1)
|
63
|
+
return if stopped?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_sleep(interval)
|
68
|
+
sleep(interval)
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset
|
72
|
+
@token = nil
|
73
|
+
@throttled_count = 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup
|
77
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Retrieving OAuth2 token...' })
|
78
|
+
|
79
|
+
@token = client.client_credentials.get_token({ scope: SCOPE })
|
80
|
+
end
|
81
|
+
|
82
|
+
def client
|
83
|
+
@client ||= OAuth2::Client.new(client_id, client_secret,
|
84
|
+
site: 'https://login.microsoftonline.com',
|
85
|
+
authorize_url: "/#{tenant_id}/oauth2/v2.0/authorize",
|
86
|
+
token_url: "/#{tenant_id}/oauth2/v2.0/token",
|
87
|
+
auth_scheme: :basic_auth)
|
88
|
+
end
|
89
|
+
|
90
|
+
def inbox_options
|
91
|
+
mailbox.inbox_options
|
92
|
+
end
|
93
|
+
|
94
|
+
def tenant_id
|
95
|
+
inbox_options[:tenant_id]
|
96
|
+
end
|
97
|
+
|
98
|
+
def client_id
|
99
|
+
inbox_options[:client_id]
|
100
|
+
end
|
101
|
+
|
102
|
+
def client_secret
|
103
|
+
inbox_options[:client_secret]
|
104
|
+
end
|
105
|
+
|
106
|
+
def poll_interval
|
107
|
+
@poll_interval ||= begin
|
108
|
+
interval = inbox_options[:poll_interval].to_i
|
109
|
+
|
110
|
+
if interval.positive?
|
111
|
+
interval
|
112
|
+
else
|
113
|
+
DEFAULT_POLL_INTERVAL_S
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_mailbox
|
119
|
+
return unless @new_message_handler
|
120
|
+
|
121
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Processing started' })
|
122
|
+
|
123
|
+
new_messages.each do |msg|
|
124
|
+
success = @new_message_handler.call(msg)
|
125
|
+
handle_delivered(msg) if success
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def handle_delivered(msg)
|
130
|
+
mark_as_read(msg)
|
131
|
+
delete_message(msg) if @mailbox.delete_after_delivery
|
132
|
+
end
|
133
|
+
|
134
|
+
def delete_message(msg)
|
135
|
+
token.delete(msg_url(msg.uid))
|
136
|
+
end
|
137
|
+
|
138
|
+
def mark_as_read(msg)
|
139
|
+
token.patch(msg_url(msg.uid),
|
140
|
+
headers: { 'Content-Type' => 'application/json' },
|
141
|
+
body: { isRead: true }.to_json)
|
142
|
+
end
|
143
|
+
|
144
|
+
def new_messages
|
145
|
+
messages_for_ids(new_message_ids)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Yields a page of message IDs at a time
|
149
|
+
def new_message_ids
|
150
|
+
url = unread_messages_url
|
151
|
+
|
152
|
+
Enumerator.new do |block|
|
153
|
+
loop do
|
154
|
+
messages, next_page_url = unread_messages(url: url)
|
155
|
+
messages.each { |msg| block.yield msg }
|
156
|
+
|
157
|
+
break unless next_page_url
|
158
|
+
|
159
|
+
url = next_page_url
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def unread_messages(url:)
|
165
|
+
body = get(url)
|
166
|
+
|
167
|
+
return [[], nil] unless body
|
168
|
+
|
169
|
+
all_unread = body['value'].map { |msg| msg['id'] }
|
170
|
+
to_deliver = all_unread.select { |uid| @mailbox.deliver?(uid) }
|
171
|
+
@mailbox.logger.info({ context: @mailbox.context, action: 'Getting new messages',
|
172
|
+
unread: { count: all_unread.count, ids: all_unread },
|
173
|
+
to_be_delivered: { count: to_deliver.count, ids: to_deliver } })
|
174
|
+
[to_deliver, body[NEXT_PAGE_KEY]]
|
175
|
+
rescue TypeError, JSON::ParserError => e
|
176
|
+
log_exception('Error parsing JSON response', e)
|
177
|
+
[[], nil]
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the JSON response
|
181
|
+
def get(url)
|
182
|
+
response = token.get(url, { raise_errors: false })
|
183
|
+
|
184
|
+
# https://docs.microsoft.com/en-us/graph/errors
|
185
|
+
case response.status
|
186
|
+
when 509, 429
|
187
|
+
raise TooManyRequestsError
|
188
|
+
when 400..599
|
189
|
+
raise OAuth2::Error, response
|
190
|
+
end
|
191
|
+
|
192
|
+
return unless response.body
|
193
|
+
|
194
|
+
body = JSON.parse(response.body)
|
195
|
+
|
196
|
+
raise TypeError, 'Response did not contain value hash' unless body.is_a?(Hash) && body.key?('value')
|
197
|
+
|
198
|
+
body
|
199
|
+
end
|
200
|
+
|
201
|
+
def messages_for_ids(message_ids)
|
202
|
+
message_ids.each_with_object([]) do |id, arr|
|
203
|
+
response = token.get(rfc822_msg_url(id))
|
204
|
+
|
205
|
+
arr << ::MailRoom::Message.new(uid: id, body: response.body)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def base_url
|
210
|
+
"https://graph.microsoft.com/v1.0/users/#{mailbox.email}/mailFolders/#{mailbox.name}/messages"
|
211
|
+
end
|
212
|
+
|
213
|
+
def unread_messages_url
|
214
|
+
"#{base_url}?$filter=isRead eq false"
|
215
|
+
end
|
216
|
+
|
217
|
+
def msg_url(id)
|
218
|
+
# Attempting to use the base_url fails with "The OData request is not supported"
|
219
|
+
"https://graph.microsoft.com/v1.0/users/#{mailbox.email}/messages/#{id}"
|
220
|
+
end
|
221
|
+
|
222
|
+
def rfc822_msg_url(id)
|
223
|
+
# Attempting to use the base_url fails with "The OData request is not supported"
|
224
|
+
"#{msg_url(id)}/$value"
|
225
|
+
end
|
226
|
+
|
227
|
+
def log_exception(message, exception)
|
228
|
+
@mailbox.logger.warn({ context: @mailbox.context, message: message, exception: exception.to_s })
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/lib/mail_room/version.rb
CHANGED
data/mail_room.gemspec
CHANGED
@@ -18,9 +18,15 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
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"
|
21
26
|
|
22
27
|
gem.add_development_dependency "rake"
|
23
28
|
gem.add_development_dependency "rspec", "~> 3.9"
|
29
|
+
gem.add_development_dependency "rubocop", "~> 1.11"
|
24
30
|
gem.add_development_dependency "mocha", "~> 1.11"
|
25
31
|
gem.add_development_dependency "simplecov"
|
26
32
|
gem.add_development_dependency "webrick", "~> 1.6"
|
@@ -29,8 +35,9 @@ Gem::Specification.new do |gem|
|
|
29
35
|
gem.add_development_dependency "faraday"
|
30
36
|
gem.add_development_dependency "mail"
|
31
37
|
gem.add_development_dependency "letter_opener"
|
32
|
-
gem.add_development_dependency "redis", "~>
|
38
|
+
gem.add_development_dependency "redis", "~> 4"
|
33
39
|
gem.add_development_dependency "redis-namespace"
|
34
40
|
gem.add_development_dependency "pg"
|
35
41
|
gem.add_development_dependency "charlock_holmes"
|
42
|
+
gem.add_development_dependency "webmock"
|
36
43
|
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(
|
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(
|
141
|
-
expect(
|
142
|
-
expect(
|
143
|
-
expect(
|
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({:
|
6
|
-
let(:coordinator) {stub(:
|
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
|
-
{:
|
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(:
|
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,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) {build_mailbox(:
|
6
|
+
let(:mailbox) {build_mailbox(location: '/tmp/somewhere')}
|
7
7
|
let(:delivery_method) {stub(:deliver!)}
|
8
8
|
let(:mail) {stub}
|
9
9
|
|
@@ -13,7 +13,7 @@ describe MailRoom::Delivery::LetterOpener do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'creates a new LetterOpener::DeliveryMethod' do
|
16
|
-
::LetterOpener::DeliveryMethod.expects(:new).with(:
|
16
|
+
::LetterOpener::DeliveryMethod.expects(:new).with(location: '/tmp/somewhere').returns(delivery_method)
|
17
17
|
|
18
18
|
MailRoom::Delivery::LetterOpener.new(mailbox).deliver('a message')
|
19
19
|
end
|
@@ -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) {build_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
|
file = stub(:sync=)
|
@@ -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
|
-
:
|
9
|
-
:
|
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
|
-
:
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
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
|
-
:
|
61
|
-
:
|
62
|
-
:
|
63
|
-
:
|
64
|
-
:
|
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,50 @@ 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
|
-
|
23
|
-
|
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
|
-
|
27
|
-
|
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
|
+
|
47
|
+
it 'connection has correct values' do
|
48
|
+
expect(redis.connection[:host]).to eq('localhost')
|
49
|
+
expect(redis.connection[:db]).to eq(4)
|
50
|
+
end
|
28
51
|
end
|
29
52
|
end
|
30
53
|
|
@@ -65,10 +88,10 @@ describe MailRoom::Delivery::Sidekiq do
|
|
65
88
|
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
66
89
|
|
67
90
|
it 'client has same specified sentinel params' do
|
68
|
-
expect(
|
69
|
-
expect(
|
70
|
-
expect(
|
71
|
-
expect(
|
91
|
+
expect(raw_client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
92
|
+
expect(raw_client.options[:host]).to eq('sentinel-master')
|
93
|
+
expect(raw_client.options[:password]).to eq('mypassword')
|
94
|
+
expect(raw_client.options[:sentinels]).to eq(sentinels)
|
72
95
|
end
|
73
96
|
end
|
74
97
|
|
@@ -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
|