require 'spec_helper'

describe Services::Base::CallLogger do
  include_context 'capture logs'

  context 'when call logging is enabled' do
    it 'logs start and end' do
      service, args = EmptyService.new, %w(foo bar)
      service.call *args
      caller_regex = /\A#{Regexp.escape __FILE__}:\d+\z/
      expect(logs.first).to match(
        message:  "START with args: #{args}, kwargs: {}",
        meta: {
          caller:  a_string_matching(caller_regex),
          service: service.class.to_s,
          id:      a_kind_of(String)
        },
        severity: 'info'
      )
      expect(logs.last).to match(
        message: 'END',
        meta: {
          duration: a_value_within(0.1).of(0.0),
          service:  service.class.to_s,
          id:       a_kind_of(String)
        },
        severity: 'info'
      )
    end

    describe 'logging the caller' do
      let(:service_calling_service) { ServiceCallingService.new }
      let(:called_service) { EmptyService.new }

      it 'filters out caller paths from lib folder' do
        require 'services/call_proxy'
        Services::CallProxy.call(called_service, :call)
        caller_regex = /\A#{Regexp.escape __FILE__}:\d+/
        expect(
          logs.detect do |log|
            log[:meta][:caller] =~ caller_regex
          end
        ).to be_present
      end

      context 'when Rails is not defined' do
        it 'logs the complete caller path' do
          service_calling_service.call called_service
          caller_regex = /\A#{Regexp.escape PROJECT_ROOT.join(TEST_SERVICES_PATH).to_s}:\d+/
          expect(
            logs.detect do |log|
              log[:meta][:caller] =~ caller_regex
            end
          ).to be_present
        end
      end

      context 'when Rails is defined' do
        before do
          class Rails
            def self.root
              PROJECT_ROOT
            end
          end
        end

        after do
          Object.send :remove_const, :Rails
        end

        it 'logs the caller path relative to `Rails.root`' do
          service_calling_service.call called_service
          caller_regex = /\A#{Regexp.escape TEST_SERVICES_PATH.to_s}:\d+/
          expect(
            logs.detect do |log|
              log[:meta][:caller] =~ caller_regex
            end
          ).to be_present
        end
      end
    end
  end

  context 'when call logging is disabled' do
    it 'does not log start and end' do
      expect(EmptyServiceWithoutCallLogging.call_logging_disabled).to eq(true)
      expect { EmptyServiceWithoutCallLogging.call }.to_not change { logs }
    end
  end

  it 'logs exceptions' do
    [ErrorService, ErrorServiceWithoutCallLogging].each do |klass|
      expect { klass.call rescue nil }.to change { logs }
    end
  end

  if RUBY_VERSION > '2.1'
    it 'logs exception causes' do
      service = NestedExceptionService.new
      expect { service.call }.to raise_error(service.class::Error)
      %w(NestedError1 NestedError2).each do |error|
        message_regex = /caused by: #{service.class}::#{error}/
        expect(
          logs.detect do |log|
            log[:message] =~ message_regex
          end
        ).to be_present
      end
    end
  end
end
