hoodoo 1.2.3 → 1.3.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,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