hoodoo 1.2.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDRkMTlhM2I2NjQyNWE2OTQxZDA3MjdiMjQ2MjgzMzVkYjhkMWEwOQ==
4
+ N2VjMWU3NjI1YmZmODQ1NDQ3OTYxMTg1MTk3YzRhNmQzM2U3YzZhNw==
5
5
  data.tar.gz: !binary |-
6
- NjMxZTIwOTI0OGFiMzgxMDEyYjRmY2JjM2MwODI3MDJmOTNjMjgyNQ==
6
+ NzFkZjIyZTk0YmUzNTVhY2IxZWMxMThiMWU0ZmZiMzhmOGNkYjk5Yw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzdiYTY3MTIyY2VkMWZjZjI5N2RmYjBkY2M1NTYyZWVjZjFiOWM0NDU4YjVk
10
- MGM4MGI1MWFlYzNhNjg0MzNlYmU0YzYwMzZhMDViNDgwYmVjN2ExZWY3MjJi
11
- NGQ0NDRmOThhZjE4MWQwNmEyODk5NmYxNmE0NTkzMmY2YTliMjE=
9
+ ZjViZWUyYzM2NmY3NmNjZjFlNTQ1ZjkyZWY1ZGE1N2VmNmQ0MDA3N2ExNmNj
10
+ M2RlNDZjYTlhOTU5NjM0NWU4NjQ5MjhhODVkMTk2YjE0MjM0NTM4ZDgzYjI3
11
+ ODRiYTVkNzE2OTQxY2IzYjE4NzA1ZGJkNGUxZDUzYmZjMGViNDk=
12
12
  data.tar.gz: !binary |-
13
- NGM2NmNlMmQ2ZGYwZmVhMmEwZWMyYjU1NjJmZTBhYTU1M2UzNzMyMjRlZjRj
14
- ZTAzNzM5MDI0MmRlMmU4OWU4MzBlZWUxNTRiMGU4MzE4YWI4YTE3M2M1MGMy
15
- ZjI1MWY1YWM5NmQwNWYyYWZhNTk0MTA1OGU4NWVkN2Y4YTUzNDE=
13
+ ZGI3OTZlM2QzYmFhNGVjZGIzYTJlNGM0NDZmZDYwN2MxOTNiODE1ZWEwYjEw
14
+ MDRmMzQxMWMwNGM1ZDJjM2RlMDk0NzZiMzg5MTBiYTg1MDJmMWFlMjFkNzQw
15
+ MGU5NGI3ZGIyYmI5OWExZjUxNDZlODljZmY4YTMxYmQzN2IzZGE=
@@ -84,6 +84,30 @@ module Hoodoo; module Services
84
84
  raise( 'Subclasses must implement #report' )
85
85
  end
86
86
 
87
+ # Similar to #report, with the same caveats; but has more information
88
+ # available.
89
+ #
90
+ # Subclasses report an exception for errors that occur within a fully
91
+ # handled Rack request context, with a high level processed Hoodoo
92
+ # representation available.
93
+ #
94
+ # Through the protected #user_data_for method, subclasses can, if the
95
+ # exception reporting backend supports it, include detailed request
96
+ # information with their contextual exception reports.
97
+ #
98
+ # Implementation is optional. If not available, the system falls back
99
+ # to the less detailed #report method. If called, all parameters must
100
+ # be provided; none are optional.
101
+ #
102
+ # +e+:: Exception (or subclass) instance to be reported.
103
+ #
104
+ # +context+:: Hoodoo::Services::Context instance describing an
105
+ # in-flight request/response cycle.
106
+ #
107
+ def contextual_report( e, context )
108
+ raise( 'Subclasses may implement #contextual_report' )
109
+ end
110
+
87
111
  # Subclasses *MUST* *NOT* override this method, which is part of the
88
112
  # base class implementation and implements
89
113
  # Hoodoo::Communicators::Slow#communicate. It calls through to the
@@ -95,7 +119,66 @@ module Hoodoo; module Services
95
119
  # instance.
96
120
  #
97
121
  def communicate( object )
98
- self.report( object.exception, object.rack_env )
122
+
123
+ env = object.rack_env || ( object.context.owning_interaction.rack_request.env rescue nil )
124
+
125
+ # The 'instance_methods( false )' call pulls only instance methods
126
+ # strictly defined in 'self' instance, not in any superclasses.
127
+ # Thus we don't see the base implementation of 'contextual_report'
128
+ # in this source file; only an overriding implementation in a real
129
+ # reporter subclass.
130
+ #
131
+ # http://ruby-doc.org/core-2.1.8/Module.html#method-i-instance_methods
132
+ #
133
+ subclass_methods = self.class.instance_methods( false )
134
+
135
+ if object.context && subclass_methods.include?( :contextual_report )
136
+ self.contextual_report( object.exception, object.context )
137
+ else
138
+ self.report( object.exception, env )
139
+ end
140
+ end
141
+
142
+ protected
143
+
144
+ # When passed a request context, extracts information that can be given
145
+ # as "user data" (or similar) to a exception reporting endpoint, if it
146
+ # supports such a concept.
147
+ #
148
+ # +context+:: Hoodoo::Services::Context instance describing an
149
+ # in-flight request/response cycle.
150
+ #
151
+ def user_data_for( context )
152
+ begin
153
+ hash = {
154
+ :interaction_id => context.owning_interaction.interaction_id,
155
+ :action => ( context.owning_interaction.requested_action || '(unknown)' ).to_s,
156
+ :resource => ( context.owning_interaction.target_interface.resource || '(unknown)' ).to_s,
157
+ :version => context.owning_interaction.target_interface.version,
158
+ :request => {
159
+ :locale => context.request.locale,
160
+ :uri_path_components => context.request.uri_path_components,
161
+ :uri_path_extension => context.request.uri_path_extension,
162
+ :embeds => context.request.embeds,
163
+ :references => context.request.references,
164
+ :headers => context.request.headers,
165
+ :list => {
166
+ :offset => context.request.list.offset,
167
+ :limit => context.request.list.limit,
168
+ :sort_data => context.request.list.sort_data,
169
+ :search_data => context.request.list.search_data,
170
+ :filter_data => context.request.list.filter_data
171
+ }
172
+ }
173
+ }
174
+
175
+ hash[ :session ] = context.session.to_h unless context.session.nil?
176
+ return hash
177
+
178
+ rescue
179
+ return nil
180
+
181
+ end
99
182
  end
100
183
  end
101
184
 
@@ -75,6 +75,21 @@ module Hoodoo; module Services
75
75
  @@reporter_pool.communicate( payload )
76
76
  end
77
77
 
78
+ # Call all added exception reporters (see ::add) to report an exception
79
+ # based on the context of an in-flight request/response cycle. Reporters
80
+ # need to support the contextual reporting mechanism. If any do not, the
81
+ # simpler ::report mechanism is used as a fallback.
82
+ #
83
+ # +exception+:: Exception or Exception subclass instance to report.
84
+ #
85
+ # +context+:: Hoodoo::Services::Context instance describing the
86
+ # in-flight request/response cycle.
87
+ #
88
+ def self.contextual_report( exception, context )
89
+ payload = Payload.new( exception: exception, context: context )
90
+ @@reporter_pool.communicate( payload )
91
+ end
92
+
78
93
  # Wait for all executing reporter threads to catch up before continuing.
79
94
  #
80
95
  # +timeout+:: Optional timeout wait delay *for* *each* *thread*. Default
@@ -99,14 +114,21 @@ module Hoodoo; module Services
99
114
  #
100
115
  attr_accessor :rack_env
101
116
 
117
+ # A Hoodoo::Services::Context instance describing the in-flight
118
+ # request/response cycle, if there is one. May be +nil+.
119
+ #
120
+ attr_accessor :context
121
+
102
122
  # Initialize this instance with named parameters:
103
123
  #
104
124
  # +exception+:: Exception (or Exception subclass) instance. Mandatory.
105
125
  # +rack_env+:: Rack environment hash. Optional.
126
+ # +context+:: Hoodoo::Services::Context instance. Optional.
106
127
  #
107
- def initialize( exception:, rack_env: nil )
128
+ def initialize( exception:, rack_env: nil, context: nil )
108
129
  @exception = exception
109
130
  @rack_env = rack_env
131
+ @context = context
110
132
  end
111
133
  end
112
134
  end
@@ -48,13 +48,36 @@ module Hoodoo; module Services
48
48
  #
49
49
  # +e+:: Exception (or subclass) instance to be reported.
50
50
  #
51
- # +env+:: Optional Rack environment hash for the inbound request, for
52
- # exception reports made in the context of Rack request
51
+ # +env+:: Optional Rack environment hash for the inbound request,
52
+ # for exception reports made in the context of Rack request
53
53
  # handling. In the case of Airbrake, the call may just hang
54
54
  # unless a Rack environment is provided.
55
55
  #
56
- def report( e, env = nil )
57
- Airbrake.notify_or_ignore( e, :rack_env => env )
56
+ def report( e, env )
57
+ opts = { :backtrace => Kernel.caller() }
58
+ opts[ :rack_env ] = env unless env.nil?
59
+
60
+ Airbrake.notify_or_ignore( e, opts )
61
+ end
62
+
63
+ # Report an exception for errors that occur within a fully handled Rack
64
+ # request context, with a high level processed Hoodoo representation
65
+ # available.
66
+ #
67
+ # +e+:: Exception (or subclass) instance to be reported.
68
+ #
69
+ # +context+:: Hoodoo::Services::Context instance describing an
70
+ # in-flight request/response cycle.
71
+ #
72
+ def contextual_report( e, context )
73
+ opts = {
74
+ :rack_env => context.owning_interaction.rack_request.env,
75
+ :backtrace => Kernel.caller(),
76
+ :environment_name => Hoodoo::Services::Middleware.environment,
77
+ :session => user_data_for( context ) || 'unknown'
78
+ }
79
+
80
+ Airbrake.notify_or_ignore( e, opts )
58
81
  end
59
82
  end
60
83
 
@@ -48,13 +48,29 @@ module Hoodoo; module Services
48
48
  #
49
49
  # +e+:: Exception (or subclass) instance to be reported.
50
50
  #
51
- # +env+:: Optional Rack environment hash for the inbound request, for
52
- # exception reports made in the context of Rack request
51
+ # +env+:: Optional Rack environment hash for the inbound request,
52
+ # for exception reports made in the context of Rack request
53
53
  # handling.
54
54
  #
55
55
  def report( e, env = nil )
56
56
  Raygun.track_exception( e, env )
57
57
  end
58
+
59
+ # Report an exception for errors that occur within a fully handled Rack
60
+ # request context, with a high level processed Hoodoo representation
61
+ # available.
62
+ #
63
+ # +e+:: Exception (or subclass) instance to be reported.
64
+ #
65
+ # +context+:: Hoodoo::Services::Context instance describing an
66
+ # in-flight request/response cycle.
67
+ #
68
+ def contextual_report( e, context )
69
+ hash = context.owning_interaction.rack_request.env rescue {}
70
+ hash = hash.merge( :custom_data => user_data_for( context ) || { 'user_data' => 'unknown' } )
71
+
72
+ Raygun.track_exception( e, hash )
73
+ end
58
74
  end
59
75
 
60
76
  end
@@ -498,8 +498,12 @@ module Hoodoo; module Services
498
498
 
499
499
  rescue => exception
500
500
  begin
501
+ if interaction && interaction.context
502
+ ExceptionReporting.contextual_report( exception, interaction.context )
503
+ else
504
+ ExceptionReporting.report( exception, env )
505
+ end
501
506
 
502
- ExceptionReporting.report( exception, env )
503
507
  record_exception( interaction, exception )
504
508
 
505
509
  return respond_for( interaction )
@@ -12,6 +12,6 @@ module Hoodoo
12
12
  # The Hoodoo gem version. If this changes, ensure that the date in
13
13
  # "hoodoo.gemspec" is correct and run "bundle install" (or "update").
14
14
  #
15
- VERSION = '1.2.3'
15
+ VERSION = '1.3.0'
16
16
 
17
17
  end
@@ -4,6 +4,8 @@ describe Hoodoo::Services::Middleware::ExceptionReporting::BaseReporter do
4
4
  class TestERBase < described_class
5
5
  end
6
6
 
7
+ # The #communicate method is checked via exception_reporting_spec.rb.
8
+
7
9
  it 'is a singleton' do
8
10
  expect( TestERBase.instance ).to be_a( TestERBase )
9
11
  end
@@ -11,6 +13,97 @@ describe Hoodoo::Services::Middleware::ExceptionReporting::BaseReporter do
11
13
  it 'provides a reporting example' do
12
14
  expect {
13
15
  TestERBase.instance.report( RuntimeError.new )
14
- }.to raise_exception( RuntimeError )
16
+ }.to raise_exception( RuntimeError, 'Subclasses must implement #report' )
17
+ end
18
+
19
+ it 'provides a contextual reporting example' do
20
+ expect {
21
+ TestERBase.instance.contextual_report( RuntimeError.new, nil )
22
+ }.to raise_exception( RuntimeError, 'Subclasses may implement #contextual_report' )
23
+ end
24
+
25
+ context '#user_data_for' do
26
+
27
+ class UserDataImplementation < Hoodoo::Services::Implementation
28
+ end
29
+
30
+ class UserDataInterface < Hoodoo::Services::Interface
31
+ interface :UserData do
32
+ version 2
33
+ endpoint :user_data, UserDataImplementation
34
+ end
35
+ end
36
+
37
+ before :each do
38
+
39
+ # This requires a great big heap of setup to avoid lots of mocking
40
+ # and keep the test reasonably meaningful.
41
+
42
+ session = Hoodoo::Services::Middleware.test_session()
43
+ request = Hoodoo::Services::Request.new
44
+
45
+ request.locale = 'en-nz'
46
+ request.uri_path_components = [ 'path', 'subpath' ]
47
+ request.uri_path_extension = 'tar.gz'
48
+ request.embeds = [ 'e1', 'e2' ]
49
+ request.references = [ 'r1', 'r2' ]
50
+ request.headers = { 'HTTP_X_EXAMPLE' => '42' }
51
+ request.list.offset = 0
52
+ request.list.limit = 50
53
+ request.list.sort_data = { 'created_at' => 'desc' }
54
+ request.list.search_data = { 'example' => '42' }
55
+ request.list.filter_data = { 'unexample' => '24' }
56
+
57
+ @mock_iid = Hoodoo::UUID.generate()
58
+ mock_env = { 'HTTP_X_INTERACTION_ID' => @mock_iid }
59
+ owning_interaction = Hoodoo::Services::Middleware::Interaction.new(
60
+ mock_env,
61
+ nil,
62
+ session
63
+ )
64
+
65
+ owning_interaction.target_interface = UserDataInterface
66
+
67
+ @context = Hoodoo::Services::Context.new(
68
+ session,
69
+ request,
70
+ nil,
71
+ owning_interaction
72
+ )
73
+ end
74
+
75
+ it 'works' do
76
+ hash = TestERBase.instance.send( :user_data_for, @context )
77
+ expect( hash ).to eq(
78
+ {
79
+ :interaction_id => @mock_iid,
80
+ :action => '(unknown)',
81
+ :resource => 'UserData',
82
+ :version => 2,
83
+ :request => {
84
+ :locale => 'en-nz',
85
+ :uri_path_components => [ 'path', 'subpath' ],
86
+ :uri_path_extension => 'tar.gz',
87
+ :embeds => [ 'e1', 'e2' ],
88
+ :references => [ 'r1', 'r2' ],
89
+ :headers => { 'HTTP_X_EXAMPLE' => '42' },
90
+ :list => {
91
+ :offset => 0,
92
+ :limit => 50,
93
+ :sort_data => { 'created_at' => 'desc' },
94
+ :search_data => { 'example' => '42' },
95
+ :filter_data => { 'unexample' => '24' }
96
+ }
97
+ },
98
+ :session => Hoodoo::Services::Middleware.test_session().to_h()
99
+ }
100
+ )
101
+ end
102
+
103
+ it 'returns "nil" for bad contexts' do
104
+ @context.owning_interaction.target_interface = nil
105
+ hash = TestERBase.instance.send( :user_data_for, @context )
106
+ expect( hash ).to be_nil
107
+ end
15
108
  end
16
109
  end
@@ -22,11 +22,22 @@ describe Hoodoo::Services::Middleware::ExceptionReporting do
22
22
  end
23
23
  end
24
24
 
25
+ class TestReporterD < Hoodoo::Services::Middleware::ExceptionReporting::BaseReporter
26
+ def report( e, env = nil )
27
+ expectable_hook_d1( e, env )
28
+ end
29
+
30
+ def contextual_report( e, context )
31
+ expectable_hook_d2( e, context )
32
+ end
33
+ end
34
+
25
35
  after :each do
26
36
  Hoodoo::Services::Middleware::ExceptionReporting.wait()
27
37
  Hoodoo::Services::Middleware::ExceptionReporting.remove( TestReporterA )
28
38
  Hoodoo::Services::Middleware::ExceptionReporting.remove( TestReporterB )
29
39
  Hoodoo::Services::Middleware::ExceptionReporting.remove( TestReporterC )
40
+ Hoodoo::Services::Middleware::ExceptionReporting.remove( TestReporterD )
30
41
  end
31
42
 
32
43
  it 'lets me add and remove handlers' do
@@ -89,4 +100,63 @@ describe Hoodoo::Services::Middleware::ExceptionReporting do
89
100
  expect( TestReporterA.instance ).to receive( :expectable_hook_a ).once.with( ex, ha )
90
101
  Hoodoo::Services::Middleware::ExceptionReporting.report( ex, ha )
91
102
  end
103
+
104
+ context 'with contextual reporting' do
105
+ before :each do
106
+ Hoodoo::Services::Middleware::ExceptionReporting.add( TestReporterD )
107
+ end
108
+
109
+ it 'supports normal behaviour' do
110
+ ex = RuntimeError.new( 'D' )
111
+ ha = { :foo => :bar }
112
+
113
+ expect( TestReporterD.instance ).to receive( :expectable_hook_d1 ).once.with( ex, ha )
114
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex, ha )
115
+ end
116
+
117
+ it 'supports contextual behaviour' do
118
+ ex = RuntimeError.new( 'D' )
119
+ co = { :bar => :baz }
120
+
121
+ expect( TestReporterD.instance ).to receive( :expectable_hook_d2 ).once.with( ex, co )
122
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
123
+ end
124
+
125
+ context 'falling back to normal' do
126
+ before :each do
127
+ Hoodoo::Services::Middleware::ExceptionReporting.add( TestReporterA )
128
+ end
129
+
130
+ # When falling back, it should extract the Rack env (mocked here) from
131
+ # the context and pass that to the normal reporter.
132
+ #
133
+ it 'extracts the Rack "env"' do
134
+ ex = RuntimeError.new( 'D' )
135
+ ha = { :foo => :bar }
136
+ co = OpenStruct.new
137
+
138
+ co.owning_interaction = OpenStruct.new
139
+ co.owning_interaction.rack_request = OpenStruct.new
140
+ co.owning_interaction.rack_request.env = ha
141
+
142
+ expect( TestReporterD.instance ).to receive( :expectable_hook_d2 ).once.with( ex, co )
143
+ expect( TestReporterA.instance ).to receive( :expectable_hook_a ).once.with( ex, ha )
144
+
145
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
146
+ end
147
+
148
+ # If it can't extract the Rack request from the context when falling back,
149
+ # the normal reporter should just be called with a "nil" second parameter.
150
+ #
151
+ it 'recovers from bad contexts' do
152
+ ex = RuntimeError.new( 'D' )
153
+ co = { :bar => :baz }
154
+
155
+ expect( TestReporterD.instance ).to receive( :expectable_hook_d2 ).once.with( ex, co )
156
+ expect( TestReporterA.instance ).to receive( :expectable_hook_a ).once.with( ex, nil )
157
+
158
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
159
+ end
160
+ end
161
+ end
92
162
  end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require 'raygun4ruby'
2
+ require 'airbrake'
3
3
 
4
4
  # This doesn't test the Airbrake gem / configuration itself - just check that
5
5
  # the appropriate Airbrake method gets called.
@@ -15,10 +15,87 @@ describe Hoodoo::Services::Middleware::ExceptionReporting::AirbrakeReporter do
15
15
  Hoodoo::Services::Middleware::ExceptionReporting.remove( described_class )
16
16
  end
17
17
 
18
- it 'calls Airbrake' do
19
- Hoodoo::Services::Middleware::ExceptionReporting.add( described_class )
20
- ex = RuntimeError.new( 'A' )
21
- expect( Airbrake ).to receive( :notify_or_ignore ).once.with( ex, { :rack_env => nil } )
22
- Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
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_or_ignore ).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 )
27
+ end
28
+
29
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
30
+ end
31
+
32
+ it 'calls Airbrake correctly with an "env"' do
33
+ ex = RuntimeError.new( 'A' )
34
+ mock_env = { 'rack' => 'request' }
35
+
36
+ expect( Airbrake ).to receive( :notify_or_ignore ).once do | e, opts |
37
+ expect( e ).to be_a( Exception )
38
+
39
+ expect( opts ).to be_a( Hash )
40
+ expect( opts ).to have_key( :backtrace )
41
+
42
+ expect( opts[ :rack_env ] ).to eq( mock_env )
43
+ end
44
+
45
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex, mock_env )
46
+ end
47
+ end
48
+
49
+ context '#contextual_report' do
50
+ it 'calls Airbrake correctly' do
51
+ ex = RuntimeError.new( 'A' )
52
+ co = OpenStruct.new
53
+ mock_user_data = { :foo => :bar }
54
+ mock_env = { 'rack' => 'request' }
55
+
56
+ co.owning_interaction = OpenStruct.new
57
+ co.owning_interaction.rack_request = OpenStruct.new
58
+ co.owning_interaction.rack_request.env = mock_env
59
+
60
+ expect( described_class.instance ).to receive( :user_data_for ).once.and_return( mock_user_data )
61
+
62
+ expect( Airbrake ).to receive( :notify_or_ignore ).once do | e, opts |
63
+ expect( e ).to be_a( Exception )
64
+
65
+ expect( opts ).to be_a( Hash )
66
+ expect( opts ).to have_key( :backtrace )
67
+
68
+ expect( opts[ :rack_env ] ).to eq( mock_env )
69
+ expect( opts[ :environment_name ] ).to eq( 'test' )
70
+ expect( opts[ :session ] ).to eq( mock_user_data )
71
+ end
72
+
73
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
74
+ end
75
+
76
+ it 'has special case handling for user data recovery failure' do
77
+ ex = RuntimeError.new( 'A' )
78
+ co = OpenStruct.new
79
+ mock_env = { 'rack' => 'request' }
80
+
81
+ co.owning_interaction = OpenStruct.new
82
+ co.owning_interaction.rack_request = OpenStruct.new
83
+ co.owning_interaction.rack_request.env = mock_env
84
+
85
+ expect( described_class.instance ).to receive( :user_data_for ).once.and_return( nil )
86
+
87
+ expect( Airbrake ).to receive( :notify_or_ignore ).once do | e, opts |
88
+ expect( e ).to be_a( Exception )
89
+
90
+ expect( opts ).to be_a( Hash )
91
+ expect( opts ).to have_key( :backtrace )
92
+
93
+ expect( opts[ :rack_env ] ).to eq( mock_env )
94
+ expect( opts[ :environment_name ] ).to eq( 'test' )
95
+ expect( opts[ :session ] ).to eq( 'unknown' )
96
+ end
97
+
98
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
99
+ end
23
100
  end
24
101
  end
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require 'airbrake'
2
+ require 'raygun4ruby'
3
3
 
4
4
  # This doesn't test the Raygun gem / configuration itself - just check that
5
5
  # the appropriate Raygun method gets called.
@@ -15,9 +15,75 @@ describe Hoodoo::Services::Middleware::ExceptionReporting::RaygunReporter do
15
15
  Hoodoo::Services::Middleware::ExceptionReporting.remove( described_class )
16
16
  end
17
17
 
18
- it 'calls Raygun' do
19
- ex = RuntimeError.new( 'A' )
20
- expect( Raygun ).to receive( :track_exception ).once.with( ex, nil )
21
- Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
18
+ context '#report' do
19
+ it 'calls Raygun correctly without an "env"' do
20
+ ex = RuntimeError.new( 'A' )
21
+
22
+ expect( Raygun ).to receive( :track_exception ).once do | e, opts |
23
+ expect( e ).to be_a( Exception )
24
+ expect( opts ).to be_nil
25
+ end
26
+
27
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex )
28
+ end
29
+
30
+ it 'calls Raygun correctly with an "env"' do
31
+ ex = RuntimeError.new( 'A' )
32
+ mock_env = { 'rack' => 'request' }
33
+
34
+ expect( Raygun ).to receive( :track_exception ).once do | e, opts |
35
+ expect( e ).to be_a( Exception )
36
+
37
+ expect( opts ).to be_a( Hash )
38
+ expect( opts ).to eq( mock_env )
39
+ end
40
+
41
+ Hoodoo::Services::Middleware::ExceptionReporting.report( ex, mock_env )
42
+ end
43
+ end
44
+
45
+ context '#contextual_report' do
46
+ it 'calls Raygun correctly' do
47
+ ex = RuntimeError.new( 'A' )
48
+ co = OpenStruct.new
49
+ mock_user_data = { :foo => :bar }
50
+ mock_env = { 'rack' => 'request' }
51
+
52
+ co.owning_interaction = OpenStruct.new
53
+ co.owning_interaction.rack_request = OpenStruct.new
54
+ co.owning_interaction.rack_request.env = mock_env
55
+
56
+ expect( described_class.instance ).to receive( :user_data_for ).once.and_return( mock_user_data )
57
+
58
+ expect( Raygun ).to receive( :track_exception ).once do | e, opts |
59
+ expect( e ).to be_a( Exception )
60
+
61
+ expect( opts ).to be_a( Hash )
62
+ expect( opts ).to eq( mock_env.merge( :custom_data => mock_user_data ) )
63
+ end
64
+
65
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
66
+ end
67
+
68
+ it 'has special case handling for user data recovery failure' do
69
+ ex = RuntimeError.new( 'A' )
70
+ co = OpenStruct.new
71
+ mock_env = { 'rack' => 'request' }
72
+
73
+ co.owning_interaction = OpenStruct.new
74
+ co.owning_interaction.rack_request = OpenStruct.new
75
+ co.owning_interaction.rack_request.env = mock_env
76
+
77
+ expect( described_class.instance ).to receive( :user_data_for ).once.and_return( nil )
78
+
79
+ expect( Raygun ).to receive( :track_exception ).once do | e, opts |
80
+ expect( e ).to be_a( Exception )
81
+
82
+ expect( opts ).to be_a( Hash )
83
+ expect( opts ).to eq( mock_env.merge( :custom_data => { 'user_data' => 'unknown' } ) )
84
+ end
85
+
86
+ Hoodoo::Services::Middleware::ExceptionReporting.contextual_report( ex, co )
87
+ end
22
88
  end
23
89
  end
@@ -367,7 +367,7 @@ describe Hoodoo::Services::Middleware do
367
367
 
368
368
  expect(Hoodoo::Services::Middleware.environment).to receive(:test?).once.and_return(false)
369
369
  expect(Hoodoo::Services::Middleware.environment).to receive(:development?).and_return(false)
370
- expect(Hoodoo::Services::Middleware::ExceptionReporting).to receive(:report).and_raise("boo!")
370
+ expect(Hoodoo::Services::Middleware::ExceptionReporting).to receive(:contextual_report).and_raise("boo!")
371
371
 
372
372
  # Route through to the unimplemented "list" call, so the subclass raises
373
373
  # an exception. This is tested independently elsewhere too. This causes
@@ -381,6 +381,28 @@ describe Hoodoo::Services::Middleware do
381
381
  expect(last_response.body).to eq('Middleware exception in exception handler')
382
382
  end
383
383
 
384
+ it 'a matching endpoint should fall back to the basic reporting API if context is not available' do
385
+
386
+ # See previous test for details.
387
+
388
+ expect(Hoodoo::Services::Middleware.environment).to receive(:test?).once.and_return(true)
389
+ expect(Hoodoo::Services::Middleware.environment).to receive(:test?).once.and_return(false)
390
+ expect(Hoodoo::Services::Middleware.environment).to receive(:development?).and_return(false)
391
+
392
+ expect(Hoodoo::Services::Middleware::Interaction).to receive(:new) do
393
+ raise("boo!")
394
+ end
395
+
396
+ # All we care about is seeing a call to #report instead of
397
+ # #contextual_report as in the previous test. Things will probably
398
+ # fail and fall to the fallback handler eventually anyway given that
399
+ # we broke the attempt to create an Interaction instance deliberately,
400
+ # so don't worry about examining the 'get' results.
401
+ #
402
+ expect(Hoodoo::Services::Middleware::ExceptionReporting).to receive(:report)
403
+ get '/v2/rspec_test_service_stub', nil, { 'CONTENT_TYPE' => 'application/json; charset=utf-8' }
404
+ end
405
+
384
406
  # -------------------------------------------------------------------------
385
407
 
386
408
  describe 'service implementation #before and #after' do
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: 1.2.3
4
+ version: 1.3.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: 2016-02-16 00:00:00.000000000 Z
11
+ date: 2016-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kgio