require 'spec_helper'

describe WebHookService, services: true do
  let(:project) { create(:empty_project) }
  let(:project_hook) { create(:project_hook) }
  let(:headers) do
    {
      'Content-Type' => 'application/json',
      'X-Gitlab-Event' => 'Push Hook'
    }
  end
  let(:data) do
    { before: 'oldrev', after: 'newrev', ref: 'ref' }
  end
  let(:service_instance) { WebHookService.new(project_hook, data, 'push_hooks') }

  describe '#execute' do
    before(:each) do
      project.hooks << [project_hook]

      WebMock.stub_request(:post, project_hook.url)
    end

    context 'when token is defined' do
      let(:project_hook) { create(:project_hook, :token) }

      it 'POSTs to the webhook URL' do
        service_instance.execute
        expect(WebMock).to have_requested(:post, project_hook.url).with(
          headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token })
        ).once
      end
    end

    it 'POSTs to the webhook URL' do
      service_instance.execute
      expect(WebMock).to have_requested(:post, project_hook.url).with(
        headers: headers
      ).once
    end

    it 'POSTs the data as JSON' do
      service_instance.execute
      expect(WebMock).to have_requested(:post, project_hook.url).with(
        headers: headers
      ).once
    end

    it 'catches exceptions' do
      WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))

      expect { service_instance.execute }.to raise_error(StandardError)
    end

    it 'handles exceptions' do
      exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout]
      exceptions.each do |exception_class|
        exception = exception_class.new('Exception message')

        WebMock.stub_request(:post, project_hook.url).to_raise(exception)
        expect(service_instance.execute).to eq([nil, exception.message])
        expect { service_instance.execute }.not_to raise_error
      end
    end

    it 'handles 200 status code' do
      WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')

      expect(service_instance.execute).to eq([200, 'Success'])
    end

    it 'handles 2xx status codes' do
      WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')

      expect(service_instance.execute).to eq([201, 'Success'])
    end

    context 'execution logging' do
      let(:hook_log) { project_hook.web_hook_logs.last }

      context 'with success' do
        before do
          WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
          service_instance.execute
        end

        it 'log successful execution' do
          expect(hook_log.trigger).to eq('push_hooks')
          expect(hook_log.url).to eq(project_hook.url)
          expect(hook_log.request_headers).to eq(headers)
          expect(hook_log.response_body).to eq('Success')
          expect(hook_log.response_status).to eq('200')
          expect(hook_log.execution_duration).to be > 0
          expect(hook_log.internal_error_message).to be_nil
        end
      end

      context 'with exception' do
        before do
          WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error'))
          service_instance.execute
        end

        it 'log failed execution' do
          expect(hook_log.trigger).to eq('push_hooks')
          expect(hook_log.url).to eq(project_hook.url)
          expect(hook_log.request_headers).to eq(headers)
          expect(hook_log.response_body).to eq('')
          expect(hook_log.response_status).to eq('internal error')
          expect(hook_log.execution_duration).to be > 0
          expect(hook_log.internal_error_message).to eq('Some HTTP Post error')
        end
      end

      context 'should not log ServiceHooks' do
        let(:service_hook) { create(:service_hook) }
        let(:service_instance) { WebHookService.new(service_hook, data, 'service_hook') }

        before do
          WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
        end

        it { expect { service_instance.execute }.not_to change(WebHookLog, :count) }
      end
    end
  end

  describe '#async_execute' do
    let(:system_hook) { create(:system_hook) }
    
    it 'enqueue WebHookWorker' do
      expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')

      WebHookService.new(project_hook, data, 'push_hooks').async_execute
    end
  end
end
