# 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/javascript_instrumentor" require "base64" class NewRelic::Agent::JavascriptInstrumentorTest < Minitest::Test attr_reader :instrumentor def setup @config = { :application_id => '5, 6', # collector can return app multiple ids :beacon => 'beacon', :browser_key => 'browserKey', :js_agent_loader => 'loader', :license_key => "\0", # no-op obfuscation key :'rum.enabled' => true, :disable_harvest_thread => true } NewRelic::Agent.config.add_config_for_testing(@config) events = stub(:subscribe => nil) @instrumentor = NewRelic::Agent::JavascriptInstrumentor.new(events) # 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.tl_get.reset end def teardown NewRelic::Agent::TransactionState.tl_clear_for_testing NewRelic::Agent.config.reset_to_defaults 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_browser_timing_header_outside_transaction assert_equal "", instrumentor.browser_timing_header end def test_browser_timing_scripts_with_rum_enabled_false with_config(:'rum.enabled' => false) do in_transaction do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_disable_transaction_tracing in_transaction do NewRelic::Agent.disable_transaction_tracing do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_disable_all_tracing in_transaction do NewRelic::Agent.disable_all_tracing do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_without_loader with_config(:js_agent_loader => '') do in_transaction do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_without_beacon with_config(:beacon => '') do in_transaction do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_without_browser_key with_config(:browser_key => '') do in_transaction do assert_equal "", instrumentor.browser_timing_header end end end def test_browser_timing_header_with_ignored_enduser in_transaction do |txn| txn.ignore_enduser! assert_equal "", instrumentor.browser_timing_header end end def test_browser_timing_header_with_default_settings in_transaction do header = instrumentor.browser_timing_header assert_has_js_agent_loader(header) assert_has_text(BEGINNING_OF_FOOTER, header) assert_has_text(END_OF_FOOTER, header) end end def test_browser_timing_header_safe_when_insert_js_fails in_transaction do begin NewRelic::Agent.stubs(:config).raises("Hahahaha") assert_equal "", instrumentor.browser_timing_header ensure # stopping the transaction touches config, so we need to ensure we # clean up after ourselves here. NewRelic::Agent.unstub(:config) end end end def test_browser_timing_header_safe_when_loader_generation_fails in_transaction do instrumentor.stubs(:html_safe_if_needed).raises("Hahahaha") assert_equal "", instrumentor.browser_timing_header end end def test_browser_timing_header_safe_when_json_dump_fails in_transaction do NewRelic::JSONWrapper.stubs(:dump).raises("Serialize? Hahahaha") assert_equal "", instrumentor.browser_timing_header end end def test_config_data_for_js_agent freeze_time with_config(CAPTURE_ATTRIBUTES => true) do in_transaction('most recent transaction') do txn = NewRelic::Agent::Transaction.tl_current txn.stubs(:queue_time).returns(0) txn.stubs(:start_time).returns(Time.now - 10) txn.stubs(:guid).returns('ABC') state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) expected = { "beacon" => "beacon", "errorBeacon" => "", "licenseKey" => "browserKey", "applicationID" => "5, 6", "transactionName" => pack("most recent transaction"), "queueTime" => 0, "applicationTime" => 10000, "agent" => "" } js = instrumentor.browser_timing_config(state) expected.each do |key, value| assert_equal(value, data[key]) assert_match(/"#{key.to_s}":#{formatted_for_matching(value)}/, js) end end end end def test_config_data_for_js_agent_attributes freeze_time with_config(CAPTURE_ATTRIBUTES => true) do in_transaction('most recent transaction') do NewRelic::Agent.add_custom_attributes(:user => "user") NewRelic::Agent::Transaction.add_agent_attribute(:agent, "attribute", NewRelic::Agent::AttributeFilter::DST_ALL) state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) # Handle packed atts key specially since it's obfuscated actual = unpack_to_object(data["atts"]) expected = { "u" => {"user" => "user"}, "a" => {"agent" => "attribute"} } assert_equal expected, actual end end end def test_ssl_for_http_not_included_by_default state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) assert_not_includes data, "sslForHttp" end def test_ssl_for_http_enabled with_config(:'browser_monitoring.ssl_for_http' => true) do state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) assert data["sslForHttp"] end end def test_ssl_for_http_disabled with_config(:'browser_monitoring.ssl_for_http' => false) do state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) assert_false data["sslForHttp"] end end ATTRIBUTES_ENABLED = :'browser_monitoring.attributes.enabled' CAPTURE_ATTRIBUTES = :'browser_monitoring.capture_attributes' def test_data_for_js_agent_doesnt_get_custom_attributes_by_default with_config({}) do in_transaction do NewRelic::Agent.add_custom_attributes({:boo => "hoo"}) assert_attributes_missing end end end def test_data_for_js_agent_doesnt_get_custom_attributes_outside_transaction with_config(CAPTURE_ATTRIBUTES => true) do NewRelic::Agent.add_custom_attributes({:boo => "hoo"}) assert_attributes_missing end end def test_data_for_js_agent_gets_custom_attributes_with_old_config with_config(CAPTURE_ATTRIBUTES => true) do in_transaction do NewRelic::Agent.add_custom_attributes({:boo => "hoo"}) assert_attributes_are('{"u":{"boo":"hoo"}}') end end end def test_data_for_js_agent_gets_custom_attributes_when_configured with_config(ATTRIBUTES_ENABLED => true) do in_transaction do NewRelic::Agent.add_custom_attributes({:boo => "hoo"}) assert_attributes_are('{"u":{"boo":"hoo"}}') end end end def test_data_for_js_agent_ignores_custom_attributes_by_config with_config(CAPTURE_ATTRIBUTES => false) do in_transaction do NewRelic::Agent.add_custom_attributes({:boo => "hoo"}) assert_attributes_missing end 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, instrumentor.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, instrumentor.html_safe_if_needed(string)) end # Helpers BEGINNING_OF_FOOTER = '' def assert_has_js_agent_loader(header) assert_match(%Q[\n], header, "expected new JS agent loader 'loader' but saw '#{header}'") end def assert_has_text(snippet, footer) assert(footer.include?(snippet), "Expected footer to include snippet: #{snippet}, but instead was #{footer}") end def assert_attributes_are(expected) state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) assert_equal pack(expected), data["atts"] end def assert_attributes_missing state = NewRelic::Agent::TransactionState.tl_get data = instrumentor.data_for_js_agent(state) assert_not_includes data, "atts" end def pack(text) [text].pack("m0").gsub("\n", "") end def unpack_to_object(text) unpacked_atts = instrumentor.obfuscator.deobfuscate(text) NewRelic::JSONWrapper.load(unpacked_atts) end def formatted_for_matching(value) case value when String %Q["#{value}"] when NilClass "null" else value end end end