hoodoo 2.1.2 → 2.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27c91b7d6239a85430ddf6e2cb47e0461775375e6f4b1969c6bbbf9017a41243
4
- data.tar.gz: 3808aceb6a8474d1c456d25b38aa5a280df7c6da547747f1a54cce3b57bbe1ab
3
+ metadata.gz: 27f8a7c4aed490d7d8e01fb68469c393b094e1d83ab1100a700e330c2288e88d
4
+ data.tar.gz: ebf5032d0056a507db7bbaf63b51269469350e5e2189b5016d06a1012c88dd7d
5
5
  SHA512:
6
- metadata.gz: 35bea308563fef3c91d9f5f015942b3a03a198a299a18f754079d8ed1319cfc7da10165deee81d356064bcf1f479e5d20958807b9a7f90a998a3d288291d25b0
7
- data.tar.gz: 072047f7dc3c8deccb4a034de3bd793149909e99107455125313ae92f4ce85b059cf90b11ad3587af4b85a86c2199f235180f592b54bded4891ebc97a0802f4e
6
+ metadata.gz: 39ffe6dc1b31ff93b3cf5fb6424a253d107d7a9c4c1aeae789d8c04d6e6ebbec06dde7b42f9b411875b55d0722bc6571e9eb3432a148a8ac8b610dc1b96310b2
7
+ data.tar.gz: 44a537dcdcf8e7b905a4ac933a2497bd2c14598e2017b63b207baed5fcab4fdd511f17117b5a7a99e27f540f6745c5ade28eac50b4acd50d6354ab8a1c5bf5ee
@@ -57,6 +57,8 @@ module Hoodoo; module Services
57
57
  def report( e, env )
58
58
  opts = { :backtrace => Kernel.caller() }
59
59
  opts[ :rack_env ] = env unless env.nil?
60
+ e = sanitize_object(e)
61
+ opts = sanitize_object(opts)
60
62
 
61
63
  # Since an ExceptionReporter is already a "slow communicatory",
62
64
  # Hoodoo is using threads for behaviour; we don't need the async
@@ -81,9 +83,37 @@ module Hoodoo; module Services
81
83
  :environment_name => Hoodoo::Services::Middleware.environment,
82
84
  :session => user_data_for( context ) || 'unknown'
83
85
  }
86
+ e = sanitize_object(e)
87
+ opts = sanitize_object(opts)
84
88
 
85
89
  Airbrake.notify_sync( e, opts )
86
90
  end
91
+
92
+ private
93
+
94
+ # Recursive sanitisation method for deeply nested hash objects, returning
95
+ # the same object in a non frozen state.
96
+ #
97
+ # Why do I exist?
98
+ #
99
+ # Due to an airbrake-ruby issue where client arguments can be mutated when within a hash,
100
+ # a recursive sanitisation process must therefore take place before our inputs are sent
101
+ # to Airbrake, ensuring no frozen hash objects are present.
102
+ #
103
+ # https://github.com/airbrake/airbrake-ruby/issues/281
104
+ #
105
+ def sanitize_object( object )
106
+ object = ( object.dup rescue object ) if object.frozen?
107
+ return object unless object.is_a?( Hash )
108
+
109
+ sanitize_hash( object )
110
+ end
111
+
112
+ def sanitize_hash( object )
113
+ object.each do | key, value |
114
+ object[key] = sanitize_object( value )
115
+ end
116
+ end
87
117
  end
88
118
 
89
119
  end
@@ -12,11 +12,11 @@ module Hoodoo
12
12
  # The Hoodoo gem version. If this changes, be sure to re-run
13
13
  # <tt>bundle install</tt> or <tt>bundle update</tt>.
14
14
  #
15
- VERSION = '2.1.2'
15
+ VERSION = '2.2.0'
16
16
 
17
17
  # The Hoodoo gem date. If this changes, be sure to re-run
18
18
  # <tt>bundle install</tt> or <tt>bundle update</tt>.
19
19
  #
20
- DATE = '2017-11-07'
20
+ DATE = '2017-11-09'
21
21
 
22
22
  end
@@ -16,33 +16,104 @@ describe Hoodoo::Services::Middleware::ExceptionReporting::AirbrakeReporter do
16
16
  end
17
17
 
18
18
  context '#report' do
19
- it 'calls Airbrake correctly without an "env"' do
20
- ex = RuntimeError.new( 'A' )
21
-
22
- expect( Airbrake ).to receive( :notify_sync ).once do | e, opts |
23
- expect( e ).to be_a( Exception )
24
- expect( opts ).to be_a( Hash )
25
- expect( opts ).to have_key( :backtrace )
26
- expect( opts ).to_not have_key( :rack_env )
19
+ context 'with Airbrake mocks' do
20
+ it 'calls Airbrake correctly without an "env"' do
21
+ ex = RuntimeError.new( 'A' )
22
+
23
+ expect( Airbrake ).to receive( :notify_sync ).once do | e, opts |
24
+ expect( e ).to be_a( Exception )
25
+ expect( opts ).to be_a( Hash )
26
+ expect( opts ).to have_key( :backtrace )
27
+ expect( opts ).to_not have_key( :rack_env )
28
+ end
29
+
30
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
27
31
  end
28
32
 
29
- Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
33
+ it 'calls Airbrake correctly with an "env"' do
34
+ ex = RuntimeError.new( 'A' )
35
+ mock_env = { 'rack' => 'request' }
36
+
37
+ expect( Airbrake ).to receive( :notify_sync ).once do | e, opts |
38
+ expect( e ).to be_a( Exception )
39
+
40
+ expect( opts ).to be_a( Hash )
41
+ expect( opts ).to have_key( :backtrace )
42
+
43
+ expect( opts[ :rack_env ] ).to eq( mock_env )
44
+ end
45
+
46
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex, mock_env )
47
+ end
30
48
  end
31
49
 
32
- it 'calls Airbrake correctly with an "env"' do
33
- ex = RuntimeError.new( 'A' )
34
- mock_env = { 'rack' => 'request' }
50
+ context 'without Airbrake mocks' do
51
+
52
+ # Airbrake does not allow the default notifier to be reconfigured, so we
53
+ # must set some dummy values here just once within this Airbrake-specific
54
+ # integration test. Without this, non-mocked tests do not run much of the
55
+ # Airbrake code that, over time, we have discovered should be tested.
56
+ #
57
+ before :all do
58
+ Airbrake.configure do | config |
59
+ config.project_id = '123456'
60
+ config.project_key = Hoodoo::UUID.generate()
61
+ end
62
+ end
35
63
 
36
- expect( Airbrake ).to receive( :notify_sync ).once do | e, opts |
37
- expect( e ).to be_a( Exception )
64
+ before :each do
65
+ WebMock.enable!
38
66
 
39
- expect( opts ).to be_a( Hash )
40
- expect( opts ).to have_key( :backtrace )
67
+ stub_request( :post, /airbrake\.io\/api/ ).
68
+ to_return( :body => "{}",
69
+ :status => 201,
70
+ :headers => { 'Content-Length' => 2 } )
71
+ end
72
+
73
+ after :each do
74
+ WebMock.reset!
75
+ WebMock.disable!
76
+ end
77
+
78
+ it 'can send frozen exceptions' do
79
+ ex = RuntimeError.new( 'A' )
80
+
81
+ # Be sure that the adaptero called Airbrake and that Airbrake did try
82
+ # to internally send the message (which we'll catch with WebMock via
83
+ # the "before :each" filter above).
84
+ #
85
+ expect( Airbrake ).to receive( :notify_sync ).once.and_call_original
86
+ expect_any_instance_of( Airbrake::SyncSender ).to receive( :send ).once.and_call_original
41
87
 
42
- expect( opts[ :rack_env ] ).to eq( mock_env )
88
+ # There shouldn't be any need to handle exceptions inside the
89
+ # communicator pool underneath the adaptor.
90
+ #
91
+ expect_any_instance_of( Hoodoo::Communicators::Pool ).to_not receive( :handle_exception )
92
+
93
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex.freeze() )
94
+ Hoodoo::Services::Middleware::ExceptionReporting.wait()
43
95
  end
44
96
 
45
- Hoodoo::Services::Middleware::ExceptionReporting.report( ex, mock_env )
97
+ it 'can send frozen data large enough to require truncation' do
98
+ ex = RuntimeError.new( 'A' )
99
+ mock_env = { 'rack' => 'request' }
100
+
101
+ 1.upto( Airbrake::Notice::PAYLOAD_MAX_SIZE + 10 ) do | i |
102
+ mock_env[ Hoodoo::UUID.generate() ] = i
103
+ end
104
+
105
+ # See previous test (above) for an explanation of the expectations
106
+ # below.
107
+
108
+ expect( Airbrake ).to receive( :notify_sync ).once.and_call_original
109
+ expect_any_instance_of( Airbrake::Truncator ).to receive( :truncate_object ).at_least( :once ).and_call_original
110
+ expect_any_instance_of( Airbrake::SyncSender ).to receive( :send ).once.and_call_original
111
+
112
+ expect_any_instance_of( Hoodoo::Communicators::Pool ).to_not receive( :handle_exception )
113
+
114
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex, mock_env.freeze() )
115
+ Hoodoo::Services::Middleware::ExceptionReporting.wait()
116
+ end
46
117
  end
47
118
  end
48
119
 
data/spec/spec_helper.rb CHANGED
@@ -19,6 +19,16 @@ end
19
19
 
20
20
  require 'byebug'
21
21
 
22
+ # Stubbing Net::HTTP with WebMock - disabled by default; only enable for
23
+ # tests where you need it, as real Net::HTTP connections are used in many
24
+ # other tests and must not be mocked.
25
+ #
26
+ # https://github.com/bblimke/webmock
27
+
28
+ require 'webmock/rspec'
29
+
30
+ WebMock.disable!
31
+
22
32
  # The ActiveRecord extensions need testing in the context of a database. I
23
33
  # did consider NullDB - https://github.com/nulldb/nulldb - but this was too
24
34
  # far from 'the real thing' for my liking. Instead, we use SQLite in memory
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hoodoo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Loyalty New Zealand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-07 00:00:00.000000000 Z
11
+ date: 2017-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dalli
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '3.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.1'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: activerecord
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -212,28 +226,28 @@ dependencies:
212
226
  requirements:
213
227
  - - "~>"
214
228
  - !ruby/object:Gem::Version
215
- version: '1.1'
229
+ version: '2.6'
216
230
  type: :development
217
231
  prerelease: false
218
232
  version_requirements: !ruby/object:Gem::Requirement
219
233
  requirements:
220
234
  - - "~>"
221
235
  - !ruby/object:Gem::Version
222
- version: '1.1'
236
+ version: '2.6'
223
237
  - !ruby/object:Gem::Dependency
224
238
  name: airbrake
225
239
  requirement: !ruby/object:Gem::Requirement
226
240
  requirements:
227
241
  - - "~>"
228
242
  - !ruby/object:Gem::Version
229
- version: '6.2'
243
+ version: '7.1'
230
244
  type: :development
231
245
  prerelease: false
232
246
  version_requirements: !ruby/object:Gem::Requirement
233
247
  requirements:
234
248
  - - "~>"
235
249
  - !ruby/object:Gem::Version
236
- version: '6.2'
250
+ version: '7.1'
237
251
  - !ruby/object:Gem::Dependency
238
252
  name: le
239
253
  requirement: !ruby/object:Gem::Requirement
@@ -518,7 +532,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
518
532
  requirements:
519
533
  - - ">="
520
534
  - !ruby/object:Gem::Version
521
- version: 2.2.7
535
+ version: 2.2.8
522
536
  required_rubygems_version: !ruby/object:Gem::Requirement
523
537
  requirements:
524
538
  - - ">="
@@ -526,7 +540,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
526
540
  version: '0'
527
541
  requirements: []
528
542
  rubyforge_project:
529
- rubygems_version: 2.7.1
543
+ rubygems_version: 2.7.2
530
544
  signing_key:
531
545
  specification_version: 4
532
546
  summary: Opinionated APIs