appsignal 3.5.6 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,220 @@
1
+ describe Appsignal::Rack::BodyWrapper do
2
+ let(:nil_txn) { Appsignal::Transaction::NilTransaction.new }
3
+
4
+ describe "with a body that supports all possible features" do
5
+ it "reduces the supported methods to just each()" do
6
+ # which is the safest thing to do, since the body is likely broken
7
+ fake_body = double(:each => nil, :call => nil, :to_ary => [], :to_path => "/tmp/foo.bin",
8
+ :close => nil)
9
+ wrapped = described_class.wrap(fake_body, nil_txn)
10
+ expect(wrapped).to respond_to(:each)
11
+ expect(wrapped).not_to respond_to(:to_ary)
12
+ expect(wrapped).not_to respond_to(:call)
13
+ expect(wrapped).to respond_to(:close)
14
+ end
15
+ end
16
+
17
+ describe "with a body only supporting each()" do
18
+ it "wraps with appropriate class" do
19
+ fake_body = double
20
+ allow(fake_body).to receive(:each)
21
+
22
+ wrapped = described_class.wrap(fake_body, nil_txn)
23
+ expect(wrapped).to respond_to(:each)
24
+ expect(wrapped).not_to respond_to(:to_ary)
25
+ expect(wrapped).not_to respond_to(:call)
26
+ expect(wrapped).to respond_to(:close)
27
+ end
28
+
29
+ it "reads out the body in full using each" do
30
+ fake_body = double
31
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
32
+ wrapped = described_class.wrap(fake_body, nil_txn)
33
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
34
+ end
35
+
36
+ it "returns an Enumerator if each() gets called without a block" do
37
+ fake_body = double
38
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
39
+
40
+ wrapped = described_class.wrap(fake_body, nil_txn)
41
+ enum = wrapped.each
42
+ expect(enum).to be_kind_of(Enumerator)
43
+ expect { |b| enum.each(&b) }.to yield_successive_args("a", "b", "c")
44
+ end
45
+
46
+ it "sets the exception raised inside each() into the Appsignal transaction" do
47
+ fake_body = double
48
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
49
+
50
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
51
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
52
+
53
+ wrapped = described_class.wrap(fake_body, txn)
54
+ expect do
55
+ expect { |b| wrapped.each(&b) }.to yield_control
56
+ end.to raise_error(/Oops/)
57
+ end
58
+
59
+ it "closes the body and the transaction when it gets closed" do
60
+ fake_body = double
61
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
62
+
63
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
64
+ expect(Appsignal::Transaction).to receive(:complete_current!).once
65
+
66
+ wrapped = described_class.wrap(fake_body, txn)
67
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
68
+ expect { wrapped.close }.not_to raise_error
69
+ end
70
+ end
71
+
72
+ describe "with a body supporting both each() and call" do
73
+ it "wraps with the wrapper that conceals call() and exposes each" do
74
+ fake_body = double
75
+ allow(fake_body).to receive(:each)
76
+ allow(fake_body).to receive(:call)
77
+
78
+ wrapped = described_class.wrap(fake_body, nil_txn)
79
+ expect(wrapped).to respond_to(:each)
80
+ expect(wrapped).not_to respond_to(:to_ary)
81
+ expect(wrapped).not_to respond_to(:call)
82
+ expect(wrapped).not_to respond_to(:to_path)
83
+ expect(wrapped).to respond_to(:close)
84
+ end
85
+ end
86
+
87
+ describe "with a body supporting both to_ary and each" do
88
+ let(:fake_body) { double(:each => nil, :to_ary => []) }
89
+ it "wraps with appropriate class" do
90
+ wrapped = described_class.wrap(fake_body, nil_txn)
91
+ expect(wrapped).to respond_to(:each)
92
+ expect(wrapped).to respond_to(:to_ary)
93
+ expect(wrapped).not_to respond_to(:call)
94
+ expect(wrapped).not_to respond_to(:to_path)
95
+ expect(wrapped).to respond_to(:close)
96
+ end
97
+
98
+ it "reads out the body in full using each" do
99
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
100
+
101
+ wrapped = described_class.wrap(fake_body, nil_txn)
102
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
103
+ end
104
+
105
+ it "sets the exception raised inside each() into the Appsignal transaction" do
106
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
107
+
108
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
109
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
110
+
111
+ wrapped = described_class.wrap(fake_body, txn)
112
+ expect do
113
+ expect { |b| wrapped.each(&b) }.to yield_control
114
+ end.to raise_error(/Oops/)
115
+ end
116
+
117
+ it "reads out the body in full using to_ary" do
118
+ expect(fake_body).to receive(:to_ary).and_return(["one", "two", "three"])
119
+
120
+ wrapped = described_class.wrap(fake_body, nil_txn)
121
+ expect(wrapped.to_ary).to eq(["one", "two", "three"])
122
+ end
123
+
124
+ it "sends the exception raised inside to_ary() into the Appsignal and closes txn" do
125
+ fake_body = double
126
+ allow(fake_body).to receive(:each)
127
+ expect(fake_body).to receive(:to_ary).once.and_raise(Exception.new("Oops"))
128
+ expect(fake_body).not_to receive(:close) # Per spec we expect the body has closed itself
129
+
130
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
131
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
132
+ expect(Appsignal::Transaction).to receive(:complete_current!).once
133
+
134
+ wrapped = described_class.wrap(fake_body, txn)
135
+ expect { wrapped.to_ary }.to raise_error(/Oops/)
136
+ end
137
+ end
138
+
139
+ describe "with a body supporting both to_path and each" do
140
+ let(:fake_body) { double(:each => nil, :to_path => nil) }
141
+
142
+ it "wraps with appropriate class" do
143
+ wrapped = described_class.wrap(fake_body, nil_txn)
144
+ expect(wrapped).to respond_to(:each)
145
+ expect(wrapped).not_to respond_to(:to_ary)
146
+ expect(wrapped).not_to respond_to(:call)
147
+ expect(wrapped).to respond_to(:to_path)
148
+ expect(wrapped).to respond_to(:close)
149
+ end
150
+
151
+ it "reads out the body in full using each()" do
152
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
153
+
154
+ wrapped = described_class.wrap(fake_body, nil_txn)
155
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
156
+ end
157
+
158
+ it "sets the exception raised inside each() into the Appsignal transaction" do
159
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
160
+
161
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
162
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
163
+
164
+ wrapped = described_class.wrap(fake_body, txn)
165
+ expect do
166
+ expect { |b| wrapped.each(&b) }.to yield_control
167
+ end.to raise_error(/Oops/)
168
+ end
169
+
170
+ it "sets the exception raised inside to_path() into the Appsignal transaction" do
171
+ allow(fake_body).to receive(:to_path).once.and_raise(Exception.new("Oops"))
172
+
173
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
174
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
175
+ expect(txn).not_to receive(:complete) # gets called by the caller via close()
176
+
177
+ wrapped = described_class.wrap(fake_body, txn)
178
+ expect { wrapped.to_path }.to raise_error(/Oops/)
179
+ end
180
+
181
+ it "exposes to_path to the sender" do
182
+ allow(fake_body).to receive(:to_path).and_return("/tmp/file.bin")
183
+
184
+ wrapped = described_class.wrap(fake_body, nil_txn)
185
+ expect(wrapped.to_path).to eq("/tmp/file.bin")
186
+ end
187
+ end
188
+
189
+ describe "with a body only supporting call()" do
190
+ let(:fake_body) { double(:call => nil) }
191
+ it "wraps with appropriate class" do
192
+ wrapped = described_class.wrap(fake_body, nil_txn)
193
+ expect(wrapped).not_to respond_to(:each)
194
+ expect(wrapped).not_to respond_to(:to_ary)
195
+ expect(wrapped).to respond_to(:call)
196
+ expect(wrapped).not_to respond_to(:to_path)
197
+ expect(wrapped).to respond_to(:close)
198
+ end
199
+
200
+ it "passes the stream into the call() of the body" do
201
+ fake_rack_stream = double("stream")
202
+ expect(fake_body).to receive(:call).with(fake_rack_stream)
203
+
204
+ wrapped = described_class.wrap(fake_body, nil_txn)
205
+ expect { wrapped.call(fake_rack_stream) }.not_to raise_error
206
+ end
207
+
208
+ it "sets the exception raised inside call() into the Appsignal transaction" do
209
+ fake_rack_stream = double
210
+ allow(fake_body).to receive(:call).with(fake_rack_stream).and_raise(Exception.new("Oopsie"))
211
+
212
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
213
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
214
+ expect(txn).not_to receive(:complete) # gets called by the caller via close()
215
+ wrapped = described_class.wrap(fake_body, txn)
216
+
217
+ expect { wrapped.call(fake_rack_stream) }.to raise_error(/Oopsie/)
218
+ end
219
+ end
220
+ end
@@ -50,7 +50,7 @@ describe Appsignal::Rack::GenericInstrumentation do
50
50
  expect(app).to receive(:call).with(env)
51
51
  end
52
52
 
53
- context "with an exception", :error => true do
53
+ context "with an exception raised from call()", :error => true do
54
54
  let(:error) { ExampleException }
55
55
  let(:app) do
56
56
  double.tap do |d|
@@ -58,8 +58,9 @@ describe Appsignal::Rack::GenericInstrumentation do
58
58
  end
59
59
  end
60
60
 
61
- it "records the exception" do
61
+ it "records the exception and completes the transaction" do
62
62
  expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
63
+ expect(Appsignal::Transaction).to receive(:complete_current!)
63
64
  end
64
65
  end
65
66
 
@@ -65,7 +65,8 @@ if DependencyHelper.rails_present?
65
65
 
66
66
  describe "#call_with_appsignal_monitoring" do
67
67
  def run
68
- middleware.call(env)
68
+ _status, _headers, body = middleware.call(env)
69
+ body.close # Rack will always call close() on the body
69
70
  end
70
71
 
71
72
  it "calls the wrapped app" do
@@ -126,7 +127,8 @@ if DependencyHelper.rails_present?
126
127
  end
127
128
  end
128
129
 
129
- it "records the exception" do
130
+ it "records the exception and completes the transaction" do
131
+ expect(Appsignal::Transaction).to receive(:complete_current!)
130
132
  expect { run }.to raise_error(error)
131
133
 
132
134
  transaction_hash = last_transaction.to_h
@@ -3,7 +3,9 @@ if DependencyHelper.sinatra_present?
3
3
 
4
4
  module SinatraRequestHelpers
5
5
  def make_request(env)
6
- middleware.call(env)
6
+ _status, _headers, body = middleware.call(env)
7
+ # Close the body so that the transaction gets completed
8
+ body&.close
7
9
  end
8
10
 
9
11
  def make_request_with_error(env, error)
@@ -90,10 +90,11 @@ describe Appsignal::Rack::StreamingListener do
90
90
  context "with an exception in the instrumentation call" do
91
91
  let(:error) { ExampleException }
92
92
 
93
- it "should add the exception to the transaction" do
93
+ it "should add the exception to the transaction and complete the transaction" do
94
94
  allow(app).to receive(:call).and_raise(error)
95
95
 
96
96
  expect(transaction).to receive(:set_error).with(error)
97
+ expect(Appsignal::Transaction).to receive(:complete_current!).and_call_original
97
98
 
98
99
  expect do
99
100
  listener.call_with_appsignal_monitoring(env)
@@ -101,64 +102,19 @@ describe Appsignal::Rack::StreamingListener do
101
102
  end
102
103
  end
103
104
 
104
- it "should wrap the body in a wrapper" do
105
- expect(Appsignal::StreamWrapper).to receive(:new)
106
- .with("body", transaction)
107
- .and_return(wrapper)
108
-
105
+ it "should wrap the response body in a wrapper" do
109
106
  body = listener.call_with_appsignal_monitoring(env)[2]
110
107
 
111
- expect(body).to be_a(Appsignal::StreamWrapper)
108
+ expect(body).to be_kind_of(Appsignal::Rack::BodyWrapper)
112
109
  end
113
110
  end
114
111
  end
115
112
 
116
113
  describe Appsignal::StreamWrapper do
117
- let(:stream) { double }
118
- let(:transaction) do
119
- Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {})
120
- end
121
- let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
122
-
123
- describe "#each" do
124
- it "calls the original stream" do
125
- expect(stream).to receive(:each)
126
-
127
- wrapper.each
128
- end
129
-
130
- context "when #each raises an error" do
131
- let(:error) { ExampleException }
132
-
133
- it "records the exception" do
134
- allow(stream).to receive(:each).and_raise(error)
135
-
136
- expect(transaction).to receive(:set_error).with(error)
137
-
138
- expect { wrapper.send(:each) }.to raise_error(error)
139
- end
140
- end
141
- end
142
-
143
- describe "#close" do
144
- it "closes the original stream and completes the transaction" do
145
- expect(stream).to receive(:close)
146
- expect(Appsignal::Transaction).to receive(:complete_current!)
147
-
148
- wrapper.close
149
- end
150
-
151
- context "when #close raises an error" do
152
- let(:error) { ExampleException }
153
-
154
- it "records the exception and completes the transaction" do
155
- allow(stream).to receive(:close).and_raise(error)
156
-
157
- expect(transaction).to receive(:set_error).with(error)
158
- expect(transaction).to receive(:complete)
159
-
160
- expect { wrapper.send(:close) }.to raise_error(error)
161
- end
162
- end
114
+ it ".new returns an EnumerableWrapper" do
115
+ fake_body = double(:each => nil)
116
+ fake_txn = double
117
+ stream_wrapper = Appsignal::StreamWrapper.new(fake_body, fake_txn)
118
+ expect(stream_wrapper).to be_kind_of(Appsignal::Rack::EnumerableBodyWrapper)
163
119
  end
164
120
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.6
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Beekman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-02-08 00:00:00.000000000 Z
13
+ date: 2024-02-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -268,6 +268,7 @@ files:
268
268
  - lib/appsignal/probes/helpers.rb
269
269
  - lib/appsignal/probes/mri.rb
270
270
  - lib/appsignal/probes/sidekiq.rb
271
+ - lib/appsignal/rack/body_wrapper.rb
271
272
  - lib/appsignal/rack/generic_instrumentation.rb
272
273
  - lib/appsignal/rack/rails_instrumentation.rb
273
274
  - lib/appsignal/rack/sinatra_instrumentation.rb
@@ -366,6 +367,7 @@ files:
366
367
  - spec/lib/appsignal/probes/gvl_spec.rb
367
368
  - spec/lib/appsignal/probes/mri_spec.rb
368
369
  - spec/lib/appsignal/probes/sidekiq_spec.rb
370
+ - spec/lib/appsignal/rack/body_wrapper_spec.rb
369
371
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
370
372
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb
371
373
  - spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb
@@ -451,7 +453,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
451
453
  - !ruby/object:Gem::Version
452
454
  version: '0'
453
455
  requirements: []
454
- rubygems_version: 3.4.11
456
+ rubygems_version: 3.4.15
455
457
  signing_key:
456
458
  specification_version: 4
457
459
  summary: Logs performance and exception data from your app to appsignal.com