# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::Services::SyncRemotesService do
  let(:version) { ReleaseTools::Version.new('15.2.0') }
  let(:service) { described_class.new(version) }

  before do
    helm_finder = instance_double(ReleaseTools::Helm::HelmVersionFinder, execute: ReleaseTools::Version.new('3.2.1'))
    allow(ReleaseTools::Helm::HelmVersionFinder).to receive(:new).and_return(helm_finder)
  end

  describe '#execute' do
    it 'syncs stable branches for the version' do
      allow(service).to receive(:sync_tags).and_return(true)

      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::GitlabCe, '15-2-stable')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::GitlabEe, '15-2-stable-ee')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::OmnibusGitlab, '15-2-stable')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::CNGImage, '15-2-stable')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::Gitaly, '15-2-stable')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::HelmGitlab, '3-2-stable')
      expect(service).to receive(:sync_branches)
        .with(ReleaseTools::Project::GitlabPages, '15-2-stable')

      without_dry_run do
        service.execute
      end
    end

    it 'syncs tags for the version' do
      service = described_class.new(version)

      allow(service).to receive(:sync_branches).and_return(true)

      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::GitlabCe, 'v15.2.0')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::GitlabEe, 'v15.2.0-ee')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::OmnibusGitlab, '15.2.0+ee.0', '15.2.0+ce.0')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::CNGImage, 'v15.2.0', 'v15.2.0-ee', 'v15.2.0-ubi8', 'v15.2.0-fips')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::Gitaly, 'v15.2.0')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::HelmGitlab, 'v3.2.1')
      expect(service).to receive(:sync_tags)
        .with(ReleaseTools::Project::GitlabPages, 'v15.2.0')

      without_dry_run do
        service.execute
      end
    end

    context 'with release_the_kas feature flag enabled' do
      before do
        enable_feature(:release_the_kas)
      end

      it 'syncs tags and branches of kas' do
        service = described_class.new(version)

        allow(service).to receive_messages(sync_branches: true, sync_tags: true)

        expect(service).to receive(:sync_branches)
          .with(ReleaseTools::Project::Kas, '15-2-stable')

        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::Kas, 'v15.2.0')

        without_dry_run do
          service.execute
        end
      end
    end

    context 'with dry_run' do
      it 'does nothing' do
        service = described_class.new(version)

        expect(service).not_to receive(:sync_branches)
        expect(service).not_to receive(:sync_tags)

        service.execute
      end
    end

    context 'for versions before when CNG FIPS tags were introduced' do
      let(:service) { described_class.new(ReleaseTools::Version.new('14.2.0')) }

      it 'does not attempt to sync FIPS tags' do
        allow(service).to receive(:sync_branches).and_return(true)

        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::GitlabCe, 'v14.2.0')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::GitlabEe, 'v14.2.0-ee')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::OmnibusGitlab, '14.2.0+ee.0', '14.2.0+ce.0')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::CNGImage, 'v14.2.0', 'v14.2.0-ee', 'v14.2.0-ubi8')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::Gitaly, 'v14.2.0')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::HelmGitlab, 'v3.2.1')
        expect(service).to receive(:sync_tags)
          .with(ReleaseTools::Project::GitlabPages, 'v14.2.0')

        without_dry_run do
          service.execute
        end
      end
    end
  end

  describe '#sync_branches' do
    let(:fake_repo) { instance_double(ReleaseTools::RemoteRepository).as_null_object }
    let(:project) { ReleaseTools::Project::GitlabEe }

    context 'with invalid remotes' do
      it 'logs an error and returns' do
        stub_const("ReleaseTools::Project::GitlabEe::REMOTES", {})

        service = described_class.new(version)

        expect(ReleaseTools::RemoteRepository).not_to receive(:get)

        service.sync_branches(project, 'branch')
      end
    end

    context 'with a successful merge' do
      it 'merges branch and pushes' do
        branch = '15-2-stable-ee'

        successful_merge = double(status: double(success?: true))

        expect(ReleaseTools::RemoteRepository).to receive(:get)
          .with(
            a_hash_including(project::REMOTES),
            a_hash_including(branch: branch)
          ).and_return(fake_repo)

        expect(fake_repo).to receive(:merge)
          .with("dev/#{branch}", { no_ff: true })
          .and_return(successful_merge)

        expect(fake_repo).to receive(:push_to_all_remotes).with(branch).and_return([true])

        without_dry_run do
          described_class.new(version).sync_branches(project, branch)
        end
      end
    end

    context 'with a failed merge' do
      it 'logs a fatal message with the output' do
        branch = '15-2-stable-ee'

        failed_merge = double(status: double(success?: false), output: 'output')

        allow(ReleaseTools::RemoteRepository).to receive(:get).and_return(fake_repo)

        expect(fake_repo).to receive(:merge).and_return(failed_merge)
        expect(fake_repo).not_to receive(:push_to_all_remotes)

        service = described_class.new(version)
        expect(service.logger).to receive(:fatal)
          .with(anything, a_hash_including(output: 'output'))

        without_dry_run do
          service.sync_branches(project, branch)
        end
      end
    end
  end

  describe '#sync_tags' do
    let(:fake_repo) { instance_double(ReleaseTools::RemoteRepository).as_null_object }
    let(:project) { ReleaseTools::Project::GitlabEe }
    let(:tag) { 'v15.2.0' }

    it 'fetches tags and pushes' do
      allow(ReleaseTools::RemoteRepository).to receive(:get).and_return(fake_repo)

      expect(fake_repo).to receive(:fetch).with("refs/tags/#{tag}", { remote: :dev })
      expect(fake_repo).to receive(:push_to_all_remotes).with(tag)

      expect(ReleaseTools::RemoteRepository).to receive(:get)
        .with(
          a_hash_including(project::REMOTES),
          a_hash_including(global_depth: 50)
        ).and_return(fake_repo)

      without_dry_run do
        described_class.new(version).sync_tags(project, tag)
      end
    end
  end
end
