dfg-airbrake 5.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake.rb +57 -0
  3. data/lib/airbrake/capistrano/tasks.rb +65 -0
  4. data/lib/airbrake/delayed_job/plugin1.rb +52 -0
  5. data/lib/airbrake/rack/middleware.rb +59 -0
  6. data/lib/airbrake/rack/notice_builder.rb +125 -0
  7. data/lib/airbrake/rack/user.rb +61 -0
  8. data/lib/airbrake/rails/action_controller.rb +37 -0
  9. data/lib/airbrake/rails/active_job.rb +35 -0
  10. data/lib/airbrake/rails/active_record.rb +36 -0
  11. data/lib/airbrake/rails/railtie.rb +79 -0
  12. data/lib/airbrake/rake/task_ext.rb +66 -0
  13. data/lib/airbrake/rake/tasks.rb +118 -0
  14. data/lib/airbrake/resque/failure.rb +19 -0
  15. data/lib/airbrake/sidekiq/error_handler.rb +35 -0
  16. data/lib/airbrake/version.rb +6 -0
  17. data/lib/generators/airbrake_generator.rb +25 -0
  18. data/lib/generators/airbrake_initializer.rb.erb +68 -0
  19. data/spec/airbrake_spec.rb +17 -0
  20. data/spec/apps/rack/dummy_app.rb +17 -0
  21. data/spec/apps/rails/dummy_app.rb +156 -0
  22. data/spec/apps/rails/dummy_task.rake +20 -0
  23. data/spec/apps/sinatra/composite_app/sinatra_app1.rb +11 -0
  24. data/spec/apps/sinatra/composite_app/sinatra_app2.rb +11 -0
  25. data/spec/apps/sinatra/dummy_app.rb +12 -0
  26. data/spec/integration/rack/rack_spec.rb +17 -0
  27. data/spec/integration/rails/rails_spec.rb +216 -0
  28. data/spec/integration/rails/rake_spec.rb +160 -0
  29. data/spec/integration/shared_examples/rack_examples.rb +126 -0
  30. data/spec/integration/sinatra/sinatra_spec.rb +77 -0
  31. data/spec/spec_helper.rb +116 -0
  32. data/spec/unit/rack/middleware_spec.rb +136 -0
  33. data/spec/unit/rack/notice_builder_spec.rb +157 -0
  34. data/spec/unit/rack/user_spec.rb +172 -0
  35. data/spec/unit/rake/tasks_spec.rb +67 -0
  36. data/spec/unit/sidekiq/error_handler_spec.rb +33 -0
  37. 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