# frozen_string_literal: true

require 'spec_helper'

describe Sentry::Client do
  let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
  let(:token) { 'test-token' }

  let(:issues_sample_response) do
    Gitlab::Utils.deep_indifferent_access(
      JSON.parse(fixture_file('sentry/issues_sample_response.json'))
    )
  end

  let(:projects_sample_response) do
    Gitlab::Utils.deep_indifferent_access(
      JSON.parse(fixture_file('sentry/list_projects_sample_response.json'))
    )
  end

  subject(:client) { described_class.new(sentry_url, token) }

  # Requires sentry_api_url and subject to be defined
  shared_examples 'no redirects' do
    let(:redirect_to) { 'https://redirected.example.com' }
    let(:other_url) { 'https://other.example.org' }

    let!(:redirected_req_stub) { stub_sentry_request(other_url) }

    let!(:redirect_req_stub) do
      stub_sentry_request(
        sentry_api_url,
        status: 302,
        headers: { location: redirect_to }
      )
    end

    it 'does not follow redirects' do
      expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response error: 302')
      expect(redirect_req_stub).to have_been_requested
      expect(redirected_req_stub).not_to have_been_requested
    end
  end

  shared_examples 'has correct return type' do |klass|
    it "returns objects of type #{klass}" do
      expect(subject).to all( be_a(klass) )
    end
  end

  shared_examples 'has correct length' do |length|
    it { expect(subject.length).to eq(length) }
  end

  # Requires sentry_api_request and subject to be defined
  shared_examples 'calls sentry api' do
    it 'calls sentry api' do
      subject

      expect(sentry_api_request).to have_been_requested
    end
  end

  describe '#list_issues' do
    let(:issue_status) { 'unresolved' }
    let(:limit) { 20 }

    let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: issues_sample_response) }

    subject { client.list_issues(issue_status: issue_status, limit: limit) }

    it_behaves_like 'calls sentry api'

    it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
    it_behaves_like 'has correct length', 1

    context 'error object created from sentry response' do
      using RSpec::Parameterized::TableSyntax

      where(:error_object, :sentry_response) do
        :id           | :id
        :first_seen   | :firstSeen
        :last_seen    | :lastSeen
        :title        | :title
        :type         | :type
        :user_count   | :userCount
        :count        | :count
        :message      | [:metadata, :value]
        :culprit      | :culprit
        :short_id     | :shortId
        :status       | :status
        :frequency    | [:stats, '24h']
        :project_id   | [:project, :id]
        :project_name | [:project, :name]
        :project_slug | [:project, :slug]
      end

      with_them do
        it { expect(subject[0].public_send(error_object)).to eq(issues_sample_response[0].dig(*sentry_response)) }
      end

      context 'external_url' do
        it 'is constructed correctly' do
          expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
        end
      end
    end

    context 'redirects' do
      let(:sentry_api_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' }

      it_behaves_like 'no redirects'
    end

    # Sentry API returns 404 if there are extra slashes in the URL!
    context 'extra slashes in URL' do
      let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' }
      let(:client) { described_class.new(sentry_url, token) }

      let!(:valid_req_stub) do
        stub_sentry_request(
          'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \
          'issues/?limit=20&query=is:unresolved'
        )
      end

      it 'removes extra slashes in api url' do
        expect(Gitlab::HTTP).to receive(:get).with(
          URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'),
          anything
        ).and_call_original

        subject

        expect(valid_req_stub).to have_been_requested
      end
    end
  end

  describe '#list_projects' do
    let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' }

    let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) }

    subject { client.list_projects }

    it_behaves_like 'calls sentry api'

    it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project
    it_behaves_like 'has correct length', 2

    context 'keys missing in API response' do
      it 'raises exception' do
        projects_sample_response[0].delete(:slug)

        stub_sentry_request(sentry_list_projects_url, body: projects_sample_response)

        expect { subject }.to raise_error(Sentry::Client::SentryError, 'Sentry API response is missing keys. key not found: "slug"')
      end
    end

    context 'error object created from sentry response' do
      using RSpec::Parameterized::TableSyntax

      where(:sentry_project_object, :sentry_response) do
        :id                | :id
        :name              | :name
        :status            | :status
        :slug              | :slug
        :organization_name | [:organization, :name]
        :organization_id   | [:organization, :id]
        :organization_slug | [:organization, :slug]
      end

      with_them do
        it { expect(subject[0].public_send(sentry_project_object)).to eq(projects_sample_response[0].dig(*sentry_response)) }
      end
    end

    context 'redirects' do
      let(:sentry_api_url) { sentry_list_projects_url }

      it_behaves_like 'no redirects'
    end

    # Sentry API returns 404 if there are extra slashes in the URL!
    context 'extra slashes in URL' do
      let(:sentry_url) { 'https://sentrytest.gitlab.com/api//0/projects//' }
      let(:client) { described_class.new(sentry_url, token) }

      let!(:valid_req_stub) do
        stub_sentry_request(sentry_list_projects_url)
      end

      it 'removes extra slashes in api url' do
        expect(Gitlab::HTTP).to receive(:get).with(
          URI(sentry_list_projects_url),
          anything
        ).and_call_original

        subject

        expect(valid_req_stub).to have_been_requested
      end
    end
  end

  private

  def stub_sentry_request(url, body: {}, status: 200, headers: {})
    WebMock.stub_request(:get, url)
      .to_return(
        status: status,
        headers: { 'Content-Type' => 'application/json' }.merge(headers),
        body: body.to_json
      )
  end
end
