gitlab-mail_room 0.0.10 → 0.0.14

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.
@@ -3,12 +3,23 @@ require 'spec_helper'
3
3
  describe MailRoom::Mailbox do
4
4
  let(:sample_message) { MailRoom::Message.new(uid: 123, body: 'a message') }
5
5
 
6
+ context 'with IMAP configuration' do
7
+ subject { build_mailbox }
8
+
9
+ describe '#imap?' do
10
+ it 'configured as an IMAP inbox' do
11
+ expect(subject.imap?).to be true
12
+ expect(subject.microsoft_graph?).to be false
13
+ end
14
+ end
15
+ end
16
+
6
17
  describe "#deliver" do
7
18
  context "with arbitration_method of noop" do
8
19
  it 'arbitrates with a Noop instance' do
9
- mailbox = build_mailbox({:arbitration_method => 'noop'})
20
+ mailbox = build_mailbox({arbitration_method: 'noop'})
10
21
  noop = stub(:deliver?)
11
- MailRoom::Arbitration['noop'].stubs(:new => noop)
22
+ MailRoom::Arbitration['noop'].stubs(new: noop)
12
23
 
13
24
  uid = 123
14
25
 
@@ -20,9 +31,9 @@ describe MailRoom::Mailbox do
20
31
 
21
32
  context "with arbitration_method of redis" do
22
33
  it 'arbitrates with a Redis instance' do
23
- mailbox = build_mailbox({:arbitration_method => 'redis'})
34
+ mailbox = build_mailbox({arbitration_method: 'redis'})
24
35
  redis = stub(:deliver?)
25
- MailRoom::Arbitration['redis'].stubs(:new => redis)
36
+ MailRoom::Arbitration['redis'].stubs(new: redis)
26
37
  uid = 123
27
38
  redis.expects(:deliver?).with(uid)
28
39
 
@@ -32,9 +43,9 @@ describe MailRoom::Mailbox do
32
43
 
33
44
  context "with delivery_method of noop" do
34
45
  it 'delivers with a Noop instance' do
35
- mailbox = build_mailbox({:delivery_method => 'noop'})
46
+ mailbox = build_mailbox({delivery_method: 'noop'})
36
47
  noop = stub(:deliver)
37
- MailRoom::Delivery['noop'].stubs(:new => noop)
48
+ MailRoom::Delivery['noop'].stubs(new: noop)
38
49
 
39
50
  noop.expects(:deliver).with(sample_message.body)
40
51
 
@@ -44,9 +55,9 @@ describe MailRoom::Mailbox do
44
55
 
45
56
  context "with delivery_method of logger" do
46
57
  it 'delivers with a Logger instance' do
47
- mailbox = build_mailbox({:delivery_method => 'logger'})
58
+ mailbox = build_mailbox({delivery_method: 'logger'})
48
59
  logger = stub(:deliver)
49
- MailRoom::Delivery['logger'].stubs(:new => logger)
60
+ MailRoom::Delivery['logger'].stubs(new: logger)
50
61
 
51
62
  logger.expects(:deliver).with(sample_message.body)
52
63
 
@@ -56,9 +67,9 @@ describe MailRoom::Mailbox do
56
67
 
57
68
  context "with delivery_method of postback" do
58
69
  it 'delivers with a Postback instance' do
59
- mailbox = build_mailbox({:delivery_method => 'postback'})
70
+ mailbox = build_mailbox({delivery_method: 'postback'})
60
71
  postback = stub(:deliver)
61
- MailRoom::Delivery['postback'].stubs(:new => postback)
72
+ MailRoom::Delivery['postback'].stubs(new: postback)
62
73
 
63
74
  postback.expects(:deliver).with(sample_message.body)
64
75
 
@@ -68,9 +79,9 @@ describe MailRoom::Mailbox do
68
79
 
69
80
  context "with delivery_method of letter_opener" do
70
81
  it 'delivers with a LetterOpener instance' do
71
- mailbox = build_mailbox({:delivery_method => 'letter_opener'})
82
+ mailbox = build_mailbox({delivery_method: 'letter_opener'})
72
83
  letter_opener = stub(:deliver)
73
- MailRoom::Delivery['letter_opener'].stubs(:new => letter_opener)
84
+ MailRoom::Delivery['letter_opener'].stubs(new: letter_opener)
74
85
 
75
86
  letter_opener.expects(:deliver).with(sample_message.body)
76
87
 
@@ -82,7 +93,7 @@ describe MailRoom::Mailbox do
82
93
  it "doesn't deliver the message" do
83
94
  mailbox = build_mailbox({ name: "magic mailbox", delivery_method: 'noop' })
84
95
  noop = stub(:deliver)
85
- MailRoom::Delivery['noop'].stubs(:new => noop)
96
+ MailRoom::Delivery['noop'].stubs(new: noop)
86
97
  noop.expects(:deliver).never
87
98
 
88
99
  mailbox.deliver(MailRoom::Message.new(uid: 1234, body: nil))
@@ -91,9 +102,9 @@ describe MailRoom::Mailbox do
91
102
 
92
103
  context "with ssl options hash" do
93
104
  it 'replaces verify mode with constant' do
94
- mailbox = build_mailbox({:ssl => {:verify_mode => :none}})
105
+ mailbox = build_mailbox({ssl: {verify_mode: :none}})
95
106
 
96
- expect(mailbox.ssl_options).to eq({:verify_mode => OpenSSL::SSL::VERIFY_NONE})
107
+ expect(mailbox.ssl_options).to eq({verify_mode: OpenSSL::SSL::VERIFY_NONE})
97
108
  end
98
109
  end
99
110
 
@@ -121,8 +132,45 @@ describe MailRoom::Mailbox do
121
132
  describe "#validate!" do
122
133
  context "with missing configuration" do
123
134
  it 'raises an error' do
124
- expect { build_mailbox({:name => nil}) }.to raise_error(MailRoom::ConfigurationError)
125
- expect { build_mailbox({:host => nil}) }.to raise_error(MailRoom::ConfigurationError)
135
+ expect { build_mailbox({name: nil}) }.to raise_error(MailRoom::ConfigurationError)
136
+ expect { build_mailbox({host: nil}) }.to raise_error(MailRoom::ConfigurationError)
137
+ end
138
+ end
139
+
140
+ context "with Microsoft Graph configuration" do
141
+ let(:options) do
142
+ {
143
+ arbitration_method: 'redis',
144
+ }.merge(REQUIRED_MICROSOFT_GRAPH_DEFAULTS)
145
+ end
146
+
147
+ subject { build_mailbox(options) }
148
+
149
+ def delete_inbox_option(key)
150
+ options[:inbox_options] = options[:inbox_options].dup.delete(key)
151
+ end
152
+
153
+ it 'allows password omission' do
154
+ expect { subject }.not_to raise_error
155
+ end
156
+
157
+ it 'configured as a Microsoft Graph inbox' do
158
+ expect(subject.imap?).to be false
159
+ expect(subject.microsoft_graph?).to be true
160
+ end
161
+
162
+ it 'raises an error when the inbox options are not present' do
163
+ options.delete(:inbox_options)
164
+
165
+ expect { subject }.to raise_error(MailRoom::ConfigurationError)
166
+ end
167
+
168
+ %i[tenant_id client_id client_secret].each do |item|
169
+ it "raises an error when the #{item} is not present" do
170
+ delete_inbox_option(item)
171
+
172
+ expect { subject }.to raise_error(MailRoom::ConfigurationError)
173
+ end
126
174
  end
127
175
  end
128
176
  end
@@ -1,61 +1,77 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MailRoom::MailboxWatcher do
4
- let(:mailbox) {build_mailbox}
5
-
6
- describe '#running?' do
7
- it 'is false by default' do
8
- watcher = MailRoom::MailboxWatcher.new(mailbox)
9
- expect(watcher.running?).to eq(false)
4
+ context 'with IMAP configured' do
5
+ let(:mailbox) {build_mailbox}
6
+
7
+ describe '#running?' do
8
+ it 'is false by default' do
9
+ watcher = MailRoom::MailboxWatcher.new(mailbox)
10
+ expect(watcher.running?).to eq(false)
11
+ end
10
12
  end
11
- end
12
13
 
13
- describe '#run' do
14
- let(:imap) {stub(:login => true, :select => true)}
15
- let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
14
+ describe '#run' do
15
+ let(:imap) {stub(login: true, select: true)}
16
+ let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
16
17
 
17
- before :each do
18
- Net::IMAP.stubs(:new).returns(imap) # prevent connection
19
- end
18
+ before :each do
19
+ Net::IMAP.stubs(:new).returns(imap) # prevent connection
20
+ end
20
21
 
21
- it 'loops over wait while running' do
22
- connection = MailRoom::IMAP::Connection.new(mailbox)
22
+ it 'loops over wait while running' do
23
+ connection = MailRoom::IMAP::Connection.new(mailbox)
23
24
 
24
- MailRoom::IMAP::Connection.stubs(:new).returns(connection)
25
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
25
26
 
26
- watcher.expects(:running?).twice.returns(true, false)
27
- connection.expects(:wait).once
28
- connection.expects(:on_new_message).once
27
+ watcher.expects(:running?).twice.returns(true, false)
28
+ connection.expects(:wait).once
29
+ connection.expects(:on_new_message).once
29
30
 
30
- watcher.run
31
- watcher.watching_thread.join # wait for finishing run
31
+ watcher.run
32
+ watcher.watching_thread.join # wait for finishing run
33
+ end
32
34
  end
33
- end
34
35
 
35
- describe '#quit' do
36
- let(:imap) {stub(:login => true, :select => true)}
37
- let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
36
+ describe '#quit' do
37
+ let(:imap) {stub(login: true, select: true)}
38
+ let(:watcher) {MailRoom::MailboxWatcher.new(mailbox)}
38
39
 
39
- before :each do
40
- Net::IMAP.stubs(:new).returns(imap) # prevent connection
41
- end
40
+ before :each do
41
+ Net::IMAP.stubs(:new).returns(imap) # prevent connection
42
+ end
43
+
44
+ it 'closes and waits for the connection' do
45
+ connection = MailRoom::IMAP::Connection.new(mailbox)
46
+ connection.stubs(:wait)
47
+ connection.stubs(:quit)
42
48
 
43
- it 'closes and waits for the connection' do
44
- connection = MailRoom::IMAP::Connection.new(mailbox)
45
- connection.stubs(:wait)
46
- connection.stubs(:quit)
49
+ MailRoom::IMAP::Connection.stubs(:new).returns(connection)
47
50
 
48
- MailRoom::IMAP::Connection.stubs(:new).returns(connection)
51
+ watcher.run
52
+
53
+ expect(watcher.running?).to eq(true)
54
+
55
+ connection.expects(:quit)
56
+
57
+ watcher.quit
58
+
59
+ expect(watcher.running?).to eq(false)
60
+ end
61
+ end
62
+ end
49
63
 
50
- watcher.run
64
+ context 'with Microsoft Graph configured' do
65
+ let(:mailbox) { build_mailbox(REQUIRED_MICROSOFT_GRAPH_DEFAULTS) }
51
66
 
52
- expect(watcher.running?).to eq(true)
67
+ subject { described_class.new(mailbox) }
53
68
 
54
- connection.expects(:quit)
69
+ it 'initializes a Microsoft Graph connection' do
70
+ connection = stub(on_new_message: nil)
55
71
 
56
- watcher.quit
72
+ MailRoom::MicrosoftGraph::Connection.stubs(:new).returns(connection)
57
73
 
58
- expect(watcher.running?).to eq(false)
74
+ expect(subject.send(:connection)).to eq(connection)
59
75
  end
60
76
  end
61
77
  end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'spec_helper'
5
+ require 'json'
6
+ require 'webmock/rspec'
7
+
8
+ describe MailRoom::MicrosoftGraph::Connection do
9
+ let(:tenant_id) { options[:inbox_options][:tenant_id] }
10
+ let(:options) do
11
+ {
12
+ delete_after_delivery: true,
13
+ expunge_deleted: true
14
+ }.merge(REQUIRED_MICROSOFT_GRAPH_DEFAULTS)
15
+ end
16
+ let(:mailbox) { build_mailbox(options) }
17
+ let(:base_url) { 'https://graph.microsoft.com/v1.0/users/user@example.com/mailFolders/inbox/messages' }
18
+ let(:message_base_url) { 'https://graph.microsoft.com/v1.0/users/user@example.com/messages' }
19
+
20
+ before do
21
+ WebMock.enable!
22
+ end
23
+
24
+ context '#wait' do
25
+ let(:connection) { described_class.new(mailbox) }
26
+ let(:uid) { 1 }
27
+ let(:access_token) { SecureRandom.hex }
28
+ let(:refresh_token) { SecureRandom.hex }
29
+ let(:expires_in) { Time.now + 3600 }
30
+ let(:unread_messages_body) { '' }
31
+ let(:status) { 200 }
32
+ let!(:stub_token) do
33
+ stub_request(:post, "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token").to_return(
34
+ body: { 'access_token' => access_token, 'refresh_token' => refresh_token, 'expires_in' => expires_in }.to_json,
35
+ headers: { 'Content-Type' => 'application/json' }
36
+ )
37
+ end
38
+ let!(:stub_unread_messages_request) do
39
+ stub_request(:get, "#{base_url}?$filter=isRead%20eq%20false").to_return(
40
+ status: status,
41
+ body: unread_messages_body.to_json,
42
+ headers: { 'Content-Type' => 'application/json' }
43
+ )
44
+ end
45
+
46
+ before do
47
+ connection.stubs(:wait_for_new_messages)
48
+ end
49
+
50
+ describe 'poll interval' do
51
+ it 'defaults to 60 seconds' do
52
+ expect(connection.send(:poll_interval)).to eq(60)
53
+ end
54
+
55
+ context 'interval set to 10' do
56
+ let(:options) do
57
+ {
58
+ inbox_method: :microsoft_graph,
59
+ inbox_options: {
60
+ tenant_id: '98776',
61
+ client_id: '12345',
62
+ client_secret: 'MY-SECRET',
63
+ poll_interval: '10'
64
+ }
65
+ }
66
+ end
67
+
68
+ it 'sets the poll interval to 10' do
69
+ expect(connection.send(:poll_interval)).to eq(10)
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'with a single message' do
75
+ let(:message_id) { SecureRandom.hex }
76
+ let(:unread_messages_body) { { value: ['id' => message_id] } }
77
+ let(:message_url) { "#{message_base_url}/#{message_id}" }
78
+ let(:message_body) { 'hello world' }
79
+
80
+ it 'requests message ID' do
81
+ stub_get = stub_request(:get, "#{message_url}/$value").to_return(
82
+ status: 200,
83
+ body: message_body
84
+ )
85
+ stub_patch = stub_request(:patch, message_url).with(body: { "isRead": true }.to_json)
86
+ stub_delete = stub_request(:delete, message_url)
87
+ message_count = 0
88
+
89
+ connection.on_new_message do |message|
90
+ message_count += 1
91
+ expect(message.uid).to eq(message_id)
92
+ expect(message.body).to eq(message_body)
93
+ end
94
+
95
+ connection.wait
96
+
97
+ assert_requested(stub_token)
98
+ assert_requested(stub_unread_messages_request)
99
+ assert_requested(stub_get)
100
+ assert_requested(stub_patch)
101
+ assert_requested(stub_delete)
102
+ expect(message_count).to eq(1)
103
+ end
104
+ end
105
+
106
+ context 'with multiple pages of messages' do
107
+ let(:message_ids) { [SecureRandom.hex, SecureRandom.hex] }
108
+ let(:next_page_url) { 'https://graph.microsoft.com/v1.0/nextPage' }
109
+ let(:unread_messages_body) { { value: ['id' => message_ids.first], '@odata.nextLink' => next_page_url } }
110
+ let(:message_body) { 'hello world' }
111
+
112
+ it 'requests message ID' do
113
+ stub_request(:get, next_page_url).to_return(
114
+ status: 200,
115
+ body: { value: ['id' => message_ids[1]] }.to_json
116
+ )
117
+
118
+ stubs = []
119
+ message_ids.each do |message_id|
120
+ rfc822_msg_url = "#{message_base_url}/#{message_id}/$value"
121
+ stubs << stub_request(:get, rfc822_msg_url).to_return(
122
+ status: 200,
123
+ body: message_body
124
+ )
125
+
126
+ msg_url = "#{message_base_url}/#{message_id}"
127
+ stubs << stub_request(:patch, msg_url).with(body: { "isRead": true }.to_json)
128
+ stubs << stub_request(:delete, msg_url)
129
+ end
130
+
131
+ message_count = 0
132
+
133
+ connection.on_new_message do |message|
134
+ expect(message.uid).to eq(message_ids[message_count])
135
+ expect(message.body).to eq(message_body)
136
+ message_count += 1
137
+ end
138
+
139
+ connection.wait
140
+
141
+ stubs.each { |stub| assert_requested(stub) }
142
+ expect(message_count).to eq(2)
143
+ end
144
+ end
145
+
146
+ shared_examples 'request backoff' do
147
+ it 'backs off' do
148
+ connection.expects(:backoff)
149
+
150
+ connection.on_new_message {}
151
+ connection.wait
152
+
153
+ expect(connection.throttled_count).to eq(1)
154
+ end
155
+ end
156
+
157
+ context 'too many requests' do
158
+ let(:status) { 429 }
159
+
160
+ it_behaves_like 'request backoff'
161
+ end
162
+
163
+ context 'too much bandwidth' do
164
+ let(:status) { 509 }
165
+
166
+ it_behaves_like 'request backoff'
167
+ end
168
+
169
+ context 'invalid JSON response' do
170
+ let(:body) { 'this is something' }
171
+
172
+ it 'ignores the message and logs a warning' do
173
+ mailbox.logger.expects(:warn)
174
+
175
+ connection.on_new_message {}
176
+ connection.wait
177
+ end
178
+ end
179
+
180
+ context '500 error' do
181
+ let(:status) { 500 }
182
+
183
+ it 'terminates due to error' do
184
+ connection.on_new_message {}
185
+
186
+ expect { connection.wait }.to raise_error(OAuth2::Error)
187
+ end
188
+ end
189
+ end
190
+ end
data/spec/spec_helper.rb CHANGED
@@ -22,11 +22,21 @@ RSpec.configure do |config|
22
22
  end
23
23
 
24
24
  REQUIRED_MAILBOX_DEFAULTS = {
25
- :name => "inbox",
26
- :email => "user@example.com",
27
- :password => "password123"
25
+ name: "inbox",
26
+ email: "user@example.com",
27
+ password: "password123"
28
28
  }
29
29
 
30
+ REQUIRED_MICROSOFT_GRAPH_DEFAULTS = {
31
+ password: nil,
32
+ inbox_method: :microsoft_graph,
33
+ inbox_options: {
34
+ tenant_id: '98776',
35
+ client_id: '12345',
36
+ client_secret: 'MY-SECRET',
37
+ }.freeze
38
+ }.freeze
39
+
30
40
  def build_mailbox(options = {})
31
41
  MailRoom::Mailbox.new(REQUIRED_MAILBOX_DEFAULTS.merge(options))
32
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-mail_room
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Pitale
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-17 00:00:00.000000000 Z
11
+ date: 2021-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-imap
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: oauth2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: io-wait
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.0
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,20 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.11'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: mocha
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +234,20 @@ dependencies:
192
234
  - - ">="
193
235
  - !ruby/object:Gem::Version
194
236
  version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: webmock
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
195
251
  description: mail_room will proxy email (gmail) from IMAP to a delivery method
196
252
  email:
197
253
  - tpitale@gmail.com
@@ -203,6 +259,8 @@ files:
203
259
  - ".gitignore"
204
260
  - ".gitlab-ci.yml"
205
261
  - ".gitlab/issue_templates/Release.md"
262
+ - ".rubocop.yml"
263
+ - ".rubocop_todo.yml"
206
264
  - ".ruby-version"
207
265
  - ".travis.yml"
208
266
  - CHANGELOG.md
@@ -236,6 +294,8 @@ files:
236
294
  - lib/mail_room/mailbox.rb
237
295
  - lib/mail_room/mailbox_watcher.rb
238
296
  - lib/mail_room/message.rb
297
+ - lib/mail_room/microsoft_graph.rb
298
+ - lib/mail_room/microsoft_graph/connection.rb
239
299
  - lib/mail_room/version.rb
240
300
  - logfile.log
241
301
  - mail_room.gemspec
@@ -257,6 +317,7 @@ files:
257
317
  - spec/lib/mailbox_spec.rb
258
318
  - spec/lib/mailbox_watcher_spec.rb
259
319
  - spec/lib/message_spec.rb
320
+ - spec/lib/microsoft_graph/connection_spec.rb
260
321
  - spec/spec_helper.rb
261
322
  homepage: http://github.com/tpitale/mail_room
262
323
  licenses: []
@@ -300,4 +361,5 @@ test_files:
300
361
  - spec/lib/mailbox_spec.rb
301
362
  - spec/lib/mailbox_watcher_spec.rb
302
363
  - spec/lib/message_spec.rb
364
+ - spec/lib/microsoft_graph/connection_spec.rb
303
365
  - spec/spec_helper.rb