# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::Commits do
  subject(:instance) { described_class.new(project) }

  let(:project) { ReleaseTools::Project::GitlabCe }

  before do
    # Reduce our fixture payload
    stub_const('ReleaseTools::Commits::MAX_COMMITS_TO_CHECK', 5)
  end

  describe '#latest_successful_on_build' do
    it 'handles a missing commit on dev' do
      allow(ReleaseTools::GitlabDevClient)
        .to receive(:commit)
        .at_least(:twice)
        .and_raise(gitlab_error(:NotFound))

      expect(instance).not_to receive(:find_last_auto_deploy_limit_sha)

      VCR.use_cassette('commits/list') do
        expect(
          instance.latest_successful_on_build(since_last_auto_deploy: false) do |_|
            true
          end
        ).to be_nil
      end
    end

    it 'returns a commit found on dev' do
      allow(ReleaseTools::GitlabDevClient)
        .to receive(:commit)
        .and_return('foo')

      expect(instance).not_to receive(:find_last_auto_deploy_limit_sha)

      VCR.use_cassette('commits/list') do
        predicate = ->(_) { true }
        expect(instance.latest_successful_on_build(since_last_auto_deploy: false, &predicate)).not_to be_nil
      end
    end

    context 'when limited to the last auto deploy package' do
      let(:project) { ReleaseTools::Project::GitlabEe }

      it 'limits the search to the merge base with the last package SHA' do
        commits = build_list(:commit, 5)
        last_pkg_sha = commits[1].id

        last_product_version = build(:product_version,
                                     releases: build(:releases_metadata,
                                                     gitlab_ee: build(:component_metadata, sha: last_pkg_sha)))

        expect(ReleaseTools::ProductVersion).to receive(:last_auto_deploy).and_return(last_product_version)
        expect(instance).to receive(:commit_list).and_return(commits)
        expect(instance).to receive(:merge_base).once.and_return(last_pkg_sha)

        first_commit_sha = commits.first.id
        block_received_first_commit_sha = false
        commit = instance.latest_successful_on_build(since_last_auto_deploy: true) do |sha|
          # verify the block was invoked
          block_received_first_commit_sha = (sha == first_commit_sha)

          # verify it was only invoked with the first commit
          expect(sha).to eq(first_commit_sha)

          false
        end

        expect(block_received_first_commit_sha).to be true
        expect(commit.id).to eq(last_pkg_sha)
      end
    end
  end

  describe '#merge_base' do
    let(:project) { ReleaseTools::Project::GitlabEe }
    let(:ref) { '12-10-auto-deploy-20200405' }
    let(:client) { double('ReleaseTools::GitlabClient') }

    subject(:commits) { described_class.new(project, client: client, ref: ref) }

    it 'returns the merge-base commit id' do
      merge_base = double('merge_base', id: '123abc')

      expect(client)
        .to receive(:merge_base)
        .with(project.auto_deploy_path, [ref, 'another_ref'])
        .and_return(merge_base)
        .once

      merge_base_id = commits.merge_base('another_ref')

      expect(merge_base_id).to eq(merge_base.id)
    end
  end

  describe '#next_commit' do
    let(:instance) { described_class.new(project, client: client) }
    let(:client) { class_spy(ReleaseTools::GitlabClient) }

    it 'returns the next newer commit from the commit_list' do
      allow(client)
        .to receive(:commits)
        .with(project.auto_deploy_path, hash_including(ref_name: 'master'))
        .and_return([double(id: 'commit1'), double(id: 'commit2')])

      expect(instance.next_commit('commit2').id).to eq('commit1')
    end

    it 'does not call commits API when memoized' do
      instance.instance_variable_set(:@commit_list, [double(id: 'commit1'), double(id: 'commit2')])

      expect(client).not_to receive(:commits)

      expect(instance.next_commit('commit2').id).to eq('commit1')
    end

    it 'returns nil if there is no newer commit' do
      allow(instance)
        .to receive(:commit_list)
        .and_return([double(id: 'commit1')])

      expect(instance.next_commit('commit1')).to be_nil
    end

    it 'returns nil if given commit cannot be found' do
      allow(instance)
        .to receive(:commit_list)
        .and_return([double(id: 'commit1')])

      expect(instance.next_commit('commit3')).to be_nil
    end
  end

  describe '#find_last_auto_deploy_limit_sha' do
    let(:project) { ReleaseTools::Project::GitlabEe }
    let(:client) { double('ReleaseTools::GitlabClient') }
    let(:instance) { described_class.new(project, client: client) }

    context 'when a previous auto-deploy package exists' do
      it 'gets the SHA from the last auto-deploy ProductVersion and finds the merge base' do
        previous_sha = 'abcde123456'
        merge_base_result = 'def456'
        product_version = build(
          :product_version,
          releases: build(:releases_metadata, gitlab_ee: build(:component_metadata, sha: previous_sha))
        )

        # Expect to get the last auto-deploy version
        expect(ReleaseTools::ProductVersion)
          .to receive(:last_auto_deploy)
          .and_return(product_version)
          .once

        # Expect to find merge base with that SHA
        expect(instance).to receive(:merge_base)
          .with(previous_sha)
          .and_return(merge_base_result)

        # Verify the correct SHA is returned
        result = instance.find_last_auto_deploy_limit_sha
        expect(result).to eq(merge_base_result)

        # Verify it gets memoized by calling again even though we're expecting last_auto_deploy to be called only once
        expect(instance.find_last_auto_deploy_limit_sha).to eq(merge_base_result)
      end
    end

    context 'when project is not a supported auto-deploy project' do
      let(:project) { ReleaseTools::Project::Gitaly }

      it 'returns nil without checking ProductVersion' do
        expect(ReleaseTools::ProductVersion).not_to receive(:last_auto_deploy)
        expect(instance).not_to receive(:merge_base)

        expect(instance.find_last_auto_deploy_limit_sha).to be_nil
      end
    end
  end
end
