airbrake 9.2.1 → 9.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake/rack/middleware.rb +21 -13
- data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +20 -2
- data/lib/airbrake/version.rb +1 -1
- metadata +5 -81
- data/spec/apps/rack/dummy_app.rb +0 -17
- data/spec/apps/rails/dummy_app.rb +0 -258
- data/spec/apps/rails/dummy_task.rake +0 -15
- data/spec/apps/rails/logs/32.log +0 -34094
- data/spec/apps/rails/logs/42.log +0 -1488
- data/spec/apps/rails/logs/52.log +0 -6321
- data/spec/apps/sinatra/sinatra_test_app.rb +0 -12
- data/spec/integration/rack/rack_spec.rb +0 -19
- data/spec/integration/rails/rails_spec.rb +0 -430
- data/spec/integration/rails/rake_spec.rb +0 -97
- data/spec/integration/shared_examples/rack_examples.rb +0 -110
- data/spec/integration/sinatra/sinatra_spec.rb +0 -30
- data/spec/spec_helper.rb +0 -105
- data/spec/support/matchers/a_notice_with.rb +0 -29
- data/spec/unit/logger_spec.rb +0 -125
- data/spec/unit/rack/context_filter_spec.rb +0 -90
- data/spec/unit/rack/http_headers_filter_spec.rb +0 -44
- data/spec/unit/rack/http_params_filter_spec.rb +0 -58
- data/spec/unit/rack/instrumentable_spec.rb +0 -105
- data/spec/unit/rack/middleware_spec.rb +0 -98
- data/spec/unit/rack/rack_spec.rb +0 -46
- data/spec/unit/rack/request_body_filter_spec.rb +0 -44
- data/spec/unit/rack/request_store_spec.rb +0 -36
- data/spec/unit/rack/route_filter_spec.rb +0 -52
- data/spec/unit/rack/session_filter_spec.rb +0 -44
- data/spec/unit/rack/user_filter_spec.rb +0 -30
- data/spec/unit/rack/user_spec.rb +0 -218
- data/spec/unit/rails/action_cable/notify_callback_spec.rb +0 -26
- data/spec/unit/rails/action_controller_notify_subscriber_spec.rb +0 -43
- data/spec/unit/rails/action_controller_performance_breakdown_subscriber_spec.rb +0 -63
- data/spec/unit/rails/action_controller_route_subscriber_spec.rb +0 -84
- data/spec/unit/rails/active_record_subscriber_spec.rb +0 -70
- data/spec/unit/rails/excon_spec.rb +0 -46
- data/spec/unit/rake/tasks_spec.rb +0 -70
- data/spec/unit/shoryuken_spec.rb +0 -55
- data/spec/unit/sidekiq/retryable_jobs_filter_spec.rb +0 -36
- data/spec/unit/sidekiq_spec.rb +0 -33
- data/spec/unit/sneakers_spec.rb +0 -83
@@ -1,90 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::ContextFilter do
|
2
|
-
def env_for(url, opts = {})
|
3
|
-
Rack::MockRequest.env_for(url, opts)
|
4
|
-
end
|
5
|
-
|
6
|
-
let(:notice) do
|
7
|
-
Airbrake.build_notice('oops').tap do |notice|
|
8
|
-
notice.stash[:rack_request] = Rack::Request.new(env_for(uri, opts))
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
let(:uri) { '/' }
|
13
|
-
let(:opts) { {} }
|
14
|
-
|
15
|
-
it "adds framework version to the context" do
|
16
|
-
subject.call(notice)
|
17
|
-
expect(notice[:context][:versions]).to include(
|
18
|
-
'rack_version' => a_string_matching(/\d.\d/),
|
19
|
-
'rack_release' => a_string_matching(/\d.\d\.\d/)
|
20
|
-
)
|
21
|
-
end
|
22
|
-
|
23
|
-
context "when URL is present" do
|
24
|
-
let(:uri) { '/bingo' }
|
25
|
-
let(:opts) { {} }
|
26
|
-
|
27
|
-
it "adds URL to the context" do
|
28
|
-
subject.call(notice)
|
29
|
-
expect(notice[:context][:url]).to eq('http://example.org/bingo')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context "when User-Agent is present" do
|
34
|
-
let(:uri) { '/' }
|
35
|
-
let(:opts) do
|
36
|
-
{ 'HTTP_USER_AGENT' => 'Bingo Agent' }
|
37
|
-
end
|
38
|
-
|
39
|
-
it "adds User-Agent to the context" do
|
40
|
-
subject.call(notice)
|
41
|
-
expect(notice[:context][:userAgent]).to eq('Bingo Agent')
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context "when visitor address is present" do
|
46
|
-
let(:opts) do
|
47
|
-
{ 'REMOTE_ADDR' => '1.2.3.4' }
|
48
|
-
end
|
49
|
-
|
50
|
-
it "adds userAddr to the context" do
|
51
|
-
subject.call(notice)
|
52
|
-
expect(notice[:context][:userAddr]).to eq('1.2.3.4')
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
context "when visitor is behind a proxy or load balancer" do
|
57
|
-
let(:opts) do
|
58
|
-
{ 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' }
|
59
|
-
end
|
60
|
-
|
61
|
-
it "adds userAddr to the context" do
|
62
|
-
subject.call(notice)
|
63
|
-
expect(notice[:context][:userAddr]).to eq('9.9.9.9')
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context "when controller is present" do
|
68
|
-
let(:controller) do
|
69
|
-
double.tap do |ctrl|
|
70
|
-
allow(ctrl).to receive(:controller_name).and_return('BingoController')
|
71
|
-
allow(ctrl).to receive(:action_name).and_return('bango_name')
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
let(:uri) { '/' }
|
76
|
-
let(:opts) do
|
77
|
-
{ 'action_controller.instance' => controller }
|
78
|
-
end
|
79
|
-
|
80
|
-
it "adds controller name as component" do
|
81
|
-
subject.call(notice)
|
82
|
-
expect(notice[:context][:component]).to eq('BingoController')
|
83
|
-
end
|
84
|
-
|
85
|
-
it "adds action name as action" do
|
86
|
-
subject.call(notice)
|
87
|
-
expect(notice[:context][:action]).to eq('bango_name')
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::HttpHeadersFilter do
|
2
|
-
def env_for(url, opts = {})
|
3
|
-
Rack::MockRequest.env_for(url, opts)
|
4
|
-
end
|
5
|
-
|
6
|
-
let(:notice) do
|
7
|
-
Airbrake.build_notice('oops').tap do |notice|
|
8
|
-
notice.stash[:rack_request] = Rack::Request.new(env_for(uri, opts))
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
let(:headers) do
|
13
|
-
{
|
14
|
-
'HTTP_HOST' => 'example.com',
|
15
|
-
'CONTENT_TYPE' => 'text/html',
|
16
|
-
'CONTENT_LENGTH' => 100500
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
let(:uri) { '/' }
|
21
|
-
let(:opts) { headers.dup }
|
22
|
-
|
23
|
-
it "preserves data that already has been added to the context" do
|
24
|
-
notice[:context]['SOME_KEY'] = 'SOME_VALUE'
|
25
|
-
subject.call(notice)
|
26
|
-
expect(notice[:context]['SOME_KEY']).to eq('SOME_VALUE')
|
27
|
-
end
|
28
|
-
|
29
|
-
context "when CONTENT_TYPE, CONTENT_LENGTH and HTTP_* headers are present" do
|
30
|
-
it "adds them to the context hash" do
|
31
|
-
subject.call(notice)
|
32
|
-
expect(notice[:context][:headers]).to eq(headers)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context "when unexpected headers are present" do
|
37
|
-
let(:opts) { headers.dup.merge('X-SOME-HEADER' => 'value') }
|
38
|
-
|
39
|
-
it "adds them to the context hash" do
|
40
|
-
subject.call(notice)
|
41
|
-
expect(notice[:context][:headers]).to eq(headers)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::HttpParamsFilter do
|
2
|
-
def env_for(url, opts = {})
|
3
|
-
Rack::MockRequest.env_for(url, opts)
|
4
|
-
end
|
5
|
-
|
6
|
-
let(:notice) do
|
7
|
-
Airbrake.build_notice('oops').tap do |notice|
|
8
|
-
notice.stash[:rack_request] = Rack::Request.new(env_for(uri, opts))
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
context "when rack params is nil" do
|
13
|
-
let(:uri) { '/' }
|
14
|
-
let(:opts) { {} }
|
15
|
-
|
16
|
-
it "doesn't overwrite the params key with nil" do
|
17
|
-
subject.call(notice)
|
18
|
-
expect(notice[:params]).to eq({})
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when form params are present" do
|
23
|
-
let(:params) do
|
24
|
-
{ a: 1, b: 2 }
|
25
|
-
end
|
26
|
-
|
27
|
-
let(:input) { StringIO.new }
|
28
|
-
let(:uri) { '/' }
|
29
|
-
let(:opts) do
|
30
|
-
{
|
31
|
-
'rack.request.form_hash' => params,
|
32
|
-
'rack.request.form_input' => input,
|
33
|
-
'rack.input' => input
|
34
|
-
}
|
35
|
-
end
|
36
|
-
|
37
|
-
it "sets the params hash" do
|
38
|
-
subject.call(notice)
|
39
|
-
expect(notice[:params]).to eq(params)
|
40
|
-
end
|
41
|
-
|
42
|
-
it "merges given params with existing params" do
|
43
|
-
notice[:params] = { bingo: :bango }
|
44
|
-
subject.call(notice)
|
45
|
-
expect(notice[:params]).to eq(bingo: :bango, a: 1, b: 2)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
context "when query string params are present" do
|
50
|
-
let(:uri) { '/?bingo=bango&bongo=bish' }
|
51
|
-
let(:opts) { {} }
|
52
|
-
|
53
|
-
it "sets the params hash" do
|
54
|
-
subject.call(notice)
|
55
|
-
expect(notice[:params]).to eq('bingo' => 'bango', 'bongo' => 'bish')
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::Instrumentable do
|
2
|
-
after { Airbrake::Rack::RequestStore.clear }
|
3
|
-
|
4
|
-
describe ".airbrake_capture_timing" do
|
5
|
-
let(:routes) { Airbrake::Rack::RequestStore[:routes] }
|
6
|
-
|
7
|
-
let(:klass) do
|
8
|
-
Class.new do
|
9
|
-
extend Airbrake::Rack::Instrumentable
|
10
|
-
|
11
|
-
def method; end
|
12
|
-
airbrake_capture_timing :method
|
13
|
-
|
14
|
-
def method_with_arg(a); end
|
15
|
-
airbrake_capture_timing :method_with_arg
|
16
|
-
|
17
|
-
def method_with_args(a, b); end
|
18
|
-
airbrake_capture_timing :method_with_args
|
19
|
-
|
20
|
-
def method_with_vla(*args); end
|
21
|
-
airbrake_capture_timing :method_with_vla
|
22
|
-
|
23
|
-
def method_with_args_and_vla(*args); end
|
24
|
-
airbrake_capture_timing :method_with_args_and_vla
|
25
|
-
|
26
|
-
def method_with_kwargs(foo:, bar:); end
|
27
|
-
airbrake_capture_timing :method_with_kwargs
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context "when request store doesn't have any routes" do
|
32
|
-
before { Airbrake::Rack::RequestStore.clear }
|
33
|
-
|
34
|
-
it "doesn't store timing of the tracked method" do
|
35
|
-
klass.new.method
|
36
|
-
expect(Airbrake::Rack::RequestStore.store).to be_empty
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "when request store has a route" do
|
41
|
-
let(:groups) { routes['/about'][:groups] }
|
42
|
-
|
43
|
-
before do
|
44
|
-
Airbrake::Rack::RequestStore[:routes] = {
|
45
|
-
'/about' => {
|
46
|
-
method: 'GET',
|
47
|
-
response_type: :html,
|
48
|
-
groups: {}
|
49
|
-
}
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
it "attaches timing for a method without an argument" do
|
54
|
-
klass.new.method
|
55
|
-
expect(groups).to match('method' => be > 0)
|
56
|
-
end
|
57
|
-
|
58
|
-
it "attaches timing for a method with an argument" do
|
59
|
-
klass.new.method_with_arg(1)
|
60
|
-
expect(groups).to match('method_with_arg' => be > 0)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "attaches timing for a variable-length argument method" do
|
64
|
-
klass.new.method_with_vla(1, 2, 3)
|
65
|
-
expect(groups).to match('method_with_vla' => be > 0)
|
66
|
-
end
|
67
|
-
|
68
|
-
it "attaches timing for a method with args and a variable-length array" do
|
69
|
-
klass.new.method_with_args_and_vla(1, 2, 3)
|
70
|
-
expect(groups).to match('method_with_args_and_vla' => be > 0)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "attaches timing for a method with kwargs" do
|
74
|
-
klass.new.method_with_kwargs(foo: 1, bar: 2)
|
75
|
-
expect(groups).to match('method_with_kwargs' => be > 0)
|
76
|
-
end
|
77
|
-
|
78
|
-
it "attaches all timings for multiple methods to the request store" do
|
79
|
-
klass.new.method
|
80
|
-
klass.new.method_with_arg(1)
|
81
|
-
|
82
|
-
expect(groups).to match(
|
83
|
-
'method' => be > 0,
|
84
|
-
'method_with_arg' => be > 0
|
85
|
-
)
|
86
|
-
end
|
87
|
-
|
88
|
-
context "and when a custom label was provided" do
|
89
|
-
let(:klass) do
|
90
|
-
Class.new do
|
91
|
-
extend Airbrake::Rack::Instrumentable
|
92
|
-
|
93
|
-
def method; end
|
94
|
-
airbrake_capture_timing :method, label: 'custom label'
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
it "attaches timing under the provided label" do
|
99
|
-
klass.new.method
|
100
|
-
expect(groups).to match('custom label' => be > 0)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::Middleware do
|
2
|
-
# The list of Rack filters that read Rack request information and append it to
|
3
|
-
# notices.
|
4
|
-
[
|
5
|
-
Airbrake::Rack::ContextFilter,
|
6
|
-
Airbrake::Rack::UserFilter,
|
7
|
-
Airbrake::Rack::SessionFilter,
|
8
|
-
Airbrake::Rack::HttpParamsFilter,
|
9
|
-
Airbrake::Rack::HttpHeadersFilter,
|
10
|
-
Airbrake::Rack::RouteFilter,
|
11
|
-
|
12
|
-
# Optional filters (must be included by users):
|
13
|
-
# Airbrake::Rack::RequestBodyFilter
|
14
|
-
].each do |filter|
|
15
|
-
Airbrake.add_filter(filter.new)
|
16
|
-
end
|
17
|
-
|
18
|
-
let(:app) { proc { |env| [200, env, 'Bingo bango content'] } }
|
19
|
-
let(:faulty_app) { proc { raise AirbrakeTestError } }
|
20
|
-
let(:endpoint) { 'https://api.airbrake.io/api/v3/projects/113743/notices' }
|
21
|
-
let(:middleware) { described_class.new(app) }
|
22
|
-
|
23
|
-
def env_for(url, opts = {})
|
24
|
-
Rack::MockRequest.env_for(url, opts)
|
25
|
-
end
|
26
|
-
|
27
|
-
def wait_for_a_request_with_body(body)
|
28
|
-
wait_for(a_request(:post, endpoint).with(body: body)).to have_been_made.once
|
29
|
-
end
|
30
|
-
|
31
|
-
before do
|
32
|
-
stub_request(:post, endpoint).to_return(status: 201, body: '{}')
|
33
|
-
end
|
34
|
-
|
35
|
-
describe "#call" do
|
36
|
-
context "when app raises an exception" do
|
37
|
-
it "rescues the exception, notifies Airbrake & re-raises it" do
|
38
|
-
expect { described_class.new(faulty_app).call(env_for('/')) }
|
39
|
-
.to raise_error(AirbrakeTestError)
|
40
|
-
|
41
|
-
wait_for_a_request_with_body(/"errors":\[{"type":"AirbrakeTestError"/)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "sends framework version and name" do
|
45
|
-
expect { described_class.new(faulty_app).call(env_for('/bingo/bango')) }
|
46
|
-
.to raise_error(AirbrakeTestError)
|
47
|
-
|
48
|
-
wait_for_a_request_with_body(
|
49
|
-
/"context":{.*"versions":{"(rails|sinatra|rack_version)"/
|
50
|
-
)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context "when app doesn't raise" do
|
55
|
-
context "and previous middleware stored an exception in env" do
|
56
|
-
shared_examples 'stored exception' do |type|
|
57
|
-
it "notifies on #{type}, but doesn't raise" do
|
58
|
-
env = env_for('/').merge(type => AirbrakeTestError.new)
|
59
|
-
described_class.new(app).call(env)
|
60
|
-
|
61
|
-
wait_for_a_request_with_body(/"errors":\[{"type":"AirbrakeTestError"/)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
['rack.exception', 'action_dispatch.exception', 'sinatra.error'].each do |type|
|
66
|
-
include_examples 'stored exception', type
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
it "doesn't notify Airbrake" do
|
71
|
-
described_class.new(app).call(env_for('/'))
|
72
|
-
sleep 1
|
73
|
-
expect(a_request(:post, endpoint)).not_to have_been_made
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
it "returns a response" do
|
78
|
-
response = described_class.new(app).call(env_for('/'))
|
79
|
-
|
80
|
-
expect(response[0]).to eq(200)
|
81
|
-
expect(response[1]).to be_a(Hash)
|
82
|
-
expect(response[2]).to eq('Bingo bango content')
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
context "when Airbrake is not configured" do
|
87
|
-
it "returns nil" do
|
88
|
-
allow(Airbrake).to receive(:build_notice).and_return(nil)
|
89
|
-
allow(Airbrake).to receive(:notify)
|
90
|
-
|
91
|
-
expect { described_class.new(faulty_app).call(env_for('/')) }
|
92
|
-
.to raise_error(AirbrakeTestError)
|
93
|
-
|
94
|
-
expect(Airbrake).to have_received(:build_notice)
|
95
|
-
expect(Airbrake).not_to have_received(:notify)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
data/spec/unit/rack/rack_spec.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack do
|
2
|
-
after { Airbrake::Rack::RequestStore.clear }
|
3
|
-
|
4
|
-
describe ".timing" do
|
5
|
-
let(:routes) { Airbrake::Rack::RequestStore[:routes] }
|
6
|
-
|
7
|
-
context "when request store doesn't have any routes" do
|
8
|
-
it "doesn't store timing" do
|
9
|
-
described_class.capture_timing('operation') {}
|
10
|
-
expect(Airbrake::Rack::RequestStore.store).to be_empty
|
11
|
-
end
|
12
|
-
|
13
|
-
it "returns the value of the block" do
|
14
|
-
expect(described_class.capture_timing('operation') { 1 }).to eq(1)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
context "when request store has a route" do
|
19
|
-
before do
|
20
|
-
Airbrake::Rack::RequestStore[:routes] = {
|
21
|
-
'/about' => {
|
22
|
-
method: 'GET',
|
23
|
-
response_type: :html,
|
24
|
-
groups: {}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
it "attaches all timings for different operations to the request store" do
|
30
|
-
described_class.capture_timing('operation 1') {}
|
31
|
-
described_class.capture_timing('operation 2') {}
|
32
|
-
described_class.capture_timing('operation 3') {}
|
33
|
-
|
34
|
-
expect(routes['/about'][:groups]).to match(
|
35
|
-
'operation 1' => be > 0,
|
36
|
-
'operation 2' => be > 0,
|
37
|
-
'operation 3' => be > 0
|
38
|
-
)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "returns the value of the block" do
|
42
|
-
expect(described_class.capture_timing('operation') { 1 }).to eq(1)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
RSpec.describe Airbrake::Rack::RequestBodyFilter do
|
2
|
-
def env_for(url, opts = {})
|
3
|
-
Rack::MockRequest.env_for(url, opts)
|
4
|
-
end
|
5
|
-
|
6
|
-
let(:notice) do
|
7
|
-
Airbrake.build_notice('oops').tap do |notice|
|
8
|
-
notice.stash[:rack_request] = Rack::Request.new(env_for(uri, opts))
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
let(:uri) { '/' }
|
13
|
-
let(:opts) do
|
14
|
-
{ 'rack.input' => body }
|
15
|
-
end
|
16
|
-
|
17
|
-
context "when a request has a body" do
|
18
|
-
let(:body) { StringIO.new('<bingo>bongo</bango>') }
|
19
|
-
|
20
|
-
it "reads the body" do
|
21
|
-
subject.call(notice)
|
22
|
-
expect(notice[:environment][:body]).to eq(body.string)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
context "when body was read" do
|
27
|
-
let(:body) { StringIO.new('<bingo>bongo</bango>' * 512) }
|
28
|
-
|
29
|
-
it "rewinds rack.input" do
|
30
|
-
subject.call(notice)
|
31
|
-
expect(body.pos).to be_zero
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "when body is bigger than the limit" do
|
36
|
-
let(:len) { 4097 }
|
37
|
-
let(:body) { StringIO.new('a' * len) }
|
38
|
-
|
39
|
-
it "reads only first 4096 bytes" do
|
40
|
-
subject.call(notice)
|
41
|
-
expect(notice[:environment][:body]).to eq(body.string[0...len - 1])
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|