appsignal 2.5.0.alpha.1-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 (211) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +66 -0
  5. data/.rubocop_todo.yml +124 -0
  6. data/.travis.yml +72 -0
  7. data/.yardopts +8 -0
  8. data/CHANGELOG.md +639 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +264 -0
  12. data/Rakefile +214 -0
  13. data/appsignal.gemspec +42 -0
  14. data/benchmark.rake +77 -0
  15. data/bin/appsignal +13 -0
  16. data/ext/Rakefile +27 -0
  17. data/ext/agent.yml +64 -0
  18. data/ext/appsignal_extension.c +692 -0
  19. data/ext/base.rb +79 -0
  20. data/ext/extconf.rb +35 -0
  21. data/gemfiles/capistrano2.gemfile +7 -0
  22. data/gemfiles/capistrano3.gemfile +7 -0
  23. data/gemfiles/grape.gemfile +7 -0
  24. data/gemfiles/no_dependencies.gemfile +5 -0
  25. data/gemfiles/padrino.gemfile +7 -0
  26. data/gemfiles/que.gemfile +5 -0
  27. data/gemfiles/rails-3.2.gemfile +6 -0
  28. data/gemfiles/rails-4.0.gemfile +6 -0
  29. data/gemfiles/rails-4.1.gemfile +6 -0
  30. data/gemfiles/rails-4.2.gemfile +10 -0
  31. data/gemfiles/rails-5.0.gemfile +5 -0
  32. data/gemfiles/rails-5.1.gemfile +5 -0
  33. data/gemfiles/resque.gemfile +12 -0
  34. data/gemfiles/sequel-435.gemfile +11 -0
  35. data/gemfiles/sequel.gemfile +11 -0
  36. data/gemfiles/sinatra.gemfile +6 -0
  37. data/gemfiles/webmachine.gemfile +5 -0
  38. data/lib/appsignal.rb +804 -0
  39. data/lib/appsignal/auth_check.rb +65 -0
  40. data/lib/appsignal/capistrano.rb +10 -0
  41. data/lib/appsignal/cli.rb +108 -0
  42. data/lib/appsignal/cli/demo.rb +63 -0
  43. data/lib/appsignal/cli/diagnose.rb +500 -0
  44. data/lib/appsignal/cli/helpers.rb +72 -0
  45. data/lib/appsignal/cli/install.rb +277 -0
  46. data/lib/appsignal/cli/notify_of_deploy.rb +113 -0
  47. data/lib/appsignal/config.rb +287 -0
  48. data/lib/appsignal/demo.rb +107 -0
  49. data/lib/appsignal/event_formatter.rb +74 -0
  50. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +24 -0
  51. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +14 -0
  52. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +14 -0
  53. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +32 -0
  54. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +19 -0
  55. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +89 -0
  56. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  57. data/lib/appsignal/extension.rb +63 -0
  58. data/lib/appsignal/extension/jruby.rb +460 -0
  59. data/lib/appsignal/garbage_collection_profiler.rb +48 -0
  60. data/lib/appsignal/hooks.rb +105 -0
  61. data/lib/appsignal/hooks/action_cable.rb +113 -0
  62. data/lib/appsignal/hooks/active_support_notifications.rb +52 -0
  63. data/lib/appsignal/hooks/celluloid.rb +30 -0
  64. data/lib/appsignal/hooks/data_mapper.rb +18 -0
  65. data/lib/appsignal/hooks/delayed_job.rb +19 -0
  66. data/lib/appsignal/hooks/mongo_ruby_driver.rb +21 -0
  67. data/lib/appsignal/hooks/net_http.rb +29 -0
  68. data/lib/appsignal/hooks/passenger.rb +22 -0
  69. data/lib/appsignal/hooks/puma.rb +35 -0
  70. data/lib/appsignal/hooks/que.rb +21 -0
  71. data/lib/appsignal/hooks/rake.rb +39 -0
  72. data/lib/appsignal/hooks/redis.rb +30 -0
  73. data/lib/appsignal/hooks/sequel.rb +60 -0
  74. data/lib/appsignal/hooks/shoryuken.rb +43 -0
  75. data/lib/appsignal/hooks/sidekiq.rb +144 -0
  76. data/lib/appsignal/hooks/unicorn.rb +40 -0
  77. data/lib/appsignal/hooks/webmachine.rb +23 -0
  78. data/lib/appsignal/integrations/capistrano/appsignal.cap +39 -0
  79. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +52 -0
  80. data/lib/appsignal/integrations/data_mapper.rb +33 -0
  81. data/lib/appsignal/integrations/delayed_job_plugin.rb +54 -0
  82. data/lib/appsignal/integrations/grape.rb +53 -0
  83. data/lib/appsignal/integrations/mongo_ruby_driver.rb +55 -0
  84. data/lib/appsignal/integrations/object.rb +35 -0
  85. data/lib/appsignal/integrations/padrino.rb +84 -0
  86. data/lib/appsignal/integrations/que.rb +43 -0
  87. data/lib/appsignal/integrations/railtie.rb +41 -0
  88. data/lib/appsignal/integrations/rake.rb +2 -0
  89. data/lib/appsignal/integrations/resque.rb +20 -0
  90. data/lib/appsignal/integrations/resque_active_job.rb +30 -0
  91. data/lib/appsignal/integrations/sinatra.rb +17 -0
  92. data/lib/appsignal/integrations/webmachine.rb +38 -0
  93. data/lib/appsignal/js_exception_transaction.rb +54 -0
  94. data/lib/appsignal/marker.rb +63 -0
  95. data/lib/appsignal/minutely.rb +42 -0
  96. data/lib/appsignal/rack/generic_instrumentation.rb +49 -0
  97. data/lib/appsignal/rack/js_exception_catcher.rb +70 -0
  98. data/lib/appsignal/rack/rails_instrumentation.rb +51 -0
  99. data/lib/appsignal/rack/sinatra_instrumentation.rb +99 -0
  100. data/lib/appsignal/rack/streaming_listener.rb +73 -0
  101. data/lib/appsignal/system.rb +81 -0
  102. data/lib/appsignal/transaction.rb +498 -0
  103. data/lib/appsignal/transmitter.rb +107 -0
  104. data/lib/appsignal/utils.rb +127 -0
  105. data/lib/appsignal/utils/params_sanitizer.rb +59 -0
  106. data/lib/appsignal/utils/query_params_sanitizer.rb +55 -0
  107. data/lib/appsignal/version.rb +3 -0
  108. data/lib/sequel/extensions/appsignal_integration.rb +3 -0
  109. data/resources/appsignal.yml.erb +39 -0
  110. data/resources/cacert.pem +3866 -0
  111. data/spec/.rubocop.yml +7 -0
  112. data/spec/lib/appsignal/auth_check_spec.rb +80 -0
  113. data/spec/lib/appsignal/capistrano2_spec.rb +224 -0
  114. data/spec/lib/appsignal/capistrano3_spec.rb +237 -0
  115. data/spec/lib/appsignal/cli/demo_spec.rb +67 -0
  116. data/spec/lib/appsignal/cli/diagnose_spec.rb +988 -0
  117. data/spec/lib/appsignal/cli/helpers_spec.rb +171 -0
  118. data/spec/lib/appsignal/cli/install_spec.rb +632 -0
  119. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +168 -0
  120. data/spec/lib/appsignal/cli_spec.rb +56 -0
  121. data/spec/lib/appsignal/config_spec.rb +637 -0
  122. data/spec/lib/appsignal/demo_spec.rb +87 -0
  123. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +44 -0
  124. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +21 -0
  125. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +21 -0
  126. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +52 -0
  127. data/spec/lib/appsignal/event_formatter/faraday/request_formatter_spec.rb +21 -0
  128. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +113 -0
  129. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +112 -0
  130. data/spec/lib/appsignal/event_formatter_spec.rb +100 -0
  131. data/spec/lib/appsignal/extension/jruby_spec.rb +43 -0
  132. data/spec/lib/appsignal/extension_spec.rb +137 -0
  133. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +66 -0
  134. data/spec/lib/appsignal/hooks/action_cable_spec.rb +370 -0
  135. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +92 -0
  136. data/spec/lib/appsignal/hooks/celluloid_spec.rb +35 -0
  137. data/spec/lib/appsignal/hooks/data_mapper_spec.rb +39 -0
  138. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +358 -0
  139. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +44 -0
  140. data/spec/lib/appsignal/hooks/net_http_spec.rb +53 -0
  141. data/spec/lib/appsignal/hooks/passenger_spec.rb +30 -0
  142. data/spec/lib/appsignal/hooks/puma_spec.rb +80 -0
  143. data/spec/lib/appsignal/hooks/que_spec.rb +19 -0
  144. data/spec/lib/appsignal/hooks/rake_spec.rb +73 -0
  145. data/spec/lib/appsignal/hooks/redis_spec.rb +55 -0
  146. data/spec/lib/appsignal/hooks/sequel_spec.rb +46 -0
  147. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +192 -0
  148. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +419 -0
  149. data/spec/lib/appsignal/hooks/unicorn_spec.rb +52 -0
  150. data/spec/lib/appsignal/hooks/webmachine_spec.rb +35 -0
  151. data/spec/lib/appsignal/hooks_spec.rb +195 -0
  152. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +65 -0
  153. data/spec/lib/appsignal/integrations/grape_spec.rb +225 -0
  154. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +127 -0
  155. data/spec/lib/appsignal/integrations/object_spec.rb +249 -0
  156. data/spec/lib/appsignal/integrations/padrino_spec.rb +323 -0
  157. data/spec/lib/appsignal/integrations/que_spec.rb +174 -0
  158. data/spec/lib/appsignal/integrations/railtie_spec.rb +129 -0
  159. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +83 -0
  160. data/spec/lib/appsignal/integrations/resque_spec.rb +92 -0
  161. data/spec/lib/appsignal/integrations/sinatra_spec.rb +73 -0
  162. data/spec/lib/appsignal/integrations/webmachine_spec.rb +69 -0
  163. data/spec/lib/appsignal/js_exception_transaction_spec.rb +128 -0
  164. data/spec/lib/appsignal/marker_spec.rb +51 -0
  165. data/spec/lib/appsignal/minutely_spec.rb +50 -0
  166. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +90 -0
  167. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +147 -0
  168. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +117 -0
  169. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +213 -0
  170. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +161 -0
  171. data/spec/lib/appsignal/system_spec.rb +131 -0
  172. data/spec/lib/appsignal/transaction_spec.rb +1146 -0
  173. data/spec/lib/appsignal/transmitter_spec.rb +152 -0
  174. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +136 -0
  175. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +192 -0
  176. data/spec/lib/appsignal/utils_spec.rb +150 -0
  177. data/spec/lib/appsignal_spec.rb +1049 -0
  178. data/spec/spec_helper.rb +116 -0
  179. data/spec/support/fixtures/containers/cgroups/docker +14 -0
  180. data/spec/support/fixtures/containers/cgroups/docker_systemd +8 -0
  181. data/spec/support/fixtures/containers/cgroups/lxc +10 -0
  182. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  183. data/spec/support/fixtures/containers/cgroups/none +1 -0
  184. data/spec/support/fixtures/generated_config.yml +24 -0
  185. data/spec/support/fixtures/uploaded_file.txt +0 -0
  186. data/spec/support/helpers/api_request_helper.rb +19 -0
  187. data/spec/support/helpers/cli_helpers.rb +26 -0
  188. data/spec/support/helpers/config_helpers.rb +21 -0
  189. data/spec/support/helpers/dependency_helper.rb +73 -0
  190. data/spec/support/helpers/directory_helper.rb +27 -0
  191. data/spec/support/helpers/env_helpers.rb +33 -0
  192. data/spec/support/helpers/example_exception.rb +13 -0
  193. data/spec/support/helpers/example_standard_error.rb +13 -0
  194. data/spec/support/helpers/log_helpers.rb +22 -0
  195. data/spec/support/helpers/std_streams_helper.rb +66 -0
  196. data/spec/support/helpers/system_helpers.rb +8 -0
  197. data/spec/support/helpers/time_helpers.rb +11 -0
  198. data/spec/support/helpers/transaction_helpers.rb +37 -0
  199. data/spec/support/matchers/contains_log.rb +7 -0
  200. data/spec/support/mocks/fake_gc_profiler.rb +19 -0
  201. data/spec/support/mocks/mock_extension.rb +6 -0
  202. data/spec/support/project_fixture/config/application.rb +0 -0
  203. data/spec/support/project_fixture/config/appsignal.yml +32 -0
  204. data/spec/support/project_fixture/config/environments/development.rb +0 -0
  205. data/spec/support/project_fixture/config/environments/production.rb +0 -0
  206. data/spec/support/project_fixture/config/environments/test.rb +0 -0
  207. data/spec/support/project_fixture/log/.gitkeep +0 -0
  208. data/spec/support/rails/my_app.rb +6 -0
  209. data/spec/support/shared_examples/instrument.rb +43 -0
  210. data/spec/support/stubs/delayed_job.rb +0 -0
  211. metadata +483 -0
@@ -0,0 +1,161 @@
1
+ require "appsignal/rack/streaming_listener"
2
+
3
+ describe Appsignal::Rack::StreamingListener do
4
+ let(:headers) { {} }
5
+ let(:env) do
6
+ {
7
+ "rack.input" => StringIO.new,
8
+ "REQUEST_METHOD" => "GET",
9
+ "PATH_INFO" => "/homepage",
10
+ "QUERY_STRING" => "param=something"
11
+ }
12
+ end
13
+ let(:app) { double(:call => [200, headers, "body"]) }
14
+ let(:listener) { Appsignal::Rack::StreamingListener.new(app, {}) }
15
+
16
+ describe "#call" do
17
+ context "when Appsignal is active" do
18
+ before { allow(Appsignal).to receive(:active?).and_return(true) }
19
+
20
+ it "should call `call_with_appsignal_monitoring`" do
21
+ expect(listener).to receive(:call_with_appsignal_monitoring)
22
+ end
23
+ end
24
+
25
+ context "when Appsignal is not active" do
26
+ before { allow(Appsignal).to receive(:active?).and_return(false) }
27
+
28
+ it "should not call `call_with_appsignal_monitoring`" do
29
+ expect(listener).to_not receive(:call_with_appsignal_monitoring)
30
+ end
31
+ end
32
+
33
+ after { listener.call(env) }
34
+ end
35
+
36
+ describe "#call_with_appsignal_monitoring" do
37
+ let!(:transaction) do
38
+ Appsignal::Transaction.create(
39
+ SecureRandom.uuid,
40
+ Appsignal::Transaction::HTTP_REQUEST,
41
+ ::Rack::Request.new(env)
42
+ )
43
+ end
44
+ let(:wrapper) { Appsignal::StreamWrapper.new("body", transaction) }
45
+ let(:raw_payload) { { :foo => :bar } }
46
+
47
+ before do
48
+ allow(SecureRandom).to receive(:uuid).and_return("123")
49
+ allow(listener).to receive(:raw_payload).and_return(raw_payload)
50
+ allow(Appsignal::Transaction).to receive(:create).and_return(transaction)
51
+ end
52
+
53
+ it "should create a transaction" do
54
+ expect(Appsignal::Transaction).to receive(:create)
55
+ .with("123", Appsignal::Transaction::HTTP_REQUEST, instance_of(Rack::Request))
56
+ .and_return(transaction)
57
+
58
+ listener.call_with_appsignal_monitoring(env)
59
+ end
60
+
61
+ it "should instrument the call" do
62
+ expect(Appsignal).to receive(:instrument)
63
+ .with("process_action.rack")
64
+ .and_yield
65
+
66
+ listener.call_with_appsignal_monitoring(env)
67
+ end
68
+
69
+ it "should add `appsignal.action` to the transaction" do
70
+ allow(Appsignal).to receive(:instrument).and_yield
71
+
72
+ env["appsignal.action"] = "Action"
73
+
74
+ expect(transaction).to receive(:set_action_if_nil).with("Action")
75
+
76
+ listener.call_with_appsignal_monitoring(env)
77
+ end
78
+
79
+ it "should add the path, method and queue start to the transaction" do
80
+ allow(Appsignal).to receive(:instrument).and_yield
81
+
82
+ expect(transaction).to receive(:set_metadata).with("path", "/homepage")
83
+ expect(transaction).to receive(:set_metadata).with("method", "GET")
84
+ expect(transaction).to receive(:set_http_or_background_queue_start)
85
+
86
+ listener.call_with_appsignal_monitoring(env)
87
+ end
88
+
89
+ context "with an exception in the instrumentation call" do
90
+ let(:error) { ExampleException }
91
+
92
+ it "should add the exception to the transaction" do
93
+ allow(app).to receive(:call).and_raise(error)
94
+
95
+ expect(transaction).to receive(:set_error).with(error)
96
+
97
+ expect do
98
+ listener.call_with_appsignal_monitoring(env)
99
+ end.to raise_error(error)
100
+ end
101
+ end
102
+
103
+ it "should wrap the body in a wrapper" do
104
+ expect(Appsignal::StreamWrapper).to receive(:new)
105
+ .with("body", transaction)
106
+ .and_return(wrapper)
107
+
108
+ body = listener.call_with_appsignal_monitoring(env)[2]
109
+
110
+ expect(body).to be_a(Appsignal::StreamWrapper)
111
+ end
112
+ end
113
+ end
114
+
115
+ describe Appsignal::StreamWrapper do
116
+ let(:stream) { double }
117
+ let(:transaction) { Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {}) }
118
+ let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
119
+
120
+ describe "#each" do
121
+ it "calls the original stream" do
122
+ expect(stream).to receive(:each)
123
+
124
+ wrapper.each
125
+ end
126
+
127
+ context "when #each raises an error" do
128
+ let(:error) { ExampleException }
129
+
130
+ it "records the exception" do
131
+ allow(stream).to receive(:each).and_raise(error)
132
+
133
+ expect(transaction).to receive(:set_error).with(error)
134
+
135
+ expect { wrapper.send(:each) }.to raise_error(error)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "#close" do
141
+ it "closes the original stream and completes the transaction" do
142
+ expect(stream).to receive(:close)
143
+ expect(Appsignal::Transaction).to receive(:complete_current!)
144
+
145
+ wrapper.close
146
+ end
147
+
148
+ context "when #close raises an error" do
149
+ let(:error) { ExampleException }
150
+
151
+ it "records the exception and completes the transaction" do
152
+ allow(stream).to receive(:close).and_raise(error)
153
+
154
+ expect(transaction).to receive(:set_error).with(error)
155
+ expect(transaction).to receive(:complete)
156
+
157
+ expect { wrapper.send(:close) }.to raise_error(error)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,131 @@
1
+ require "appsignal/system"
2
+
3
+ describe Appsignal::System do
4
+ describe ".heroku?" do
5
+ subject { described_class.heroku? }
6
+
7
+ context "when on Heroku" do
8
+ around { |example| recognize_as_heroku { example.run } }
9
+
10
+ it "returns true" do
11
+ is_expected.to eq(true)
12
+ end
13
+ end
14
+
15
+ context "when not on Heroku" do
16
+ it "returns false" do
17
+ is_expected.to eq(false)
18
+ end
19
+ end
20
+ end
21
+
22
+ describe ".installed_agent_architecture" do
23
+ let(:const_name) { "GEM_EXT_PATH".freeze }
24
+ let(:tmp_ext_dir) { File.join(tmp_dir, "ext") }
25
+ let(:architecture_file) { File.join(Appsignal::System::GEM_EXT_PATH, "appsignal.architecture") }
26
+ around do |example|
27
+ original_gem_ext_path = Appsignal::System.const_get(const_name)
28
+ Appsignal::System.send(:remove_const, const_name)
29
+ Appsignal::System.const_set(const_name, tmp_ext_dir)
30
+ example.run
31
+ Appsignal::System.send(:remove_const, const_name)
32
+ Appsignal::System.const_set(const_name, original_gem_ext_path)
33
+ end
34
+ after { FileUtils.rm_rf(tmp_ext_dir) }
35
+ subject { described_class.installed_agent_architecture }
36
+
37
+ context "with an ext/appsignal.architecture file" do
38
+ before do
39
+ FileUtils.mkdir_p(Appsignal::System::GEM_EXT_PATH)
40
+ File.open(architecture_file, "w") do |file|
41
+ file.write "foo"
42
+ end
43
+ end
44
+
45
+ it "returns the contents of the file" do
46
+ expect(subject).to eq("foo")
47
+ end
48
+ end
49
+
50
+ context "without an ext/appsignal.architecture file" do
51
+ it "returns nil" do
52
+ expect(subject).to be_nil
53
+ end
54
+ end
55
+ end
56
+
57
+ describe ".agent_platform" do
58
+ let(:os) { "linux-gnu" }
59
+ let(:ldd_output) { "" }
60
+ before do
61
+ allow(described_class).to receive(:ldd_version_output).and_return(ldd_output)
62
+ allow(RbConfig::CONFIG).to receive(:[])
63
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(os)
64
+ end
65
+ subject { described_class.agent_platform }
66
+
67
+ context "when the system detection doesn't work" do
68
+ it "returns the libc build" do
69
+ is_expected.to eq("linux")
70
+ end
71
+ end
72
+
73
+ context "when using the APPSIGNAL_BUILD_FOR_MUSL env var" do
74
+ it "returns the musl build" do
75
+ ENV["APPSIGNAL_BUILD_FOR_MUSL"] = "1"
76
+ is_expected.to eq("linux-musl")
77
+ ENV.delete("APPSIGNAL_BUILD_FOR_MUSL")
78
+ end
79
+ end
80
+
81
+ context "when on a musl system" do
82
+ let(:ldd_output) { "musl libc (x86_64)\nVersion 1.1.16" }
83
+
84
+ it "returns the musl build" do
85
+ is_expected.to eq("linux-musl")
86
+ end
87
+ end
88
+
89
+ context "when on a libc system" do
90
+ let(:ldd_output) { "ldd (Debian GLIBC 2.15-18+deb8u7) 2.15" }
91
+
92
+ it "returns the libc build" do
93
+ is_expected.to eq("linux")
94
+ end
95
+
96
+ context "when on an old libc system" do
97
+ let(:ldd_output) { "ldd (Debian GLIBC 2.14-18+deb8u7) 2.14" }
98
+
99
+ it "returns the musl build" do
100
+ is_expected.to eq("linux-musl")
101
+ end
102
+ end
103
+
104
+ context "when on a very old libc system" do
105
+ let(:ldd_output) { "ldd (Debian GLIBC 2.5-18+deb8u7) 2.5" }
106
+
107
+ it "returns the musl build" do
108
+ is_expected.to eq("linux-musl")
109
+ end
110
+ end
111
+ end
112
+
113
+ context "when on macOS" do
114
+ let(:os) { "darwin16.7.0" }
115
+ let(:ldd_output) { "ldd: command not found" }
116
+
117
+ it "returns the darwin build" do
118
+ is_expected.to eq("darwin")
119
+ end
120
+ end
121
+
122
+ context "when on FreeBSD" do
123
+ let(:os) { "freebsd11" }
124
+ let(:ldd_output) { "ldd: illegal option -- -" }
125
+
126
+ it "returns the darwin build" do
127
+ is_expected.to eq("freebsd")
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,1146 @@
1
+ describe Appsignal::Transaction do
2
+ before :context do
3
+ start_agent
4
+ end
5
+
6
+ let(:transaction_id) { "1" }
7
+ let(:time) { Time.at(fixed_time) }
8
+ let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
9
+ let(:env) { {} }
10
+ let(:merged_env) { http_request_env_with_data(env) }
11
+ let(:options) { {} }
12
+ let(:request) { Rack::Request.new(merged_env) }
13
+ let(:transaction) { Appsignal::Transaction.new(transaction_id, namespace, request, options) }
14
+ let(:log) { StringIO.new }
15
+
16
+ before { Timecop.freeze(time) }
17
+ after { Timecop.return }
18
+ around do |example|
19
+ use_logger_with log do
20
+ example.run
21
+ end
22
+ end
23
+
24
+ describe "class methods" do
25
+ def current_transaction
26
+ Appsignal::Transaction.current
27
+ end
28
+
29
+ describe ".create" do
30
+ def create_transaction(id = transaction_id)
31
+ Appsignal::Transaction.create(id, namespace, request, options)
32
+ end
33
+
34
+ context "when no transaction is running" do
35
+ let!(:transaction) { create_transaction }
36
+
37
+ it "returns the created transaction" do
38
+ expect(transaction).to be_a Appsignal::Transaction
39
+ expect(transaction.transaction_id).to eq transaction_id
40
+ expect(transaction.namespace).to eq namespace
41
+ expect(transaction.request).to eq request
42
+
43
+ expect(transaction.to_h).to include(
44
+ "id" => transaction_id,
45
+ "namespace" => namespace
46
+ )
47
+ end
48
+
49
+ it "assigns the transaction to current" do
50
+ expect(transaction).to eq current_transaction
51
+ end
52
+ end
53
+
54
+ context "when a transaction is already running" do
55
+ before { create_transaction }
56
+
57
+ it "does not create a new transaction, but returns the current transaction" do
58
+ expect do
59
+ new_transaction = create_transaction("2")
60
+ expect(new_transaction).to eq(current_transaction)
61
+ expect(new_transaction.transaction_id).to eq(transaction_id)
62
+ end.to_not(change { current_transaction })
63
+ end
64
+
65
+ it "logs a debug message" do
66
+ create_transaction("2")
67
+ expect(log_contents(log)).to contains_log :debug,
68
+ "Trying to start new transaction with id '2', but a " \
69
+ "transaction with id '#{transaction_id}' is already " \
70
+ "running. Using transaction '#{transaction_id}'."
71
+ end
72
+
73
+ context "with option :force => true" do
74
+ it "returns the newly created (and current) transaction" do
75
+ original_transaction = current_transaction
76
+ expect(original_transaction).to_not be_nil
77
+ expect(current_transaction.transaction_id).to eq transaction_id
78
+
79
+ options[:force] = true
80
+ expect(create_transaction("2")).to_not eq original_transaction
81
+ expect(current_transaction.transaction_id).to eq "2"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe ".current" do
88
+ subject { Appsignal::Transaction.current }
89
+
90
+ context "when there is a current transaction" do
91
+ let!(:transaction) do
92
+ Appsignal::Transaction.create(transaction_id, namespace, request, options)
93
+ end
94
+
95
+ it "reads :appsignal_transaction from the current Thread" do
96
+ expect(subject).to eq Thread.current[:appsignal_transaction]
97
+ expect(subject).to eq transaction
98
+ end
99
+
100
+ it "is not a NilTransaction" do
101
+ expect(subject.nil_transaction?).to eq false
102
+ expect(subject).to be_a Appsignal::Transaction
103
+ end
104
+ end
105
+
106
+ context "when there is no current transaction" do
107
+ it "has no :appsignal_transaction registered on the current Thread" do
108
+ expect(Thread.current[:appsignal_transaction]).to be_nil
109
+ end
110
+
111
+ it "returns a NilTransaction stub" do
112
+ expect(subject.nil_transaction?).to eq true
113
+ expect(subject).to be_a Appsignal::Transaction::NilTransaction
114
+ end
115
+ end
116
+ end
117
+
118
+ describe ".complete_current!" do
119
+ let!(:transaction) { Appsignal::Transaction.create(transaction_id, namespace, options) }
120
+
121
+ it "completes the current transaction" do
122
+ expect(transaction).to eq current_transaction
123
+ expect(transaction).to receive(:complete).and_call_original
124
+
125
+ Appsignal::Transaction.complete_current!
126
+ end
127
+
128
+ it "unsets the current transaction on the current Thread" do
129
+ expect do
130
+ Appsignal::Transaction.complete_current!
131
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
132
+ end
133
+
134
+ context "when encountering an error while completing" do
135
+ before do
136
+ expect(transaction).to receive(:complete).and_raise ExampleStandardError
137
+ end
138
+
139
+ it "logs an error message" do
140
+ Appsignal::Transaction.complete_current!
141
+ expect(log_contents(log)).to contains_log :error,
142
+ "Failed to complete transaction ##{transaction.transaction_id}. ExampleStandardError"
143
+ end
144
+
145
+ it "clears the current transaction" do
146
+ expect do
147
+ Appsignal::Transaction.complete_current!
148
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#complete" do
155
+ context "when transaction is being sampled" do
156
+ it "samples data" do
157
+ expect(transaction.ext).to receive(:finish).and_return(true)
158
+ # Stub call to extension, because that would remove the transaction
159
+ # from the extension.
160
+ expect(transaction.ext).to receive(:complete)
161
+
162
+ transaction.set_tags(:foo => "bar")
163
+ transaction.complete
164
+ expect(transaction.to_h["sample_data"]).to include(
165
+ "tags" => { "foo" => "bar" }
166
+ )
167
+ end
168
+ end
169
+
170
+ context "when transaction is not being sampled" do
171
+ it "does not sample data" do
172
+ expect(transaction).to_not receive(:sample_data)
173
+ expect(transaction.ext).to receive(:finish).and_return(false)
174
+ expect(transaction.ext).to receive(:complete).and_call_original
175
+
176
+ transaction.complete
177
+ end
178
+ end
179
+
180
+ context "when a transaction is marked as discarded" do
181
+ it "does not complete the transaction" do
182
+ expect(transaction.ext).to_not receive(:complete)
183
+
184
+ expect do
185
+ transaction.discard!
186
+ end.to change { transaction.discarded? }.from(false).to(true)
187
+
188
+ transaction.complete
189
+ end
190
+
191
+ it "logs a debug message" do
192
+ transaction.discard!
193
+ transaction.complete
194
+
195
+ expect(log_contents(log)).to contains_log :debug,
196
+ "Skipping transaction '#{transaction_id}' because it was manually discarded."
197
+ end
198
+
199
+ context "when a discarded transaction is restored" do
200
+ before { transaction.discard! }
201
+
202
+ it "completes the transaction" do
203
+ expect(transaction.ext).to receive(:complete).and_call_original
204
+
205
+ expect do
206
+ transaction.restore!
207
+ end.to change { transaction.discarded? }.from(true).to(false)
208
+
209
+ transaction.complete
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ context "pausing" do
216
+ describe "#pause!" do
217
+ it "should change the pause flag to true" do
218
+ expect do
219
+ transaction.pause!
220
+ end.to change(transaction, :paused).from(false).to(true)
221
+ end
222
+ end
223
+
224
+ describe "#resume!" do
225
+ before { transaction.pause! }
226
+
227
+ it "should change the pause flag to false" do
228
+ expect do
229
+ transaction.resume!
230
+ end.to change(transaction, :paused).from(true).to(false)
231
+ end
232
+ end
233
+
234
+ describe "#paused?" do
235
+ it "should return the pause state" do
236
+ expect(transaction.paused?).to be_falsy
237
+ end
238
+
239
+ context "when paused" do
240
+ before { transaction.pause! }
241
+
242
+ it "should return the pause state" do
243
+ expect(transaction.paused?).to be_truthy
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ context "with transaction instance" do
250
+ context "initialization" do
251
+ it "loads the AppSignal extension" do
252
+ expect(transaction.ext).to_not be_nil
253
+ end
254
+
255
+ it "sets the transaction id" do
256
+ expect(transaction.transaction_id).to eq "1"
257
+ end
258
+
259
+ it "sets the namespace to http_request" do
260
+ expect(transaction.namespace).to eq "http_request"
261
+ end
262
+
263
+ it "sets the request" do
264
+ expect(transaction.request).to_not be_nil
265
+ end
266
+
267
+ it "sets the request not to paused" do
268
+ expect(transaction.paused).to be_falsy
269
+ end
270
+
271
+ it "sets no tags by default" do
272
+ expect(transaction.tags).to eq({})
273
+ end
274
+
275
+ describe "#options" do
276
+ subject { transaction.options }
277
+
278
+ it "sets the default :params_method" do
279
+ expect(subject[:params_method]).to eq :params
280
+ end
281
+
282
+ context "with overridden options" do
283
+ let(:options) { { :params_method => :filtered_params } }
284
+
285
+ it "sets the overriden :params_method" do
286
+ expect(subject[:params_method]).to eq :filtered_params
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ describe "#store" do
293
+ it "should return an empty store when it's not already present" do
294
+ expect(transaction.store("test")).to eql({})
295
+ end
296
+
297
+ it "should store changes to the store" do
298
+ transaction_store = transaction.store("test")
299
+ transaction_store["transaction"] = "value"
300
+
301
+ expect(transaction.store("test")).to eql("transaction" => "value")
302
+ end
303
+ end
304
+
305
+ describe "#params" do
306
+ subject { transaction.params }
307
+
308
+ context "with custom params set on transaction" do
309
+ before do
310
+ transaction.params = { :foo => "bar" }
311
+ end
312
+
313
+ it "returns custom parameters" do
314
+ expect(subject).to eq(:foo => "bar")
315
+ end
316
+ end
317
+
318
+ context "without custom params set on transaction" do
319
+ it "returns parameters from request" do
320
+ expect(subject).to eq(
321
+ "action" => "show",
322
+ "controller" => "blog_posts",
323
+ "id" => "1"
324
+ )
325
+ end
326
+ end
327
+ end
328
+
329
+ describe "#params=" do
330
+ it "sets params on the transaction" do
331
+ transaction.params = { :foo => "bar" }
332
+ expect(transaction.params).to eq(:foo => "bar")
333
+ end
334
+ end
335
+
336
+ describe "#set_tags" do
337
+ it "should add tags to transaction" do
338
+ expect do
339
+ transaction.set_tags("a" => "b")
340
+ end.to change(transaction, :tags).to("a" => "b")
341
+ end
342
+ end
343
+
344
+ describe "set_action" do
345
+ it "should set the action in extension" do
346
+ expect(transaction.ext).to receive(:set_action).with(
347
+ "PagesController#show"
348
+ ).once
349
+
350
+ transaction.set_action("PagesController#show")
351
+
352
+ expect(transaction.action).to eq "PagesController#show"
353
+ end
354
+
355
+ it "should not set the action in extension when value is nil" do
356
+ expect(Appsignal::Extension).to_not receive(:set_action)
357
+
358
+ transaction.set_action(nil)
359
+ end
360
+ end
361
+
362
+ describe "set_action_if_nil" do
363
+ context "if action is currently nil" do
364
+ it "should set the action" do
365
+ expect(transaction.ext).to receive(:set_action).with(
366
+ "PagesController#show"
367
+ ).once
368
+
369
+ transaction.set_action_if_nil("PagesController#show")
370
+ end
371
+ end
372
+
373
+ context "if action is currently set" do
374
+ it "should not set the action" do
375
+ transaction.set_action("something")
376
+
377
+ expect(transaction.ext).not_to receive(:set_action)
378
+
379
+ transaction.set_action_if_nil("PagesController#show")
380
+ end
381
+ end
382
+ end
383
+
384
+ describe "set_namespace" do
385
+ it "should set the action in extension" do
386
+ expect(transaction.ext).to receive(:set_namespace).with(
387
+ "custom"
388
+ ).once
389
+
390
+ transaction.set_namespace("custom")
391
+
392
+ expect(transaction.namespace).to eq "custom"
393
+ end
394
+
395
+ it "should not set the action in extension when value is nil" do
396
+ expect(Appsignal::Extension).to_not receive(:set_namespace)
397
+
398
+ transaction.set_action(nil)
399
+ end
400
+ end
401
+
402
+ describe "#set_http_or_background_action" do
403
+ context "for a hash with controller and action" do
404
+ let(:from) { { :controller => "HomeController", :action => "show" } }
405
+
406
+ it "should set the action" do
407
+ expect(transaction).to receive(:set_action).with("HomeController#show")
408
+ end
409
+ end
410
+
411
+ context "for a hash with just action" do
412
+ let(:from) { { :action => "show" } }
413
+
414
+ it "should set the action" do
415
+ expect(transaction).to receive(:set_action).with("show")
416
+ end
417
+ end
418
+
419
+ context "for a hash with class and method" do
420
+ let(:from) { { :class => "Worker", :method => "perform" } }
421
+
422
+ it "should set the action" do
423
+ expect(transaction).to receive(:set_action).with("Worker#perform")
424
+ end
425
+ end
426
+
427
+ after { transaction.set_http_or_background_action(from) }
428
+ end
429
+
430
+ describe "set_queue_start" do
431
+ it "should set the queue start in extension" do
432
+ expect(transaction.ext).to receive(:set_queue_start).with(
433
+ 10.0
434
+ ).once
435
+
436
+ transaction.set_queue_start(10.0)
437
+ end
438
+
439
+ it "should not set the queue start in extension when value is nil" do
440
+ expect(transaction.ext).to_not receive(:set_queue_start)
441
+
442
+ transaction.set_queue_start(nil)
443
+ end
444
+
445
+ it "should not raise an error when the queue start is too big" do
446
+ expect(transaction.ext).to receive(:set_queue_start).and_raise(RangeError)
447
+
448
+ expect(Appsignal.logger).to receive(:warn).with("Queue start value 10 is too big")
449
+
450
+ expect do
451
+ transaction.set_queue_start(10)
452
+ end.to_not raise_error
453
+ end
454
+ end
455
+
456
+ describe "#set_http_or_background_queue_start" do
457
+ context "for a http transaction" do
458
+ let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
459
+ let(:env) { { "HTTP_X_REQUEST_START" => (fixed_time * 1000).to_s } }
460
+
461
+ it "should set the queue start on the transaction" do
462
+ expect(transaction).to receive(:set_queue_start).with(13_897_836_000)
463
+
464
+ transaction.set_http_or_background_queue_start
465
+ end
466
+ end
467
+
468
+ context "for a background transaction" do
469
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
470
+ let(:env) { { :queue_start => fixed_time } }
471
+
472
+ it "should set the queue start on the transaction" do
473
+ expect(transaction).to receive(:set_queue_start).with(1_389_783_600_000)
474
+
475
+ transaction.set_http_or_background_queue_start
476
+ end
477
+ end
478
+ end
479
+
480
+ describe "#set_metadata" do
481
+ it "should set the metdata in extension" do
482
+ expect(transaction.ext).to receive(:set_metadata).with(
483
+ "request_method",
484
+ "GET"
485
+ ).once
486
+
487
+ transaction.set_metadata("request_method", "GET")
488
+ end
489
+
490
+ it "should not set the metdata in extension when value is nil" do
491
+ expect(transaction.ext).to_not receive(:set_metadata)
492
+
493
+ transaction.set_metadata("request_method", nil)
494
+ end
495
+ end
496
+
497
+ describe "set_sample_data" do
498
+ it "should set the data" do
499
+ expect(transaction.ext).to receive(:set_sample_data).with(
500
+ "params",
501
+ Appsignal::Utils.data_generate("controller" => "blog_posts", "action" => "show", "id" => "1")
502
+ ).once
503
+
504
+ transaction.set_sample_data(
505
+ "params",
506
+ :controller => "blog_posts",
507
+ :action => "show",
508
+ :id => "1"
509
+ )
510
+ end
511
+
512
+ it "should do nothing if the data cannot be converted to json" do
513
+ expect(transaction.ext).to_not receive(:set_sample_data).with(
514
+ "params",
515
+ kind_of(String)
516
+ )
517
+
518
+ transaction.set_sample_data("params", "string")
519
+ end
520
+ end
521
+
522
+ describe "#sample_data" do
523
+ it "should sample data" do
524
+ expect(transaction.ext).to receive(:set_sample_data).with(
525
+ "environment",
526
+ Appsignal::Utils.data_generate(
527
+ "CONTENT_LENGTH" => "0",
528
+ "REQUEST_METHOD" => "GET",
529
+ "SERVER_NAME" => "example.org",
530
+ "SERVER_PORT" => "80",
531
+ "PATH_INFO" => "/blog"
532
+ )
533
+ ).once
534
+ expect(transaction.ext).to receive(:set_sample_data).with(
535
+ "session_data",
536
+ Appsignal::Utils.data_generate({})
537
+ ).once
538
+ expect(transaction.ext).to receive(:set_sample_data).with(
539
+ "params",
540
+ Appsignal::Utils.data_generate("controller" => "blog_posts", "action" => "show", "id" => "1")
541
+ ).once
542
+ expect(transaction.ext).to receive(:set_sample_data).with(
543
+ "metadata",
544
+ Appsignal::Utils.data_generate("key" => "value")
545
+ ).once
546
+ expect(transaction.ext).to receive(:set_sample_data).with(
547
+ "tags",
548
+ Appsignal::Utils.data_generate({})
549
+ ).once
550
+
551
+ transaction.sample_data
552
+ end
553
+ end
554
+
555
+ describe "#set_error" do
556
+ let(:env) { http_request_env_with_data }
557
+ let(:error) { double(:error, :message => "test message", :backtrace => ["line 1"]) }
558
+
559
+ it "should also respond to add_exception for backwords compatibility" do
560
+ expect(transaction).to respond_to(:add_exception)
561
+ end
562
+
563
+ it "should not add the error if appsignal is not active" do
564
+ allow(Appsignal).to receive(:active?).and_return(false)
565
+ expect(transaction.ext).to_not receive(:set_error)
566
+
567
+ transaction.set_error(error)
568
+ end
569
+
570
+ context "for a http request" do
571
+ it "should set an error in the extension" do
572
+ expect(transaction.ext).to receive(:set_error).with(
573
+ "RSpec::Mocks::Double",
574
+ "test message",
575
+ Appsignal::Utils.data_generate(["line 1"])
576
+ )
577
+
578
+ transaction.set_error(error)
579
+ end
580
+ end
581
+
582
+ context "when error message is nil" do
583
+ let(:error) { double(:error, :message => nil, :backtrace => ["line 1"]) }
584
+
585
+ it "should not raise an error" do
586
+ expect { transaction.set_error(error) }.to_not raise_error
587
+ end
588
+
589
+ it "should set an error in the extension" do
590
+ expect(transaction.ext).to receive(:set_error).with(
591
+ "RSpec::Mocks::Double",
592
+ "",
593
+ Appsignal::Utils.data_generate(["line 1"])
594
+ )
595
+
596
+ transaction.set_error(error)
597
+ end
598
+ end
599
+ end
600
+
601
+ describe "#start_event" do
602
+ it "should start the event in the extension" do
603
+ expect(transaction.ext).to receive(:start_event).with(0).and_call_original
604
+
605
+ transaction.start_event
606
+ end
607
+ end
608
+
609
+ describe "#finish_event" do
610
+ let(:fake_gc_time) { 123 }
611
+ before do
612
+ expect(described_class.garbage_collection_profiler)
613
+ .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
614
+ end
615
+
616
+ it "should finish the event in the extension" do
617
+ expect(transaction.ext).to receive(:finish_event).with(
618
+ "name",
619
+ "title",
620
+ "body",
621
+ 1,
622
+ fake_gc_time
623
+ ).and_call_original
624
+
625
+ transaction.finish_event(
626
+ "name",
627
+ "title",
628
+ "body",
629
+ 1
630
+ )
631
+ end
632
+
633
+ it "should finish the event in the extension with nil arguments" do
634
+ expect(transaction.ext).to receive(:finish_event).with(
635
+ "name",
636
+ "",
637
+ "",
638
+ 0,
639
+ fake_gc_time
640
+ ).and_call_original
641
+
642
+ transaction.finish_event(
643
+ "name",
644
+ nil,
645
+ nil,
646
+ nil
647
+ )
648
+ end
649
+ end
650
+
651
+ describe "#record_event" do
652
+ let(:fake_gc_time) { 123 }
653
+ before do
654
+ expect(described_class.garbage_collection_profiler)
655
+ .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
656
+ end
657
+
658
+ it "should record the event in the extension" do
659
+ expect(transaction.ext).to receive(:record_event).with(
660
+ "name",
661
+ "title",
662
+ "body",
663
+ 1,
664
+ 1000,
665
+ fake_gc_time
666
+ ).and_call_original
667
+
668
+ transaction.record_event(
669
+ "name",
670
+ "title",
671
+ "body",
672
+ 1000,
673
+ 1
674
+ )
675
+ end
676
+
677
+ it "should finish the event in the extension with nil arguments" do
678
+ expect(transaction.ext).to receive(:record_event).with(
679
+ "name",
680
+ "",
681
+ "",
682
+ 0,
683
+ 1000,
684
+ fake_gc_time
685
+ ).and_call_original
686
+
687
+ transaction.record_event(
688
+ "name",
689
+ nil,
690
+ nil,
691
+ 1000,
692
+ nil
693
+ )
694
+ end
695
+ end
696
+
697
+ describe "#instrument" do
698
+ it_behaves_like "instrument helper" do
699
+ let(:instrumenter) { transaction }
700
+ end
701
+ end
702
+
703
+ context "generic request" do
704
+ let(:env) { {} }
705
+ subject { Appsignal::Transaction::GenericRequest.new(env) }
706
+
707
+ it "initializes with an empty env" do
708
+ expect(subject.env).to be_empty
709
+ end
710
+
711
+ context "when given an env" do
712
+ let(:env) do
713
+ {
714
+ :params => { :id => 1 },
715
+ :queue_start => 10
716
+ }
717
+ end
718
+
719
+ it "sets the given env" do
720
+ expect(subject.env).to eq env
721
+ end
722
+
723
+ it "sets the params present in the env" do
724
+ expect(subject.params).to eq(:id => 1)
725
+ end
726
+ end
727
+ end
728
+
729
+ # private
730
+
731
+ describe "#background_queue_start" do
732
+ subject { transaction.send(:background_queue_start) }
733
+
734
+ context "when request is nil" do
735
+ let(:request) { nil }
736
+
737
+ it { is_expected.to eq nil }
738
+ end
739
+
740
+ context "when env is nil" do
741
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
742
+
743
+ it { is_expected.to eq nil }
744
+ end
745
+
746
+ context "when queue start is nil" do
747
+ it { is_expected.to eq nil }
748
+ end
749
+
750
+ context "when queue start is set" do
751
+ let(:env) { background_env_with_data }
752
+
753
+ it { is_expected.to eq 1_389_783_590_000 }
754
+ end
755
+ end
756
+
757
+ describe "#http_queue_start" do
758
+ let(:slightly_earlier_time) { fixed_time - 0.4 }
759
+ let(:slightly_earlier_time_value) { (slightly_earlier_time * factor).to_i }
760
+ subject { transaction.send(:http_queue_start) }
761
+
762
+ shared_examples "http queue start" do
763
+ context "when request is nil" do
764
+ let(:request) { nil }
765
+
766
+ it { is_expected.to be_nil }
767
+ end
768
+
769
+ context "when env is nil" do
770
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
771
+
772
+ it { is_expected.to be_nil }
773
+ end
774
+
775
+ context "with no relevant header set" do
776
+ let(:env) { {} }
777
+
778
+ it { is_expected.to be_nil }
779
+ end
780
+
781
+ context "with the HTTP_X_REQUEST_START header set" do
782
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{slightly_earlier_time_value}" } }
783
+
784
+ it { is_expected.to eq 1_389_783_599_600 }
785
+
786
+ context "with unparsable content" do
787
+ let(:env) { { "HTTP_X_REQUEST_START" => "something" } }
788
+
789
+ it { is_expected.to be_nil }
790
+ end
791
+
792
+ context "with some cruft" do
793
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{slightly_earlier_time_value}aaaa" } }
794
+
795
+ it { is_expected.to eq 1_389_783_599_600 }
796
+ end
797
+
798
+ context "with a really low number" do
799
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=100" } }
800
+
801
+ it { is_expected.to be_nil }
802
+ end
803
+
804
+ context "with the alternate HTTP_X_QUEUE_START header set" do
805
+ let(:env) { { "HTTP_X_QUEUE_START" => "t=#{slightly_earlier_time_value}" } }
806
+
807
+ it { is_expected.to eq 1_389_783_599_600 }
808
+ end
809
+ end
810
+ end
811
+
812
+ context "time in miliseconds" do
813
+ let(:factor) { 1_000 }
814
+
815
+ it_should_behave_like "http queue start"
816
+ end
817
+
818
+ context "time in microseconds" do
819
+ let(:factor) { 1_000_000 }
820
+
821
+ it_should_behave_like "http queue start"
822
+ end
823
+ end
824
+
825
+ describe "#sanitized_params" do
826
+ subject { transaction.send(:sanitized_params) }
827
+
828
+ context "with custom params" do
829
+ before do
830
+ transaction.params = { :foo => "bar", :baz => :bat }
831
+ end
832
+
833
+ it "returns custom params" do
834
+ is_expected.to eq(:foo => "bar", :baz => :bat)
835
+ end
836
+
837
+ context "with AppSignal filtering" do
838
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
839
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
840
+
841
+ it "returns sanitized custom params" do
842
+ expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
843
+ end
844
+ end
845
+ end
846
+
847
+ context "without request params" do
848
+ before { allow(transaction.request).to receive(:params).and_return(nil) }
849
+
850
+ it { is_expected.to be_nil }
851
+ end
852
+
853
+ context "when request params crashes" do
854
+ before { allow(transaction.request).to receive(:params).and_raise(NoMethodError) }
855
+
856
+ it { is_expected.to be_nil }
857
+ end
858
+
859
+ context "when request params method does not exist" do
860
+ let(:options) { { :params_method => :nonsense } }
861
+
862
+ it { is_expected.to be_nil }
863
+ end
864
+
865
+ context "when not sending params" do
866
+ before { Appsignal.config.config_hash[:send_params] = false }
867
+ after { Appsignal.config.config_hash[:send_params] = true }
868
+
869
+ it { is_expected.to be_nil }
870
+ end
871
+
872
+ context "with an array" do
873
+ let(:request) do
874
+ Appsignal::Transaction::GenericRequest.new(background_env_with_data(:params => %w[arg1 arg2]))
875
+ end
876
+
877
+ it { is_expected.to eq %w[arg1 arg2] }
878
+
879
+ context "with AppSignal filtering" do
880
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
881
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
882
+
883
+ it { is_expected.to eq %w[arg1 arg2] }
884
+ end
885
+ end
886
+
887
+ context "with env" do
888
+ context "with sanitization" do
889
+ let(:request) do
890
+ Appsignal::Transaction::GenericRequest.new \
891
+ http_request_env_with_data(:params => { :foo => :bar })
892
+ end
893
+
894
+ it "should call the params sanitizer" do
895
+ expect(subject).to eq(:foo => :bar)
896
+ end
897
+ end
898
+
899
+ context "with AppSignal filtering" do
900
+ let(:request) do
901
+ Appsignal::Transaction::GenericRequest.new \
902
+ http_request_env_with_data(:params => { :foo => :bar, :baz => :bat })
903
+ end
904
+ before { Appsignal.config.config_hash[:filter_parameters] = %w[foo] }
905
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
906
+
907
+ it "should call the params sanitizer with filtering" do
908
+ expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
909
+ end
910
+ end
911
+ end
912
+ end
913
+
914
+ describe "#sanitized_environment" do
915
+ let(:whitelisted_keys) { Appsignal::Transaction::ENV_METHODS }
916
+
917
+ subject { transaction.send(:sanitized_environment) }
918
+
919
+ context "when request is nil" do
920
+ let(:request) { nil }
921
+
922
+ it { is_expected.to be_nil }
923
+ end
924
+
925
+ context "when env is nil" do
926
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
927
+
928
+ it { is_expected.to be_nil }
929
+ end
930
+
931
+ context "when env is present" do
932
+ let(:env) do
933
+ {}.tap do |hash|
934
+ whitelisted_keys.each { |o| hash[o] = 1 } # use all whitelisted keys
935
+ hash[whitelisted_keys] = nil # don't add if nil
936
+ hash[:not_whitelisted] = "I will be sanitized"
937
+ end
938
+ end
939
+
940
+ it "only sets whitelisted keys" do
941
+ expect(subject.keys).to match_array(whitelisted_keys)
942
+ end
943
+ end
944
+ end
945
+
946
+ describe "#sanitized_session_data" do
947
+ subject { transaction.send(:sanitized_session_data) }
948
+
949
+ context "when request is nil" do
950
+ let(:request) { nil }
951
+
952
+ it { is_expected.to be_nil }
953
+ end
954
+
955
+ context "when session is nil" do
956
+ before { expect(transaction.request).to receive(:session).and_return(nil) }
957
+
958
+ it { is_expected.to be_nil }
959
+ end
960
+
961
+ context "when session is empty" do
962
+ before { expect(transaction.request).to receive(:session).and_return({}) }
963
+
964
+ it { is_expected.to eq({}) }
965
+ end
966
+
967
+ context "when request class does not have a session method" do
968
+ let(:request) { Appsignal::Transaction::GenericRequest.new({}) }
969
+
970
+ it { is_expected.to be_nil }
971
+ end
972
+
973
+ context "when there is a session" do
974
+ before do
975
+ expect(transaction).to respond_to(:request)
976
+ allow(transaction).to receive_message_chain(:request, :session => { :foo => :bar })
977
+ allow(transaction).to receive_message_chain(:request, :fullpath => :bar)
978
+ end
979
+
980
+ it "passes the session data into the params sanitizer" do
981
+ expect(Appsignal::Utils::ParamsSanitizer).to receive(:sanitize).with(:foo => :bar)
982
+ .and_return(:sanitized_foo)
983
+ expect(subject).to eq :sanitized_foo
984
+ end
985
+
986
+ if defined? ActionDispatch::Request::Session
987
+ context "with ActionDispatch::Request::Session" do
988
+ before do
989
+ expect(transaction).to respond_to(:request)
990
+ allow(transaction).to receive_message_chain(:request, :session => action_dispatch_session)
991
+ allow(transaction).to receive_message_chain(:request, :fullpath => :bar)
992
+ end
993
+
994
+ it "should return an session hash" do
995
+ expect(Appsignal::Utils::ParamsSanitizer).to receive(:sanitize).with("foo" => :bar)
996
+ .and_return(:sanitized_foo)
997
+ subject
998
+ end
999
+
1000
+ def action_dispatch_session
1001
+ store = Class.new do
1002
+ def load_session(_env)
1003
+ [1, { :foo => :bar }]
1004
+ end
1005
+
1006
+ def session_exists?(_env)
1007
+ true
1008
+ end
1009
+ end.new
1010
+ ActionDispatch::Request::Session.create(store, ActionDispatch::Request.new("rack.input" => StringIO.new), {})
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context "when skipping session data" do
1016
+ before do
1017
+ Appsignal.config = { :skip_session_data => true }
1018
+ end
1019
+
1020
+ it "does not pass the session data into the params sanitizer" do
1021
+ expect(Appsignal::Utils::ParamsSanitizer).to_not receive(:sanitize)
1022
+ expect(subject).to be_nil
1023
+ end
1024
+ end
1025
+ end
1026
+ end
1027
+
1028
+ describe "#metadata" do
1029
+ subject { transaction.send(:metadata) }
1030
+
1031
+ context "when request is nil" do
1032
+ let(:request) { nil }
1033
+
1034
+ it { is_expected.to be_nil }
1035
+ end
1036
+
1037
+ context "when env is nil" do
1038
+ before { expect(transaction.request).to receive(:env).and_return(nil) }
1039
+
1040
+ it { is_expected.to be_nil }
1041
+ end
1042
+
1043
+ context "when env is present" do
1044
+ let(:env) { { :metadata => { :key => "value" } } }
1045
+
1046
+ it { is_expected.to eq env[:metadata] }
1047
+ end
1048
+ end
1049
+
1050
+ describe "#sanitized_tags" do
1051
+ before do
1052
+ transaction.set_tags(
1053
+ :valid_key => "valid_value",
1054
+ "valid_string_key" => "valid_value",
1055
+ :both_symbols => :valid_value,
1056
+ :integer_value => 1,
1057
+ :hash_value => { "invalid" => "hash" },
1058
+ :array_value => %w[invalid array],
1059
+ :to_long_value => SecureRandom.urlsafe_base64(101),
1060
+ :object => Object.new,
1061
+ SecureRandom.urlsafe_base64(101) => "to_long_key"
1062
+ )
1063
+ end
1064
+ subject { transaction.send(:sanitized_tags).keys }
1065
+
1066
+ it "should only return whitelisted data" do
1067
+ is_expected.to match_array([
1068
+ :valid_key,
1069
+ "valid_string_key",
1070
+ :both_symbols,
1071
+ :integer_value
1072
+ ])
1073
+ end
1074
+ end
1075
+
1076
+ describe "#cleaned_backtrace" do
1077
+ subject { transaction.send(:cleaned_backtrace, ["line 1", "line 2"]) }
1078
+
1079
+ it "returns the backtrace" do
1080
+ expect(subject).to eq ["line 1", "line 2"]
1081
+ end
1082
+
1083
+ if rails_present?
1084
+ context "with rails" do
1085
+ it "cleans the backtrace with the Rails backtrace cleaner" do
1086
+ ::Rails.backtrace_cleaner.add_filter do |line|
1087
+ line.tr("2", "?")
1088
+ end
1089
+ expect(subject).to eq ["line 1", "line ?"]
1090
+ end
1091
+ end
1092
+ end
1093
+ end
1094
+ end
1095
+
1096
+ describe ".to_hash / .to_h" do
1097
+ subject { transaction.to_hash }
1098
+
1099
+ context "when extension returns serialized JSON" do
1100
+ it "parses the result and returns a Hash" do
1101
+ expect(subject).to include(
1102
+ "action" => nil,
1103
+ "error" => nil,
1104
+ "events" => [],
1105
+ "id" => transaction_id,
1106
+ "metadata" => {},
1107
+ "namespace" => namespace,
1108
+ "sample_data" => {}
1109
+ )
1110
+ end
1111
+ end
1112
+
1113
+ context "when the extension returns invalid serialized JSON" do
1114
+ before do
1115
+ expect(transaction.ext).to receive(:to_json).and_return("foo")
1116
+ end
1117
+
1118
+ it "raises a JSON parse error" do
1119
+ expect { subject }.to raise_error(JSON::ParserError)
1120
+ end
1121
+ end
1122
+ end
1123
+
1124
+ describe Appsignal::Transaction::NilTransaction do
1125
+ subject { Appsignal::Transaction::NilTransaction.new }
1126
+
1127
+ it "should have method stubs" do
1128
+ expect do
1129
+ subject.complete
1130
+ subject.pause!
1131
+ subject.resume!
1132
+ subject.paused?
1133
+ subject.store(:key)
1134
+ subject.set_tags(:tag => 1)
1135
+ subject.set_action("action")
1136
+ subject.set_http_or_background_action
1137
+ subject.set_queue_start(1)
1138
+ subject.set_http_or_background_queue_start
1139
+ subject.set_metadata("key", "value")
1140
+ subject.set_sample_data("key", "data")
1141
+ subject.sample_data
1142
+ subject.set_error("a")
1143
+ end.to_not raise_error
1144
+ end
1145
+ end
1146
+ end