# encoding: utf-8 # This file is distributed under New Relic's license terms. # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details. ENV['SKIP_RAILS'] = 'true' require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper')) require "new_relic/agent/browser_monitoring" require "new_relic/rack/browser_monitoring" require 'ostruct' class NewRelic::Agent::BrowserMonitoringTest < Test::Unit::TestCase include NewRelic::Agent::BrowserMonitoring include NewRelic::Agent::Instrumentation::ControllerInstrumentation def setup NewRelic::Agent.manual_start @config = { :beacon => 'beacon', :disable_mobile_headers => false, :browser_key => 'browserKey', :application_id => '5, 6', # collector can return app multiple ids :'rum.enabled' => true, :episodes_file => 'this_is_my_file', :'rum.jsonp' => true, :license_key => 'a' * 40 } NewRelic::Agent.config.apply_config(@config) @episodes_file = "this_is_my_file" NewRelic::Agent.instance.instance_eval do @beacon_configuration = NewRelic::Agent::BeaconConfiguration.new end def teardown Thread.current[:last_metric_frame] = nil NewRelic::Agent::TransactionInfo.clear NewRelic::Agent.config.remove_config(@config) end mocha_teardown end def test_auto_instrumentation_config_defaults_to_enabled assert NewRelic::Agent.config[:'browser_monitoring.auto_instrument'] end def test_browser_monitoring_start_time_is_reset_each_request_when_auto_instrument_is_disabled controller = Object.new def controller.perform_action_without_newrelic_trace(method, options={}); # noop; instrument me end def controller.newrelic_metric_path; "foo"; end controller.extend ::NewRelic::Agent::Instrumentation::ControllerInstrumentation controller.extend ::NewRelic::Agent::BrowserMonitoring with_config(:'browser_monitoring.auto_instrument' => false) do controller.perform_action_with_newrelic_trace(:index) first_request_start_time = controller.send(:browser_monitoring_start_time) controller.perform_action_with_newrelic_trace(:index) second_request_start_time = controller.send(:browser_monitoring_start_time) # assert that these aren't the same time object # the start time should be reinitialized each request to the controller assert !(first_request_start_time.equal? second_request_start_time) end end def test_browser_timing_header_with_no_beacon_configuration NewRelic::Agent.instance.stubs(:beacon_configuration).returns( nil) header = browser_timing_header assert_equal "", header end def test_browser_timing_header header = browser_timing_header assert_equal "", header end def test_browser_timing_header_with_rum_enabled_not_specified NewRelic::Agent.instance.stubs(:beacon_configuration).returns( NewRelic::Agent::BeaconConfiguration.new) header = browser_timing_header assert_equal "", header end def test_browser_timing_header_with_rum_enabled_false with_config(:'rum.enabled' => false) do NewRelic::Agent.instance.stubs(:beacon_configuration).returns( NewRelic::Agent::BeaconConfiguration.new) header = browser_timing_header assert_equal "", header end end def test_browser_timing_header_disable_all_tracing header = nil NewRelic::Agent.disable_all_tracing do header = browser_timing_header end assert_equal "", header end def test_browser_timing_header_disable_transaction_tracing header = nil NewRelic::Agent.disable_transaction_tracing do header = browser_timing_header end assert_equal "", header end def test_browser_timing_footer with_config(:license_key => 'a' * 13) do browser_timing_header footer = browser_timing_footer snippet = '" assert(footer.include?(beginning_snippet), "expected footer to include beginning snippet: #{beginning_snippet}, but was #{footer}") assert(footer.include?(ending_snippet), "expected footer to include ending snippet: #{ending_snippet}, but was #{footer}") end def test_browser_timing_footer_with_no_beacon_configuration browser_timing_header NewRelic::Agent.instance.stubs(:beacon_configuration).returns( nil) footer = browser_timing_footer assert_equal "", footer end def test_browser_timing_footer_disable_all_tracing browser_timing_header footer = nil NewRelic::Agent.disable_all_tracing do footer = browser_timing_footer end assert_equal "", footer end def test_browser_timing_footer_disable_transaction_tracing browser_timing_header footer = nil NewRelic::Agent.disable_transaction_tracing do footer = browser_timing_footer end assert_equal "", footer end def test_browser_timing_footer_browser_key_missing with_config(:browser_key => '') do fake_config = mock('beacon configuration') NewRelic::Agent.instance.stubs(:beacon_configuration).returns(fake_config) fake_config.expects(:nil?).returns(false) fake_config.expects(:enabled?).returns(true) self.expects(:generate_footer_js).never assert_equal('', browser_timing_footer, "should not return a footer when there is no key") end end def test_generate_footer_js_null_case self.expects(:browser_monitoring_start_time).returns(nil) assert_equal('', generate_footer_js(NewRelic::Agent.instance.beacon_configuration), "should not send javascript when there is no start time") end def test_generate_footer_js_with_start_time with_config(:browser_key => 'a' * 40) do self.expects(:browser_monitoring_start_time).returns(Time.at(100)) fake_bc = mock('beacon configuration') NewRelic::Agent.instance.stubs(:beacon_configuration).returns(fake_bc) self.expects(:footer_js_string).with(NewRelic::Agent.instance.beacon_configuration).returns('footer js') assert_equal('footer js', generate_footer_js(NewRelic::Agent.instance.beacon_configuration), 'should generate and return the footer JS when there is a start time') end end def test_browser_monitoring_transaction_name_basic mock = mock('transaction sample') NewRelic::Agent::TransactionInfo.set(mock) mock.stubs(:transaction_name).returns('a transaction name') assert_equal('a transaction name', browser_monitoring_transaction_name, "should take the value from the thread local") end def test_browser_monitoring_transaction_name_empty mock = mock('transaction sample') NewRelic::Agent::TransactionInfo.set(mock) mock.stubs(:transaction_name).returns('') assert_equal('', browser_monitoring_transaction_name, "should take the value even when it is empty") end def test_browser_monitoring_transaction_name_nil assert_equal('(unknown)', browser_monitoring_transaction_name, "should fill in a default when it is nil") end def test_browser_monitoring_transaction_name_when_tt_disabled with_config(:'transaction_tracer.enabled' => false) do perform_action_with_newrelic_trace(:name => 'disabled_transactions') do self.class.inspect end assert_match(/disabled_transactions/, browser_monitoring_transaction_name, "should name transaction when transaction tracing disabled") end end def test_browser_monitoring_start_time mock = mock('transaction info') NewRelic::Agent::TransactionInfo.set(mock) mock.stubs(:start_time).returns(Time.at(100)) mock.stubs(:guid).returns('ABC') assert_equal(Time.at(100), browser_monitoring_start_time, "should take the value from the thread local") end def test_clamp_to_positive assert_equal(0.0, clamp_to_positive(-1), "should clamp a negative value to zero") assert_equal(1232, clamp_to_positive(1232), "should pass through the value when it is positive") assert_equal(0, clamp_to_positive(0), "should not mess with zero when passing it through") end def test_browser_monitoring_app_time_nonzero start = Time.now self.expects(:browser_monitoring_start_time).returns(start - 1) Time.stubs(:now).returns(start) assert_equal(1000, browser_monitoring_app_time, 'should return a rounded time') end def test_browser_monitoring_queue_time_nil assert_equal(0.0, browser_monitoring_queue_time, 'should return zero when there is no queue time') end def test_browser_monitoring_queue_time_zero frame = Thread.current[:last_metric_frame] = mock('metric frame') frame.expects(:queue_time).returns(0.0) assert_equal(0.0, browser_monitoring_queue_time, 'should return zero when there is zero queue time') end def test_browser_monitoring_queue_time_ducks frame = Thread.current[:last_metric_frame] = mock('metric frame') frame.expects(:queue_time).returns('a duck') assert_equal(0.0, browser_monitoring_queue_time, 'should return zero when there is an incorrect queue time') end def test_browser_monitoring_queue_time_nonzero frame = Thread.current[:last_metric_frame] = mock('metric frame') frame.expects(:queue_time).returns(3.00002) assert_equal(3000, browser_monitoring_queue_time, 'should return a rounded time') end def test_footer_js_string_basic # mocking this because JRuby thinks that Time.now - Time.now # always takes at least 1ms self.expects(:browser_monitoring_app_time).returns(0) frame = Thread.current[:last_metric_frame] = mock('metric frame') user_attributes = {:user => "user", :account => "account", :product => "product"} frame.expects(:user_attributes).returns(user_attributes).at_least_once frame.expects(:queue_time).returns(0) sample = mock('transaction info') NewRelic::Agent::TransactionInfo.set(sample) sample.stubs(:start_time).returns(Time.at(100)) sample.stubs(:guid).returns('ABC') sample.stubs(:transaction_name).returns('most recent transaction') sample.stubs(:include_guid?).returns(true) sample.stubs(:duration).returns(12.0) sample.stubs(:token).returns('0123456789ABCDEF') self.expects(:obfuscate).with(NewRelic::Agent.instance.beacon_configuration, 'most recent transaction').returns('most recent transaction') self.expects(:obfuscate).with(NewRelic::Agent.instance.beacon_configuration, 'user').returns('user') self.expects(:obfuscate).with(NewRelic::Agent.instance.beacon_configuration, 'account').returns('account') self.expects(:obfuscate).with(NewRelic::Agent.instance.beacon_configuration, 'product').returns('product') value = footer_js_string(NewRelic::Agent.instance.beacon_configuration) assert_equal(%'', value, "should return the javascript given some default values") end def test_html_safe_if_needed_unsafed string = mock('string') # here to handle 1.9 encoding - we stub this out because it should # be handled automatically and is outside the scope of this test string.stubs(:respond_to?).with(:encoding).returns(false) string.expects(:respond_to?).with(:html_safe).returns(false) assert_equal(string, html_safe_if_needed(string)) end def test_html_safe_if_needed_safed string = mock('string') string.expects(:respond_to?).with(:html_safe).returns(true) string.expects(:html_safe).returns(string) # here to handle 1.9 encoding - we stub this out because it should # be handled automatically and is outside the scope of this test string.stubs(:respond_to?).with(:encoding).returns(false) assert_equal(string, html_safe_if_needed(string)) end def test_obfuscate_basic text = 'a happy piece of small text' key = (1..40).to_a NewRelic::Agent.instance.beacon_configuration.expects(:license_bytes).returns(key) output = obfuscate(NewRelic::Agent.instance.beacon_configuration, text) assert_equal('YCJrZXV2fih5Y25vaCFtZSR2a2ZkZSp/aXV1', output, "should output obfuscated text") end def test_obfuscate_long_string text = 'a happy piece of small text' * 5 key = (1..40).to_a NewRelic::Agent.instance.beacon_configuration.expects(:license_bytes).returns(key) output = obfuscate(NewRelic::Agent.instance.beacon_configuration, text) assert_equal('YCJrZXV2fih5Y25vaCFtZSR2a2ZkZSp/aXV1YyNsZHZ3cSl6YmluZCJsYiV1amllZit4aHl2YiRtZ3d4cCp7ZWhiZyNrYyZ0ZWhmZyx5ZHp3ZSVuZnh5cyt8ZGRhZiRqYCd7ZGtnYC11Z3twZCZvaXl6cix9aGdgYSVpYSh6Z2pgYSF2Znxx', output, "should output obfuscated text") end def test_no_mobile_response_header_if_no_mobile_request_header_given request = Rack::Request.new({}) response = Rack::Response.new NewRelic::Agent::BrowserMonitoring.insert_mobile_response_header(request, response) assert_nil response['X-NewRelic-Beacon-Url'] end def test_no_mobile_response_header_if_mobile_request_header_is_false request = Rack::Request.new('HTTP_X_NEWRELIC_MOBILE_TRACE' => 'false') response = Rack::Response.new NewRelic::Agent::BrowserMonitoring.insert_mobile_response_header(request, response) assert_nil response['X-NewRelic-Beacon-Url'] end def test_place_beacon_url_header_when_given_mobile_request_header response = mobile_transaction assert_equal('http://beacon/mobile/1/browserKey', response['X-NewRelic-Beacon-Url']) end def test_place_beacon_url_header_when_given_mobile_request_header_with_https request = Rack::Request.new('X_NEWRELIC_MOBILE_TRACE' => 'true', 'rack.url_scheme' => 'https') response = mobile_transaction(request) assert_equal('https://beacon/mobile/1/browserKey', response['X-NewRelic-Beacon-Url']) end def test_place_beacon_payload_head_when_given_mobile_request_header Time.stubs(:now).returns(Time.at(6)) response = mobile_transaction txn_name = obfuscate(NewRelic::Agent.instance.beacon_configuration, browser_monitoring_transaction_name) expected_payload = %|["5, 6","#{txn_name}",#{browser_monitoring_queue_time},#{browser_monitoring_app_time}]| assert_equal expected_payload, response['X-NewRelic-App-Server-Metrics'].strip end def mobile_transaction(request=nil) request ||= Rack::Request.new('X-NewRelic-Mobile-Trace' => 'true') response = Rack::Response.new txn_data = OpenStruct.new(:transaction_name => 'a transaction name', :start_time => 5, :force_persist_sample? => false) NewRelic::Agent::TransactionInfo.set(txn_data) NewRelic::Agent::BrowserMonitoring.insert_mobile_response_header(request, response) response end end