# 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. require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper')) require "new_relic/agent/browser_monitoring" require "new_relic/rack/browser_monitoring" require 'base64' class NewRelic::Agent::BrowserMonitoringTest < Test::Unit::TestCase include NewRelic::Agent::BrowserMonitoring def setup @config = { :beacon => 'beacon', :browser_key => 'browserKey', :application_id => '5, 6', # collector can return app multiple ids :'rum.enabled' => true, :license_key => "\0" # no-op obfuscation key } NewRelic::Agent.config.apply_config(@config) NewRelic::Agent.instance.instance_eval do @beacon_configuration = NewRelic::Agent::BeaconConfiguration.new end # By default we expect our transaction to have a start time # All sorts of basics don't output without this setup initially NewRelic::Agent::TransactionState.reset(nil) current_transaction.start_time = Time.now end def teardown NewRelic::Agent::TransactionState.clear NewRelic::Agent.config.remove_config(@config) mocha_teardown end def test_js_errors_beta_default_gets_default_loader assert_equal "rum", NewRelic::Agent.config[:'browser_monitoring.loader'] end def test_js_errors_beta_gets_full_loader with_config(:js_errors_beta => true) do assert_equal "full", NewRelic::Agent.config[:'browser_monitoring.loader'] end end def test_js_errors_beta_off_gets_default_loader with_config(:js_errors_beta => false) do assert_equal "rum", NewRelic::Agent.config[:'browser_monitoring.loader'] end end def test_auto_instrumentation_config_defaults_to_enabled assert NewRelic::Agent.config[:'browser_monitoring.auto_instrument'] end def test_start_time_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 = current_transaction.start_time controller.perform_action_with_newrelic_trace(:index) second_request_start_time = current_transaction.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) assert_equal "", browser_timing_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) assert_equal "", browser_timing_header end end def test_browser_timing_header_disable_all_tracing NewRelic::Agent.disable_all_tracing do assert_equal "", browser_timing_header end end def test_browser_timing_header_disable_transaction_tracing NewRelic::Agent.disable_transaction_tracing do assert_equal "", browser_timing_header end end def test_browser_timing_header_without_loader with_config(:js_agent_loader => '') do assert_equal "", browser_timing_header end end def test_browser_timing_header_without_rum_enabled with_config(:js_agent_loader => 'loader', :'rum.enabled' => false) do assert_equal "", browser_timing_header end end def test_browser_timing_header_with_loader with_config(:js_agent_loader => 'loader') do assert_has_js_agent_loader(browser_timing_header) end end def assert_has_js_agent_loader(header) assert_equal("\n", header, "expected new JS agent loader 'loader' but saw '#{header}'") end BEGINNING_OF_FOOTER = '' def test_browser_timing_footer with_config(:license_key => 'a' * 13) do browser_timing_header footer = browser_timing_footer assert_has_text(BEGINNING_OF_FOOTER, footer) assert_has_text(END_OF_FOOTER, footer) end end def assert_has_text(snippet, footer) assert(footer.include?(snippet), "Expected footer to include snippet: #{snippet}, but instead was #{footer}") end def test_browser_timing_footer_with_no_browser_key_rum_enabled with_config(:browser_key => '') do browser_timing_header NewRelic::Agent.instance.stubs(:beacon_configuration).returns(NewRelic::Agent::BeaconConfiguration.new) footer = browser_timing_footer assert_equal "", footer end end def test_browser_timing_footer_with_no_browser_key_rum_disabled with_config(:'rum.enabled' => false) do browser_timing_header NewRelic::Agent.instance.stubs(:beacon_configuration).returns(NewRelic::Agent::BeaconConfiguration.new) footer = browser_timing_footer assert_equal "", footer end end def test_browser_timing_footer_with_rum_enabled_not_specified footer = browser_timing_footer beginning_snippet = BEGINNING_OF_FOOTER assert_has_text(BEGINNING_OF_FOOTER, footer) assert_has_text(END_OF_FOOTER, 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 current_transaction.start_time = 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 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_timing_footer_with_loader with_config(:js_agent_loader => 'loader') do footer = browser_timing_footer beginning_snippet = "\n' 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 end def test_browser_monitoring_transaction_name_basic txn = NewRelic::Agent::Transaction.new txn.name = 'a transaction name' NewRelic::Agent::TransactionState.get.transaction = txn 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 txn = NewRelic::Agent::Transaction.new txn.name = '' NewRelic::Agent::TransactionState.get.transaction = txn 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 in_transaction('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_extra_data in_transaction do with_config(ANALYTICS_TXN_IN_PAGE => true) do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_equal({:boo => "hoo"}, extra_data) end end end def test_extra_data_outside_transaction NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert extra_data.empty? end def test_format assert_formatted({:a => "1", "b" => 2}, "a=1", "b=#2") end def test_format_extra_data_escaping assert_formatted({"semi;colon" => "gets;escaped"}, "semi:colon=gets:escaped") assert_formatted({"equal=key" => "equal=value"}, "equal-key=equal-value") assert_formatted({'"quoted"' => '"marks"'}, %Q['quoted'='marks']) end def test_format_extra_data_disallowed_types assert_formatted_empty({"nested" => { "hashes?" => "nope" }}) assert_formatted_empty({"lists" => ["are", "they", "allowed?", "nope"]}) end def assert_formatted(data, *expected) result = format_extra_data(data).split(";") expected.each do |expect| assert_includes(result, expect) end end def assert_formatted_empty(data) result = format_extra_data(data) assert_equal("", result) end def test_footer_js_data freeze_time in_transaction do with_config(ANALYTICS_TXN_IN_PAGE => true) do NewRelic::Agent.set_user_attributes(:user => "user") txn = NewRelic::Agent::Transaction.current txn.stubs(:queue_time).returns(0) txn.stubs(:start_time).returns(Time.now - 10) txn.name = 'most recent transaction' state = NewRelic::Agent::TransactionState.get state.request_token = '0123456789ABCDEF' state.request_guid = 'ABC' data = js_data(NewRelic::Agent.instance.beacon_configuration) expected = { "beacon" => "beacon", "errorBeacon" => nil, "licenseKey" => "browserKey", "applicationID" => "5, 6", "transactionName" => pack("most recent transaction"), "queueTime" => 0, "applicationTime" => 10000, "ttGuid" => "ABC", "agentToken" => "0123456789ABCDEF", "agent" => nil, "extra" => pack("user=user") } assert_equal(expected, data) js = footer_js_string(NewRelic::Agent.instance.beacon_configuration) expected.each do |key, value| assert_match(/"#{key.to_s}":#{formatted_for_matching(value)}/, js) end end end end ANALYTICS_ENABLED = :'analytics_events.enabled' ANALYTICS_TXN_ENABLED = :'analytics_events.transactions.enabled' ANALYTICS_TXN_IN_PAGE = :'capture_attributes.page_view_events' def test_js_data_doesnt_pick_up_extras_by_default in_transaction do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_extra_data_is("") end end def test_js_data_picks_up_extras_when_configured in_transaction do with_config(ANALYTICS_ENABLED => true, ANALYTICS_TXN_ENABLED => true, ANALYTICS_TXN_IN_PAGE => true) do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_extra_data_is("boo=hoo") end end end def test_js_data_ignores_extras_if_no_analytics in_transaction do with_config(ANALYTICS_ENABLED => false, ANALYTICS_TXN_ENABLED => true, ANALYTICS_TXN_IN_PAGE => true) do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_extra_data_is("") end end end def test_js_data_ignores_extras_if_no_transaction_analytics in_transaction do with_config(ANALYTICS_ENABLED => true, ANALYTICS_TXN_ENABLED => false, ANALYTICS_TXN_IN_PAGE => true) do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_extra_data_is("") end end end def test_js_data_ignores_extras_if_not_allowed_in_page in_transaction do with_config(ANALYTICS_ENABLED => true, ANALYTICS_TXN_ENABLED => true, ANALYTICS_TXN_IN_PAGE => false) do NewRelic::Agent.add_custom_parameters({:boo => "hoo"}) assert_extra_data_is("") end end end def assert_extra_data_is(expected) data = js_data(NewRelic::Agent.instance.beacon_configuration) assert_equal pack(expected), data["extra"] end def pack(text) [text].pack("m0").gsub("\n", "") end def formatted_for_matching(value) case value when String %Q["#{value}"] when NilClass "null" else value end 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_obfuscate_utf8 text = "foooooééoooo - blah" key = (1..40).to_a NewRelic::Agent.instance.beacon_configuration.expects(:license_bytes).returns(key).at_least_once output = obfuscate(NewRelic::Agent.instance.beacon_configuration, text) assert_equal('Z21sa2ppxKHKo2RjYm4iLiRnamZg', output, "should output obfuscated text") unoutput = obfuscate(NewRelic::Agent.instance.beacon_configuration, Base64.decode64(output)) assert_equal Base64.encode64(text).gsub("\n", ''), unoutput end def test_freezes_transaction_name_when_footer_is_written with_config(:license_key => 'a' * 13) do in_transaction do assert !NewRelic::Agent::Transaction.current.name_frozen? browser_timing_footer assert NewRelic::Agent::Transaction.current.name_frozen? end end end end