web_hook_service_spec.rb 6.73 KB
Newer Older
1 2
require 'spec_helper'

3
describe WebHookService do
4
  let(:project) { create(:project) }
5 6 7 8 9 10 11 12 13 14
  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
15
  let(:service_instance) { described_class.new(project_hook, data, :push_hooks) }
16

17 18 19 20 21 22 23 24 25 26 27 28 29 30
  describe '#initialize' do
    it 'allow_local_requests is true if hook is a SystemHook' do
      instance = described_class.new(build(:system_hook), data, :system_hook)
      expect(instance.request_options[:allow_local_requests]).to be_truthy
    end

    it 'allow_local_requests is false if hook is not a SystemHook' do
      %i(project_hook service_hook web_hook_log).each do |hook|
        instance = described_class.new(build(hook), data, hook)
        expect(instance.request_options[:allow_local_requests]).to be_falsey
      end
    end
  end

31
  describe '#execute' do
32
    before do
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
      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

63
    context 'when auth credentials are present' do
64
      let(:url) {'https://example.org'}
65 66 67 68 69 70 71 72 73 74 75 76 77 78
      let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }

      it 'uses the credentials' do
        WebMock.stub_request(:post, url)

        service_instance.execute

        expect(WebMock).to have_requested(:post, url).with(
          headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v')
        ).once
      end
    end

    context 'when auth credentials are partial present' do
79
      let(:url) {'https://example.org'}
80 81 82 83 84 85 86 87 88 89 90 91 92
      let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }

      it 'uses the credentials anyways' do
        WebMock.stub_request(:post, url)

        service_instance.execute

        expect(WebMock).to have_requested(:post, url).with(
          headers: headers.merge('Authorization' => 'Basic ZGVtbzo=')
        ).once
      end
    end

93 94 95 96 97 98 99
    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
100
      exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep]
101 102 103 104
      exceptions.each do |exception_class|
        exception = exception_class.new('Exception message')

        WebMock.stub_request(:post, project_hook.url).to_raise(exception)
105
        expect(service_instance.execute).to eq({ status: :error, message: exception.to_s })
106 107 108 109 110 111 112
        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')

113
      expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
114 115 116 117 118
    end

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

119
      expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
    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

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      context 'with unsafe response body' do
        before do
          WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "\xBB")
          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('')
          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

176 177
      context 'should not log ServiceHooks' do
        let(:service_hook) { create(:service_hook) }
178
        let(:service_instance) { described_class.new(service_hook, data, 'service_hook') }
179 180 181 182 183 184 185 186 187 188 189 190

        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) }
191

192
    it 'enqueue WebHookWorker' do
193
      expect(WebHookWorker).to receive(:perform_async).with(project_hook.id, data, 'push_hooks')
194

195
      described_class.new(project_hook, data, 'push_hooks').async_execute
196 197 198
    end
  end
end