appsignal 2.11.0.alpha.2-java → 2.11.0.beta.5-java

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +94 -10
  4. data/CHANGELOG.md +31 -1
  5. data/README.md +4 -4
  6. data/Rakefile +16 -4
  7. data/appsignal.gemspec +1 -1
  8. data/build_matrix.yml +7 -3
  9. data/ext/Rakefile +2 -0
  10. data/ext/agent.yml +19 -19
  11. data/ext/base.rb +7 -0
  12. data/ext/extconf.rb +2 -0
  13. data/gemfiles/rails-4.2.gemfile +9 -2
  14. data/gemfiles/rails-5.0.gemfile +1 -0
  15. data/gemfiles/rails-5.1.gemfile +1 -0
  16. data/gemfiles/rails-5.2.gemfile +1 -0
  17. data/gemfiles/rails-6.0.gemfile +1 -0
  18. data/gemfiles/resque-1.gemfile +7 -0
  19. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  20. data/lib/appsignal.rb +1 -0
  21. data/lib/appsignal/auth_check.rb +4 -2
  22. data/lib/appsignal/cli/diagnose.rb +1 -1
  23. data/lib/appsignal/config.rb +35 -2
  24. data/lib/appsignal/extension.rb +6 -5
  25. data/lib/appsignal/extension/jruby.rb +6 -5
  26. data/lib/appsignal/hooks.rb +25 -0
  27. data/lib/appsignal/hooks/active_job.rb +137 -0
  28. data/lib/appsignal/hooks/puma.rb +0 -1
  29. data/lib/appsignal/hooks/resque.rb +60 -0
  30. data/lib/appsignal/hooks/sidekiq.rb +17 -94
  31. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  32. data/lib/appsignal/integrations/que.rb +1 -1
  33. data/lib/appsignal/integrations/resque.rb +9 -12
  34. data/lib/appsignal/integrations/resque_active_job.rb +9 -32
  35. data/lib/appsignal/probes.rb +7 -0
  36. data/lib/appsignal/probes/puma.rb +1 -1
  37. data/lib/appsignal/probes/sidekiq.rb +3 -1
  38. data/lib/appsignal/transaction.rb +10 -0
  39. data/lib/appsignal/utils/deprecation_message.rb +6 -2
  40. data/lib/appsignal/version.rb +1 -1
  41. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  42. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  43. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  44. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  45. data/spec/lib/appsignal/config_spec.rb +21 -0
  46. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  47. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  48. data/spec/lib/appsignal/hooks/activejob_spec.rb +591 -0
  49. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  50. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  51. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +222 -268
  52. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  53. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  54. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  55. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  56. data/spec/lib/appsignal/marker_spec.rb +1 -1
  57. data/spec/lib/appsignal/probes/sidekiq_spec.rb +10 -7
  58. data/spec/lib/appsignal/transaction_spec.rb +5 -7
  59. data/spec/spec_helper.rb +5 -0
  60. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  61. data/spec/support/helpers/config_helpers.rb +3 -2
  62. data/spec/support/helpers/dependency_helper.rb +9 -2
  63. data/spec/support/helpers/transaction_helpers.rb +6 -0
  64. data/spec/support/stubs/sidekiq/api.rb +1 -1
  65. data/spec/support/testing.rb +19 -19
  66. metadata +16 -4
@@ -227,25 +227,14 @@ describe Appsignal::Hooks::DelayedJobHook do
227
227
  end
228
228
  end
229
229
 
230
- context "without job name" do
231
- let(:job_data) do
232
- { :name => "", :payload_object => payload_object }
233
- end
234
-
235
- it "wraps it in a transaction using the class method job name" do
236
- perform
237
- expect(last_transaction.to_h["action"]).to eql("unknown")
238
- end
239
- end
240
-
241
- context "with invalid job name" do
230
+ context "with only job class name" do
242
231
  let(:job_data) do
243
232
  { :name => "Banana", :payload_object => payload_object }
244
233
  end
245
234
 
246
- it "wraps it in a transaction using the class method job name" do
235
+ it "appends #perform to the class name" do
247
236
  perform
248
- expect(last_transaction.to_h["action"]).to eql("unknown")
237
+ expect(last_transaction.to_h["action"]).to eql("Banana#perform")
249
238
  end
250
239
  end
251
240
 
@@ -0,0 +1,185 @@
1
+ describe Appsignal::Hooks::ResqueHook do
2
+ describe "#dependency_present?" do
3
+ subject { described_class.new.dependencies_present? }
4
+
5
+ context "when Resque is loaded" do
6
+ before { stub_const "Resque", 1 }
7
+
8
+ it { is_expected.to be_truthy }
9
+ end
10
+
11
+ context "when Resque is not loaded" do
12
+ before { hide_const "Resque" }
13
+
14
+ it { is_expected.to be_falsy }
15
+ end
16
+ end
17
+
18
+ if DependencyHelper.resque_present?
19
+ describe "#install" do
20
+ def perform_job(klass, options = {})
21
+ payload = { "class" => klass.to_s }.merge(options)
22
+ job = ::Resque::Job.new(queue, payload)
23
+ keep_transactions { job.perform }
24
+ end
25
+
26
+ let(:queue) { "default" }
27
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
28
+ before do
29
+ start_agent
30
+
31
+ class ResqueTestJob
32
+ def self.perform(*_args)
33
+ end
34
+ end
35
+
36
+ class ResqueErrorTestJob
37
+ def self.perform
38
+ raise "resque job error"
39
+ end
40
+ end
41
+
42
+ expect(Appsignal).to receive(:stop) # Resque calls stop after every job
43
+ end
44
+ around do |example|
45
+ keep_transactions { example.run }
46
+ end
47
+ after do
48
+ Object.send(:remove_const, :ResqueTestJob)
49
+ Object.send(:remove_const, :ResqueErrorTestJob)
50
+ end
51
+
52
+ it "tracks a transaction on perform" do
53
+ perform_job(ResqueTestJob)
54
+
55
+ transaction = last_transaction
56
+ transaction_hash = transaction.to_h
57
+ expect(transaction_hash).to include(
58
+ "id" => kind_of(String),
59
+ "action" => "ResqueTestJob#perform",
60
+ "error" => nil,
61
+ "namespace" => namespace,
62
+ "metadata" => {},
63
+ "sample_data" => { "tags" => { "queue" => queue } }
64
+ )
65
+ expect(transaction_hash["events"].map { |e| e["name"] })
66
+ .to eql(["perform.resque"])
67
+ end
68
+
69
+ context "with error" do
70
+ it "tracks the error on the transaction" do
71
+ expect do
72
+ perform_job(ResqueErrorTestJob)
73
+ end.to raise_error(RuntimeError, "resque job error")
74
+
75
+ transaction = last_transaction
76
+ transaction_hash = transaction.to_h
77
+ expect(transaction_hash).to include(
78
+ "id" => kind_of(String),
79
+ "action" => "ResqueErrorTestJob#perform",
80
+ "error" => {
81
+ "name" => "RuntimeError",
82
+ "message" => "resque job error",
83
+ "backtrace" => kind_of(String)
84
+ },
85
+ "namespace" => namespace,
86
+ "metadata" => {},
87
+ "sample_data" => { "tags" => { "queue" => queue } }
88
+ )
89
+ end
90
+ end
91
+
92
+ context "with arguments" do
93
+ before do
94
+ Appsignal.config = project_fixture_config("production")
95
+ Appsignal.config[:filter_parameters] = ["foo"]
96
+ end
97
+
98
+ it "filters out configured arguments" do
99
+ perform_job(
100
+ ResqueTestJob,
101
+ "args" => [
102
+ "foo",
103
+ {
104
+ "foo" => "secret",
105
+ "bar" => "Bar",
106
+ "baz" => { "1" => "foo" }
107
+ }
108
+ ]
109
+ )
110
+
111
+ transaction = last_transaction
112
+ transaction_hash = transaction.to_h
113
+ expect(transaction_hash).to include(
114
+ "id" => kind_of(String),
115
+ "action" => "ResqueTestJob#perform",
116
+ "error" => nil,
117
+ "namespace" => namespace,
118
+ "metadata" => {},
119
+ "sample_data" => {
120
+ "tags" => { "queue" => queue },
121
+ "params" => [
122
+ "foo",
123
+ {
124
+ "foo" => "[FILTERED]",
125
+ "bar" => "Bar",
126
+ "baz" => { "1" => "foo" }
127
+ }
128
+ ]
129
+ }
130
+ )
131
+ end
132
+ end
133
+
134
+ context "with active job" do
135
+ before do
136
+ module ActiveJobMock
137
+ module QueueAdapters
138
+ module ResqueAdapter
139
+ module JobWrapper
140
+ class << self
141
+ def perform(job_data)
142
+ # Basic ActiveJob stub for this test.
143
+ # I haven't found a way to run Resque in a testing mode.
144
+ Appsignal.set_action(job_data["job_class"])
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ stub_const "ActiveJob", ActiveJobMock
153
+ end
154
+ after { Object.send(:remove_const, :ActiveJobMock) }
155
+
156
+ it "does not set arguments but lets the ActiveJob intergration handle it" do
157
+ perform_job(
158
+ ResqueTestJob,
159
+ "class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
160
+ "args" => [
161
+ {
162
+ "job_class" => "ResqueTestJobByActiveJob#perform",
163
+ "arguments" => ["an argument", "second argument"]
164
+ }
165
+ ]
166
+ )
167
+
168
+ transaction = last_transaction
169
+ transaction_hash = transaction.to_h
170
+ expect(transaction_hash).to include(
171
+ "id" => kind_of(String),
172
+ "action" => "ResqueTestJobByActiveJob#perform",
173
+ "error" => nil,
174
+ "namespace" => namespace,
175
+ "metadata" => {},
176
+ "sample_data" => {
177
+ "tags" => { "queue" => queue }
178
+ # Params will be set by the ActiveJob integration
179
+ }
180
+ )
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,3 +1,54 @@
1
+ describe Appsignal::Hooks::SidekiqHook do
2
+ describe "#dependencies_present?" do
3
+ subject { described_class.new.dependencies_present? }
4
+
5
+ context "when Sidekiq constant is found" do
6
+ before { stub_const "Sidekiq", Class.new }
7
+
8
+ it { is_expected.to be_truthy }
9
+ end
10
+
11
+ context "when Sidekiq constant is not found" do
12
+ before { hide_const "Sidekiq" }
13
+
14
+ it { is_expected.to be_falsy }
15
+ end
16
+ end
17
+
18
+ describe "#install" do
19
+ class SidekiqMiddlewareMock < Set
20
+ def exists?(middleware)
21
+ include?(middleware)
22
+ end
23
+ end
24
+ module SidekiqMock
25
+ def self.middlewares
26
+ @middlewares ||= SidekiqMiddlewareMock.new
27
+ end
28
+
29
+ def self.configure_server
30
+ yield self
31
+ end
32
+
33
+ def self.server_middleware
34
+ yield middlewares if block_given?
35
+ middlewares
36
+ end
37
+ end
38
+
39
+ before do
40
+ Appsignal.config = project_fixture_config
41
+ stub_const "Sidekiq", SidekiqMock
42
+ end
43
+
44
+ it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
45
+ described_class.new.install
46
+
47
+ expect(Sidekiq.server_middleware.exists?(Appsignal::Hooks::SidekiqPlugin)).to be(true)
48
+ end
49
+ end
50
+ end
51
+
1
52
  describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
2
53
  class DelayedTestClass; end
3
54
 
@@ -25,9 +76,10 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
25
76
  ]
26
77
  end
27
78
  let(:job_class) { "TestClass" }
79
+ let(:jid) { "b4a577edbccf1d805744efa9" }
28
80
  let(:item) do
29
81
  {
30
- "jid" => "b4a577edbccf1d805744efa9",
82
+ "jid" => jid,
31
83
  "class" => job_class,
32
84
  "retry_count" => 0,
33
85
  "queue" => "default",
@@ -48,37 +100,13 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
48
100
  expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
49
101
  end
50
102
 
51
- shared_examples "unknown job action name" do
52
- it "sets the action name to unknown" do
53
- transaction_hash = transaction.to_h
54
- expect(transaction_hash).to include("action" => "unknown")
55
- end
56
-
57
- it "stores no sample data" do
58
- transaction_hash = transaction.to_h
59
- expect(transaction_hash).to include(
60
- "sample_data" => {
61
- "environment" => {},
62
- "params" => [],
63
- "tags" => {}
64
- }
65
- )
66
- end
67
-
68
- it "logs a debug message" do
69
- expect(log_contents(log)).to contains_log(
70
- :debug, "Unable to determine an action name from Sidekiq payload: #{item}"
71
- )
72
- end
73
- end
74
-
75
103
  describe "internal Sidekiq job values" do
76
104
  it "does not save internal Sidekiq values as metadata on transaction" do
77
105
  perform_job
78
106
 
79
107
  transaction_hash = transaction.to_h
80
108
  expect(transaction_hash["metadata"].keys)
81
- .to_not include(*Appsignal::Hooks::SidekiqPlugin::JOB_KEYS)
109
+ .to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
82
110
  end
83
111
  end
84
112
 
@@ -124,7 +152,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
124
152
  context "when using the Sidekiq delayed extension" do
125
153
  let(:item) do
126
154
  {
127
- "jid" => "efb140489485999d32b5504c",
155
+ "jid" => jid,
128
156
  "class" => "Sidekiq::Extensions::DelayedClass",
129
157
  "queue" => "default",
130
158
  "args" => [
@@ -164,7 +192,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
164
192
  context "when using the Sidekiq ActiveRecord instance delayed extension" do
165
193
  let(:item) do
166
194
  {
167
- "jid" => "efb140489485999d32b5504c",
195
+ "jid" => jid,
168
196
  "class" => "Sidekiq::Extensions::DelayedModel",
169
197
  "queue" => "default",
170
198
  "args" => [
@@ -201,217 +229,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
201
229
  end
202
230
  end
203
231
 
204
- context "when using ActiveJob" do
205
- let(:item) do
206
- {
207
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
208
- "wrapped" => "ActiveJobTestClass",
209
- "queue" => "default",
210
- "args" => [{
211
- "job_class" => "ActiveJobTestJob",
212
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
213
- "queue_name" => "default",
214
- "arguments" => [
215
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
216
- ]
217
- }],
218
- "retry" => true,
219
- "jid" => "efb140489485999d32b5504c",
220
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
221
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
222
- }
223
- end
224
-
225
- it "creates a transaction with events" do
226
- perform_job
227
-
228
- transaction_hash = transaction.to_h
229
- expect(transaction_hash).to include(
230
- "id" => kind_of(String),
231
- "action" => "ActiveJobTestClass#perform",
232
- "error" => nil,
233
- "namespace" => namespace,
234
- "metadata" => {
235
- "queue" => "default"
236
- },
237
- "sample_data" => {
238
- "environment" => {},
239
- "params" => [
240
- "foo",
241
- {
242
- "foo" => "Foo",
243
- "bar" => "Bar",
244
- "baz" => { "1" => "bar" }
245
- }
246
- ],
247
- "tags" => {}
248
- }
249
- )
250
- # TODO: Not available in transaction.to_h yet.
251
- # https://github.com/appsignal/appsignal-agent/issues/293
252
- expect(transaction.request.env).to eq(
253
- :queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
254
- )
255
- expect_transaction_to_have_sidekiq_event(transaction_hash)
256
- end
257
-
258
- context "with ActionMailer job" do
259
- let(:item) do
260
- {
261
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
262
- "wrapped" => "ActionMailer::DeliveryJob",
263
- "queue" => "default",
264
- "args" => [{
265
- "job_class" => "ActiveMailerTestJob",
266
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
267
- "queue_name" => "default",
268
- "arguments" => [
269
- "MailerClass", "mailer_method", "deliver_now",
270
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
271
- ]
272
- }],
273
- "retry" => true,
274
- "jid" => "efb140489485999d32b5504c",
275
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
276
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
277
- }
278
- end
279
-
280
- it "creates a transaction for the ActionMailer class" do
281
- perform_job
282
-
283
- transaction_hash = transaction.to_h
284
- expect(transaction_hash).to include(
285
- "id" => kind_of(String),
286
- "action" => "MailerClass#mailer_method",
287
- "error" => nil,
288
- "namespace" => namespace,
289
- "metadata" => {
290
- "queue" => "default"
291
- },
292
- "sample_data" => {
293
- "environment" => {},
294
- "params" => [
295
- "foo",
296
- {
297
- "foo" => "Foo",
298
- "bar" => "Bar",
299
- "baz" => { "1" => "bar" }
300
- }
301
- ],
302
- "tags" => {}
303
- }
304
- )
305
- end
306
- end
307
-
308
- context "with parameter filtering" do
309
- before do
310
- Appsignal.config = project_fixture_config("production")
311
- Appsignal.config[:filter_parameters] = ["foo"]
312
- end
313
-
314
- it "filters selected arguments" do
315
- perform_job
316
-
317
- transaction_hash = transaction.to_h
318
- expect(transaction_hash["sample_data"]).to include(
319
- "params" => [
320
- "foo",
321
- {
322
- "foo" => "[FILTERED]",
323
- "bar" => "Bar",
324
- "baz" => { "1" => "bar" }
325
- }
326
- ]
327
- )
328
- end
329
- end
330
-
331
- context "when Sidekiq job payload is missing the 'wrapped' value" do
332
- let(:item) do
333
- {
334
- "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
335
- "queue" => "default",
336
- "args" => [first_argument],
337
- "retry" => true,
338
- "jid" => "efb140489485999d32b5504c",
339
- "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
340
- "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
341
- }
342
- end
343
- before { perform_job }
344
-
345
- context "when the first argument is not a Hash object" do
346
- let(:first_argument) { "foo" }
347
-
348
- include_examples "unknown job action name"
349
- end
350
-
351
- context "when the first argument is a Hash object not containing a job payload" do
352
- let(:first_argument) { { "foo" => "bar" } }
353
-
354
- include_examples "unknown job action name"
355
-
356
- context "when the argument contains an invalid job_class value" do
357
- let(:first_argument) { { "job_class" => :foo } }
358
-
359
- include_examples "unknown job action name"
360
- end
361
- end
362
-
363
- context "when the first argument is a Hash object containing a job payload" do
364
- let(:first_argument) do
365
- {
366
- "job_class" => "ActiveMailerTestJob",
367
- "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
368
- "queue_name" => "default",
369
- "arguments" => [
370
- "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
371
- ]
372
- }
373
- end
374
-
375
- it "sets the action name to the job class in the first argument" do
376
- transaction_hash = transaction.to_h
377
- expect(transaction_hash).to include(
378
- "action" => "ActiveMailerTestJob#perform"
379
- )
380
- end
381
-
382
- it "stores the job metadata on the transaction" do
383
- transaction_hash = transaction.to_h
384
- expect(transaction_hash).to include(
385
- "id" => kind_of(String),
386
- "error" => nil,
387
- "namespace" => namespace,
388
- "metadata" => {
389
- "queue" => "default"
390
- },
391
- "sample_data" => {
392
- "environment" => {},
393
- "params" => [
394
- "foo",
395
- {
396
- "foo" => "Foo",
397
- "bar" => "Bar",
398
- "baz" => { "1" => "bar" }
399
- }
400
- ],
401
- "tags" => {}
402
- }
403
- )
404
- end
405
-
406
- it "does not log a debug message" do
407
- expect(log_contents(log)).to_not contains_log(
408
- :debug, "Unable to determine an action name from Sidekiq payload"
409
- )
410
- end
411
- end
412
- end
413
- end
414
-
415
232
  context "with an error" do
416
233
  let(:error) { ExampleException }
417
234
 
@@ -427,7 +244,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
427
244
 
428
245
  transaction_hash = transaction.to_h
429
246
  expect(transaction_hash).to include(
430
- "id" => kind_of(String),
247
+ "id" => jid,
431
248
  "action" => "TestClass#perform",
432
249
  "error" => {
433
250
  "name" => "ExampleException",
@@ -461,7 +278,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
461
278
 
462
279
  transaction_hash = transaction.to_h
463
280
  expect(transaction_hash).to include(
464
- "id" => kind_of(String),
281
+ "id" => jid,
465
282
  "action" => "TestClass#perform",
466
283
  "error" => nil,
467
284
  "metadata" => {
@@ -510,47 +327,184 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
510
327
  end
511
328
  end
512
329
 
513
- describe Appsignal::Hooks::SidekiqHook do
514
- describe "#dependencies_present?" do
515
- subject { described_class.new.dependencies_present? }
330
+ if DependencyHelper.active_job_present?
331
+ require "active_job"
332
+ require "action_mailer"
333
+ require "sidekiq/testing"
334
+
335
+ describe "Sidekiq ActiveJob integration" do
336
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
337
+ let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
338
+ let(:log) { StringIO.new }
339
+ let(:given_args) do
340
+ [
341
+ "foo",
342
+ {
343
+ :foo => "Foo",
344
+ "bar" => "Bar",
345
+ "baz" => { "1" => "foo" }
346
+ }
347
+ ]
348
+ end
349
+ let(:expected_args) do
350
+ [
351
+ "foo",
352
+ {
353
+ "_aj_symbol_keys" => ["foo"],
354
+ "foo" => "Foo",
355
+ "bar" => "Bar",
356
+ "baz" => {
357
+ "_aj_symbol_keys" => [],
358
+ "1" => "foo"
359
+ }
360
+ }
361
+ ]
362
+ end
363
+ let(:expected_tags) do
364
+ {}.tap do |hash|
365
+ hash["active_job_id"] = kind_of(String)
366
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
367
+ hash["provider_job_id"] = kind_of(String)
368
+ end
369
+ end
370
+ end
371
+ before do
372
+ start_agent
373
+ Appsignal.logger = test_logger(log)
374
+ ActiveJob::Base.queue_adapter = :sidekiq
516
375
 
517
- context "when Sidekiq constant is found" do
518
- before { Object.const_set("Sidekiq", 1) }
519
- after { Object.send(:remove_const, "Sidekiq") }
376
+ class ActiveJobSidekiqTestJob < ActiveJob::Base
377
+ self.queue_adapter = :sidekiq
520
378
 
521
- it { is_expected.to be_truthy }
379
+ def perform(*_args)
380
+ end
381
+ end
382
+
383
+ class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
384
+ self.queue_adapter = :sidekiq
385
+
386
+ def perform(*_args)
387
+ raise "uh oh"
388
+ end
389
+ end
390
+ # Manually add the AppSignal middleware for the Testing environment.
391
+ # It doesn't use configured middlewares by default looks like.
392
+ # We test somewhere else if the middleware is installed properly.
393
+ Sidekiq::Testing.server_middleware do |chain|
394
+ chain.add Appsignal::Hooks::SidekiqPlugin
395
+ end
396
+ end
397
+ around do |example|
398
+ keep_transactions do
399
+ Sidekiq::Testing.fake! do
400
+ example.run
401
+ end
402
+ end
403
+ end
404
+ after do
405
+ Object.send(:remove_const, :ActiveJobSidekiqTestJob)
406
+ Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
522
407
  end
523
408
 
524
- context "when Sidekiq constant is not found" do
525
- before { Object.send(:remove_const, "Sidekiq") if defined?(Sidekiq) }
409
+ it "reports the transaction from the ActiveJob integration" do
410
+ perform_job(ActiveJobSidekiqTestJob, given_args)
526
411
 
527
- it { is_expected.to be_falsy }
412
+ transaction = last_transaction
413
+ transaction_hash = transaction.to_h
414
+ expect(transaction_hash).to include(
415
+ "action" => "ActiveJobSidekiqTestJob#perform",
416
+ "error" => nil,
417
+ "namespace" => namespace,
418
+ "metadata" => hash_including(
419
+ "queue" => "default"
420
+ ),
421
+ "sample_data" => hash_including(
422
+ "environment" => {},
423
+ "params" => [expected_args],
424
+ "tags" => expected_tags.merge("queue" => "default")
425
+ )
426
+ )
427
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
428
+ events = transaction_hash["events"]
429
+ .sort_by { |e| e["start"] }
430
+ .map { |event| event["name"] }
431
+ expect(events)
432
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
528
433
  end
529
- end
530
434
 
531
- describe "#install" do
532
- before do
533
- Appsignal.config = project_fixture_config
534
- module Sidekiq
535
- def self.middlewares
536
- @middlewares ||= Set.new
537
- end
435
+ context "with error" do
436
+ it "reports the error on the transaction from the ActiveRecord integration" do
437
+ expect do
438
+ perform_job(ActiveJobSidekiqErrorTestJob, given_args)
439
+ end.to raise_error(RuntimeError, "uh oh")
538
440
 
539
- def self.configure_server
540
- yield self
541
- end
441
+ transaction = last_transaction
442
+ transaction_hash = transaction.to_h
443
+ expect(transaction_hash).to include(
444
+ "action" => "ActiveJobSidekiqErrorTestJob#perform",
445
+ "error" => {
446
+ "name" => "RuntimeError",
447
+ "message" => "uh oh",
448
+ "backtrace" => kind_of(String)
449
+ },
450
+ "namespace" => namespace,
451
+ "metadata" => hash_including(
452
+ "queue" => "default"
453
+ ),
454
+ "sample_data" => hash_including(
455
+ "environment" => {},
456
+ "params" => [expected_args],
457
+ "tags" => expected_tags.merge("queue" => "default")
458
+ )
459
+ )
460
+ expect(transaction.request.env).to eq(:queue_start => time.to_f)
461
+ events = transaction_hash["events"]
462
+ .sort_by { |e| e["start"] }
463
+ .map { |event| event["name"] }
464
+ expect(events)
465
+ .to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
466
+ end
467
+ end
468
+
469
+ context "with ActionMailer" do
470
+ include ActionMailerHelpers
542
471
 
543
- def self.server_middleware
544
- yield middlewares
472
+ before do
473
+ class ActionMailerSidekiqTestJob < ActionMailer::Base
474
+ def welcome(*args)
475
+ end
545
476
  end
546
477
  end
478
+
479
+ it "reports ActionMailer data on the transaction" do
480
+ perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
481
+
482
+ transaction = last_transaction
483
+ transaction_hash = transaction.to_h
484
+ expect(transaction_hash).to include(
485
+ "action" => "ActionMailerSidekiqTestJob#welcome",
486
+ "sample_data" => hash_including(
487
+ "params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
488
+ )
489
+ )
490
+ end
547
491
  end
548
- after { Object.send(:remove_const, "Sidekiq") }
549
492
 
550
- it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
551
- described_class.new.install
493
+ def perform_sidekiq
494
+ Timecop.freeze(time) do
495
+ yield
496
+ # Combined with Sidekiq::Testing.fake! and drain_all we get a
497
+ # enqueue_at in the job data.
498
+ Sidekiq::Worker.drain_all
499
+ end
500
+ end
501
+
502
+ def perform_job(job_class, args)
503
+ perform_sidekiq { job_class.perform_later(args) }
504
+ end
552
505
 
553
- expect(Sidekiq.middlewares).to include(Appsignal::Hooks::SidekiqPlugin)
506
+ def perform_mailer(mailer, method, args = nil)
507
+ perform_sidekiq { perform_action_mailer(mailer, method, args) }
554
508
  end
555
509
  end
556
510
  end