diff --git a/spec/lib/appsignal/demo_spec.rb b/spec/lib/appsignal/demo_spec.rb index 3519fb2fd..dea49761d 100644 --- a/spec/lib/appsignal/demo_spec.rb +++ b/spec/lib/appsignal/demo_spec.rb @@ -32,56 +32,53 @@ end describe ".create_example_error_request" do - let!(:error_transaction) { http_request_transaction } - let(:config) { project_fixture_config("production") } - before do - Appsignal.config = config - expect(Appsignal::Transaction).to receive(:new).with( - kind_of(String), - Appsignal::Transaction::HTTP_REQUEST, - kind_of(::Rack::Request), - kind_of(Hash) - ).and_return(error_transaction) - end - subject { described_class.send(:create_example_error_request) } + before { start_agent } + around { |example| keep_transactions { example.run } } it "sets an error" do - expect(error_transaction).to receive(:set_error).with(kind_of(described_class::TestError)) - expect(error_transaction).to receive(:set_metadata).with("path", "/hello") - expect(error_transaction).to receive(:set_metadata).with("method", "GET") - expect(error_transaction).to receive(:set_metadata).with("demo_sample", "true") - expect(error_transaction).to receive(:complete) - subject + described_class.send(:create_example_error_request) + + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to have_action("DemoController#hello") + expect(transaction).to have_error( + "Appsignal::Demo::TestError", + "Hello world! This is an error used for demonstration purposes." + ) + expect(transaction).to include_metadata( + "path" => "/hello", + "method" => "GET", + "demo_sample" => "true" + ) + expect(transaction).to be_completed end end describe ".create_example_performance_request" do - let!(:performance_transaction) { http_request_transaction } - let(:config) { project_fixture_config("production") } - before do - Appsignal.config = config - expect(Appsignal::Transaction).to receive(:new).with( - kind_of(String), - Appsignal::Transaction::HTTP_REQUEST, - kind_of(::Rack::Request), - kind_of(Hash) - ).and_return(performance_transaction) - end - subject { described_class.send(:create_example_performance_request) } + before { start_agent } + around { |example| keep_transactions { example.run } } it "sends a performance sample" do - expect(performance_transaction).to receive(:start_event) - expect(performance_transaction).to receive(:finish_event).with( - "action_view.render", - "Render hello.html.erb", - "

Hello world!

", - Appsignal::EventFormatter::DEFAULT + described_class.send(:create_example_performance_request) + + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to have_action("DemoController#hello") + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "path" => "/hello", + "method" => "GET", + "demo_sample" => "true" + ) + expect(transaction).to include_event( + "name" => "action_view.render", + "title" => "Render hello.html.erb", + "body" => "

Hello world!

", + "body_format" => Appsignal::EventFormatter::DEFAULT ) - expect(performance_transaction).to receive(:set_metadata).with("path", "/hello") - expect(performance_transaction).to receive(:set_metadata).with("method", "GET") - expect(performance_transaction).to receive(:set_metadata).with("demo_sample", "true") - expect(performance_transaction).to receive(:complete) - subject + expect(transaction).to be_completed end end end diff --git a/spec/lib/appsignal/hooks/action_cable_spec.rb b/spec/lib/appsignal/hooks/action_cable_spec.rb index 17f3f09cf..a016430e5 100644 --- a/spec/lib/appsignal/hooks/action_cable_spec.rb +++ b/spec/lib/appsignal/hooks/action_cable_spec.rb @@ -46,12 +46,13 @@ def self.to_s http_request_env_with_data("action_dispatch.request_id" => request_id, :params => params) end let(:instance) { channel.new(connection, identifier, params) } - subject { transaction.to_h } before do start_agent expect(Appsignal.active?).to be_truthy transaction + set_current_transaction(transaction) + expect(Appsignal::Transaction).to receive(:create).with( transaction_id, Appsignal::Transaction::ACTION_CABLE, @@ -70,35 +71,25 @@ def self.to_s it "creates a transaction for an action" do instance.perform_action("message" => "foo", "action" => "speak") - expect(subject).to include( - "action" => "MyChannel#speak", - "error" => nil, - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to have_action("MyChannel#speak") + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) - expect(subject["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "perform_action.action_cable", "title" => "" ) - expect(subject["sample_data"]).to include( - "params" => { - "action" => "speak", - "message" => "foo" - } + expect(transaction).to include_params( + "action" => "speak", + "message" => "foo" ) end @@ -120,7 +111,7 @@ def self.to_s it "uses its own internal request_id set by the subscribed callback" do # Subscribe action, sets the request_id instance.subscribe_to_channel - expect(transaction.to_h["id"]).to eq(transaction_id) + expect(transaction).to have_id(transaction_id) # Expect another transaction for the action. # This transaction will use the same request_id as the @@ -136,7 +127,7 @@ def self.to_s expect(action_transaction.ext).to receive(:complete) instance.perform_action("message" => "foo", "action" => "speak") - expect(action_transaction.to_h["id"]).to eq(transaction_id) + expect(action_transaction).to have_id(transaction_id) end end @@ -158,25 +149,18 @@ def self.to_s instance.perform_action("message" => "foo", "action" => "speak") end.to raise_error(ExampleException) - expect(subject).to include( - "action" => "MyChannel#speak", - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } - ) - expect(subject["error"]).to include( - "backtrace" => kind_of(String), - "name" => "ExampleException", - "message" => "oh no!" + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#speak") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to have_error("ExampleException", "oh no!") + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) - expect(subject["sample_data"]).to include( - "params" => { - "action" => "speak", - "message" => "foo" - } + expect(transaction).to include_params( + "action" => "speak", + "message" => "foo" ) end end @@ -188,33 +172,23 @@ def self.to_s it "creates a transaction for a subscription" do instance.subscribe_to_channel - expect(subject).to include( - "action" => "MyChannel#subscribed", - "error" => nil, - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#subscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) - expect(subject["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to include_params("internal" => "true") + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "subscribed.action_cable", "title" => "" ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } - ) end context "without request_id (standalone server)" do @@ -226,7 +200,7 @@ def self.to_s end it "uses its own internal request_id" do - expect(subject["id"]).to eq(transaction_id) + expect(last_transaction).to have_id(transaction_id) end end @@ -248,23 +222,16 @@ def self.to_s instance.subscribe_to_channel end.to raise_error(ExampleException) - expect(subject).to include( - "action" => "MyChannel#subscribed", - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } - ) - expect(subject["error"]).to include( - "backtrace" => kind_of(String), - "name" => "ExampleException", - "message" => "oh no!" - ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#subscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to have_error("ExampleException", "oh no!") + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) + expect(transaction).to include_params("internal" => "true") end end @@ -280,33 +247,23 @@ def self.to_s it "does not fail on missing `#env` method on `ConnectionStub`" do instance.subscribe_to_channel - expect(subject).to include( - "action" => "MyChannel#subscribed", - "error" => nil, - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "" # No path as the ConnectionStub doesn't have the real request env - } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#subscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "" # No path as the ConnectionStub doesn't have the real request env ) - expect(subject["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to include_params("internal" => "true") + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "subscribed.action_cable", "title" => "" ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } - ) end end end @@ -318,33 +275,23 @@ def self.to_s it "creates a transaction for a subscription" do instance.unsubscribe_from_channel - expect(subject).to include( - "action" => "MyChannel#unsubscribed", - "error" => nil, - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#unsubscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) - expect(subject["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to include_params("internal" => "true") + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "unsubscribed.action_cable", "title" => "" ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } - ) end context "without request_id (standalone server)" do @@ -356,7 +303,7 @@ def self.to_s end it "uses its own internal request_id" do - expect(subject["id"]).to eq(transaction_id) + expect(transaction).to have_id(transaction_id) end end @@ -378,23 +325,16 @@ def self.to_s instance.unsubscribe_from_channel end.to raise_error(ExampleException) - expect(subject).to include( - "action" => "MyChannel#unsubscribed", - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "/blog" - } - ) - expect(subject["error"]).to include( - "backtrace" => kind_of(String), - "name" => "ExampleException", - "message" => "oh no!" - ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#unsubscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to have_error("ExampleException", "oh no!") + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "/blog" ) + expect(transaction).to include_params("internal" => "true") end end @@ -410,33 +350,23 @@ def self.to_s it "does not fail on missing `#env` method on `ConnectionStub`" do instance.unsubscribe_from_channel - expect(subject).to include( - "action" => "MyChannel#unsubscribed", - "error" => nil, - "id" => transaction_id, - "namespace" => Appsignal::Transaction::ACTION_CABLE, - "metadata" => { - "method" => "websocket", - "path" => "" # No path as the ConnectionStub doesn't have the real request env - } + transaction = last_transaction + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_action("MyChannel#unsubscribed") + expect(transaction).to have_namespace(Appsignal::Transaction::ACTION_CABLE) + expect(transaction).to_not have_error + expect(transaction).to include_metadata( + "method" => "websocket", + "path" => "" # No path as the ConnectionStub doesn't have the real request env ) - expect(subject["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to include_params("internal" => "true") + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "unsubscribed.action_cable", "title" => "" ) - expect(subject["sample_data"]).to include( - "params" => { "internal" => "true" } - ) end end end diff --git a/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb b/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb index 0f3ec27fc..3e22ee406 100644 --- a/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +++ b/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb @@ -5,22 +5,13 @@ listeners_state = instrumenter.start("sql.active_record", {}) instrumenter.finish_with_state(listeners_state, "sql.active_record", :sql => "SQL") - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SQL", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "SQL", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end it "does not instrument events whose name starts with a bang" do @@ -30,6 +21,6 @@ listeners_state = instrumenter.start("!sql.active_record", {}) instrumenter.finish_with_state(listeners_state, "!sql.active_record", :sql => "SQL") - expect(transaction.to_h["events"]).to be_empty + expect(transaction).to_not include_events end end diff --git a/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb b/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb index 93ff16123..065e82531 100644 --- a/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +++ b/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb @@ -5,22 +5,13 @@ end expect(return_value).to eq "value" - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SQL", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "SQL", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end it "instruments an ActiveSupport::Notifications.instrument event with no registered formatter" do @@ -29,53 +20,34 @@ end expect(return_value).to eq "value" - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "no-registered.formatter", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "", + "body_format" => Appsignal::EventFormatter::DEFAULT, + "count" => 1, + "name" => "no-registered.formatter", + "title" => "" + ) end it "converts non-string names to strings" do as.instrument(:not_a_string) {} # rubocop:disable Lint/EmptyBlock - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "not_a_string", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "", + "body_format" => Appsignal::EventFormatter::DEFAULT, + "count" => 1, + "name" => "not_a_string", + "title" => "" + ) end it "does not instrument events whose name starts with a bang" do - expect(Appsignal::Transaction.current).not_to receive(:start_event) - expect(Appsignal::Transaction.current).not_to receive(:finish_event) - return_value = as.instrument("!sql.active_record", :sql => "SQL") do "value" end expect(return_value).to eq "value" + + expect(transaction).to_not include_events end context "when an error is raised in an instrumented block" do @@ -86,22 +58,13 @@ end end.to raise_error(ExampleException, "foo") - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SQL", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "SQL", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end end @@ -113,22 +76,13 @@ end end.to throw_symbol(:foo) - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SQL", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "SQL", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end end @@ -139,7 +93,7 @@ Appsignal::Transaction.complete_current! end - expect(transaction.to_h["events"]).to match([]) + expect(transaction).to_not include_events end end end diff --git a/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb b/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb index 5289fe6f3..3ed1f7db4 100644 --- a/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +++ b/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb @@ -5,44 +5,26 @@ instrumenter.start("sql.active_record", :sql => "SQL") instrumenter.finish("sql.active_record", {}) - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end it "instruments an ActiveSupport::Notifications.start/finish event with payload on finish" do instrumenter.start("sql.active_record", {}) instrumenter.finish("sql.active_record", :sql => "SQL") - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SQL", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "sql.active_record", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "SQL", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "sql.active_record", + "title" => "" + ) end it "does not instrument events whose name starts with a bang" do @@ -52,7 +34,7 @@ instrumenter.start("!sql.active_record", {}) instrumenter.finish("!sql.active_record", {}) - expect(transaction.to_h["events"]).to be_empty + expect(transaction).to_not include_events end context "when a transaction is completed in an instrumented block" do @@ -63,7 +45,7 @@ Appsignal::Transaction.complete_current! instrumenter.finish("sql.active_record", {}) - expect(transaction.to_h["events"]).to match([]) + expect(transaction).to_not include_events end end end diff --git a/spec/lib/appsignal/hooks/activejob_spec.rb b/spec/lib/appsignal/hooks/activejob_spec.rb index 8755068fa..d80bca0e8 100644 --- a/spec/lib/appsignal/hooks/activejob_spec.rb +++ b/spec/lib/appsignal/hooks/activejob_spec.rb @@ -119,25 +119,19 @@ def perform(*_args) queue_job(ActiveJobTestJob) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActiveJobTestJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => hash_including( - "params" => [], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => queue, - "executions" => 1 - } - ) + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ActiveJobTestJob#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_metadata + expect(transaction).to include_params([]) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => queue, + "executions" => 1 ) - events = transaction_hash["events"] + events = transaction.to_h["events"] .sort_by { |e| e["start"] } .map { |event| event["name"] } - expect(events).to eq(expected_perform_events) end @@ -148,13 +142,7 @@ def perform(*_args) .with("active_job_queue_job_count", 1, tags.merge(:status => :processed)) queue_job(ActiveJobCustomQueueTestJob) - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including("queue" => "custom_queue") - ) - ) + expect(last_transaction).to include_tags("queue" => "custom_queue") end end @@ -182,13 +170,7 @@ def perform(*_args) queue_job(ActiveJobPriorityTestJob) - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including("queue" => queue, "priority" => 10) - ) - ) + expect(last_transaction).to include_tags("queue" => queue, "priority" => 10) end end end @@ -207,29 +189,20 @@ def perform(*_args) end.to raise_error(RuntimeError, "uh oh") transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActiveJobErrorTestJob#perform", - "error" => { - "name" => "RuntimeError", - "message" => "uh oh", - "backtrace" => kind_of(String) - }, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => hash_including( - "params" => [], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => queue, - "executions" => 1 - } - ) + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ActiveJobErrorTestJob#perform") + expect(transaction).to have_error("RuntimeError", "uh oh") + expect(transaction).to_not include_metadata + expect(transaction).to include_params([]) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => queue, + "executions" => 1 ) - events = transaction_hash["events"] + + events = transaction.to_h["events"] .sort_by { |e| e["start"] } .map { |event| event["name"] } - expect(events).to eq(expected_perform_events) end @@ -247,9 +220,7 @@ def perform(*_args) queue_job(ActiveJobErrorTestJob) end.to raise_error(RuntimeError, "uh oh") - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include("error" => nil) + expect(last_transaction).to_not have_error end end @@ -270,15 +241,8 @@ def perform(*_args) end transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "error" => nil, - "sample_data" => hash_including( - "tags" => hash_including( - "executions" => 1 - ) - ) - ) + expect(transaction).to_not have_error + expect(transaction).to include_tags("executions" => 1) end it "reports error when discarding the job" do @@ -294,19 +258,8 @@ def perform(*_args) end transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "error" => { - "name" => "RuntimeError", - "message" => "uh oh", - "backtrace" => kind_of(String) - }, - "sample_data" => hash_including( - "tags" => hash_including( - "executions" => 2 - ) - ) - ) + expect(transaction).to have_error("RuntimeError", "uh oh") + expect(transaction).to include_tags("executions" => 2) end end end @@ -343,13 +296,7 @@ def perform(*_args) queue_job(ActiveJobErrorPriorityTestJob) end.to raise_error(RuntimeError, "uh oh") - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including("queue" => queue, "priority" => 10) - ) - ) + expect(last_transaction).to include_tags("queue" => queue, "priority" => 10) end end end @@ -363,51 +310,39 @@ def perform(*_args) end.to raise_error(RuntimeError, "uh oh") end - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including("executions" => 2) - ) - ) + expect(last_transaction).to include_tags("executions" => 2) end end context "when wrapped in another transaction" do it "does not create a new transaction or close the currently open one" do current_transaction = background_job_transaction - allow(current_transaction).to receive(:complete).and_call_original set_current_transaction current_transaction queue_job(ActiveJobTestJob) expect(created_transactions.count).to eql(1) - expect(current_transaction).to_not have_received(:complete) - current_transaction.complete transaction = current_transaction - transaction_hash = transaction.to_h + expect(transaction).to_not be_completed + transaction._sample # It does set data on the transaction - expect(transaction_hash).to include( - "id" => current_transaction.transaction_id, - "action" => "ActiveJobTestJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => hash_including( - "params" => [], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => queue, - "executions" => 1 - } - ) + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_id(current_transaction.transaction_id) + expect(transaction).to have_action("ActiveJobTestJob#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_metadata + expect(transaction).to include_params([]) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => queue, + "executions" => 1 ) - events = transaction_hash["events"] + + events = transaction.to_h["events"] .reject { |e| e["name"] == "enqueue.active_job" } .sort_by { |e| e["start"] } .map { |event| event["name"] } - expect(events).to eq(expected_perform_events) end end @@ -467,9 +402,7 @@ def perform(*_args) it "sets provider_job_id as tag" do queue_job(ProviderWrappedActiveJobTestJob) - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash["sample_data"]["tags"]).to include( + expect(last_transaction).to include_tags( "provider_job_id" => "my_provider_job_id" ) end @@ -506,13 +439,10 @@ def perform(*_args) end it "sets queue time on transaction" do - allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original queue_job(ProviderWrappedActiveJobTestJob) - transaction = last_transaction queue_time = Time.parse("2020-10-10T10:10:10Z") - expect(transaction).to have_received(:set_queue_start) - .with((queue_time.to_f * 1_000).to_i) + expect(last_transaction).to have_queue_start((queue_time.to_f * 1_000).to_i) end end @@ -534,18 +464,14 @@ def welcome(_first_arg = nil, _second_arg = nil) perform_mailer(ActionMailerTestJob, :welcome) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestJob#welcome", - "sample_data" => hash_including( - "params" => ["ActionMailerTestJob", "welcome", - "deliver_now"] + active_job_args_wrapper, - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 - } - ) + expect(transaction).to have_action("ActionMailerTestJob#welcome") + expect(transaction).to include_params( + ["ActionMailerTestJob", "welcome", "deliver_now"] + active_job_args_wrapper + ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end end @@ -555,18 +481,15 @@ def welcome(_first_arg = nil, _second_arg = nil) perform_mailer(ActionMailerTestJob, :welcome, method_given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestJob#welcome", - "sample_data" => hash_including( - "params" => ["ActionMailerTestJob", "welcome", - "deliver_now"] + active_job_args_wrapper(:args => method_expected_args), - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 - } - ) + expect(transaction).to have_action("ActionMailerTestJob#welcome") + expect(transaction).to include_params( + ["ActionMailerTestJob", "welcome", + "deliver_now"] + active_job_args_wrapper(:args => method_expected_args) + ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end end @@ -577,21 +500,18 @@ def welcome(_first_arg = nil, _second_arg = nil) perform_mailer(ActionMailerTestJob, :welcome, parameterized_given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestJob#welcome", - "sample_data" => hash_including( - "params" => [ - "ActionMailerTestJob", - "welcome", - "deliver_now" - ] + active_job_args_wrapper(:params => parameterized_expected_args), - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 - } - ) + expect(transaction).to have_action("ActionMailerTestJob#welcome") + expect(transaction).to include_params( + [ + "ActionMailerTestJob", + "welcome", + "deliver_now" + ] + active_job_args_wrapper(:params => parameterized_expected_args) + ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end end @@ -618,22 +538,19 @@ def welcome(*_args) perform_mailer(ActionMailerTestMailDeliveryJob, :welcome) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestMailDeliveryJob#welcome", - "sample_data" => hash_including( - "params" => [ - "ActionMailerTestMailDeliveryJob", - "welcome", - "deliver_now", - { active_job_internal_key => ["args"], "args" => [] } - ], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 - } - ) + expect(transaction).to have_action("ActionMailerTestMailDeliveryJob#welcome") + expect(transaction).to include_params( + [ + "ActionMailerTestMailDeliveryJob", + "welcome", + "deliver_now", + { active_job_internal_key => ["args"], "args" => [] } + ] + ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end @@ -642,25 +559,22 @@ def welcome(*_args) perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, method_given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestMailDeliveryJob#welcome", - "sample_data" => hash_including( - "params" => [ - "ActionMailerTestMailDeliveryJob", - "welcome", - "deliver_now", - { - active_job_internal_key => ["args"], - "args" => method_expected_args - } - ], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 + expect(transaction).to have_action("ActionMailerTestMailDeliveryJob#welcome") + expect(transaction).to include_params( + [ + "ActionMailerTestMailDeliveryJob", + "welcome", + "deliver_now", + { + active_job_internal_key => ["args"], + "args" => method_expected_args } - ) + ] + ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end end @@ -670,11 +584,9 @@ def welcome(*_args) perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, parameterized_given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerTestMailDeliveryJob#welcome", - "sample_data" => hash_including( - "params" => [ + expect(transaction).to have_action("ActionMailerTestMailDeliveryJob#welcome") + expect(transaction).to include_params( + [ "ActionMailerTestMailDeliveryJob", "welcome", "deliver_now", @@ -683,13 +595,12 @@ def welcome(*_args) "args" => [], "params" => parameterized_expected_args } - ], - "tags" => { - "active_job_id" => kind_of(String), - "queue" => "mailers", - "executions" => 1 - } + ] ) + expect(transaction).to include_tags( + "active_job_id" => kind_of(String), + "queue" => "mailers", + "executions" => 1 ) end end diff --git a/spec/lib/appsignal/hooks/delayed_job_spec.rb b/spec/lib/appsignal/hooks/delayed_job_spec.rb index 072b04112..609286e3f 100644 --- a/spec/lib/appsignal/hooks/delayed_job_spec.rb +++ b/spec/lib/appsignal/hooks/delayed_job_spec.rb @@ -64,23 +64,18 @@ def perform context "with a normal call" do it "wraps it in a transaction" do perform - transaction_data = last_transaction.to_h - expect(transaction_data).to include( - "action" => "TestClass#perform", - "namespace" => "background_job", - "error" => nil - ) - expect(transaction_data["events"].map { |e| e["name"] }) - .to eql(["perform_job.delayed_job"]) - expect(transaction_data["sample_data"]).to include( - "metadata" => { - "priority" => 1, - "attempts" => 1, - "queue" => "default", - "id" => "123" - }, - "params" => ["argument"] + transaction = last_transaction + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to have_namespace("background_job") + expect(transaction).to_not have_error + expect(transaction).to include_event(:name => "perform_job.delayed_job") + expect(transaction).to include_sample_metadata( + "priority" => 1, + "attempts" => 1, + "queue" => "default", + "id" => "123" ) + expect(transaction).to include_params(["argument"]) end context "with more complex params" do @@ -93,13 +88,8 @@ def perform it "adds the more complex arguments" do perform - transaction_data = last_transaction.to_h - expect(transaction_data["sample_data"]).to include( - "params" => { - "foo" => "Foo", - "bar" => "Bar" - } - ) + + expect(last_transaction).to include_params("foo" => "Foo", "bar" => "Bar") end context "with parameter filtering" do @@ -110,13 +100,8 @@ def perform it "filters selected arguments" do perform - transaction_data = last_transaction.to_h - expect(transaction_data["sample_data"]).to include( - "params" => { - "foo" => "[FILTERED]", - "bar" => "Bar" - } - ) + + expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar") end end end @@ -125,13 +110,9 @@ def perform let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") } it "reports queue_start with run_at time" do - # TODO: Not available in transaction.to_h yet. - # https://github.com/appsignal/appsignal-agent/issues/293 - expect(Appsignal).to receive(:monitor_transaction).with( - "perform_job.delayed_job", - a_hash_including(:queue_start => run_at) - ).and_call_original perform + + expect(last_transaction).to have_queue_start(run_at.to_i * 1000) end end @@ -142,7 +123,7 @@ def perform it "wraps it in a transaction using the class method job name" do perform - expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform") + expect(last_transaction).to have_action("CustomClassMethod.perform") end end @@ -155,7 +136,7 @@ def perform let(:payload_object) { double(:appsignal_name => "CustomClass#perform") } it "wraps it in a transaction using the custom name" do - expect(last_transaction.to_h["action"]).to eql("CustomClass#perform") + expect(last_transaction).to have_action("CustomClass#perform") end end @@ -163,7 +144,7 @@ def perform let(:payload_object) { double(:appsignal_name => Object.new) } it "wraps it in a transaction using the original job name" do - expect(last_transaction.to_h["action"]).to eql("TestClass#perform") + expect(last_transaction).to have_action("TestClass#perform") end end @@ -172,7 +153,7 @@ def perform it "wraps it in a transaction using the custom name" do perform - expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform") + expect(last_transaction).to have_action("CustomClassMethod.perform") end end end @@ -182,7 +163,7 @@ def perform let(:payload_object) { double(:appsignal_name => "CustomClassHash#perform") } it "wraps it in a transaction using the custom name" do - expect(last_transaction.to_h["action"]).to eql("CustomClassHash#perform") + expect(last_transaction).to have_action("CustomClassHash#perform") end end @@ -190,7 +171,7 @@ def perform let(:payload_object) { double(:appsignal_name => Object.new) } it "wraps it in a transaction using the original job name" do - expect(last_transaction.to_h["action"]).to eql("TestClass#perform") + expect(last_transaction).to have_action("TestClass#perform") end end @@ -199,7 +180,7 @@ def perform it "wraps it in a transaction using the custom name" do perform - expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform") + expect(last_transaction).to have_action("CustomClassMethod.perform") end end end @@ -221,7 +202,7 @@ def self.appsignal_name # of `self.appsignal_name`. Since this isn't a valid `String` # we return the default job name as action name. it "wraps it in a transaction using the original job name" do - expect(last_transaction.to_h["action"]).to eql("TestClass#perform") + expect(last_transaction).to have_action("TestClass#perform") end end end @@ -234,7 +215,7 @@ def self.appsignal_name it "appends #perform to the class name" do perform - expect(last_transaction.to_h["action"]).to eql("Banana#perform") + expect(last_transaction).to have_action("Banana#perform") end end @@ -267,23 +248,19 @@ def self.appsignal_name it "wraps it in a transaction with the correct params" do perform - transaction_data = last_transaction.to_h - expect(transaction_data).to include( - "action" => "TestClass#perform", - "namespace" => "background_job", - "error" => nil - ) - expect(transaction_data["events"].map { |e| e["name"] }) - .to eql(["perform_job.delayed_job"]) - expect(transaction_data["sample_data"]).to include( - "metadata" => { - "priority" => 1, - "attempts" => 1, - "queue" => "default", - "id" => "123" - }, - "params" => ["activejob_argument"] + + transaction = last_transaction + expect(transaction).to have_namespace("background_job") + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to_not have_error + expect(transaction).to include_event("name" => "perform_job.delayed_job") + expect(transaction).to include_sample_metadata( + "priority" => 1, + "attempts" => 1, + "queue" => "default", + "id" => "123" ) + expect(transaction).to include_params(["activejob_argument"]) end context "with more complex params" do @@ -296,13 +273,11 @@ def self.appsignal_name it "adds the more complex arguments" do perform - transaction_data = last_transaction.to_h - expect(transaction_data).to include("action" => "TestClass#perform") - expect(transaction_data["sample_data"]).to include( - "params" => { - "foo" => "Foo", - "bar" => "Bar" - } + transaction = last_transaction + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to include_params( + "foo" => "Foo", + "bar" => "Bar" ) end @@ -314,13 +289,11 @@ def self.appsignal_name it "filters selected arguments" do perform - transaction_data = last_transaction.to_h - expect(transaction_data).to include("action" => "TestClass#perform") - expect(transaction_data["sample_data"]).to include( - "params" => { - "foo" => "[FILTERED]", - "bar" => "Bar" - } + transaction = last_transaction + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to include_params( + "foo" => "[FILTERED]", + "bar" => "Bar" ) end end @@ -330,11 +303,9 @@ def self.appsignal_name let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") } it "reports queue_start with run_at time" do - expect(Appsignal).to receive(:monitor_transaction).with( - "perform_job.delayed_job", - a_hash_including(:queue_start => run_at) - ).and_call_original perform + + expect(last_transaction).to have_queue_start(run_at.to_i * 1000) end end end @@ -352,18 +323,10 @@ def self.appsignal_name perform end.to raise_error(error) - transaction_data = last_transaction.to_h - expect(transaction_data).to include( - "action" => "TestClass#perform", - "namespace" => "background_job", - "error" => { - "name" => "ExampleException", - "message" => "uh oh", - # TODO: backtrace should be an Array of Strings - # https://github.com/appsignal/appsignal-agent/issues/294 - "backtrace" => kind_of(String) - } - ) + transaction = last_transaction + expect(transaction).to have_namespace("background_job") + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to have_error("ExampleException", "uh oh") end end end diff --git a/spec/lib/appsignal/hooks/dry_monitor_spec.rb b/spec/lib/appsignal/hooks/dry_monitor_spec.rb index 5077f7493..0b6dcd9a5 100644 --- a/spec/lib/appsignal/hooks/dry_monitor_spec.rb +++ b/spec/lib/appsignal/hooks/dry_monitor_spec.rb @@ -53,22 +53,13 @@ it "creates an sql event" do notifications.instrument(event_id, payload) - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "SELECT * FROM users", - "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "query.postgres", - "start" => kind_of(Float), - "title" => "query.postgres" - } - ]) + expect(transaction).to include_event( + "body" => "SELECT * FROM users", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT, + "count" => 1, + "name" => "query.postgres", + "title" => "query.postgres" + ) end end @@ -82,22 +73,13 @@ it "creates a generic event" do notifications.instrument(event_id, payload) - expect(transaction.to_h["events"]).to match([ - { - "allocation_count" => kind_of(Integer), - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), - "count" => 1, - "duration" => kind_of(Float), - "gc_duration" => kind_of(Float), - "name" => "foo", - "start" => kind_of(Float), - "title" => "" - } - ]) + expect(transaction).to include_event( + "body" => "", + "body_format" => Appsignal::EventFormatter::DEFAULT, + "count" => 1, + "name" => "foo", + "title" => "" + ) end end end diff --git a/spec/lib/appsignal/hooks/excon_spec.rb b/spec/lib/appsignal/hooks/excon_spec.rb index 8329f752e..cc5bd7f72 100644 --- a/spec/lib/appsignal/hooks/excon_spec.rb +++ b/spec/lib/appsignal/hooks/excon_spec.rb @@ -40,12 +40,10 @@ def self.defaults } Excon.defaults[:instrumentor].instrument("excon.request", data) {} # rubocop:disable Lint/EmptyBlock - expect(transaction.to_h["events"]).to include( - hash_including( - "name" => "request.excon", - "title" => "GET http://www.google.com", - "body" => "" - ) + expect(transaction).to include_event( + "name" => "request.excon", + "title" => "GET http://www.google.com", + "body" => "" ) end @@ -53,12 +51,10 @@ def self.defaults data = { :host => "www.google.com" } Excon.defaults[:instrumentor].instrument("excon.response", data) {} # rubocop:disable Lint/EmptyBlock - expect(transaction.to_h["events"]).to include( - hash_including( - "name" => "response.excon", - "title" => "www.google.com", - "body" => "" - ) + expect(transaction).to include_event( + "name" => "response.excon", + "title" => "www.google.com", + "body" => "" ) end end diff --git a/spec/lib/appsignal/hooks/rake_spec.rb b/spec/lib/appsignal/hooks/rake_spec.rb index 6251b3c93..9f56183d6 100644 --- a/spec/lib/appsignal/hooks/rake_spec.rb +++ b/spec/lib/appsignal/hooks/rake_spec.rb @@ -15,8 +15,7 @@ def perform end it "creates no transaction" do - expect(Appsignal::Transaction).to_not receive(:new) - expect { perform }.to_not(change { created_transactions }) + expect { perform }.to_not(change { created_transactions.count }) end it "calls the original task" do @@ -42,20 +41,13 @@ def perform it "creates a background job transaction" do perform - expect(last_transaction).to be_completed - expect(last_transaction.to_h).to include( - "id" => kind_of(String), - "namespace" => Appsignal::Transaction::BACKGROUND_JOB, - "action" => "task:name", - "error" => { - "name" => "ExampleException", - "message" => "my error message", - "backtrace" => kind_of(String) - }, - "sample_data" => hash_including( - "params" => { "foo" => "bar" } - ) - ) + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_action("task:name") + expect(transaction).to have_error("ExampleException", "my error message") + expect(transaction).to include_params("foo" => "bar") + expect(transaction).to be_completed end context "when first argument is not a `Rake::TaskArguments`" do @@ -64,9 +56,7 @@ def perform it "does not add the params to the transaction" do perform - expect(last_transaction.to_h).to include( - "sample_data" => hash_excluding("params") - ) + expect(last_transaction).to_not include_params end end end diff --git a/spec/lib/appsignal/hooks/redis_client_spec.rb b/spec/lib/appsignal/hooks/redis_client_spec.rb index 21693a953..73579416c 100644 --- a/spec/lib/appsignal/hooks/redis_client_spec.rb +++ b/spec/lib/appsignal/hooks/redis_client_spec.rb @@ -88,13 +88,10 @@ def write(_commands) connection = RedisClient::RubyConnection.new client_config expect(connection.write([:get, "key"])).to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "get ?", - "title" => "stub_id" - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "get ?", + "title" => "stub_id" ) end @@ -103,16 +100,13 @@ def write(_commands) script = "return redis.call('set',KEYS[1],ARGV[1])" keys = ["foo"] argv = ["bar"] - expect(connection.write([:eval, script, keys.size, keys, - argv])).to eql("stub_write") + expect(connection.write([:eval, script, keys.size, keys, argv])) + .to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "#{script} ? ?", - "title" => "stub_id" - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "#{script} ? ?", + "title" => "stub_id" ) end end @@ -181,13 +175,10 @@ def write(_commands) connection = RedisClient::HiredisConnection.new client_config expect(connection.write([:get, "key"])).to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "get ?", - "title" => "stub_id" - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "get ?", + "title" => "stub_id" ) end @@ -199,13 +190,10 @@ def write(_commands) expect(connection.write([:eval, script, keys.size, keys, argv])).to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "#{script} ? ?", - "title" => "stub_id" - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "#{script} ? ?", + "title" => "stub_id" ) end end diff --git a/spec/lib/appsignal/hooks/redis_spec.rb b/spec/lib/appsignal/hooks/redis_spec.rb index 028c05030..dcd474531 100644 --- a/spec/lib/appsignal/hooks/redis_spec.rb +++ b/spec/lib/appsignal/hooks/redis_spec.rb @@ -81,14 +81,11 @@ def write(_commands) client = Redis::Client.new expect(client.write([:get, "key"])).to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "get ?", - "title" => "stub_id" - ) - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "get ?", + "title" => "stub_id" + ) end it "instrument a redis script call" do @@ -98,14 +95,11 @@ def write(_commands) argv = ["bar"] expect(client.write([:eval, script, keys.size, keys, argv])).to eql("stub_write") - transaction_hash = transaction.to_h - expect(transaction_hash["events"]).to include( - hash_including( - "name" => "query.redis", - "body" => "#{script} ? ?", - "title" => "stub_id" - ) - ) + expect(transaction).to include_event( + "name" => "query.redis", + "body" => "#{script} ? ?", + "title" => "stub_id" + ) end end end diff --git a/spec/lib/appsignal/hooks/resque_spec.rb b/spec/lib/appsignal/hooks/resque_spec.rb index 8758d4311..ed13e53c9 100644 --- a/spec/lib/appsignal/hooks/resque_spec.rb +++ b/spec/lib/appsignal/hooks/resque_spec.rb @@ -53,20 +53,14 @@ def self.perform perform_rescue_job(ResqueTestJob) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => kind_of(String), - "action" => "ResqueTestJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => { - "breadcrumbs" => [], - "tags" => { "queue" => queue } - } - ) - expect(transaction_hash["events"].map { |e| e["name"] }) - .to eql(["perform.resque"]) + expect(transaction).to have_id + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ResqueTestJob#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_metadata + expect(transaction).to_not include_breadcrumbs + expect(transaction).to include_tags("queue" => queue) + expect(transaction).to include_event("name" => "perform.resque") end context "with error" do @@ -76,22 +70,14 @@ def self.perform end.to raise_error(RuntimeError, "resque job error") transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => kind_of(String), - "action" => "ResqueErrorTestJob#perform", - "error" => { - "name" => "RuntimeError", - "message" => "resque job error", - "backtrace" => kind_of(String) - }, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => { - "breadcrumbs" => [], - "tags" => { "queue" => queue } - } - ) + expect(transaction).to have_id + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ResqueErrorTestJob#perform") + expect(transaction).to have_error("RuntimeError", "resque job error") + expect(transaction).to_not include_metadata + expect(transaction).to_not include_breadcrumbs + expect(transaction).to include_tags("queue" => queue) + expect(transaction).to include_event("name" => "perform.resque") end end @@ -115,25 +101,23 @@ def self.perform ) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => kind_of(String), - "action" => "ResqueTestJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => { - "tags" => { "queue" => queue }, - "breadcrumbs" => [], - "params" => [ - "foo", - { - "foo" => "[FILTERED]", - "bar" => "Bar", - "baz" => { "1" => "foo" } - } - ] - } + expect(transaction).to have_id + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ResqueTestJob#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_metadata + expect(transaction).to_not include_breadcrumbs + expect(transaction).to include_tags("queue" => queue) + expect(transaction).to include_event("name" => "perform.resque") + expect(transaction).to include_params( + [ + "foo", + { + "foo" => "[FILTERED]", + "bar" => "Bar", + "baz" => { "1" => "foo" } + } + ] ) end end @@ -173,19 +157,15 @@ def perform(job_data) ) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => kind_of(String), - "action" => "ResqueTestJobByActiveJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => {}, - "sample_data" => { - "breadcrumbs" => [], - "tags" => { "queue" => queue } - # Params will be set by the ActiveJob integration - } - ) + expect(transaction).to have_id + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ResqueTestJobByActiveJob#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_metadata + expect(transaction).to_not include_breadcrumbs + expect(transaction).to include_tags("queue" => queue) + expect(transaction).to include_event("name" => "perform.resque") + expect(transaction).to_not include_params end end end diff --git a/spec/lib/appsignal/hooks/shoryuken_spec.rb b/spec/lib/appsignal/hooks/shoryuken_spec.rb index e80fe111a..3591ce6da 100644 --- a/spec/lib/appsignal/hooks/shoryuken_spec.rb +++ b/spec/lib/appsignal/hooks/shoryuken_spec.rb @@ -37,37 +37,25 @@ def perform_shoryuken_job(&block) expect { perform_shoryuken_job }.to change { created_transactions.length }.by(1) transaction = last_transaction - expect(transaction).to be_completed - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "DemoShoryukenWorker#perform", - "id" => kind_of(String), # AppSignal generated id - "namespace" => Appsignal::Transaction::BACKGROUND_JOB, - "error" => nil - ) - expect(transaction_hash["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_action("DemoShoryukenWorker#perform") + expect(transaction).to_not have_error + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "perform_job.shoryuken", "title" => "" ) - expect(transaction_hash["sample_data"]).to include( - "params" => { "foo" => "Foo", "bar" => "Bar" }, - "metadata" => { - "message_id" => "msg1", - "queue" => queue, - "SentTimestamp" => sent_timestamp - } + expect(transaction).to include_params("foo" => "Foo", "bar" => "Bar") + expect(transaction).to include_sample_metadata( + "message_id" => "msg1", + "queue" => queue, + "SentTimestamp" => sent_timestamp ) - expect(transaction).to have_received(:set_queue_start).with(sent_timestamp) + expect(transaction).to have_queue_start(sent_timestamp) + expect(transaction).to be_completed end context "with parameter filtering" do @@ -82,10 +70,7 @@ def perform_shoryuken_job(&block) it "filters selected arguments" do perform_shoryuken_job - transaction_hash = last_transaction.to_h - expect(transaction_hash["sample_data"]).to include( - "params" => { "foo" => "[FILTERED]", "bar" => "Bar" } - ) + expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar") end end end @@ -96,10 +81,7 @@ def perform_shoryuken_job(&block) it "handles string arguments" do perform_shoryuken_job - transaction_hash = last_transaction.to_h - expect(transaction_hash["sample_data"]).to include( - "params" => { "params" => body } - ) + expect(last_transaction).to include_params("params" => body) end end @@ -109,10 +91,7 @@ def perform_shoryuken_job(&block) it "handles primitive types as arguments" do perform_shoryuken_job - transaction_hash = last_transaction.to_h - expect(transaction_hash["sample_data"]).to include( - "params" => { "params" => body } - ) + expect(last_transaction).to include_params("params" => body) end end end @@ -126,18 +105,11 @@ def perform_shoryuken_job(&block) end.to change { created_transactions.length }.by(1) transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_action("DemoShoryukenWorker#perform") + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_error("ExampleException", "error message") expect(transaction).to be_completed - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "DemoShoryukenWorker#perform", - "id" => kind_of(String), # AppSignal generated id - "namespace" => Appsignal::Transaction::BACKGROUND_JOB, - "error" => { - "name" => "ExampleException", - "message" => "error message", - "backtrace" => kind_of(String) - } - ) end end @@ -171,41 +143,28 @@ def perform_shoryuken_job(&block) end.to change { created_transactions.length }.by(1) transaction = last_transaction - expect(transaction).to be_completed - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "DemoShoryukenWorker#perform", - "id" => kind_of(String), # AppSignal generated id - "namespace" => Appsignal::Transaction::BACKGROUND_JOB, - "error" => nil - ) - expect(transaction_hash["events"].first).to include( - "allocation_count" => kind_of(Integer), + expect(transaction).to have_id + expect(transaction).to have_action("DemoShoryukenWorker#perform") + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to_not have_error + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "perform_job.shoryuken", "title" => "" ) - expect(transaction_hash["sample_data"]).to include( - "params" => { - "msg2" => "foo bar", - "msg1" => { "id" => "123", "foo" => "Foo", "bar" => "Bar" } - }, - "metadata" => { - "batch" => true, - "queue" => "some-funky-queue-name", - "SentTimestamp" => sent_timestamp.to_s # Earliest/oldest timestamp from messages - } + expect(transaction).to include_params( + "msg2" => "foo bar", + "msg1" => { "id" => "123", "foo" => "Foo", "bar" => "Bar" } + ) + expect(transaction).to include_sample_metadata( + "batch" => true, + "queue" => "some-funky-queue-name", + "SentTimestamp" => sent_timestamp.to_s # Earliest/oldest timestamp from messages ) # Queue time based on earliest/oldest timestamp from messages - expect(transaction).to have_received(:set_queue_start).with(sent_timestamp) + expect(transaction).to have_queue_start(sent_timestamp) end end end diff --git a/spec/lib/appsignal/integrations/hanami_spec.rb b/spec/lib/appsignal/integrations/hanami_spec.rb index d4247d157..55c51a514 100644 --- a/spec/lib/appsignal/integrations/hanami_spec.rb +++ b/spec/lib/appsignal/integrations/hanami_spec.rb @@ -159,9 +159,7 @@ def make_request(env) it "does not set the action name" do make_request(env) - expect(transaction.to_h).to include( - "action" => nil - ) + expect(transaction).to_not have_action end end @@ -171,9 +169,7 @@ def make_request(env) it "sets action name on the transaction" do make_request(env) - expect(transaction.to_h).to include( - "action" => "HanamiApp::Actions::Books::Index::TestClass" - ) + expect(transaction).to have_action("HanamiApp::Actions::Books::Index::TestClass") end end end diff --git a/spec/lib/appsignal/integrations/http_spec.rb b/spec/lib/appsignal/integrations/http_spec.rb index a7907fa4e..4e2ca52c3 100644 --- a/spec/lib/appsignal/integrations/http_spec.rb +++ b/spec/lib/appsignal/integrations/http_spec.rb @@ -23,9 +23,8 @@ HTTP.get("http://www.google.com") - transaction_hash = transaction.to_h - expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST) - expect(transaction_hash["events"].first).to include( + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, "name" => "request.http_rb", @@ -38,9 +37,8 @@ HTTP.get("https://www.google.com") - transaction_hash = transaction.to_h - expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST) - expect(transaction_hash["events"].first).to include( + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, "name" => "request.http_rb", @@ -54,7 +52,7 @@ HTTP.get("https://www.google.com", :params => { :q => "Appsignal" }) - expect(transaction.to_h["events"].first).to include( + expect(transaction).to include_event( "body" => "", "title" => "GET https://www.google.com" ) @@ -66,7 +64,7 @@ HTTP.post("https://www.google.com", :json => { :q => "Appsignal" }) - expect(transaction.to_h["events"].first).to include( + expect(transaction).to include_event( "body" => "", "title" => "POST https://www.google.com" ) @@ -83,20 +81,14 @@ HTTP.get("https://www.google.com") end.to raise_error(ExampleException) - transaction_hash = transaction.to_h - expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST) - expect(transaction_hash["events"].first).to include( + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, "name" => "request.http_rb", "title" => "GET https://www.google.com" ) - - expect(transaction_hash["error"]).to include( - "backtrace" => kind_of(String), - "name" => error.class.name, - "message" => error.message - ) + expect(transaction).to have_error(error.class.name, error.message) end end @@ -112,7 +104,7 @@ def to_s HTTP.get(request_uri.new("http://www.google.com")) - expect(transaction.to_h["events"].first).to include( + expect(transaction).to include_event( "name" => "request.http_rb", "title" => "GET http://www.google.com" ) @@ -123,7 +115,7 @@ def to_s HTTP.get(URI("http://www.google.com")) - expect(transaction.to_h["events"].first).to include( + expect(transaction).to include_event( "name" => "request.http_rb", "title" => "GET http://www.google.com" ) @@ -134,7 +126,7 @@ def to_s HTTP.get("http://www.google.com") - expect(transaction.to_h["events"].first).to include( + expect(transaction).to include_event( "name" => "request.http_rb", "title" => "GET http://www.google.com" ) diff --git a/spec/lib/appsignal/integrations/que_spec.rb b/spec/lib/appsignal/integrations/que_spec.rb index a3bc94b64..aa9dc9b04 100644 --- a/spec/lib/appsignal/integrations/que_spec.rb +++ b/spec/lib/appsignal/integrations/que_spec.rb @@ -53,38 +53,27 @@ def perform_que_job(job) perform_que_job(instance) end.to change { created_transactions.length }.by(1) - expect(last_transaction).to be_completed - transaction_hash = last_transaction.to_h - expect(transaction_hash).to include( - "action" => "MyQueJob#run", - "id" => instance_of(String), - "namespace" => Appsignal::Transaction::BACKGROUND_JOB - ) - expect(transaction_hash["error"]).to be_nil - expect(transaction_hash["events"].first).to include( - "allocation_count" => kind_of(Integer), + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_action("MyQueJob#run") + expect(transaction).to_not have_error + expect(transaction).to include_event( "body" => "", "body_format" => Appsignal::EventFormatter::DEFAULT, - "child_allocation_count" => kind_of(Integer), - "child_duration" => kind_of(Float), - "child_gc_duration" => kind_of(Float), "count" => 1, - "gc_duration" => kind_of(Float), - "start" => kind_of(Float), - "duration" => kind_of(Float), "name" => "perform_job.que", "title" => "" ) - expect(transaction_hash["sample_data"]).to include( - "params" => %w[1 birds], - "metadata" => { - "attempts" => 0, - "id" => 123, - "priority" => 100, - "queue" => "dfl", - "run_at" => fixed_time.to_s - } + expect(transaction).to include_params(%w[1 birds]) + expect(transaction).to include_sample_metadata( + "attempts" => 0, + "id" => 123, + "priority" => 100, + "queue" => "dfl", + "run_at" => fixed_time.to_s ) + expect(transaction).to be_completed end end @@ -100,28 +89,20 @@ def perform_que_job(job) end.to raise_error(ExampleException) end.to change { created_transactions.length }.by(1) - expect(last_transaction).to be_completed - transaction_hash = last_transaction.to_h - expect(transaction_hash).to include( - "action" => "MyQueJob#run", - "id" => instance_of(String), - "namespace" => Appsignal::Transaction::BACKGROUND_JOB - ) - expect(transaction_hash["error"]).to include( - "backtrace" => kind_of(String), - "name" => error.class.name, - "message" => error.message - ) - expect(transaction_hash["sample_data"]).to include( - "params" => %w[1 birds], - "metadata" => { - "attempts" => 0, - "id" => 123, - "priority" => 100, - "queue" => "dfl", - "run_at" => fixed_time.to_s - } + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_action("MyQueJob#run") + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_error(error.class.name, error.message) + expect(transaction).to include_params(%w[1 birds]) + expect(transaction).to include_sample_metadata( + "attempts" => 0, + "id" => 123, + "priority" => 100, + "queue" => "dfl", + "run_at" => fixed_time.to_s ) + expect(transaction).to be_completed end end @@ -133,28 +114,20 @@ def perform_que_job(job) expect { perform_que_job(instance) }.to change { created_transactions.length }.by(1) - expect(last_transaction).to be_completed - transaction_hash = last_transaction.to_h - expect(transaction_hash).to include( - "action" => "MyQueJob#run", - "id" => instance_of(String), - "namespace" => Appsignal::Transaction::BACKGROUND_JOB - ) - expect(transaction_hash["error"]).to include( - "backtrace" => kind_of(String), - "name" => error.class.name, - "message" => error.message - ) - expect(transaction_hash["sample_data"]).to include( - "params" => %w[1 birds], - "metadata" => { - "attempts" => 0, - "id" => 123, - "priority" => 100, - "queue" => "dfl", - "run_at" => fixed_time.to_s - } + transaction = last_transaction + expect(transaction).to have_id + expect(transaction).to have_action("MyQueJob#run") + expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB) + expect(transaction).to have_error(error.class.name, error.message) + expect(transaction).to include_params(%w[1 birds]) + expect(transaction).to include_sample_metadata( + "attempts" => 0, + "id" => 123, + "priority" => 100, + "queue" => "dfl", + "run_at" => fixed_time.to_s ) + expect(transaction).to be_completed end end @@ -170,9 +143,9 @@ def run(*_args) it "uses the custom action" do perform_que_job(instance) - expect(last_transaction).to be_completed - transaction_hash = last_transaction.to_h - expect(transaction_hash).to include("action" => "MyCustomJob#perform") + transaction = last_transaction + expect(transaction).to have_action("MyCustomJob#perform") + expect(transaction).to be_completed end end end diff --git a/spec/lib/appsignal/integrations/railtie_spec.rb b/spec/lib/appsignal/integrations/railtie_spec.rb index 7a289a8b9..3f9021884 100644 --- a/spec/lib/appsignal/integrations/railtie_spec.rb +++ b/spec/lib/appsignal/integrations/railtie_spec.rb @@ -136,8 +136,8 @@ def subscribe(subscriber) it "does nothing" do with_rails_error_reporter do expect do - Rails.error.record { raise ExampleStandardError } - end.to raise_error(ExampleStandardError) + Rails.error.record { raise ExampleStandardError, "error message" } + end.to raise_error(ExampleStandardError, "error message") end expect(created_transactions).to be_empty @@ -156,24 +156,15 @@ def subscribe(subscriber) with_rails_error_reporter do with_current_transaction current_transaction do - Rails.error.handle { raise ExampleStandardError } + Rails.error.handle { raise ExampleStandardError, "error message" } transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "CustomAction", - "namespace" => "custom", - "error" => { - "name" => "ExampleStandardError", - "message" => "ExampleStandardError", - "backtrace" => kind_of(String) - }, - "sample_data" => hash_including( - "tags" => hash_including( - "duplicated_tag" => "duplicated value", - "severity" => "warning" - ) - ) + expect(transaction).to have_namespace("custom") + expect(transaction).to have_action("CustomAction") + expect(transaction).to have_error("ExampleStandardError", "error message") + expect(transaction).to include_tags( + "duplicated_tag" => "duplicated value", + "severity" => "warning" ) end end @@ -188,16 +179,10 @@ def subscribe(subscriber) given_context = { :tag1 => "value1", :tag2 => "value2" } Rails.error.handle(:context => given_context) { raise ExampleStandardError } - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including( - "tag1" => "value1", - "tag2" => "value2", - "severity" => "warning" - ) - ) + expect(last_transaction).to include_tags( + "tag1" => "value1", + "tag2" => "value2", + "severity" => "warning" ) end end @@ -219,14 +204,9 @@ def subscribe(subscriber) Rails.error.handle(:context => given_context) { raise ExampleStandardError } transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "custom_data" => { - "array" => [1, 2], - "hash" => { "one" => 1, "two" => 2 } - } - ) + expect(transaction).to include_custom_data( + "array" => [1, 2], + "hash" => { "one" => 1, "two" => 2 } ) end end @@ -245,11 +225,8 @@ def subscribe(subscriber) Rails.error.handle(:context => given_context) { raise ExampleStandardError } transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "namespace" => "context", - "action" => "ContextAction" - ) + expect(transaction).to have_namespace("context") + expect(transaction).to have_action("ContextAction") end end end @@ -301,17 +278,9 @@ def arguments end transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ExampleRailsControllerMock#index", - "metadata" => hash_including( - "path" => "path", - "method" => "GET" - ), - "sample_data" => hash_including( - "params" => { "user_id" => 123, "password" => "[FILTERED]" } - ) - ) + expect(transaction).to have_action("ExampleRailsControllerMock#index") + expect(transaction).to include_metadata("path" => "path", "method" => "GET") + expect(transaction).to include_params("user_id" => 123, "password" => "[FILTERED]") end it "sets no action if no execution context is present" do @@ -320,11 +289,7 @@ def arguments Rails.error.handle { raise ExampleStandardError } end - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => nil - ) + expect(last_transaction).to_not have_action end end @@ -340,16 +305,10 @@ def arguments Rails.error.handle(:context => given_context) { raise ExampleStandardError } end - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => hash_including( - "tag1" => "value1", - "tag2" => "value2", - "severity" => "warning" - ) - ) + expect(last_transaction).to include_tags( + "tag1" => "value1", + "tag2" => "value2", + "severity" => "warning" ) end end diff --git a/spec/lib/appsignal/integrations/sidekiq_spec.rb b/spec/lib/appsignal/integrations/sidekiq_spec.rb index 07d3271cd..b7b42d1bd 100644 --- a/spec/lib/appsignal/integrations/sidekiq_spec.rb +++ b/spec/lib/appsignal/integrations/sidekiq_spec.rb @@ -20,17 +20,11 @@ def call_handler end def expect_error_on_transaction - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleStandardError", - "message" => "uh oh", - "backtrace" => kind_of(String) - ) - ) + expect(last_transaction).to have_error("ExampleStandardError", "uh oh") end def expect_no_error_on_transaction - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end context "when sidekiq_report_errors = none" do @@ -90,21 +84,13 @@ def expect_report_internal_error described_class.new.call(exception, job_context) end.to(change { created_transactions.count }.by(1)) - transaction_hash = last_transaction.to_h - expect(transaction_hash).to include( - "action" => "SidekiqInternal", - "error" => hash_including( - "name" => "ExampleStandardError", - "message" => "uh oh", - "backtrace" => kind_of(String) - ) - ) - expect(transaction_hash["sample_data"]).to include( - "params" => { - "jobstr" => "{ bad json }" - } + transaction = last_transaction + expect(transaction).to have_action("SidekiqInternal") + expect(transaction).to have_error("ExampleStandardError", "uh oh") + expect(transaction).to include_params( + "jobstr" => "{ bad json }" ) - expect(transaction_hash["metadata"]).to include( + expect(transaction).to include_metadata( "sidekiq_error" => "Sidekiq internal error!" ) end @@ -149,17 +135,11 @@ def call_handler end def expect_error_on_transaction - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleStandardError", - "message" => "uh oh", - "backtrace" => kind_of(String) - ) - ) + expect(last_transaction).to have_error("ExampleStandardError", "uh oh") end def expect_no_error_on_transaction - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end context "when sidekiq_report_errors = none" do @@ -270,9 +250,8 @@ class DelayedTestClass; end it "filters selected arguments" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["sample_data"]).to include( - "params" => [ + expect(transaction).to include_params( + [ "foo", { "foo" => "[FILTERED]", @@ -293,10 +272,7 @@ class DelayedTestClass; end it "replaces the last argument (the secret bag) with an [encrypted data] string" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["sample_data"]).to include( - "params" => expected_args << "[encrypted data]" - ) + expect(transaction).to include_params(expected_args << "[encrypted data]") end end @@ -319,11 +295,8 @@ class DelayedTestClass; end it "uses the delayed class and method name for the action" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method") - expect(transaction_hash["sample_data"]).to include( - "params" => ["bar" => "baz"] - ) + expect(transaction).to have_action("DelayedTestClass.foo_method") + expect(transaction).to include_params(["bar" => "baz"]) end context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do @@ -332,9 +305,8 @@ class DelayedTestClass; end it "logs a warning and uses the default argument" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform") - expect(transaction_hash["sample_data"]).to include("params" => []) + expect(transaction).to have_action("Sidekiq::Extensions::DelayedClass#perform") + expect(transaction).to include_params([]) expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML") end end @@ -359,11 +331,8 @@ class DelayedTestClass; end it "uses the delayed class and method name for the action" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method") - expect(transaction_hash["sample_data"]).to include( - "params" => ["bar" => "baz"] - ) + expect(transaction).to have_action("DelayedTestClass#foo_method") + expect(transaction).to include_params(["bar" => "baz"]) end context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do @@ -372,9 +341,8 @@ class DelayedTestClass; end it "logs a warning and uses the default argument" do perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform") - expect(transaction_hash["sample_data"]).to include("params" => []) + expect(transaction).to have_action("Sidekiq::Extensions::DelayedModel#perform") + expect(transaction).to include_params([]) expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML") end end @@ -394,31 +362,20 @@ class DelayedTestClass; end perform_sidekiq_job { raise error, "uh oh" } end.to raise_error(error) - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => jid, - "action" => "TestClass#perform", - "error" => { - "name" => "ExampleException", - "message" => "uh oh", - # TODO: backtrace should be an Array of Strings - # https://github.com/appsignal/appsignal-agent/issues/294 - "backtrace" => kind_of(String) - }, - "metadata" => { - "extra" => "data", - "queue" => "default", - "retry_count" => "0" - }, - "namespace" => namespace, - "sample_data" => { - "environment" => {}, - "params" => expected_args, - "tags" => {}, - "breadcrumbs" => [] - } + expect(transaction).to have_id(jid) + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to have_error("ExampleException", "uh oh") + expect(transaction).to include_metadata( + "extra" => "data", + "queue" => "default", + "retry_count" => "0" ) - expect_transaction_to_have_sidekiq_event(transaction_hash) + expect(transaction).to_not include_environment + expect(transaction).to include_params(expected_args) + expect(transaction).to_not include_tags + expect(transaction).to_not include_breadcrumbs + expect_transaction_to_have_sidekiq_event(transaction) end end @@ -438,17 +395,17 @@ class DelayedTestClass; end end expect(created_transactions.count).to eq(2) - expected_transaction = { - "namespace" => "background_job", - "action" => "TestClass#perform", - "sample_data" => hash_including( - "tags" => hash_including("test_tag" => "value") - ) - } - sidekiq_transaction = created_transactions.first.to_h - error_reporter_transaction = created_transactions.last.to_h - expect(sidekiq_transaction).to include(expected_transaction) - expect(error_reporter_transaction).to include(expected_transaction) + tags = { "test_tag" => "value" } + sidekiq_transaction = created_transactions.first + error_reporter_transaction = created_transactions.last + + expect(sidekiq_transaction).to have_namespace("background_job") + expect(sidekiq_transaction).to have_action("TestClass#perform") + expect(sidekiq_transaction).to include_tags(tags) + + expect(error_reporter_transaction).to have_namespace("background_job") + expect(error_reporter_transaction).to have_action("TestClass#perform") + expect(error_reporter_transaction).to include_tags(tags) end end end @@ -461,30 +418,21 @@ class DelayedTestClass; end .with("sidekiq_queue_job_count", 1, { :queue => "default", :status => :processed }) perform_sidekiq_job - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "id" => jid, - "action" => "TestClass#perform", - "error" => nil, - "metadata" => { - "extra" => "data", - "queue" => "default", - "retry_count" => "0" - }, - "namespace" => namespace, - "sample_data" => { - "environment" => {}, - "params" => expected_args, - "tags" => {}, - "breadcrumbs" => [] - } - ) - # TODO: Not available in transaction.to_h yet. - # https://github.com/appsignal/appsignal-agent/issues/293 - expect(transaction.request.env).to eq( - :queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f + expect(transaction).to have_id(jid) + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("TestClass#perform") + expect(transaction).to_not have_error + expect(transaction).to_not include_tags + expect(transaction).to_not include_environment + expect(transaction).to_not include_breadcrumbs + expect(transaction).to_not include_params(expected_args) + expect(transaction).to include_metadata( + "extra" => "data", + "queue" => "default", + "retry_count" => "0" ) - expect_transaction_to_have_sidekiq_event(transaction_hash) + expect(transaction).to have_queue_start(Time.parse("2001-01-01 10:00:00UTC").to_i * 1000) + expect_transaction_to_have_sidekiq_event(transaction) end end @@ -505,10 +453,9 @@ def transaction last_transaction end - def expect_transaction_to_have_sidekiq_event(transaction_hash) - events = transaction_hash["events"] - expect(events.count).to eq(1) - expect(events.first).to include( + def expect_transaction_to_have_sidekiq_event(transaction) + expect(transaction.to_h["events"].count).to eq(1) + expect(transaction).to include_event( "name" => "perform_job.sidekiq", "title" => "", "count" => 1, @@ -621,25 +568,18 @@ def perform(*_args) perform_sidekiq_job(ActiveJobSidekiqTestJob, given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActiveJobSidekiqTestJob#perform", - "error" => nil, - "namespace" => namespace, - "metadata" => hash_including( - "queue" => "default" - ), - "sample_data" => hash_including( - "environment" => {}, - "params" => [expected_args], - "tags" => expected_tags.merge("queue" => "default") - ) - ) - expect(transaction.request.env).to eq(:queue_start => time.to_f) - events = transaction_hash["events"] + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ActiveJobSidekiqTestJob#perform") + expect(transaction).to_not have_error + expect(transaction).to include_metadata("queue" => "default") + expect(transaction).to_not include_environment + expect(transaction).to include_params([expected_args]) + expect(transaction).to include_tags(expected_tags.merge("queue" => "default")) + expect(transaction).to have_queue_start(time.to_i * 1000) + + events = transaction.to_h["events"] .sort_by { |e| e["start"] } .map { |event| event["name"] } - expect(events).to eq(expected_perform_events) end @@ -650,29 +590,18 @@ def perform(*_args) end.to raise_error(RuntimeError, "uh oh") transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActiveJobSidekiqErrorTestJob#perform", - "error" => { - "name" => "RuntimeError", - "message" => "uh oh", - "backtrace" => kind_of(String) - }, - "namespace" => namespace, - "metadata" => hash_including( - "queue" => "default" - ), - "sample_data" => hash_including( - "environment" => {}, - "params" => [expected_args], - "tags" => expected_tags.merge("queue" => "default") - ) - ) - expect(transaction.request.env).to eq(:queue_start => time.to_f) - events = transaction_hash["events"] + expect(transaction).to have_namespace(namespace) + expect(transaction).to have_action("ActiveJobSidekiqErrorTestJob#perform") + expect(transaction).to have_error("RuntimeError", "uh oh") + expect(transaction).to include_metadata("queue" => "default") + expect(transaction).to_not include_environment + expect(transaction).to include_params([expected_args]) + expect(transaction).to include_tags(expected_tags.merge("queue" => "default")) + expect(transaction).to have_queue_start(time.to_i * 1000) + + events = transaction.to_h["events"] .sort_by { |e| e["start"] } .map { |event| event["name"] } - expect(events).to eq(expected_perform_events) end end @@ -691,13 +620,10 @@ def welcome(*args) perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "ActionMailerSidekiqTestJob#welcome", - "sample_data" => hash_including( - "params" => ["ActionMailerSidekiqTestJob", "welcome", - "deliver_now"] + expected_wrapped_args - ) + expect(transaction).to have_action("ActionMailerSidekiqTestJob#welcome") + expect(transaction).to include_params( + ["ActionMailerSidekiqTestJob", "welcome", + "deliver_now"] + expected_wrapped_args ) end end diff --git a/spec/lib/appsignal/rack/abstract_middleware_spec.rb b/spec/lib/appsignal/rack/abstract_middleware_spec.rb index 2ba9ee93c..54cd2ed62 100644 --- a/spec/lib/appsignal/rack/abstract_middleware_spec.rb +++ b/spec/lib/appsignal/rack/abstract_middleware_spec.rb @@ -1,5 +1,5 @@ describe Appsignal::Rack::AbstractMiddleware do - let(:app) { double(:call => true) } + let(:app) { DummyApp.new } let(:request_path) { "/some/path" } let(:env) do Rack::MockRequest.env_for( @@ -9,224 +9,184 @@ ) end let(:options) { {} } - let(:middleware) { Appsignal::Rack::AbstractMiddleware.new(app, options) } + let(:middleware) { described_class.new(app, options) } before(:context) { start_agent } around { |example| keep_transactions { example.run } } - def make_request(env) + def make_request middleware.call(env) end - def make_request_with_error(env, error_class, error_message) - expect { make_request(env) }.to raise_error(error_class, error_message) + def make_request_with_error(error_class, error_message) + expect { make_request }.to raise_error(error_class, error_message) end describe "#call" do - context "when appsignal is not active" do + context "when not active" do before { allow(Appsignal).to receive(:active?).and_return(false) } - it "does not instrument requests" do - expect { make_request(env) }.to_not(change { created_transactions.count }) + it "does not instrument the request" do + expect { make_request }.to_not(change { created_transactions.count }) end it "calls the next middleware in the stack" do - expect(app).to receive(:call).with(env) - make_request(env) + make_request + expect(app).to be_called end end context "when appsignal is active" do before { allow(Appsignal).to receive(:active?).and_return(true) } - it "calls the next middleware in the stack" do - make_request(env) + it "creates a transaction for the request" do + expect { make_request }.to(change { created_transactions.count }.by(1)) - expect(app).to have_received(:call).with(env) + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) end - context "without an exception" do - it "create a transaction for the request" do - expect { make_request(env) }.to(change { created_transactions.count }.by(1)) + context "without an error" do + before { make_request } - expect(last_transaction.to_h).to include( - "namespace" => Appsignal::Transaction::HTTP_REQUEST, - "action" => nil, - "error" => nil - ) + it "calls the next middleware in the stack" do + expect(app).to be_called end - it "reports a process.abstract event" do - make_request(env) - - expect(last_transaction.to_h).to include( - "events" => [ - hash_including( - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "count" => 1, - "name" => "process.abstract", - "title" => "" - ) - ] - ) + it "does not record an error" do + expect(last_transaction).to_not have_error end - context "when instrument_span_name option is nil" do - let(:options) { { :instrument_span_name => nil } } - - it "does not report an event" do - make_request(env) - - expect(last_transaction.to_h).to include( - "events" => [] - ) - end + it "records an instrumentation event" do + expect(last_transaction).to include_event(:name => "process.abstract") end it "completes the transaction" do - make_request(env) expect(last_transaction).to be_completed + expect(Appsignal::Transaction.current) + .to be_kind_of(Appsignal::Transaction::NilTransaction) + end + + context "when instrument_span_name option is nil" do + let(:options) { { :instrument_span_name => nil } } + + it "does not record an instrumentation event" do + expect(last_transaction).to_not include_events + end end end - context "with an exception" do + context "with an error" do let(:error) { ExampleException.new("error message") } let(:app) { lambda { |_env| raise ExampleException, "error message" } } - before do - expect { make_request_with_error(env, ExampleException, "error message") } - .to(change { created_transactions.count }.by(1)) - end - it "creates a transaction for the request and records the exception" do - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message", - "backtrace" => kind_of(String) - ) - ) - end + it "create a transaction for the request" do + expect { make_request_with_error(ExampleException, "error message") } + .to(change { created_transactions.count }.by(1)) - it "completes the transaction" do - expect(last_transaction).to be_completed + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) end - context "with :report_errors set to false" do - let(:app) { lambda { |_env| raise ExampleException, "error message" } } - let(:options) { { :report_errors => false } } - - it "does not record the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + describe "error" do + before do + make_request_with_error(ExampleException, "error message") + end - expect(last_transaction.to_h).to include("error" => nil) + it "records the error" do + expect(last_transaction).to have_error("ExampleException", "error message") end - end - context "with :report_errors set to true" do - let(:app) { lambda { |_env| raise ExampleException, "error message" } } - let(:options) { { :report_errors => true } } + it "completes the transaction" do + expect(last_transaction).to be_completed + expect(Appsignal::Transaction.current) + .to be_kind_of(Appsignal::Transaction::NilTransaction) + end - it "records the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + context "with :report_errors set to false" do + let(:app) { lambda { |_env| raise ExampleException, "error message" } } + let(:options) { { :report_errors => false } } - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + it "does not record the exception on the transaction" do + expect(last_transaction).to_not have_error + end end - end - context "with :report_errors set to a lambda that returns false" do - let(:app) { lambda { |_env| raise ExampleException, "error message" } } - let(:options) { { :report_errors => lambda { |_env| false } } } + context "with :report_errors set to true" do + let(:app) { lambda { |_env| raise ExampleException, "error message" } } + let(:options) { { :report_errors => true } } - it "does not record the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") - - expect(last_transaction.to_h).to include("error" => nil) + it "records the exception on the transaction" do + expect(last_transaction).to have_error("ExampleException", "error message") + end end - end - context "with :report_errors set to a lambda that returns true" do - let(:app) { lambda { |_env| raise ExampleException, "error message" } } - let(:options) { { :report_errors => lambda { |_env| true } } } + context "with :report_errors set to a lambda that returns false" do + let(:app) { lambda { |_env| raise ExampleException, "error message" } } + let(:options) { { :report_errors => lambda { |_env| false } } } - it "records the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "does not record the exception on the transaction" do + expect(last_transaction).to_not have_error + end + end + + context "with :report_errors set to a lambda that returns true" do + let(:app) { lambda { |_env| raise ExampleException, "error message" } } + let(:options) { { :report_errors => lambda { |_env| true } } } - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + it "records the exception on the transaction" do + expect(last_transaction).to have_error("ExampleException", "error message") + end end end end context "without action name metadata" do it "reports no action name" do - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => nil) + expect(last_transaction).to_not have_action end end context "with appsignal.route env" do - before do - env["appsignal.route"] = "POST /my-route" - end - it "reports the appsignal.route value as the action name" do - make_request(env) + env["appsignal.route"] = "POST /my-route" + make_request - expect(last_transaction.to_h).to include("action" => "POST /my-route") + expect(last_transaction).to have_action("POST /my-route") end end context "with appsignal.action env" do - before do - env["appsignal.action"] = "POST /my-action" - end - it "reports the appsignal.route value as the action name" do - make_request(env) + env["appsignal.action"] = "POST /my-action" + make_request - expect(last_transaction.to_h).to include("action" => "POST /my-action") + expect(last_transaction).to have_action("POST /my-action") end end describe "request metadata" do - before do + it "sets request metadata" do env.merge!("PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET") - end + make_request - it "sets request metadata" do - make_request(env) - - expect(last_transaction.to_h).to include( - "metadata" => { - "method" => "GET", - "path" => "/some/path" - }, - "sample_data" => hash_including( - "environment" => hash_including( - "REQUEST_METHOD" => "GET", - "PATH_INFO" => "/some/path" - # and more, but we don't need to test Rack mock defaults - ) - ) + expect(last_transaction).to include_metadata( + "method" => "GET", + "path" => "/some/path" + ) + expect(last_transaction).to include_environment( + "REQUEST_METHOD" => "GET", + "PATH_INFO" => "/some/path" + # and more, but we don't need to test Rack mock defaults ) end context "with an invalid HTTP request method" do it "stores the invalid HTTP request method" do - make_request(env.merge("REQUEST_METHOD" => "FOO")) + env["REQUEST_METHOD"] = "FOO" + make_request - expect(last_transaction.to_h["metadata"]).to include("method" => "FOO") + expect(last_transaction).to include_metadata("method" => "FOO") end end @@ -239,54 +199,45 @@ def request_method let(:options) { { :request_class => BrokenRequestMethodRequest } } it "does not store the invalid HTTP request method" do - make_request(env.merge("REQUEST_METHOD" => "FOO")) + env["REQUEST_METHOD"] = "FOO" + make_request - expect(last_transaction.to_h["metadata"]).to_not have_key("method") + expect(last_transaction).to_not include_metadata("method" => anything) end end it "sets request parameters" do - make_request(env) - - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "params" => hash_including( - "page" => "2", - "query" => "lorem" - ) - ) + make_request + + expect(last_transaction).to include_params( + "page" => "2", + "query" => "lorem" ) end context "when setting custom params" do let(:app) do - lambda { |_env| Appsignal::Transaction.current.set_params("custom" => "param") } + DummyApp.new do |_env| + Appsignal::Transaction.current.set_params("custom" => "param") + end end it "allow custom request parameters to be set" do - make_request(env) - - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "params" => hash_including( - "custom" => "param" - ) - ) - ) + make_request + + expect(last_transaction).to include_params("custom" => "param") end end end context "with queue start header" do let(:queue_start_time) { fixed_time * 1_000 } - before do - env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds - end it "sets the queue start" do - make_request(env) + env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds + make_request - expect(last_transaction.ext.queue_start).to eq(queue_start_time) + expect(last_transaction).to have_queue_start(queue_start_time) end end @@ -316,13 +267,9 @@ def filtered_params end it "uses the overridden request class and params method to fetch params" do - make_request(env) + make_request - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "params" => { "abc" => "123" } - ) - ) + expect(last_transaction).to include_params("abc" => "123") end end @@ -332,23 +279,23 @@ def filtered_params end it "uses the existing transaction" do - make_request(env) + make_request - expect { make_request(env) }.to_not(change { created_transactions.count }) + expect { make_request }.to_not(change { created_transactions.count }) end - context "with exception" do + context "with error" do let(:app) { lambda { |_env| raise ExampleException, "error message" } } - it "doesn't record the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "doesn't record the error on the transaction" do + make_request_with_error(ExampleException, "error message") - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end it "doesn't complete the existing transaction" do - make_request(env) + make_request expect(env[Appsignal::Rack::APPSIGNAL_TRANSACTION]).to_not be_completed end @@ -357,9 +304,9 @@ def filtered_params it "does not overwrite the action name" do env[Appsignal::Rack::APPSIGNAL_TRANSACTION].set_action("My custom action") env["appsignal.action"] = "POST /my-action" - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => "My custom action") + expect(last_transaction).to have_action("My custom action") end end @@ -367,10 +314,10 @@ def filtered_params let(:app) { lambda { |_env| raise ExampleException, "error message" } } let(:options) { { :report_errors => false } } - it "does not record the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "does not record the error on the transaction" do + make_request_with_error(ExampleException, "error message") - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end @@ -378,15 +325,10 @@ def filtered_params let(:app) { lambda { |_env| raise ExampleException, "error message" } } let(:options) { { :report_errors => true } } - it "records the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "records the error on the transaction" do + make_request_with_error(ExampleException, "error message") - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + expect(last_transaction).to have_error("ExampleException", "error message") end end @@ -395,9 +337,9 @@ def filtered_params let(:options) { { :report_errors => lambda { |_env| false } } } it "does not record the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + make_request_with_error(ExampleException, "error message") - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end @@ -405,15 +347,10 @@ def filtered_params let(:app) { lambda { |_env| raise ExampleException, "error message" } } let(:options) { { :report_errors => lambda { |_env| true } } } - it "records the exception on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "records the error on the transaction" do + make_request_with_error(ExampleException, "error message") - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + expect(last_transaction).to have_error("ExampleException", "error message") end end end diff --git a/spec/lib/appsignal/rack/event_handler_spec.rb b/spec/lib/appsignal/rack/event_handler_spec.rb index 202a17e2e..aec2d237b 100644 --- a/spec/lib/appsignal/rack/event_handler_spec.rb +++ b/spec/lib/appsignal/rack/event_handler_spec.rb @@ -31,12 +31,10 @@ def on_error(error) expect { on_start }.to change { created_transactions.length }.by(1) transaction = last_transaction - expect(transaction.to_h).to include( - "id" => kind_of(String), - "namespace" => Appsignal::Transaction::HTTP_REQUEST - ) + expect(transaction).to have_id + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) - expect(Appsignal::Transaction.current).to eq(last_transaction) + expect(Appsignal::Transaction.current).to eq(transaction) end context "when the handler is nested in another EventHandler" do @@ -127,13 +125,7 @@ def on_error(error) on_start on_error(ExampleStandardError.new("the error")) - expect(last_transaction.to_h).to include( - "error" => { - "name" => "ExampleStandardError", - "message" => "the error", - "backtrace" => kind_of(String) - } - ) + expect(last_transaction).to have_error("ExampleStandardError", "the error") end context "when the handler is nested in another EventHandler" do @@ -141,7 +133,7 @@ def on_error(error) on_start described_class.new.on_error(request, response, ExampleStandardError.new("the error")) - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end @@ -174,11 +166,9 @@ def on_finish(given_request = request, given_response = response) on_finish - expect(last_transaction.to_h).to include( - "action" => nil, - "sample_data" => {}, - "events" => [] - ) + expect(last_transaction).to_not have_action + expect(last_transaction).to_not include_events + expect(last_transaction).to include("sample_data" => {}) expect(last_transaction).to_not be_completed end @@ -186,18 +176,12 @@ def on_finish(given_request = request, given_response = response) on_start on_finish - expect(last_transaction.to_h).to include( - # The action is not set on purpose, as we can't set a normalized route - # It requires the app to set an action name - "action" => nil, - "sample_data" => hash_including( - "environment" => { - "REQUEST_METHOD" => "GET", - "PATH_INFO" => "/path" - } - ) + expect(last_transaction).to_not have_action + expect(last_transaction).to include_environment( + "REQUEST_METHOD" => "GET", + "PATH_INFO" => "/path" ) - expect(last_transaction.ext.queue_start).to eq(queue_start_time) + expect(last_transaction).to have_queue_start(queue_start_time) expect(last_transaction).to be_completed end @@ -206,18 +190,14 @@ def on_finish(given_request = request, given_response = response) on_start on_finish(request, nil) - expect(last_transaction.to_h).to include( - # The action is not set on purpose, as we can't set a normalized route - # It requires the app to set an action name - "action" => nil, - "sample_data" => hash_including( - "environment" => { - "REQUEST_METHOD" => "GET", - "PATH_INFO" => "/path" - } - ) + # The action is not set on purpose, as we can't set a normalized route + # It requires the app to set an action name + expect(last_transaction).to_not have_action + expect(last_transaction).to include_environment( + "REQUEST_METHOD" => "GET", + "PATH_INFO" => "/path" ) - expect(last_transaction.ext.queue_start).to eq(queue_start_time) + expect(last_transaction).to have_queue_start(queue_start_time) expect(last_transaction).to be_completed end @@ -225,7 +205,7 @@ def on_finish(given_request = request, given_response = response) on_start on_finish(request, nil) - expect(last_transaction.to_h.dig("sample_data", "tags")).to_not have_key("response_status") + expect(last_transaction).to_not include_tags("response_status" => anything) end it "does not report a response_status counter metric" do @@ -242,11 +222,7 @@ def on_finish(given_request = request, given_response = response) on_error(ExampleStandardError.new("the error")) on_finish(request, nil) - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "tags" => { "response_status" => 500 } - ) - ) + expect(last_transaction).to include_tags("response_status" => 500) end it "increments the response status counter for response status 500" do @@ -283,12 +259,10 @@ def on_finish(given_request = request, given_response = response) on_start described_class.new.on_finish(request, response) - expect(last_transaction.to_h).to include( - "action" => nil, - "metadata" => {}, - "sample_data" => {}, - "events" => [] - ) + expect(last_transaction).to_not have_action + expect(last_transaction).to_not include_metadata + expect(last_transaction).to_not include_events + expect(last_transaction.to_h).to include("sample_data" => {}) expect(last_transaction).to_not be_completed end end @@ -298,25 +272,14 @@ def on_finish(given_request = request, given_response = response) last_transaction.set_action("My action") on_finish - expect(last_transaction.to_h).to include( - "action" => "My action" - ) + expect(last_transaction).to have_action("My action") end it "finishes the process_request.rack event" do on_start on_finish - expect(last_transaction.to_h).to include( - "events" => [ - hash_including( - "name" => "process_request.rack", - "title" => "", - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT - ) - ] - ) + expect(last_transaction).to include_event("name" => "process_request.rack") end context "with response" do @@ -324,11 +287,7 @@ def on_finish(given_request = request, given_response = response) on_start on_finish - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "tags" => { "response_status" => 200 } - ) - ) + expect(last_transaction).to include_tags("response_status" => 200) end it "increments the response status counter for response status" do @@ -345,11 +304,7 @@ def on_finish(given_request = request, given_response = response) on_error(ExampleStandardError.new("the error")) on_finish - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "tags" => { "response_status" => 200 } - ) - ) + expect(last_transaction).to include_tags("response_status" => 200) end it "increments the response status counter based on the response" do diff --git a/spec/lib/appsignal/rack/generic_instrumentation_spec.rb b/spec/lib/appsignal/rack/generic_instrumentation_spec.rb index fef5f1b7b..927562cb3 100644 --- a/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +++ b/spec/lib/appsignal/rack/generic_instrumentation_spec.rb @@ -14,17 +14,7 @@ def make_request(env) it "reports a process_action.generic event" do make_request(env) - expect(last_transaction.to_h).to include( - "events" => [ - hash_including( - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "count" => 1, - "name" => "process_action.generic", - "title" => "" - ) - ] - ) + expect(last_transaction).to include_event("name" => "process_action.generic") end end @@ -32,7 +22,7 @@ def make_request(env) it "reports 'unknown' as the action name" do make_request(env) - expect(last_transaction.to_h).to include("action" => "unknown") + expect(last_transaction).to have_action("unknown") end end end diff --git a/spec/lib/appsignal/rack/grape_middleware_spec.rb b/spec/lib/appsignal/rack/grape_middleware_spec.rb index bfa3a35c3..4312934d8 100644 --- a/spec/lib/appsignal/rack/grape_middleware_spec.rb +++ b/spec/lib/appsignal/rack/grape_middleware_spec.rb @@ -83,13 +83,7 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets the error" do make_request_with_exception(env, ExampleException, "error message") - expect(last_transaction.to_h).to include( - "error" => { - "name" => "ExampleException", - "message" => "error message", - "backtrace" => kind_of(String) - } - ) + expect(last_transaction).to have_error("ExampleException", "error message") end context "with env['grape.skip_appsignal_error'] = true" do @@ -106,7 +100,7 @@ def make_request_with_exception(env, exception_class, exception_message) it "does not add the error" do make_request_with_exception(env, ExampleException, "error message") - expect(last_transaction).to include("error" => nil) + expect(last_transaction).to_not have_error end end end @@ -129,13 +123,8 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets non-unique route path" do make_request(env) - expect(last_transaction.to_h).to include( - "action" => "GET::GrapeExample::Api#/hello", - "metadata" => { - "path" => "/hello", - "method" => "GET" - } - ) + expect(last_transaction).to have_action("GET::GrapeExample::Api#/hello") + expect(last_transaction).to include_metadata("path" => "/hello", "method" => "GET") end end @@ -162,13 +151,8 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets non-unique route_param path" do make_request(env) - expect(last_transaction.to_h).to include( - "action" => "GET::GrapeExample::Api#/users/:id/", - "metadata" => { - "path" => "/users/:id/", - "method" => "GET" - } - ) + expect(last_transaction).to have_action("GET::GrapeExample::Api#/users/:id/") + expect(last_transaction).to include_metadata("path" => "/users/:id/", "method" => "GET") end end @@ -190,13 +174,9 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets namespaced path" do make_request(env) - expect(last_transaction.to_h).to include( - "action" => "POST::GrapeExample::Api#/v1/beta/ping", - "metadata" => { - "path" => "/v1/beta/ping", - "method" => "POST" - } - ) + expect(last_transaction).to have_action("POST::GrapeExample::Api#/v1/beta/ping") + expect(last_transaction).to include_metadata("path" => "/v1/beta/ping", + "method" => "POST") end end @@ -218,12 +198,10 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets namespaced path" do make_request(env) - expect(last_transaction.to_h).to include( - "action" => "POST::GrapeExample::Api#/v1/beta/ping", - "metadata" => { - "path" => "/v1/beta/ping", - "method" => "POST" - } + expect(last_transaction).to have_action("POST::GrapeExample::Api#/v1/beta/ping") + expect(last_transaction).to include_metadata( + "path" => "/v1/beta/ping", + "method" => "POST" ) end end @@ -245,13 +223,9 @@ def make_request_with_exception(env, exception_class, exception_message) it "sets namespaced path" do make_request(env) - expect(last_transaction.to_h).to include( - "action" => "POST::GrapeExample::Api#/v1/beta/ping", - "metadata" => { - "path" => "/v1/beta/ping", - "method" => "POST" - } - ) + expect(last_transaction).to have_action("POST::GrapeExample::Api#/v1/beta/ping") + expect(last_transaction).to include_metadata("path" => "/v1/beta/ping", + "method" => "POST") end end end diff --git a/spec/lib/appsignal/rack/hanami_middleware_spec.rb b/spec/lib/appsignal/rack/hanami_middleware_spec.rb index 83834e0ae..260e14815 100644 --- a/spec/lib/appsignal/rack/hanami_middleware_spec.rb +++ b/spec/lib/appsignal/rack/hanami_middleware_spec.rb @@ -23,28 +23,14 @@ def make_request(env) it "sets request parameters on the transaction" do make_request(env) - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "params" => { "param1" => "value1", "param2" => "value2" } - ) - ) + expect(last_transaction).to include_params("param1" => "value1", "param2" => "value2") end end it "reports a process_action.hanami event" do make_request(env) - expect(last_transaction.to_h).to include( - "events" => [ - hash_including( - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "count" => 1, - "name" => "process_action.hanami", - "title" => "" - ) - ] - ) + expect(last_transaction).to include_event("name" => "process_action.hanami") end end end diff --git a/spec/lib/appsignal/rack/rails_instrumentation_spec.rb b/spec/lib/appsignal/rack/rails_instrumentation_spec.rb index d8419c6b5..21e83bb16 100644 --- a/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +++ b/spec/lib/appsignal/rack/rails_instrumentation_spec.rb @@ -10,7 +10,7 @@ class MockController; end Rack::Request.new(env) ) end - let(:app) { double(:call => true) } + let(:app) { DummyApp.new } let(:params) do { "controller" => "blog_posts", @@ -40,85 +40,80 @@ class MockController; end env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = transaction end - def make_request(env) + def make_request middleware.call(env) - last_transaction.complete # Manually close transaction to set sample data + last_transaction&._sample end - def make_request_with_error(env, error_class, error_message) - expect { make_request(env) }.to raise_error(error_class, error_message) + def make_request_with_error(error_class, error_message) + expect { make_request }.to raise_error(error_class, error_message) end - context "with a request without an error" do - it "does not report an event" do - make_request(env) + context "with a request that doesn't raise an error" do + before { make_request } - expect(last_transaction.to_h).to include( - "events" => [] - ) + it "calls the next middleware in the stack" do + expect(app).to be_called + end + + it "does not instrument an event" do + expect(last_transaction).to_not include_events end end context "with a request that raises an error" do - let(:app) { lambda { |_env| raise ExampleException, "error message" } } + let(:app) do + DummyApp.new { |_env| raise ExampleException, "error message" } + end + before do + make_request_with_error(ExampleException, "error message") + end - it "reports the error on the transaction" do - make_request_with_error(env, ExampleException, "error message") + it "calls the next middleware in the stack" do + expect(app).to be_called + end - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + it "reports the error on the transaction" do + expect(last_transaction).to have_error("ExampleException", "error message") end end it "sets the controller action as the action name" do - make_request(env) + make_request - expect(last_transaction.to_h).to include( - "namespace" => Appsignal::Transaction::HTTP_REQUEST, - "action" => "MockController#index" - ) + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(last_transaction).to have_action("MockController#index") end it "sets request metadata on the transaction" do - make_request(env) - - expect(last_transaction.to_h).to include( - "metadata" => hash_including( - "method" => "GET", - "path" => "/blog" - ), - "sample_data" => hash_including( - "tags" => { "request_id" => "request_id123" } - ) + make_request + + expect(last_transaction).to include_metadata( + "method" => "GET", + "path" => "/blog" ) + expect(last_transaction).to include_tags("request_id" => "request_id123") end it "reports Rails filter parameters" do - make_request(env) - - expect(last_transaction.to_h).to include( - "sample_data" => hash_including( - "params" => { - "controller" => "blog_posts", - "action" => "show", - "id" => "1", - "my_custom_param" => "[FILTERED]", - "password" => "[FILTERED]" - } - ) + make_request + + expect(last_transaction).to include_params( + "controller" => "blog_posts", + "action" => "show", + "id" => "1", + "my_custom_param" => "[FILTERED]", + "password" => "[FILTERED]" ) end context "with an invalid HTTP request method" do it "does not store the invalid HTTP request method" do - make_request(env.merge(:request_method => "FOO", "REQUEST_METHOD" => "FOO")) + env[:request_method] = "FOO" + env["REQUEST_METHOD"] = "FOO" + make_request - transaction_hash = last_transaction.to_h - expect(transaction_hash["metadata"]).to_not have_key("method") + expect(last_transaction).to_not include_metadata("method" => anything) expect(log_contents(log)) .to contains_log(:error, "Unable to report HTTP request method: '") end @@ -126,16 +121,11 @@ def make_request_with_error(env, error_class, error_message) context "with a request path that's not a route" do it "doesn't set an action name" do - make_request( - env.merge( - :path => "/unknown-route", - "action_controller.instance" => nil - ) - ) + env[:path] = "/unknown-route" + env["action_controller.instance"] = nil + make_request - expect(last_transaction.to_h).to include( - "action" => nil - ) + expect(last_transaction).to_not have_action end end end diff --git a/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb b/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb index 2e9865685..64d28affa 100644 --- a/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +++ b/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb @@ -2,11 +2,11 @@ require "appsignal/integrations/sinatra" module SinatraRequestHelpers - def make_request(env) + def make_request middleware.call(env) end - def make_request_with_error(env, error) + def make_request_with_error(error) expect { middleware.call(env) }.to raise_error(error) end end @@ -30,7 +30,7 @@ def make_request_with_error(env, error) before { allow(middleware).to receive(:raw_payload).and_return({}) } it "doesn't instrument requests" do - expect { make_request(env) }.to_not(change { created_transactions.count }) + expect { make_request }.to_not(change { created_transactions.count }) end end @@ -100,54 +100,48 @@ def make_request_with_error(env, error) before { allow(Appsignal).to receive(:active?).and_return(false) } it "does not instrument requests" do - expect { make_request(env) }.to_not(change { created_transactions.count }) + expect { make_request }.to_not(change { created_transactions.count }) end it "calls the next middleware in the stack" do - make_request(env) + make_request expect(app).to have_received(:call).with(env) end end context "when appsignal is active" do - context "without an exception" do + context "without an error" do + it "creates a transaction for the request" do + expect { make_request }.to(change { created_transactions.count }.by(1)) + + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + end + it "reports a process_action.sinatra event" do - make_request(env) - - expect(last_transaction.to_h).to include( - "events" => [ - hash_including( - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT, - "count" => 1, - "name" => "process_action.sinatra", - "title" => "" - ) - ] - ) + make_request + + expect(last_transaction).to include_event("name" => "process_action.sinatra") end end context "with an error in sinatra.error" do let(:error) { ExampleException.new("error message") } - before do - env["sinatra.error"] = error + before { env["sinatra.error"] = error } + + it "creates a transaction for the request" do + expect { make_request }.to(change { created_transactions.count }.by(1)) + + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) end context "when raise_errors is off" do let(:settings) { double(:raise_errors => false) } - it "record the error" do - expect { make_request(env) } - .to(change { created_transactions.count }.by(1)) + it "records the error" do + make_request - expect(last_transaction.to_h).to include( - "error" => hash_including( - "name" => "ExampleException", - "message" => "error message" - ) - ) + expect(last_transaction).to have_error("ExampleException", "error message") end end @@ -155,10 +149,9 @@ def make_request_with_error(env, error) let(:settings) { double(:raise_errors => true) } it "does not record the error" do - expect { make_request(env) } - .to(change { created_transactions.count }.by(1)) + make_request - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end @@ -171,19 +164,18 @@ def make_request_with_error(env, error) end it "does not record the error" do - expect { make_request(env) } - .to(change { created_transactions.count }.by(1)) + make_request - expect(last_transaction.to_h).to include("error" => nil) + expect(last_transaction).to_not have_error end end end describe "action name" do it "sets the action to the request method and path" do - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => "GET /path") + expect(last_transaction).to have_action("GET /path") end context "without 'sinatra.route' env" do @@ -192,9 +184,9 @@ def make_request_with_error(env, error) end it "doesn't set an action name" do - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => nil) + expect(last_transaction).to_not have_action end end @@ -202,9 +194,9 @@ def make_request_with_error(env, error) before { env["SCRIPT_NAME"] = "/api" } it "sets the action name with an application prefix path" do - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => "GET /api/path") + expect(last_transaction).to have_action("GET /api/path") end context "without 'sinatra.route' env" do @@ -213,9 +205,9 @@ def make_request_with_error(env, error) end it "doesn't set an action name" do - make_request(env) + make_request - expect(last_transaction.to_h).to include("action" => nil) + expect(last_transaction).to_not have_action end end end diff --git a/spec/lib/appsignal/transaction_spec.rb b/spec/lib/appsignal/transaction_spec.rb index 29731946b..7f8d8d655 100644 --- a/spec/lib/appsignal/transaction_spec.rb +++ b/spec/lib/appsignal/transaction_spec.rb @@ -40,10 +40,8 @@ def create_transaction(id = transaction_id) expect(transaction.namespace).to eq namespace expect(transaction.request).to eq request - expect(transaction.to_h).to include( - "id" => transaction_id, - "namespace" => namespace - ) + expect(transaction).to have_id(transaction_id) + expect(transaction).to have_namespace(namespace) end it "assigns the transaction to current" do @@ -166,9 +164,7 @@ def current_transaction it "samples data" do transaction.set_tags(:foo => "bar") keep_transactions { transaction.complete } - expect(transaction.to_h["sample_data"]).to include( - "tags" => { "foo" => "bar" } - ) + expect(transaction).to include_tags("foo" => "bar") end end @@ -181,13 +177,13 @@ def current_transaction context "when a transaction is marked as discarded" do it "does not complete the transaction" do - expect(transaction.ext).to_not receive(:complete) - expect do transaction.discard! end.to change { transaction.discarded? }.from(false).to(true) transaction.complete + + expect(transaction).to_not be_completed end it "logs a debug message" do @@ -202,13 +198,13 @@ def current_transaction before { transaction.discard! } it "completes the transaction" do - expect(transaction.ext).to receive(:complete).and_call_original - expect do transaction.restore! end.to change { transaction.discarded? }.from(true).to(false) transaction.complete + + expect(transaction).to be_completed end end end @@ -354,9 +350,9 @@ def current_transaction params = { "foo" => "bar" } silence { transaction.params = params } - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(params) + expect(transaction).to include_params(params) end it "logs a deprecation warning" do @@ -378,9 +374,9 @@ def current_transaction params = { "key" => "value" } transaction.set_params(params) - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(params) + expect(transaction).to include_params(params) end end @@ -390,9 +386,9 @@ def current_transaction transaction.set_params(params) transaction.set_params(nil) - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(params) + expect(transaction).to include_params(params) end end end @@ -405,9 +401,9 @@ def current_transaction params = { "key" => "value" } transaction.set_params_if_nil(params) - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(params) + expect(transaction).to include_params(params) end context "when the given params is nil" do @@ -416,9 +412,9 @@ def current_transaction transaction.set_params(params) transaction.set_params_if_nil(nil) - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(params) + expect(transaction).to include_params(params) end end end @@ -430,9 +426,9 @@ def current_transaction transaction.set_params(preset_params) transaction.set_params_if_nil(params) - transaction.complete # Sample the data + transaction._sample expect(transaction.params).to eq(preset_params) - expect(transaction.to_h.dig("sample_data", "params")).to eq(preset_params) + expect(transaction).to include_params(preset_params) end end end @@ -455,7 +451,7 @@ def current_transaction end it "stores tags on the transaction" do - expect(transaction.to_h["sample_data"]["tags"]).to eq( + expect(transaction).to include_tags( "valid_key" => "valid_value", "valid_string_key" => "valid_value", "both_symbols" => "valid_value", @@ -507,12 +503,13 @@ def current_transaction transaction.sample_data timeframe_end = Time.now.utc.to_i - breadcrumb = transaction.to_h["sample_data"]["breadcrumbs"][0] - expect(breadcrumb["category"]).to eq("user_action") - expect(breadcrumb["action"]).to eq("clicked HOME") - expect(breadcrumb["message"]).to eq("") - expect(breadcrumb["time"]).to be_between(timeframe_start, timeframe_end) - expect(breadcrumb["metadata"]).to eq({}) + expect(transaction).to include_breadcrumb( + "clicked HOME", + "user_action", + "", + {}, + be_between(timeframe_start, timeframe_end) + ) end end @@ -521,7 +518,7 @@ def current_transaction transaction.add_breadcrumb("category", "action", "message", "invalid metadata") transaction.sample_data - expect(transaction.to_h["sample_data"]["breadcrumbs"]).to be_empty + expect(transaction).to_not include_breadcrumbs expect(log_contents(log)).to contains_log( :error, "add_breadcrumb: Cannot add breadcrumb. The given metadata argument is not a Hash." @@ -537,7 +534,7 @@ def current_transaction transaction.set_action(action_name) expect(transaction.action).to eq(action_name) - expect(transaction.to_h["action"]).to eq(action_name) + expect(transaction).to have_action(action_name) end end @@ -548,7 +545,7 @@ def current_transaction transaction.set_action(nil) expect(transaction.action).to eq(action_name) - expect(transaction.to_h["action"]).to eq(action_name) + expect(transaction).to have_action(action_name) end end end @@ -557,13 +554,13 @@ def current_transaction context "when the action is not set" do it "updates the action name on the transaction" do expect(transaction.action).to eq(nil) - expect(transaction.to_h["action"]).to eq(nil) + expect(transaction).to_not have_action action_name = "PagesController#show" transaction.set_action_if_nil(action_name) expect(transaction.action).to eq(action_name) - expect(transaction.to_h["action"]).to eq(action_name) + expect(transaction).to have_action(action_name) end context "when the given action is nil" do @@ -573,7 +570,7 @@ def current_transaction transaction.set_action_if_nil(nil) expect(transaction.action).to eq(action_name) - expect(transaction.to_h["action"]).to eq(action_name) + expect(transaction).to have_action(action_name) end end end @@ -585,7 +582,7 @@ def current_transaction transaction.set_action_if_nil("something else") expect(transaction.action).to eq(action_name) - expect(transaction.to_h["action"]).to eq(action_name) + expect(transaction).to have_action(action_name) end end end @@ -597,7 +594,7 @@ def current_transaction transaction.set_namespace(namespace) expect(transaction.namespace).to eq namespace - expect(transaction.to_h["namespace"]).to eq(namespace) + expect(transaction).to have_namespace(namespace) end end @@ -608,7 +605,7 @@ def current_transaction transaction.set_namespace(nil) expect(transaction.namespace).to eq(namespace) - expect(transaction.to_h["namespace"]).to eq(namespace) + expect(transaction).to have_namespace(namespace) end end end @@ -620,21 +617,21 @@ def current_transaction :controller => "HomeController", :action => "show" ) - expect(transaction.to_h["action"]).to eql("HomeController#show") + expect(transaction).to have_action("HomeController#show") end end context "for a hash with just action" do it "sets the action" do transaction.set_http_or_background_action(:action => "show") - expect(transaction.to_h["action"]).to eql("show") + expect(transaction).to have_action("show") end end context "for a hash with class and method" do it "sets the action" do transaction.set_http_or_background_action(:class => "Worker", :method => "perform") - expect(transaction.to_h["action"]).to eql("Worker#perform") + expect(transaction).to have_action("Worker#perform") end end @@ -642,7 +639,7 @@ def current_transaction it "does not overwrite the set action" do transaction.set_action("MyCustomAction#perform") transaction.set_http_or_background_action(:class => "Worker", :method => "perform") - expect(transaction.to_h["action"]).to eql("MyCustomAction#perform") + expect(transaction).to have_action("MyCustomAction#perform") end end end @@ -716,7 +713,7 @@ def current_transaction it "updates the metadata on the transaction" do transaction.set_metadata("request_method", "GET") - expect(transaction.to_h["metadata"]).to eq("request_method" => "GET") + expect(transaction).to include_metadata("request_method" => "GET") end context "when filter_metadata includes metadata key" do @@ -727,7 +724,7 @@ def current_transaction transaction.set_metadata(:filter_key, "filtered value") transaction.set_metadata("filter_key", "filtered value") - expect(transaction.to_h["metadata"].keys).to_not include("filter_key") + expect(transaction).to_not include_metadata("filter_key" => anything) end end @@ -735,7 +732,7 @@ def current_transaction it "does not update the metadata on the transaction" do transaction.set_metadata(nil, "GET") - expect(transaction.to_h["metadata"]).to eq({}) + expect(transaction).to_not include_metadata end end @@ -743,7 +740,7 @@ def current_transaction it "does not update the metadata on the transaction" do transaction.set_metadata("request_method", nil) - expect(transaction.to_h["metadata"]).to eq({}) + expect(transaction).to_not include_metadata end end end @@ -757,12 +754,10 @@ def current_transaction :id => "1" ) - expect(transaction.to_h["sample_data"]).to eq( - "params" => { - "action" => "show", - "controller" => "blog_posts", - "id" => "1" - } + expect(transaction).to include_params( + "action" => "show", + "controller" => "blog_posts", + "id" => "1" ) end @@ -785,7 +780,7 @@ def to_s end transaction.set_sample_data("params", klass.new => 1) - expect(transaction.to_h["sample_data"]).to eq({}) + expect(transaction).to_not include_params expect(log_contents(log)).to contains_log :error, "Error generating data (RuntimeError: foo) for" end @@ -800,27 +795,26 @@ def to_s transaction.add_breadcrumb "category", "action", "message", "key" => "value" transaction.sample_data - sample_data = transaction.to_h["sample_data"] - expect(sample_data["environment"]).to include( + expect(transaction).to include_environment( "REQUEST_METHOD" => "GET", "SERVER_NAME" => "example.org", "SERVER_PORT" => "80", "PATH_INFO" => "/blog" ) - expect(sample_data["session_data"]).to eq("session" => "value") - expect(sample_data["params"]).to eq( + expect(transaction).to include_session_data("session" => "value") + expect(transaction).to include_params( "controller" => "blog_posts", "action" => "show", "id" => "1" ) - expect(sample_data["metadata"]).to eq("key" => "value") - expect(sample_data["tags"]).to eq("tag" => "value") - expect(sample_data["breadcrumbs"]).to contain_exactly( - "action" => "action", - "category" => "category", - "message" => "message", - "metadata" => { "key" => "value" }, - "time" => kind_of(Integer) + expect(transaction).to include_sample_metadata("key" => "value") + expect(transaction).to include_tags("tag" => "value") + expect(transaction).to include_breadcrumb( + "action", + "category", + "message", + { "key" => "value" }, + kind_of(Integer) ) end end diff --git a/spec/lib/appsignal_spec.rb b/spec/lib/appsignal_spec.rb index fa88acbea..19071bff6 100644 --- a/spec/lib/appsignal_spec.rb +++ b/spec/lib/appsignal_spec.rb @@ -1,5 +1,6 @@ describe Appsignal do include EnvironmentMetadataHelper + around { |example| keep_transactions { example.run } } before do # Make sure we have a clean state because we want to test @@ -399,8 +400,6 @@ end describe ".monitor_single_transaction" do - around { |example| keep_transactions { example.run } } - context "with a successful call" do it "calls monitor_transaction and Appsignal.stop" do expect(Appsignal).to receive(:stop) @@ -414,18 +413,8 @@ end transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "my_controller#my_action" - ) - expect(transaction_hash["events"]).to match([ - hash_including( - "name" => "perform_job.something", - "title" => "", - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT - ) - ]) + expect(transaction).to have_action("my_controller#my_action") + expect(transaction).to include_event("name" => "perform_job.something") end end @@ -446,75 +435,55 @@ end.to raise_error(error) transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "action" => "my_controller#my_action" - ) - expect(transaction_hash["events"]).to match([ - hash_including( - "name" => "perform_job.something", - "title" => "", - "body" => "", - "body_format" => Appsignal::EventFormatter::DEFAULT - ) - ]) + expect(transaction).to have_action("my_controller#my_action") + expect(transaction).to include_event("name" => "perform_job.something") end end end describe ".tag_request" do - let(:transaction) { http_request_transaction } around do |example| start_agent - with_current_transaction transaction do - keep_transactions { example.run } - end + with_current_transaction(transaction) { example.run } end context "with transaction" do + let(:transaction) { http_request_transaction } + it "calls set_tags on the current transaction" do Appsignal.tag_request("a" => "b") - transaction.complete # Manually trigger transaction sampling - expect(transaction.to_h).to include( - "sample_data" => hash_including( - "tags" => { "a" => "b" } - ) - ) + transaction._sample + expect(transaction).to include_tags("a" => "b") end end context "without transaction" do let(:transaction) { nil } - it "should call set_tags on transaction" do + it "does not set tags on the transaction" do expect(Appsignal.tag_request).to be_falsy + Appsignal.tag_request("a" => "b") + + expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_tags) end end - it "should also listen to tag_job" do + it "also listens to tag_job" do expect(Appsignal).to respond_to(:tag_job) end end describe ".add_breadcrumb" do - before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) } + around do |example| + start_agent + with_current_transaction(transaction) { example.run } + end context "with transaction" do let(:transaction) { http_request_transaction } - around do |example| - Appsignal.config = project_fixture_config - set_current_transaction transaction do - example.run - end - end - - it "should call add_breadcrumb on transaction" do - expect(transaction).to receive(:add_breadcrumb) - .with("Network", "http", "User made network request", { :response => 200 }, fixed_time) - end - after do + it "adds the breadcrumb to the transaction" do Appsignal.add_breadcrumb( "Network", "http", @@ -522,13 +491,22 @@ { :response => 200 }, fixed_time ) + + transaction._sample + expect(transaction).to include_breadcrumb( + "http", + "Network", + "User made network request", + { "response" => 200 }, + fixed_time + ) end end context "without transaction" do let(:transaction) { nil } - it "should not call add_breadcrumb on transaction" do + it "does not add a breadcrumb to any transaction" do expect(Appsignal.add_breadcrumb("Network", "http")).to be_falsy end end @@ -557,8 +535,11 @@ end it "should not raise an exception when out of range" do - expect(Appsignal::Extension).to receive(:set_gauge).with("key", 10, - Appsignal::Extension.data_map_new).and_raise(RangeError) + expect(Appsignal::Extension).to receive(:set_gauge).with( + "key", + 10, + Appsignal::Extension.data_map_new + ).and_raise(RangeError) expect(Appsignal.internal_logger).to receive(:warn) .with("Gauge value 10 for key 'key' is too big") expect do @@ -713,46 +694,39 @@ end describe ".send_error" do - let(:transaction) do - Appsignal::Transaction.new( - SecureRandom.uuid, - Appsignal::Transaction::HTTP_REQUEST, - Appsignal::Transaction::GenericRequest.new({}) - ) - end - let(:error) { ExampleException.new } + let(:error) { ExampleException.new("error message") } let(:err_stream) { std_stream } let(:stderr) { err_stream.read } - around { |example| keep_transactions { example.run } } + around do |example| + keep_transactions { example.run } + end it "sends the error to AppSignal" do - expect(Appsignal::Transaction).to receive(:new).with( - kind_of(String), - Appsignal::Transaction::HTTP_REQUEST, - kind_of(Appsignal::Transaction::GenericRequest) - ).and_return(transaction) - expect(transaction).to receive(:set_error).with(error) - expect(transaction).to_not receive(:set_tags) - expect(transaction).to receive(:complete) + expect { Appsignal.send_error(error) }.to(change { created_transactions.count }.by(1)) - Appsignal.send_error(error) + transaction = last_transaction + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to_not have_action + expect(transaction).to have_error("ExampleException", "error message") + expect(transaction).to_not include_tags + expect(transaction).to be_completed end context "when given error is not an Exception" do - let(:error) { double } + let(:error) { "string value" } it "logs an error message" do - expect(Appsignal.internal_logger).to receive(:error).with( + logs = capture_logs { Appsignal.send_error(error) } + expect(logs).to contains_log( + :error, "Appsignal.send_error: Cannot send error. " \ "The given value is not an exception: #{error.inspect}" ) end it "does not send the error" do - expect(Appsignal::Transaction).to_not receive(:create) + expect { Appsignal.send_error(error) }.to_not(change { created_transactions.count }) end - - after { Appsignal.send_error(error) } end context "with tags" do @@ -767,13 +741,7 @@ end.to change { created_transactions.count }.by(1) end - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include( - "sample_data" => hash_including( - "tags" => { "a" => "a", "b" => "b" } - ) - ) + expect(last_transaction).to include_tags("a" => "a", "b" => "b") message = "The tags argument for `Appsignal.send_error` is deprecated. " \ "Please use the block method to set tags instead.\n\n" \ @@ -798,9 +766,7 @@ end.to change { created_transactions.count }.by(1) end - transaction = last_transaction - transaction_hash = transaction.to_h - expect(transaction_hash).to include("namespace" => namespace) + expect(last_transaction).to have_namespace(namespace) message = "The namespace argument for `Appsignal.send_error` is deprecated. " \ "Please use the block method to set the namespace instead.\n\n" \ @@ -815,23 +781,15 @@ context "when given a block" do it "yields the transaction and allows additional metadata to be set" do - captured_transaction = nil keep_transactions do Appsignal.send_error(StandardError.new("my_error")) do |transaction| - captured_transaction = transaction transaction.set_action("my_action") transaction.set_namespace("my_namespace") end end - expect(captured_transaction.to_h).to include( - "namespace" => "my_namespace", - "action" => "my_action", - "error" => { - "name" => "StandardError", - "message" => "my_error", - "backtrace" => kind_of(String) # TODO: should be Array - } - ) + expect(last_transaction).to have_namespace("my_namespace") + expect(last_transaction).to have_action("my_action") + expect(last_transaction).to have_error("StandardError", "my_error") end end end @@ -848,17 +806,10 @@ end.to raise_error(ExampleException, "I am an exception") end.to change { created_transactions.count }.by(1) - expect(last_transaction.to_h).to include( - "error" => { - "name" => "ExampleException", - "message" => "I am an exception", - "backtrace" => kind_of(String) - }, - "namespace" => Appsignal::Transaction::HTTP_REQUEST, # Default namespace - "sample_data" => hash_including( - "tags" => {} - ) - ) + # Default namespace + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(last_transaction).to have_error("ExampleException", "I am an exception") + expect(last_transaction).to_not include_tags end context "with tags" do @@ -871,17 +822,10 @@ end.to raise_error(ExampleException, "I am an exception") end.to change { created_transactions.count }.by(1) - expect(last_transaction.to_h).to include( - "error" => { - "name" => "ExampleException", - "message" => "I am an exception", - "backtrace" => kind_of(String) - }, - "namespace" => Appsignal::Transaction::HTTP_REQUEST, # Default namespace - "sample_data" => hash_including( - "tags" => { "foo" => "bar" } - ) - ) + # Default namespace + expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(last_transaction).to have_error("ExampleException", "I am an exception") + expect(last_transaction).to include_tags("foo" => "bar") end end @@ -895,17 +839,10 @@ end.to raise_error(ExampleException, "I am an exception") end.to change { created_transactions.count }.by(1) - expect(last_transaction.to_h).to include( - "error" => { - "name" => "ExampleException", - "message" => "I am an exception", - "backtrace" => kind_of(String) - }, - "namespace" => "custom_namespace", - "sample_data" => hash_including( - "tags" => {} - ) - ) + # Default namespace + expect(last_transaction).to have_namespace("custom_namespace") + expect(last_transaction).to have_error("ExampleException", "I am an exception") + expect(last_transaction).to_not include_tags end end end @@ -914,42 +851,56 @@ let(:err_stream) { std_stream } let(:stderr) { err_stream.read } let(:error) { ExampleException.new("I am an exception") } - before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) } + let(:transaction) { http_request_transaction } around { |example| keep_transactions { example.run } } context "when there is an active transaction" do - it "adds the error to the active transaction" do - expect(transaction).to receive(:set_error).with(error) - expect(transaction).to_not receive(:set_tags) - expect(transaction).to_not receive(:set_namespace) + before { set_current_transaction(transaction) } + it "adds the error to the active transaction" do Appsignal.set_error(error) + + transaction._sample + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + expect(transaction).to have_error("ExampleException", "I am an exception") + expect(transaction).to_not include_tags end context "when the error is not an Exception" do let(:error) { Object.new } + it "does not set an error" do + silence { Appsignal.set_error(error) } + + transaction._sample + expect(transaction).to_not have_error + expect(transaction).to_not include_tags + end + it "logs an error" do - expect(Appsignal.internal_logger).to receive(:error).with( + logs = capture_logs { Appsignal.set_error(error) } + expect(logs).to contains_log( + :error, "Appsignal.set_error: Cannot set error. " \ "The given value is not an exception: #{error.inspect}" ) - expect(transaction).to_not receive(:set_error) - expect(transaction).to_not receive(:set_tags) - expect(transaction).to_not receive(:set_namespace) - - Appsignal.set_error(error) end end context "with tags" do let(:tags) { { "foo" => "bar" } } - it "prints a deprecation warning and tags the transaction" do - expect(transaction).to receive(:set_error).with(error) - expect(transaction).to receive(:set_tags).with(tags) - expect(transaction).to_not receive(:set_namespace) + it "tags the transaction" do + silence(:allowed => ["set_error", "The tags argument for"]) do + Appsignal.set_error(error, tags) + end + + transaction._sample + expect(transaction).to have_error(error) + expect(transaction).to include_tags(tags) + end + it "prints a deprecation warning and tags the transaction" do logs = capture_logs do capture_std_streams(std_stream, err_stream) do Appsignal.set_error(error, tags) @@ -970,11 +921,17 @@ context "with namespace" do let(:namespace) { "admin" } - it "prints a deprecation warning andsets the namespace on the transaction" do - expect(transaction).to receive(:set_error).with(error) - expect(transaction).to_not receive(:set_tags) - expect(transaction).to receive(:set_namespace).with(namespace) + it "sets the namespace on the transaction" do + silence(:allowed => ["set_error", "The namespace argument for"]) do + Appsignal.set_error(error, nil, namespace) + end + expect(transaction).to have_error("ExampleException", "I am an exception") + expect(transaction).to have_namespace(namespace) + expect(transaction).to_not include_tags + end + + it "prints a deprecation warning andsets the namespace on the transaction" do logs = capture_logs do capture_std_streams(std_stream, err_stream) do Appsignal.set_error(error, nil, namespace) @@ -994,135 +951,137 @@ context "when given a block" do it "yields the transaction and allows additional metadata to be set" do - captured_transaction = nil - keep_transactions do - Appsignal.set_error(StandardError.new("my_error")) do |transaction| - captured_transaction = transaction - transaction.set_action("my_action") - transaction.set_namespace("my_namespace") - end + Appsignal.set_error(StandardError.new("my_error")) do |t| + t.set_action("my_action") + t.set_namespace("my_namespace") end - expect(transaction).to eql(captured_transaction) - expect(captured_transaction.to_h).to include( - "namespace" => "my_namespace", - "action" => "my_action", - "error" => { - "name" => "StandardError", - "message" => "my_error", - "backtrace" => kind_of(String) - } - ) + expect(transaction).to have_namespace("my_namespace") + expect(transaction).to have_action("my_action") + expect(transaction).to have_error("StandardError", "my_error") end end end context "when there is no active transaction" do it "does nothing" do - allow(Appsignal::Transaction).to receive(:current).and_return(nil) - - expect(transaction).to_not receive(:set_error) - Appsignal.set_error(error) + + expect(transaction).to_not have_error end end end describe ".set_action" do - before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) } + around { |example| keep_transactions { example.run } } - it "should set the namespace to the current transaction" do - expect(transaction).to receive(:set_action).with("custom") + context "with current transaction" do + before { set_current_transaction(transaction) } - Appsignal.set_action("custom") - end + it "sets the namespace on the current transaction" do + Appsignal.set_action("custom") - it "should do nothing if there is no current transaction" do - allow(Appsignal::Transaction).to receive(:current).and_return(nil) + expect(transaction).to have_action("custom") + end - expect(transaction).to_not receive(:set_action) + it "does not set the action if the action is nil" do + Appsignal.set_action(nil) - Appsignal.set_action("custom") + expect(transaction).to_not have_action + end end - it "should do nothing if the error is nil" do - expect(transaction).to_not receive(:set_action) + context "without current transaction" do + it "does not set ther action" do + Appsignal.set_action("custom") - Appsignal.set_action(nil) + expect(transaction).to_not have_action + end end end describe ".set_namespace" do - before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) } + around { |example| keep_transactions { example.run } } - it "should set the namespace to the current transaction" do - expect(transaction).to receive(:set_namespace).with("custom") + context "with current transaction" do + before { set_current_transaction(transaction) } - Appsignal.set_namespace("custom") - end + it "should set the namespace to the current transaction" do + Appsignal.set_namespace("custom") - it "should do nothing if there is no current transaction" do - allow(Appsignal::Transaction).to receive(:current).and_return(nil) + expect(transaction).to have_namespace("custom") + end - expect(transaction).to_not receive(:set_namespace) + it "does not update the namespace if the namespace is nil" do + Appsignal.set_namespace(nil) - Appsignal.set_namespace("custom") + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + end end - it "should do nothing if the error is nil" do - expect(transaction).to_not receive(:set_namespace) + context "without current transaction" do + it "does not update the namespace" do + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) - Appsignal.set_namespace(nil) + Appsignal.set_namespace("custom") + + expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST) + end end end describe ".instrument" do it_behaves_like "instrument helper" do let(:instrumenter) { Appsignal } - before do - expect(Appsignal::Transaction).to receive(:current).at_least(:once) - .and_return(transaction) - end + before { set_current_transaction(transaction) } end end describe ".instrument_sql" do - before do - expect(Appsignal::Transaction).to receive(:current).at_least(:once) - .and_return(transaction) - end + around { |example| keep_transactions { example.run } } + before { set_current_transaction(transaction) } it "creates an SQL event on the transaction" do - expect(transaction).to receive(:start_event) - expect(transaction).to receive(:finish_event) - .with("name", "title", "body", Appsignal::EventFormatter::SQL_BODY_FORMAT) + result = + Appsignal.instrument_sql "name", "title", "body" do + "return value" + end - result = Appsignal.instrument_sql "name", "title", "body" do - "return value" - end expect(result).to eq "return value" + expect(transaction).to include_event( + "name" => "name", + "title" => "title", + "body" => "body", + "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT + ) end end describe ".without_instrumentation" do + around { |example| keep_transactions { example.run } } let(:transaction) { http_request_transaction } - before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) } - it "does not record events on the transaction" do - expect(transaction).to receive(:pause!).and_call_original - expect(transaction).to receive(:resume!).and_call_original - Appsignal.instrument("register.this.event") { :do_nothing } - Appsignal.without_instrumentation do - Appsignal.instrument("dont.register.this.event") { :do_nothing } + context "with current transaction" do + before { set_current_transaction(transaction) } + + it "does not record events on the transaction" do + expect(transaction).to receive(:pause!).and_call_original + expect(transaction).to receive(:resume!).and_call_original + + Appsignal.instrument("register.this.event") { :do_nothing } + Appsignal.without_instrumentation do + Appsignal.instrument("dont.register.this.event") { :do_nothing } + end + + expect(transaction).to include_event("name" => "register.this.event") + expect(transaction).to_not include_event("name" => "dont.register.this.event") end - expect(transaction.to_h["events"].map { |e| e["name"] }) - .to match_array("register.this.event") end - context "without transaction" do + context "without current transaction" do let(:transaction) { nil } - it "should not crash" do + it "does not crash" do Appsignal.without_instrumentation { :do_nothing } end end diff --git a/spec/support/helpers/std_streams_helper.rb b/spec/support/helpers/std_streams_helper.rb index 0520816ee..cc4ba2bce 100644 --- a/spec/support/helpers/std_streams_helper.rb +++ b/spec/support/helpers/std_streams_helper.rb @@ -89,6 +89,6 @@ def filter_allowed_errors(output, allowed_errors) end end reject - end.join(",") + end.join end end diff --git a/spec/support/matchers/be_completed.rb b/spec/support/matchers/be_completed.rb deleted file mode 100644 index 4eba036c7..000000000 --- a/spec/support/matchers/be_completed.rb +++ /dev/null @@ -1,5 +0,0 @@ -RSpec::Matchers.define :be_completed do - match do |transaction| - values_match? transaction.ext._completed?, true - end -end diff --git a/spec/support/matchers/transaction.rb b/spec/support/matchers/transaction.rb new file mode 100644 index 000000000..2d7ef19a6 --- /dev/null +++ b/spec/support/matchers/transaction.rb @@ -0,0 +1,185 @@ +def define_transaction_metadata_matcher_for(matcher_key, value_key = matcher_key) + value_key = value_key.to_s + + RSpec::Matchers.define "have_#{matcher_key}" do |expected_value| + match(:notify_expectation_failures => true) do |transaction| + actual_value = transaction.to_h[value_key] + if expected_value + expect(actual_value).to eq(expected_value) + else + expect(actual_value).to_not be_nil + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + actual_value = transaction.to_h[value_key] + if expected_value + expect(actual_value).to_not eq(expected_value) + else + expect(actual_value).to be_nil + end + end + end +end + +define_transaction_metadata_matcher_for(:id) +define_transaction_metadata_matcher_for(:namespace) +define_transaction_metadata_matcher_for(:action) + +def define_transaction_sample_matcher_for(matcher_key, value_key = matcher_key) + value_key = value_key.to_s + + RSpec::Matchers.define "include_#{matcher_key}" do |expected_value| + match(:notify_expectation_failures => true) do |transaction| + sample_data = transaction.to_h.dig("sample_data", value_key) || {} + + if expected_value + expected_value = hash_including(expected_value) if expected_value.is_a?(Hash) + expect(sample_data).to match(expected_value) + else + expect(sample_data).to be_present + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + sample_data = transaction.to_h.dig("sample_data", value_key) || {} + + if expected_value + expect(sample_data).to_not include(expected_value) + else + expect(sample_data).to be_empty + end + end + end +end + +define_transaction_sample_matcher_for(:sample_metadata, :metadata) +define_transaction_sample_matcher_for(:params) +define_transaction_sample_matcher_for(:environment) +define_transaction_sample_matcher_for(:session_data) +define_transaction_sample_matcher_for(:tags) +define_transaction_sample_matcher_for(:custom_data) + +RSpec::Matchers.define :be_completed do + match(:notify_expectation_failures => true) do |transaction| + values_match? transaction.ext._completed?, true + end +end + +RSpec::Matchers.define :have_error do |error_class, error_message| + match(:notify_expectation_failures => true) do |transaction| + transaction_error = transaction.to_h["error"] + if error_class && error_message + expect(transaction_error).to include( + "name" => error_class, + "message" => error_message, + "backtrace" => kind_of(String) + ) + else + expect(transaction_error).to be_any + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + transaction_error = transaction.to_h["error"] + if error_class && error_message + expect(transaction_error).to_not include( + "name" => error_class, + "message" => error_message, + "backtrace" => kind_of(String) + ) + else + expect(transaction_error).to be_nil + end + end +end + +RSpec::Matchers.define :include_event do |event| + match(:notify_expectation_failures => true) do |transaction| + events = transaction.to_h["events"] + if event + expect(events).to include(format_event(event)) + else + expect(events).to be_any + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + events = transaction.to_h["events"] + if event + expect(events).to_not include(format_event(event)) + else + expect(events).to be_empty + end + end + + def format_event(event) + hash_including({ + "body" => "", + "body_format" => Appsignal::EventFormatter::DEFAULT, + "count" => 1, + "name" => kind_of(String), + "title" => "" + }.merge(event.transform_keys(&:to_s))) + end +end +RSpec::Matchers.alias_matcher :include_events, :include_event + +RSpec::Matchers.define :include_metadata do |metadata| + match(:notify_expectation_failures => true) do |transaction| + actual_metadata = transaction.to_h["metadata"] + if metadata + expect(actual_metadata).to include(metadata) + else + expect(actual_metadata).to be_any + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + actual_metadata = transaction.to_h["metadata"] + if metadata + expect(actual_metadata).to_not include(metadata) + else + expect(actual_metadata).to be_empty + end + end +end + +RSpec::Matchers.define :include_breadcrumb do |action, category, message, metadata, time| + match(:notify_expectation_failures => true) do |transaction| + breadcrumbs = transaction.to_h.dig("sample_data", "breadcrumbs") + if action + breadcrumb = format_breadcrumb(action, category, message, metadata, time) + expect(breadcrumbs).to include(breadcrumb) + else + expect(transaction.to_h.dig("sample_data", "breadcrumbs")).to be_any + end + end + + match_when_negated(:notify_expectation_failures => true) do |transaction| + breadcrumbs = transaction.to_h.dig("sample_data", "breadcrumbs") + if action + breadcrumb = format_breadcrumb(action, category, message, metadata, time) + expect(breadcrumbs).to_not include(breadcrumb) + else + expect(breadcrumbs).to_not be_any + end + end + + def format_breadcrumb(action, category, message, metadata, time) + { + "action" => action, + "category" => category, + "message" => message, + "metadata" => metadata, + "time" => time + } + end +end +RSpec::Matchers.alias_matcher :include_breadcrumbs, :include_breadcrumb + +RSpec::Matchers.define :have_queue_start do |queue_start_time| + match(:notify_expectation_failures => true) do |transaction| + expect(transaction.ext.queue_start).to eq(queue_start_time) + end +end diff --git a/spec/support/mocks/dummy_app.rb b/spec/support/mocks/dummy_app.rb new file mode 100644 index 000000000..2a7cc1931 --- /dev/null +++ b/spec/support/mocks/dummy_app.rb @@ -0,0 +1,16 @@ +class DummyApp + def initialize(&app) + @app = app + @called = false + end + + def call(env) + @app&.call(env) + ensure + @called = true + end + + def called? + @called + end +end diff --git a/spec/support/shared_examples/instrument.rb b/spec/support/shared_examples/instrument.rb index 01933a3e7..2ebcab060 100644 --- a/spec/support/shared_examples/instrument.rb +++ b/spec/support/shared_examples/instrument.rb @@ -1,22 +1,14 @@ RSpec.shared_examples "instrument helper" do - let(:stub) { double } - before do - expect(stub).to receive(:method_call).and_return("return value") - - expect(transaction).to receive(:start_event) - expect(transaction).to receive(:finish_event).with( - "name", - "title", - "body", - 0 - ) - end + around { |example| keep_transactions { example.run } } + let(:stub) { double(:method_call => "return value") } it "records an event around the given block" do return_value = instrumenter.instrument "name", "title", "body" do stub.method_call end expect(return_value).to eq "return value" + + expect_transaction_to_have_event end context "with an error raised in the passed block" do @@ -27,6 +19,8 @@ raise ExampleException, "foo" end end.to raise_error(ExampleException, "foo") + + expect_transaction_to_have_event end end @@ -38,6 +32,17 @@ throw :foo end end.to throw_symbol(:foo) + + expect_transaction_to_have_event end end + + def expect_transaction_to_have_event + expect(transaction).to include_event( + "name" => "name", + "title" => "title", + "body" => "body", + "body_format" => Appsignal::EventFormatter::DEFAULT + ) + end end diff --git a/spec/support/testing.rb b/spec/support/testing.rb index 66350ed97..95c72b8ac 100644 --- a/spec/support/testing.rb +++ b/spec/support/testing.rb @@ -120,16 +120,25 @@ def to_json # rubocop:disable Lint/ToJSON module AppsignalTest module Transaction - # Override the {Appsignal::Transaction.new} method so we can track which - # transactions are created on the {Appsignal::Testing.transactions} list. - # - # @see TransactionHelpers#last_transaction - def new(*_args) - transaction = super - Appsignal::Testing.transactions << transaction - transaction + module ClassMethods + # Override the {Appsignal::Transaction.new} method so we can track which + # transactions are created on the {Appsignal::Testing.transactions} list. + # + # @see TransactionHelpers#last_transaction + def new(*_args) + transaction = super + Appsignal::Testing.transactions << transaction + transaction + end + end + + module InstanceMethods + def _sample + sample_data + end end end end -Appsignal::Transaction.extend(AppsignalTest::Transaction) +Appsignal::Transaction.extend(AppsignalTest::Transaction::ClassMethods) +Appsignal::Transaction.prepend(AppsignalTest::Transaction::InstanceMethods)