require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))

module NewRelic::Agent
  class CrossAppMonitorTest < Minitest::Test
    NEWRELIC_ID_HEADER        = NewRelic::Agent::CrossAppMonitor::NEWRELIC_ID_HEADER
    NEWRELIC_TXN_HEADER       = NewRelic::Agent::CrossAppMonitor::NEWRELIC_TXN_HEADER

    AGENT_CROSS_APP_ID        = "qwerty"
    REQUEST_CROSS_APP_ID      = "42#1234"
    TRANSACTION_GUID          = '941B0E8001E444E8'
    REF_TRANSACTION_GUID      = '830092CDE59421D4'

    TRANSACTION_NAME          = 'transaction'
    QUEUE_TIME                = 1000
    APP_TIME                  = 2000

    ENCODING_KEY_NOOP         = "\0"
    TRUSTED_ACCOUNT_IDS       = [42,13]

    CROSS_APP_ID_POSITION     = 0
    TRANSACTION_NAME_POSITION = 1
    QUEUE_TIME_POSITION       = 2
    APP_TIME_POSITION         = 3
    CONTENT_LENGTH_POSITION   = 4

    def setup
      NewRelic::Agent.reset_config
      NewRelic::Agent.instance.stats_engine.clear_stats
      NewRelic::Agent.instance.events.clear
      @response = {}

      @monitor = NewRelic::Agent::CrossAppMonitor.new()
      @config = {
        :cross_process_id => AGENT_CROSS_APP_ID,
        :encoding_key => ENCODING_KEY_NOOP,
        :trusted_account_ids => TRUSTED_ACCOUNT_IDS
      }

      NewRelic::Agent.config.apply_config( @config )
      @monitor.on_finished_configuring
    end

    def teardown
      NewRelic::Agent.config.remove_config( @config )
      NewRelic::Agent.instance.events.clear
    end


    #
    # Tests
    #

    def test_adds_response_header
      with_default_timings

      when_request_runs

      assert_equal 'WyJxd2VydHkiLCJ0cmFuc2FjdGlvbiIsMTAwMC4wLDIwMDAuMCwtMSwiOTQxQjBFODAwMUU0NDRFOCJd', response_app_data
      assert_equal [AGENT_CROSS_APP_ID, TRANSACTION_NAME, QUEUE_TIME, APP_TIME, -1, TRANSACTION_GUID], unpacked_response
    end

    def test_encodes_transaction_name
      NewRelic::Agent::TransactionState.any_instance.stubs(:timings).returns(stub(
          :transaction_name => "\"'goo",
          :queue_time_in_seconds => QUEUE_TIME,
          :app_time_in_seconds => APP_TIME))

      when_request_runs

      assert_equal "\"'goo", unpacked_response[TRANSACTION_NAME_POSITION]
    end

    def test_doesnt_write_response_header_if_id_blank
      with_default_timings

      when_request_runs(for_id(''))
      assert_nil response_app_data
    end

    def test_doesnt_write_response_header_if_untrusted_id
      with_default_timings

      when_request_runs(for_id("4#1234"))
      assert_nil response_app_data
    end

    def test_doesnt_write_response_header_if_improperly_formatted_id
      with_default_timings

      when_request_runs(for_id("42"))
      assert_nil response_app_data
    end

    def test_doesnt_add_header_if_no_id_in_request
      when_request_runs({})
      assert_nil response_app_data
    end

    def test_doesnt_add_header_if_no_id_on_agent
      with_config( :cross_process_id => '' ) do
        when_request_runs
        assert_nil response_app_data
      end
    end

    def test_doesnt_add_header_if_config_disabled
      with_config(:"cross_application_tracer.enabled" => false, :cross_application_tracing => false) do
        when_request_runs
        assert_nil response_app_data
      end
    end

    def test_doesnt_add_header_if_missing_encoding_key
      with_config( :encoding_key => '' ) do
        when_request_runs
        assert_nil response_app_data
      end
    end

    def test_includes_content_length
      with_default_timings

      when_request_runs(for_id(REQUEST_CROSS_APP_ID).merge("Content-Length" => 3000))
      assert_equal 3000, unpacked_response[CONTENT_LENGTH_POSITION]
    end

    def test_finds_content_length_from_headers
      %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH cOnTeNt-LeNgTh}.each do |key|
        request = { key => 42 }

        assert_equal(42, @monitor.content_length_from_request(request), \
          "Failed to find header on key #{key}")
      end
    end

    def test_writes_custom_parameters
      with_default_timings

      NewRelic::Agent.expects(:add_custom_parameters).with(:client_cross_process_id => REQUEST_CROSS_APP_ID)
      NewRelic::Agent.expects(:add_custom_parameters).with(:referring_transaction_guid => REF_TRANSACTION_GUID)

      when_request_runs
    end

    def test_error_writes_custom_parameters
      with_default_timings

      options = when_request_has_error

      assert_equal REQUEST_CROSS_APP_ID, options[:client_cross_process_id]
    end

    def test_error_doesnt_write_custom_parameters_if_no_id
      with_default_timings

      options = when_request_has_error(for_id(''))

      assert_equal false, options.key?(:client_cross_process_id)
    end

    def test_writes_metric
      with_default_timings

      when_request_runs

      assert_metrics_recorded(["ClientApplication/#{REQUEST_CROSS_APP_ID}/all"])
    end

    def test_doesnt_write_metric_if_id_blank
      with_default_timings

      when_request_runs(for_id(''))

      assert_metrics_recorded_exclusive([])
    end

    def test_setting_response_headers_freezes_transaction_name
      in_transaction do
        assert !NewRelic::Agent::Transaction.current.name_frozen?
        when_request_runs
        assert NewRelic::Agent::Transaction.current.name_frozen?
      end
    end

    #
    # Helpers
    #

    def when_request_runs(request=for_id(REQUEST_CROSS_APP_ID))
      event_listener = NewRelic::Agent.instance.events
      event_listener.notify(:before_call, request)
      in_transaction('transaction') do
        # Fake out our GUID for easier comparison in tests
        NewRelic::Agent::Transaction.current.stubs(:guid).returns(TRANSACTION_GUID)
      end
      event_listener.notify(:after_call, request, [200, @response, ''])
    end

    def when_request_has_error(request=for_id(REQUEST_CROSS_APP_ID))
      options = {}
      event_listener = NewRelic::Agent.instance.events
      event_listener.notify(:before_call, request)
      event_listener.notify(:notice_error, nil, options)
      event_listener.notify(:after_call, request, [500, @response, ''])

      options
    end

    def with_default_timings
      NewRelic::Agent::TransactionState.any_instance.stubs(:timings).returns(stub(
          :transaction_name => TRANSACTION_NAME,
          :queue_time_in_seconds => QUEUE_TIME,
          :app_time_in_seconds => APP_TIME))
    end

    def for_id(id)
      encoded_id = id == "" ? "" : Base64.encode64(id)
      encoded_txn_info = Base64.encode64( NewRelic::JSONWrapper.dump([ REF_TRANSACTION_GUID, false ]) )

      return {
        NEWRELIC_ID_HEADER => encoded_id,
        NEWRELIC_TXN_HEADER => encoded_txn_info,
      }
    end

    def response_app_data
      @response['X-NewRelic-App-Data']
    end

    def unpacked_response
      return nil unless response_app_data
      NewRelic::JSONWrapper.load(Base64.decode64(response_app_data))
    end

  end
end
