coach 0.5.2 → 1.0.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 +5 -5
- data/.circleci/config.yml +96 -0
- data/.rubocop.yml +16 -113
- data/.rubocop_todo.yml +27 -0
- data/CHANGELOG.md +21 -1
- data/Gemfile +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +30 -6
- data/coach.gemspec +13 -12
- data/lib/coach.rb +13 -13
- data/lib/coach/handler.rb +32 -6
- data/lib/coach/middleware.rb +33 -6
- data/lib/coach/middleware_item.rb +2 -0
- data/lib/coach/notifications.rb +21 -13
- data/lib/coach/request_benchmark.rb +3 -3
- data/lib/coach/request_serializer.rb +3 -3
- data/lib/coach/router.rb +5 -7
- data/lib/coach/rspec.rb +10 -6
- data/lib/coach/version.rb +1 -1
- data/spec/lib/coach/handler_spec.rb +118 -38
- data/spec/lib/coach/middleware_spec.rb +5 -1
- data/spec/lib/coach/middleware_validator_spec.rb +4 -4
- data/spec/lib/coach/notifications_spec.rb +22 -21
- data/spec/lib/coach/request_benchmark_spec.rb +7 -7
- data/spec/lib/coach/request_serializer_spec.rb +15 -15
- data/spec/lib/coach/router_spec.rb +21 -17
- data/spec/spec_helper.rb +4 -4
- metadata +18 -17
- data/.travis.yml +0 -22
data/lib/coach/middleware.rb
CHANGED
@@ -16,7 +16,7 @@ module Coach
|
|
16
16
|
|
17
17
|
def self.provides(*new_provided)
|
18
18
|
if new_provided.include?(:_metadata)
|
19
|
-
raise
|
19
|
+
raise "Cannot provide :_metadata, Coach uses this internally!"
|
20
20
|
end
|
21
21
|
|
22
22
|
provided.concat(new_provided)
|
@@ -73,10 +73,14 @@ module Coach
|
|
73
73
|
# Use ActiveSupport to instrument the execution of the subsequent chain.
|
74
74
|
def instrument
|
75
75
|
proc do
|
76
|
-
|
77
|
-
|
78
|
-
ActiveSupport::Notifications.
|
79
|
-
|
76
|
+
publish_start
|
77
|
+
|
78
|
+
if ActiveSupport::Notifications.notifier.listening?("coach.middleware.finish")
|
79
|
+
instrument_deprecated { call }
|
80
|
+
else
|
81
|
+
ActiveSupport::Notifications.
|
82
|
+
instrument("finish_middleware.coach", middleware_event) { call }
|
83
|
+
end
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
@@ -99,8 +103,31 @@ module Coach
|
|
99
103
|
def middleware_event
|
100
104
|
{
|
101
105
|
middleware: self.class.name,
|
102
|
-
request: request
|
106
|
+
request: request,
|
103
107
|
}
|
104
108
|
end
|
109
|
+
|
110
|
+
def publish_start
|
111
|
+
if ActiveSupport::Notifications.notifier.listening?("coach.middleware.start")
|
112
|
+
ActiveSupport::Deprecation.warn("The 'coach.middleware.start' event has " \
|
113
|
+
"been renamed to 'start_middleware.coach' and the old name will be " \
|
114
|
+
"removed in a future version.")
|
115
|
+
ActiveSupport::Notifications.
|
116
|
+
publish("coach.middleware.start", middleware_event)
|
117
|
+
end
|
118
|
+
ActiveSupport::Notifications.
|
119
|
+
publish("start_middleware.coach", middleware_event)
|
120
|
+
end
|
121
|
+
|
122
|
+
def instrument_deprecated(&block)
|
123
|
+
ActiveSupport::Deprecation.warn("The 'coach.middleware.finish' event has " \
|
124
|
+
"been renamed to 'finish_middleware.coach' and the old name will be " \
|
125
|
+
"removed in a future version.")
|
126
|
+
ActiveSupport::Notifications.
|
127
|
+
instrument("coach.middleware.finish", middleware_event) do
|
128
|
+
ActiveSupport::Notifications.
|
129
|
+
instrument("finish_middleware.coach", middleware_event, &block)
|
130
|
+
end
|
131
|
+
end
|
105
132
|
end
|
106
133
|
end
|
@@ -24,11 +24,13 @@ module Coach
|
|
24
24
|
end
|
25
25
|
|
26
26
|
# Assigns the parent for this middleware, allowing config inheritance
|
27
|
+
# rubocop:disable Naming/AccessorMethodName
|
27
28
|
def set_parent(parent)
|
28
29
|
@parent = parent
|
29
30
|
|
30
31
|
self
|
31
32
|
end
|
33
|
+
# rubocop:enable Naming/AccessorMethodName
|
32
34
|
|
33
35
|
# Generates config by either cloning our given config (if it's a hash) else if a
|
34
36
|
# lambda value, then will compute the config by calling the lambda with this
|
data/lib/coach/notifications.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
1
|
+
require_relative "request_benchmark"
|
2
|
+
require_relative "request_serializer"
|
3
3
|
|
4
4
|
module Coach
|
5
5
|
# By default, Coach will trigger ActiveSupport::Notifications at specific times in a
|
@@ -8,15 +8,15 @@ module Coach
|
|
8
8
|
# Notifications is used to coordinate the listening and aggregation of these middleware
|
9
9
|
# notifications, while RequestEvent processes the published data.
|
10
10
|
#
|
11
|
-
# Once a request has completed, Notifications will emit a 'coach
|
11
|
+
# Once a request has completed, Notifications will emit a 'requst.coach' with
|
12
12
|
# aggregated request data.
|
13
13
|
class Notifications
|
14
|
-
# Begin processing/emitting 'coach
|
14
|
+
# Begin processing/emitting 'request.coach's
|
15
15
|
def self.subscribe!
|
16
16
|
instance.subscribe!
|
17
17
|
end
|
18
18
|
|
19
|
-
# Cease to emit 'coach
|
19
|
+
# Cease to emit 'request.coach's
|
20
20
|
def self.unsubscribe!
|
21
21
|
instance.unsubscribe!
|
22
22
|
end
|
@@ -28,15 +28,15 @@ module Coach
|
|
28
28
|
def subscribe!
|
29
29
|
return if active?
|
30
30
|
|
31
|
-
@subscriptions << subscribe(
|
31
|
+
@subscriptions << subscribe("start_handler") do |_, event|
|
32
32
|
@benchmarks[event[:request].uuid] = RequestBenchmark.new(event[:middleware])
|
33
33
|
end
|
34
34
|
|
35
|
-
@subscriptions << subscribe(
|
35
|
+
@subscriptions << subscribe("finish_middleware") do |_name, start, finish, _, event|
|
36
36
|
log_middleware_finish(event, start, finish)
|
37
37
|
end
|
38
38
|
|
39
|
-
@subscriptions << subscribe(
|
39
|
+
@subscriptions << subscribe("finish_handler") do |_name, start, finish, _, event|
|
40
40
|
log_handler_finish(event, start, finish)
|
41
41
|
end
|
42
42
|
end
|
@@ -63,7 +63,10 @@ module Coach
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def subscribe(event, &block)
|
66
|
-
|
66
|
+
# New key formats don't include a period. If they do, they're the old deprecated
|
67
|
+
# format. No need to warn here since the warnings will show up from elsewhere.
|
68
|
+
key = event.include?(".") ? "coach.#{event}" : "#{event}.coach"
|
69
|
+
ActiveSupport::Notifications.subscribe(key, &block)
|
67
70
|
end
|
68
71
|
|
69
72
|
def log_middleware_finish(event, start, finish)
|
@@ -79,12 +82,17 @@ module Coach
|
|
79
82
|
end
|
80
83
|
|
81
84
|
# Receives a handler.finish event, with processed benchmark. Publishes to
|
82
|
-
# coach
|
85
|
+
# request.coach notification.
|
83
86
|
def broadcast(event, benchmark)
|
84
87
|
serialized = RequestSerializer.new(event[:request]).serialize.
|
85
|
-
|
86
|
-
|
87
|
-
ActiveSupport::Notifications.
|
88
|
+
merge(benchmark.stats).
|
89
|
+
merge(event.slice(:response, :metadata))
|
90
|
+
if ActiveSupport::Notifications.notifier.listening?("coach.request")
|
91
|
+
ActiveSupport::Deprecation.warn("The 'coach.request' event has been renamed " \
|
92
|
+
"to 'request.coach' and the old name will be removed in a future version.")
|
93
|
+
ActiveSupport::Notifications.publish("coach.request", serialized)
|
94
|
+
end
|
95
|
+
ActiveSupport::Notifications.publish("request.coach", serialized)
|
88
96
|
end
|
89
97
|
end
|
90
98
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Coach
|
2
2
|
# This class is built to aggregate data during the course of the request. It relies on
|
3
|
-
# 'coach
|
3
|
+
# 'start_middleware.coach' and 'finish_middleware.coach' events to register the
|
4
4
|
# start/end of each middleware element, and thereby calculate running times for each.
|
5
5
|
#
|
6
6
|
# Coach::Notifications makes use of this class to produce benchmark data for
|
@@ -15,7 +15,7 @@ module Coach
|
|
15
15
|
event = { name: name, start: start, finish: finish }
|
16
16
|
|
17
17
|
duration_of_children = child_events_for(event).
|
18
|
-
|
18
|
+
inject(0) { |total, e| total + e[:duration] }
|
19
19
|
event[:duration] = (finish - start) - duration_of_children
|
20
20
|
|
21
21
|
@events.push(event)
|
@@ -34,7 +34,7 @@ module Coach
|
|
34
34
|
duration: format_ms(@duration),
|
35
35
|
chain: sorted_chain.map do |event|
|
36
36
|
{ name: event[:name], duration: format_ms(event[:duration]) }
|
37
|
-
end
|
37
|
+
end,
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
@@ -8,7 +8,7 @@ module Coach
|
|
8
8
|
# that will determine how to transform the original header value, otherwise a default
|
9
9
|
# string is used.
|
10
10
|
def self.sanitize_header(header, &rule)
|
11
|
-
header_rules[header] = rule || ->(
|
11
|
+
header_rules[header] = rule || ->(_value) { "[FILTERED]" }
|
12
12
|
end
|
13
13
|
|
14
14
|
# Applies sanitizing rules. Expects `header` to be in 'http_header_name' form.
|
@@ -39,7 +39,7 @@ module Coach
|
|
39
39
|
|
40
40
|
# Extra request info
|
41
41
|
headers: filtered_headers,
|
42
|
-
session_id: @request.remote_ip
|
42
|
+
session_id: @request.remote_ip,
|
43
43
|
}
|
44
44
|
end
|
45
45
|
|
@@ -47,7 +47,7 @@ module Coach
|
|
47
47
|
|
48
48
|
def request_path
|
49
49
|
@request.fullpath
|
50
|
-
rescue
|
50
|
+
rescue StandardError
|
51
51
|
"unknown"
|
52
52
|
end
|
53
53
|
|
data/lib/coach/router.rb
CHANGED
@@ -5,10 +5,10 @@ module Coach
|
|
5
5
|
class Router
|
6
6
|
ACTION_TRAITS = {
|
7
7
|
index: { method: :get },
|
8
|
-
show: { method: :get, url:
|
8
|
+
show: { method: :get, url: ":id" },
|
9
9
|
create: { method: :post },
|
10
|
-
update: { method: :put, url:
|
11
|
-
destroy: { method: :delete, url:
|
10
|
+
update: { method: :put, url: ":id" },
|
11
|
+
destroy: { method: :delete, url: ":id" },
|
12
12
|
}.each_value(&:freeze).freeze
|
13
13
|
|
14
14
|
def initialize(mapper)
|
@@ -45,9 +45,7 @@ module Coach
|
|
45
45
|
# ...which will load the default route for `show` and `index`, while also configuring
|
46
46
|
# a refund route.
|
47
47
|
def action_traits(list_of_actions)
|
48
|
-
if list_of_actions.last.is_a?(Hash)
|
49
|
-
*list_of_actions, traits = list_of_actions
|
50
|
-
end
|
48
|
+
*list_of_actions, traits = list_of_actions if list_of_actions.last.is_a?(Hash)
|
51
49
|
|
52
50
|
list_of_actions.reduce(traits || {}) do |memo, action|
|
53
51
|
trait = ACTION_TRAITS.fetch(action) do
|
@@ -60,7 +58,7 @@ module Coach
|
|
60
58
|
|
61
59
|
# Applies trait url to base, removing duplicate /'s
|
62
60
|
def action_url(base, traits)
|
63
|
-
[base, traits[:url]].compact.join(
|
61
|
+
[base, traits[:url]].compact.join("/").gsub(%r{/+}, "/")
|
64
62
|
end
|
65
63
|
|
66
64
|
# Turns a snake_case string/symbol into a CamelCase
|
data/lib/coach/rspec.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "rspec/expectations"
|
2
|
+
require "coach/middleware"
|
3
3
|
|
4
4
|
# Middleware stubbing ######################################
|
5
5
|
|
6
|
+
# rubocop:disable Metrics/MethodLength
|
7
|
+
# rubocop:disable Metrics/AbcSize
|
6
8
|
def build_middleware(name)
|
7
9
|
Class.new(Coach::Middleware) do
|
8
10
|
# To access `name`, we need to use `define_method` instead of `def`
|
@@ -25,6 +27,8 @@ def build_middleware(name)
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
30
|
+
# rubocop:enable Metrics/AbcSize
|
31
|
+
# rubocop:enable Metrics/MethodLength
|
28
32
|
|
29
33
|
def null_middleware
|
30
34
|
double(call: nil)
|
@@ -39,7 +43,7 @@ RSpec::Matchers.define :respond_with_status do |expected_status|
|
|
39
43
|
@response[0] == expected_status
|
40
44
|
end
|
41
45
|
|
42
|
-
failure_message do |
|
46
|
+
failure_message do |_actual|
|
43
47
|
"expected #{@middleware.class.name} to respond with #{expected_status} but got " \
|
44
48
|
"#{@response[0]}"
|
45
49
|
end
|
@@ -51,7 +55,7 @@ RSpec::Matchers.define :respond_with_body_that_matches do |body_regex|
|
|
51
55
|
@response_body.match(body_regex)
|
52
56
|
end
|
53
57
|
|
54
|
-
failure_message do |
|
58
|
+
failure_message do |_actual|
|
55
59
|
"expected that \"#{@response_body}\" would match #{body_regex}"
|
56
60
|
end
|
57
61
|
end
|
@@ -65,7 +69,7 @@ RSpec::Matchers.define :respond_with_envelope do |envelope, keys = []|
|
|
65
69
|
expect(@envelope).to match(hash_including(*keys))
|
66
70
|
end
|
67
71
|
|
68
|
-
failure_message do |
|
72
|
+
failure_message do |_actual|
|
69
73
|
"expected that \"#{@response}\" would have envelope \"#{envelope}\" that matches " \
|
70
74
|
"hash_including(#{keys})"
|
71
75
|
end
|
@@ -78,7 +82,7 @@ RSpec::Matchers.define :respond_with_header do |header, value_regex|
|
|
78
82
|
@header_value.match(value_regex)
|
79
83
|
end
|
80
84
|
|
81
|
-
failure_message do |
|
85
|
+
failure_message do |_actual|
|
82
86
|
"expected #{header} header in response to match #{value_regex} but found " \
|
83
87
|
"\"#{@header_value}\""
|
84
88
|
end
|
data/lib/coach/version.rb
CHANGED
@@ -5,27 +5,30 @@ require "coach/middleware"
|
|
5
5
|
require "coach/errors"
|
6
6
|
|
7
7
|
describe Coach::Handler do
|
8
|
+
subject(:handler) { described_class.new(terminal_middleware, handler: true) }
|
9
|
+
|
8
10
|
let(:middleware_a) { build_middleware("A") }
|
9
11
|
let(:middleware_b) { build_middleware("B") }
|
10
12
|
let(:middleware_c) { build_middleware("C") }
|
11
13
|
let(:middleware_d) { build_middleware("D") }
|
12
14
|
|
13
15
|
let(:terminal_middleware) { build_middleware("Terminal") }
|
14
|
-
subject(:handler) { Coach::Handler.new(terminal_middleware, handler: true) }
|
15
16
|
|
16
17
|
before { Coach::Notifications.unsubscribe! }
|
17
18
|
|
18
19
|
describe "#call" do
|
19
|
-
let(:
|
20
|
-
let(:
|
20
|
+
let(:a_double) { double }
|
21
|
+
let(:b_double) { double }
|
21
22
|
|
22
|
-
before
|
23
|
-
|
23
|
+
before do
|
24
|
+
terminal_middleware.uses(middleware_a, callback: a_double)
|
25
|
+
terminal_middleware.uses(middleware_b, callback: b_double)
|
26
|
+
end
|
24
27
|
|
25
28
|
it "invokes all middleware in the chain" do
|
29
|
+
expect(a_double).to receive(:call)
|
30
|
+
expect(b_double).to receive(:call)
|
26
31
|
result = handler.call({})
|
27
|
-
expect(a_spy).to have_received(:call)
|
28
|
-
expect(b_spy).to have_received(:call)
|
29
32
|
expect(result).to eq(%w[A{} B{} Terminal{:handler=>true}])
|
30
33
|
end
|
31
34
|
end
|
@@ -45,9 +48,11 @@ describe Coach::Handler do
|
|
45
48
|
end
|
46
49
|
|
47
50
|
context "given a route that includes nested middleware" do
|
48
|
-
before
|
49
|
-
|
50
|
-
|
51
|
+
before do
|
52
|
+
middleware_b.uses(middleware_c)
|
53
|
+
middleware_a.uses(middleware_b)
|
54
|
+
terminal_middleware.uses(middleware_a)
|
55
|
+
end
|
51
56
|
|
52
57
|
it "assembles a sequence including all middleware" do
|
53
58
|
expect(sequence).to match_array([middleware_c, middleware_b,
|
@@ -56,10 +61,12 @@ describe Coach::Handler do
|
|
56
61
|
end
|
57
62
|
|
58
63
|
context "when a middleware has been included more than once" do
|
59
|
-
before
|
60
|
-
|
61
|
-
|
62
|
-
|
64
|
+
before do
|
65
|
+
middleware_a.uses(middleware_c)
|
66
|
+
middleware_b.uses(middleware_c)
|
67
|
+
terminal_middleware.uses(middleware_a)
|
68
|
+
terminal_middleware.uses(middleware_b)
|
69
|
+
end
|
63
70
|
|
64
71
|
it "only appears once" do
|
65
72
|
expect(sequence).to match_array([middleware_c, middleware_a,
|
@@ -79,8 +86,10 @@ describe Coach::Handler do
|
|
79
86
|
end
|
80
87
|
|
81
88
|
describe "#build_request_chain" do
|
82
|
-
before
|
83
|
-
|
89
|
+
before do
|
90
|
+
terminal_middleware.uses(middleware_a)
|
91
|
+
terminal_middleware.uses(middleware_b, b: true)
|
92
|
+
end
|
84
93
|
|
85
94
|
let(:root_item) { Coach::MiddlewareItem.new(terminal_middleware) }
|
86
95
|
let(:sequence) { handler.build_sequence(root_item, {}) }
|
@@ -98,8 +107,10 @@ describe Coach::Handler do
|
|
98
107
|
end
|
99
108
|
|
100
109
|
context "with inheriting config" do
|
101
|
-
before
|
102
|
-
|
110
|
+
before do
|
111
|
+
middleware_b.uses(middleware_c, ->(config) { config.slice(:b) })
|
112
|
+
middleware_b.uses(middleware_d)
|
113
|
+
end
|
103
114
|
|
104
115
|
it "calls lambda with parent middlewares config" do
|
105
116
|
expect(handler.build_request_chain(sequence, {}).call).
|
@@ -111,19 +122,20 @@ describe Coach::Handler do
|
|
111
122
|
describe "#call" do
|
112
123
|
before { terminal_middleware.uses(middleware_a) }
|
113
124
|
|
114
|
-
describe
|
115
|
-
before { Coach::Notifications.subscribe! }
|
116
|
-
|
117
|
-
# Prevent RequestSerializer from erroring due to insufficient request mock
|
125
|
+
describe "notifications" do
|
118
126
|
before do
|
127
|
+
Coach::Notifications.subscribe!
|
128
|
+
|
129
|
+
# Prevent RequestSerializer from erroring due to insufficient request mock
|
119
130
|
allow(Coach::RequestSerializer).
|
120
131
|
to receive(:new).
|
121
|
-
and_return(
|
132
|
+
and_return(instance_double("Coach::RequestSerializer", serialize: {}))
|
122
133
|
end
|
123
134
|
|
124
135
|
subject(:coach_events) do
|
125
136
|
events = []
|
126
|
-
subscription = ActiveSupport::Notifications.
|
137
|
+
subscription = ActiveSupport::Notifications.
|
138
|
+
subscribe(/\.coach$/) do |name, *_args|
|
127
139
|
events << name
|
128
140
|
end
|
129
141
|
|
@@ -132,19 +144,17 @@ describe Coach::Handler do
|
|
132
144
|
events
|
133
145
|
end
|
134
146
|
|
135
|
-
it { is_expected.to include(
|
136
|
-
it { is_expected.to include(
|
137
|
-
it { is_expected.to include(
|
138
|
-
it { is_expected.to include(
|
139
|
-
it { is_expected.to include(
|
147
|
+
it { is_expected.to include("start_handler.coach") }
|
148
|
+
it { is_expected.to include("start_middleware.coach") }
|
149
|
+
it { is_expected.to include("request.coach") }
|
150
|
+
it { is_expected.to include("finish_middleware.coach") }
|
151
|
+
it { is_expected.to include("finish_handler.coach") }
|
140
152
|
|
141
153
|
context "when an exception is raised in the chain" do
|
142
|
-
let(:explosive_action) { -> { raise "AH" } }
|
143
|
-
before { terminal_middleware.uses(middleware_a, callback: explosive_action) }
|
144
|
-
|
145
154
|
subject(:coach_events) do
|
146
155
|
events = []
|
147
|
-
subscription = ActiveSupport::Notifications.
|
156
|
+
subscription = ActiveSupport::Notifications.
|
157
|
+
subscribe(/\.coach$/) do |name, *args|
|
148
158
|
events << [name, args.last]
|
149
159
|
end
|
150
160
|
|
@@ -157,22 +167,92 @@ describe Coach::Handler do
|
|
157
167
|
events
|
158
168
|
end
|
159
169
|
|
160
|
-
|
170
|
+
let(:explosive_action) { -> { raise "AH" } }
|
171
|
+
|
172
|
+
before { terminal_middleware.uses(middleware_a, callback: explosive_action) }
|
173
|
+
|
174
|
+
it "captures the error event with the metadata" do
|
161
175
|
is_expected.
|
162
|
-
to include([
|
176
|
+
to include(["finish_handler.coach", hash_including(
|
163
177
|
response: { status: 500 },
|
164
|
-
metadata: { A: true }
|
178
|
+
metadata: { A: true },
|
165
179
|
)])
|
166
180
|
end
|
167
181
|
|
168
|
-
it "
|
182
|
+
it "bubbles the error to the next handler" do
|
169
183
|
expect { handler.call({}) }.to raise_error(StandardError, "AH")
|
170
184
|
end
|
171
185
|
end
|
186
|
+
|
187
|
+
context "deprecations" do
|
188
|
+
subject(:coach_events) do
|
189
|
+
events = []
|
190
|
+
subscription = ActiveSupport::Notifications.
|
191
|
+
subscribe(/^coach\./) do |name, *_args|
|
192
|
+
events << name
|
193
|
+
end
|
194
|
+
|
195
|
+
handler.call({})
|
196
|
+
ActiveSupport::Notifications.unsubscribe(subscription)
|
197
|
+
events
|
198
|
+
end
|
199
|
+
|
200
|
+
let!(:deprecations_silenced) do
|
201
|
+
ActiveSupport::Deprecation.silenced
|
202
|
+
end
|
203
|
+
|
204
|
+
before do
|
205
|
+
ActiveSupport::Deprecation.silenced = true
|
206
|
+
end
|
207
|
+
|
208
|
+
after do
|
209
|
+
ActiveSupport::Deprecation.silenced = deprecations_silenced
|
210
|
+
end
|
211
|
+
|
212
|
+
it { is_expected.to include("coach.handler.start") }
|
213
|
+
it { is_expected.to include("coach.middleware.start") }
|
214
|
+
it { is_expected.to include("coach.request") }
|
215
|
+
it { is_expected.to include("coach.middleware.finish") }
|
216
|
+
it { is_expected.to include("coach.handler.finish") }
|
217
|
+
|
218
|
+
context "when an exception is raised in the chain" do
|
219
|
+
subject(:coach_events) do
|
220
|
+
events = []
|
221
|
+
subscription = ActiveSupport::Notifications.
|
222
|
+
subscribe(/^coach\./) do |name, *args|
|
223
|
+
events << [name, args.last]
|
224
|
+
end
|
225
|
+
|
226
|
+
begin
|
227
|
+
handler.call({})
|
228
|
+
rescue
|
229
|
+
:continue_anyway
|
230
|
+
end
|
231
|
+
ActiveSupport::Notifications.unsubscribe(subscription)
|
232
|
+
events
|
233
|
+
end
|
234
|
+
|
235
|
+
let(:explosive_action) { -> { raise "AH" } }
|
236
|
+
|
237
|
+
before { terminal_middleware.uses(middleware_a, callback: explosive_action) }
|
238
|
+
|
239
|
+
it "captures the error event with the metadata" do
|
240
|
+
is_expected.
|
241
|
+
to include(["coach.handler.finish", hash_including(
|
242
|
+
response: { status: 500 },
|
243
|
+
metadata: { A: true },
|
244
|
+
)])
|
245
|
+
end
|
246
|
+
|
247
|
+
it "bubbles the error to the next handler" do
|
248
|
+
expect { handler.call({}) }.to raise_error(StandardError, "AH")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
172
252
|
end
|
173
253
|
end
|
174
254
|
|
175
255
|
describe "#inspect" do
|
176
|
-
its(:inspect) { is_expected.to eql(
|
256
|
+
its(:inspect) { is_expected.to eql("#<Coach::Handler[Terminal]>") }
|
177
257
|
end
|
178
258
|
end
|