dfg-airbrake 5.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/airbrake.rb +57 -0
- data/lib/airbrake/capistrano/tasks.rb +65 -0
- data/lib/airbrake/delayed_job/plugin1.rb +52 -0
- data/lib/airbrake/rack/middleware.rb +59 -0
- data/lib/airbrake/rack/notice_builder.rb +125 -0
- data/lib/airbrake/rack/user.rb +61 -0
- data/lib/airbrake/rails/action_controller.rb +37 -0
- data/lib/airbrake/rails/active_job.rb +35 -0
- data/lib/airbrake/rails/active_record.rb +36 -0
- data/lib/airbrake/rails/railtie.rb +79 -0
- data/lib/airbrake/rake/task_ext.rb +66 -0
- data/lib/airbrake/rake/tasks.rb +118 -0
- data/lib/airbrake/resque/failure.rb +19 -0
- data/lib/airbrake/sidekiq/error_handler.rb +35 -0
- data/lib/airbrake/version.rb +6 -0
- data/lib/generators/airbrake_generator.rb +25 -0
- data/lib/generators/airbrake_initializer.rb.erb +68 -0
- data/spec/airbrake_spec.rb +17 -0
- data/spec/apps/rack/dummy_app.rb +17 -0
- data/spec/apps/rails/dummy_app.rb +156 -0
- data/spec/apps/rails/dummy_task.rake +20 -0
- data/spec/apps/sinatra/composite_app/sinatra_app1.rb +11 -0
- data/spec/apps/sinatra/composite_app/sinatra_app2.rb +11 -0
- data/spec/apps/sinatra/dummy_app.rb +12 -0
- data/spec/integration/rack/rack_spec.rb +17 -0
- data/spec/integration/rails/rails_spec.rb +216 -0
- data/spec/integration/rails/rake_spec.rb +160 -0
- data/spec/integration/shared_examples/rack_examples.rb +126 -0
- data/spec/integration/sinatra/sinatra_spec.rb +77 -0
- data/spec/spec_helper.rb +116 -0
- data/spec/unit/rack/middleware_spec.rb +136 -0
- data/spec/unit/rack/notice_builder_spec.rb +157 -0
- data/spec/unit/rack/user_spec.rb +172 -0
- data/spec/unit/rake/tasks_spec.rb +67 -0
- data/spec/unit/sidekiq/error_handler_spec.rb +33 -0
- metadata +247 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Airbrake::Rack::Middleware do
|
4
|
+
let(:app) do
|
5
|
+
proc { |env| [200, env, 'Bingo bango content'] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:faulty_app) do
|
9
|
+
proc { raise AirbrakeTestError }
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:endpoint) do
|
13
|
+
'https://airbrake.io/api/v3/projects/113743/notices?key=fd04e13d806a90f96614ad8e529b2822'
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:middleware) { described_class.new(app) }
|
17
|
+
|
18
|
+
def env_for(url, opts = {})
|
19
|
+
Rack::MockRequest.env_for(url, opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def wait_for_a_request_with_body(body)
|
23
|
+
wait_for(a_request(:post, endpoint).with(body: body)).to have_been_made.once
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
stub_request(:post, endpoint).to_return(status: 201, body: '{}')
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#call" do
|
31
|
+
context "when app raises an exception" do
|
32
|
+
context "and when the notifier name is specified" do
|
33
|
+
let(:notifier_name) { :rack_middleware_initialize }
|
34
|
+
|
35
|
+
let(:bingo_endpoint) do
|
36
|
+
'https://airbrake.io/api/v3/projects/92123/notices?key=ad04e13d806a90f96614ad8e529b2821'
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:expected_body) do
|
40
|
+
/"errors":\[{"type":"AirbrakeTestError"/
|
41
|
+
end
|
42
|
+
|
43
|
+
before do
|
44
|
+
Airbrake.configure(notifier_name) do |c|
|
45
|
+
c.project_id = 92123
|
46
|
+
c.project_key = 'ad04e13d806a90f96614ad8e529b2821'
|
47
|
+
c.logger = Logger.new('/dev/null')
|
48
|
+
c.app_version = '3.2.1'
|
49
|
+
end
|
50
|
+
|
51
|
+
stub_request(:post, bingo_endpoint).to_return(status: 201, body: '{}')
|
52
|
+
end
|
53
|
+
|
54
|
+
after { Airbrake.close(notifier_name) }
|
55
|
+
|
56
|
+
it "notifies via the specified notifier" do
|
57
|
+
expect do
|
58
|
+
described_class.new(faulty_app, notifier_name).call(env_for('/'))
|
59
|
+
end.to raise_error(AirbrakeTestError)
|
60
|
+
|
61
|
+
wait_for(
|
62
|
+
a_request(:post, bingo_endpoint).
|
63
|
+
with(body: expected_body)
|
64
|
+
).to have_been_made.once
|
65
|
+
|
66
|
+
expect(
|
67
|
+
a_request(:post, endpoint).
|
68
|
+
with(body: expected_body)
|
69
|
+
).not_to have_been_made
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "and when the notifier is not configured" do
|
74
|
+
it "rescues the exception, notifies Airbrake & re-raises it" do
|
75
|
+
expect { described_class.new(faulty_app).call(env_for('/')) }.
|
76
|
+
to raise_error(AirbrakeTestError)
|
77
|
+
|
78
|
+
wait_for_a_request_with_body(/"errors":\[{"type":"AirbrakeTestError"/)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "sends framework version and name" do
|
82
|
+
expect { described_class.new(faulty_app).call(env_for('/bingo/bango')) }.
|
83
|
+
to raise_error(AirbrakeTestError)
|
84
|
+
|
85
|
+
wait_for_a_request_with_body(
|
86
|
+
%r("context":{.*"version":"1.2.3 (Rails|Sinatra|Rack\.version)/.+".+})
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when app doesn't raise" do
|
93
|
+
context "and previous middleware stored an exception in env" do
|
94
|
+
shared_examples 'stored exception' do |type|
|
95
|
+
it "notifies on #{type}, but doesn't raise" do
|
96
|
+
env = env_for('/').merge(type => AirbrakeTestError.new)
|
97
|
+
described_class.new(app).call(env)
|
98
|
+
|
99
|
+
wait_for_a_request_with_body(/"errors":\[{"type":"AirbrakeTestError"/)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
['rack.exception', 'action_dispatch.exception', 'sinatra.error'].each do |type|
|
104
|
+
include_examples 'stored exception', type
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "doesn't notify Airbrake" do
|
109
|
+
described_class.new(app).call(env_for('/'))
|
110
|
+
sleep 1
|
111
|
+
expect(a_request(:post, endpoint)).not_to have_been_made
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "returns a response" do
|
116
|
+
response = described_class.new(app).call(env_for('/'))
|
117
|
+
|
118
|
+
expect(response[0]).to eq(200)
|
119
|
+
expect(response[1]).to be_a(Hash)
|
120
|
+
expect(response[2]).to eq('Bingo bango content')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "when Airbrake is not configured" do
|
125
|
+
it "returns nil" do
|
126
|
+
allow(Airbrake).to receive(:build_notice).and_return(nil)
|
127
|
+
allow(Airbrake).to receive(:notify)
|
128
|
+
|
129
|
+
expect { described_class.new(faulty_app).call(env_for('/')) }.
|
130
|
+
to raise_error(AirbrakeTestError)
|
131
|
+
|
132
|
+
expect(Airbrake).to have_received(:build_notice)
|
133
|
+
expect(Airbrake).not_to have_received(:notify)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Airbrake::Rack::NoticeBuilder do
|
4
|
+
def env_for(url, opts = {})
|
5
|
+
Rack::MockRequest.env_for(url, opts)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#build_notice" do
|
9
|
+
it "doesn't overwrite the session key with nil" do
|
10
|
+
notice_builder = described_class.new(env_for('/', 'rack.session' => nil))
|
11
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
12
|
+
|
13
|
+
expect(notice[:session]).to eq({})
|
14
|
+
end
|
15
|
+
|
16
|
+
it "sets session if it is present" do
|
17
|
+
session = { a: 1, b: 2 }
|
18
|
+
notice_builder = described_class.new(env_for('/', 'rack.session' => session))
|
19
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
20
|
+
|
21
|
+
expect(notice[:session]).to eq(session)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "doesn't overwrite the params key with nil" do
|
25
|
+
notice_builder = described_class.new(env_for('/'))
|
26
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
27
|
+
|
28
|
+
expect(notice[:session]).to eq({})
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets form params if they're present" do
|
32
|
+
params = { a: 1, b: 2 }
|
33
|
+
input = StringIO.new
|
34
|
+
|
35
|
+
notice_builder = described_class.new(
|
36
|
+
'rack.request.form_hash' => params,
|
37
|
+
'rack.request.form_input' => input,
|
38
|
+
'rack.input' => input
|
39
|
+
)
|
40
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
41
|
+
|
42
|
+
expect(notice[:params]).to eq(params)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "sets query string params if they're present" do
|
46
|
+
notice_builder = described_class.new(env_for('/?bingo=bango&bongo=bish'))
|
47
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
48
|
+
|
49
|
+
expect(notice[:params]).to eq('bingo' => 'bango', 'bongo' => 'bish')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "adds CONTENT_TYPE, CONTENT_LENGTH and HTTP_* headers in the environment" do
|
53
|
+
headers = {
|
54
|
+
"HTTP_HOST" => "example.com",
|
55
|
+
"CONTENT_TYPE" => "text/html",
|
56
|
+
"CONTENT_LENGTH" => 100500
|
57
|
+
}
|
58
|
+
notice_builder = described_class.new(env_for('/', headers.dup))
|
59
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
60
|
+
expect(notice[:environment][:headers]).to eq(headers)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "skips headers that were not selected to be stored in the environment" do
|
64
|
+
headers = {
|
65
|
+
"HTTP_HOST" => "example.com",
|
66
|
+
"CONTENT_TYPE" => "text/html",
|
67
|
+
"CONTENT_LENGTH" => 100500
|
68
|
+
}
|
69
|
+
notice_builder = described_class.new(
|
70
|
+
env_for('/', headers.merge("X-SOME-HEADER" => "value"))
|
71
|
+
)
|
72
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
73
|
+
|
74
|
+
expect(notice[:environment][:headers]).to eq(headers)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "preserves data that already has been added to the environment" do
|
78
|
+
headers = {
|
79
|
+
"HTTP_HOST" => "example.com",
|
80
|
+
"CONTENT_TYPE" => "text/html",
|
81
|
+
"CONTENT_LENGTH" => 100500
|
82
|
+
}
|
83
|
+
allow(Airbrake).to receive(:build_notice).and_wrap_original do |method, *args|
|
84
|
+
notice = method.call(*args)
|
85
|
+
notice[:environment]["SOME_KEY"] = "SOME_VALUE"
|
86
|
+
notice
|
87
|
+
end
|
88
|
+
notice_builder = described_class.new(env_for('/', headers))
|
89
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
90
|
+
|
91
|
+
expect(notice[:environment]["SOME_KEY"]).to eq("SOME_VALUE")
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when a custom builder is defined" do
|
95
|
+
before do
|
96
|
+
described_class.add_builder do |notice, request|
|
97
|
+
notice[:params][:remoteIp] = request.env['REMOTE_IP']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
after do
|
102
|
+
described_class.instance_variable_get(:@builders).pop
|
103
|
+
end
|
104
|
+
|
105
|
+
it "runs the builder against notices" do
|
106
|
+
notice_builder = described_class.new(env_for('/', 'REMOTE_IP' => '127.0.0.1'))
|
107
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
108
|
+
|
109
|
+
expect(notice[:params][:remoteIp]).to eq("127.0.0.1")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when Airbrake is not configured" do
|
114
|
+
it "returns nil" do
|
115
|
+
allow(Airbrake).to receive(:build_notice).and_return(nil)
|
116
|
+
notice_builder = described_class.new(env_for('/', 'bingo' => 'bango'))
|
117
|
+
|
118
|
+
expect(notice_builder.build_notice('bongo')).to be_nil
|
119
|
+
expect(Airbrake).to have_received(:build_notice)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when a request has a body" do
|
124
|
+
it "reads the body" do
|
125
|
+
body = StringIO.new('<bingo>bongo</bango>')
|
126
|
+
notice_builder = described_class.new(
|
127
|
+
env_for('/', 'rack.input' => body)
|
128
|
+
)
|
129
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
130
|
+
|
131
|
+
expect(notice[:environment][:body]).to eq(body.string)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "rewinds rack.input" do
|
135
|
+
body = StringIO.new('<bingo>bongo</bango>' * 512)
|
136
|
+
notice_builder = described_class.new(
|
137
|
+
env_for('/', 'rack.input' => body)
|
138
|
+
)
|
139
|
+
|
140
|
+
notice_builder.build_notice(AirbrakeTestError.new)
|
141
|
+
|
142
|
+
expect(body.pos).to be_zero
|
143
|
+
end
|
144
|
+
|
145
|
+
it "reads only first 4096 bytes" do
|
146
|
+
len = 4097
|
147
|
+
body = StringIO.new('a' * len)
|
148
|
+
notice_builder = described_class.new(
|
149
|
+
env_for('/', 'rack.input' => body)
|
150
|
+
)
|
151
|
+
notice = notice_builder.build_notice(AirbrakeTestError.new)
|
152
|
+
|
153
|
+
expect(notice[:environment][:body]).to eq(body.string[0...len - 1])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Airbrake::Rack::User do
|
4
|
+
let(:endpoint) do
|
5
|
+
'https://airbrake.io/api/v3/projects/113743/notices?key=fd04e13d806a90f96614ad8e529b2822'
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:user) do
|
9
|
+
OpenStruct.new(
|
10
|
+
id: 1,
|
11
|
+
email: 'qa@example.com',
|
12
|
+
username: 'qa-dept',
|
13
|
+
first_name: 'Bingo',
|
14
|
+
last_name: 'Bongo'
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def env_for(url, opts = {})
|
19
|
+
Rack::MockRequest.env_for(url, opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
stub_request(:post, endpoint).to_return(status: 201, body: '{}')
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".extract" do
|
27
|
+
context "when the Warden authentication framework is present" do
|
28
|
+
it "returns the wrapped user" do
|
29
|
+
warden = instance_double('Warden::Proxy')
|
30
|
+
allow(warden).to receive(:user) { user }
|
31
|
+
|
32
|
+
retval = described_class.extract(env_for('/', 'warden' => warden))
|
33
|
+
expect(retval).to be_a(described_class)
|
34
|
+
end
|
35
|
+
|
36
|
+
context "and the warden user is nil" do
|
37
|
+
it "returns nil" do
|
38
|
+
warden = instance_double('Warden::Proxy')
|
39
|
+
allow(warden).to receive(:user) { nil }
|
40
|
+
|
41
|
+
retval = described_class.extract(env_for('/', 'warden' => warden))
|
42
|
+
expect(retval).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the user was not found" do
|
48
|
+
it "returns nil" do
|
49
|
+
retval = described_class.extract(env_for('/'))
|
50
|
+
expect(retval).to be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when the current_user Rails controller method is defined" do
|
55
|
+
let(:controller) { instance_double('DummyController') }
|
56
|
+
let(:env) { env_for('/', 'action_controller.instance' => controller) }
|
57
|
+
|
58
|
+
context "and it is nil" do
|
59
|
+
it "returns nil" do
|
60
|
+
allow(controller).to receive(:current_user) { nil }
|
61
|
+
|
62
|
+
retval = described_class.extract(env)
|
63
|
+
expect(retval).to be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "and it is not nil" do
|
68
|
+
it "returns the wrapped user" do
|
69
|
+
allow(controller).to receive(:current_user) { user }
|
70
|
+
|
71
|
+
retval = described_class.extract(env)
|
72
|
+
expect(retval).to be_a(described_class)
|
73
|
+
end
|
74
|
+
|
75
|
+
context "but it requires parameters" do
|
76
|
+
let(:controller) { dummy_controller.new }
|
77
|
+
subject { described_class.extract(env) }
|
78
|
+
|
79
|
+
context ": current_user(a)" do
|
80
|
+
let(:dummy_controller) do
|
81
|
+
Class.new do
|
82
|
+
def current_user(_a)
|
83
|
+
"username"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it { should be_nil }
|
89
|
+
end
|
90
|
+
|
91
|
+
context ": current_user(a, b)" do
|
92
|
+
let(:dummy_controller) do
|
93
|
+
Class.new do
|
94
|
+
def current_user(_a, _b)
|
95
|
+
"username"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it { should be_nil }
|
101
|
+
end
|
102
|
+
|
103
|
+
context ": current_user(a, *b)" do
|
104
|
+
let(:dummy_controller) do
|
105
|
+
Class.new do
|
106
|
+
def current_user(_a, *_b)
|
107
|
+
"username"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it { should be_nil }
|
113
|
+
end
|
114
|
+
|
115
|
+
context ": current_user(a, b, *c, &d)" do
|
116
|
+
let(:dummy_controller) do
|
117
|
+
Class.new do
|
118
|
+
def current_user(_a, _b, *_c, &_d)
|
119
|
+
"username"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it { should be_nil }
|
125
|
+
end
|
126
|
+
|
127
|
+
context ": current_user(*a)" do
|
128
|
+
let(:dummy_controller) do
|
129
|
+
Class.new do
|
130
|
+
def current_user(*_a)
|
131
|
+
"username"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it { should be_a(described_class) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#as_json" do
|
144
|
+
context "when Rack user contains all expect fields" do
|
145
|
+
let(:user_data) { described_class.new(user).as_json[:user] }
|
146
|
+
|
147
|
+
it "contains the 'id' key" do
|
148
|
+
expect(user_data).to include(:id)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "contains the 'name' key" do
|
152
|
+
expect(user_data).to include(:name)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "contains the 'username' key" do
|
156
|
+
expect(user_data).to include(:username)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "contains the 'email' key" do
|
160
|
+
expect(user_data).to include(:email)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when Rack user doesn't contain any of the expect fields" do
|
165
|
+
let(:user_data) { described_class.new(OpenStruct.new).as_json }
|
166
|
+
|
167
|
+
it "is empty" do
|
168
|
+
expect(user_data).to be_empty
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|