# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::Services::BranchesStatusService do
  describe '#execute' do
    subject(:branch_verifier) { described_class.new(release_type: release_type, versions: versions) }

    let(:notifier) { stub_const('ReleaseTools::Slack::ReleaseJobEndNotifier', spy) }
    let(:projects) { [ReleaseTools::Project::GitlabCe, ReleaseTools::Project::GitlabEe] }
    let(:statuses) do
      [
        { project_name: 'project_name', branch: 'branch_name', link: 'link', status: 'failed' }
      ]
    end

    before do
      stub_const("ReleaseTools::Services::BranchesStatusService::PROJECTS", projects)
    end

    context "on monthly release" do
      let(:release_type) { :monthly }
      let(:versions) { [ReleaseTools::Version.new("15.9")] }

      it "returns all stable branches are green on success status" do
        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '15-9-stable',
          release_type: :monthly
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '15-9-stable-ee',
          release_type: :monthly
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(branch_verifier.logger).to receive(:info).with(/All branches are green/)
        expect(notifier).to receive(:send_notification)

        branch_verifier.execute
      end

      context 'when some statuses are not success and raises an exception' do
        it 'outputs the component versions that are not successful' do
          logger_output = "

❌ The following branches do not have green pipelines:

- gitlab-ce - 15-9-stable - failed: https://gitlab.com/gitlab-org/gitlab-foss/-/commits/15-9-stable
- gitlab-ee - 15-9-stable-ee - canceled: https://gitlab.com/gitlab-org/gitlab/-/commits/15-9-stable-ee"

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '15-9-stable',
            release_type: :monthly
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '15-9-stable-ee',
            release_type: :monthly
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'canceled'))

          expect(branch_verifier.logger).to receive(:info).with(logger_output)
          expect(notifier).to receive(:send_notification)

          expect { branch_verifier.execute }.to raise_error(described_class::FailedBranchError)
        end
      end

      context 'when the job is unable to complete and an error is raised' do
        before do
          allow(branch_verifier).to receive(:statuses).and_return(statuses)
        end

        it 'outputs manual instructions and raises an error' do
          log_message = "Project status check failed. If this job continues to fail, the status of the projects can be checked manually:\n\n- project_name - branch_name: link"

          allow(ReleaseTools::Services::ComponentStatusService).to receive(:new)
            .and_raise(Gitlab::Error::Error)

          expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new).with(
            job_type: 'Branches Status',
            status: :failed,
            release_type: :monthly
          ).and_return(instance_double(ReleaseTools::Slack::ReleaseJobEndNotifier, send_notification: true))

          expect(branch_verifier.logger).to receive(:fatal).with(log_message, error: anything)

          expect { branch_verifier.execute }.to raise_error(Gitlab::Error::Error)
        end
      end
    end

    context "on patch release preparation" do
      let(:release_type) { :patch }
      let(:versions) { [ReleaseTools::Version.new("17.1"), ReleaseTools::Version.new("16.10"), ReleaseTools::Version.new("15.11")] }

      before do
        allow(branch_verifier).to receive(:projects).and_return(projects)
      end

      it "returns all branches are green on success status" do
        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '17-1-stable',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '16-10-stable',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '15-11-stable',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '17-1-stable-ee',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '16-10-stable-ee',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '15-11-stable-ee',
          release_type: :patch
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(branch_verifier.logger).to receive(:info).with(/All branches are green/)
        expect(notifier).to receive(:send_notification)

        branch_verifier.execute
      end

      context 'when some statuses are not success and raises an exception' do
        it 'outputs the component versions that are not successful' do
          logger_output = "

❌ The following branches do not have green pipelines:

- gitlab-ce - 17-1-stable - failed: https://gitlab.com/gitlab-org/security/gitlab-foss/-/commits/17-1-stable
- gitlab-ce - 16-10-stable - failed: https://gitlab.com/gitlab-org/security/gitlab-foss/-/commits/16-10-stable
- gitlab-ce - 15-11-stable - failed: https://gitlab.com/gitlab-org/security/gitlab-foss/-/commits/15-11-stable
- gitlab-ee - 17-1-stable-ee - canceled: https://gitlab.com/gitlab-org/security/gitlab/-/commits/17-1-stable-ee
- gitlab-ee - 16-10-stable-ee - running: https://gitlab.com/gitlab-org/security/gitlab/-/commits/16-10-stable-ee
- gitlab-ee - 15-11-stable-ee - failed: https://gitlab.com/gitlab-org/security/gitlab/-/commits/15-11-stable-ee"

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '17-1-stable',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '16-10-stable',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '15-11-stable',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '17-1-stable-ee',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'canceled'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '16-10-stable-ee',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'running'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '15-11-stable-ee',
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(branch_verifier.logger).to receive(:info).with(logger_output)
          expect(notifier).to receive(:send_notification)

          expect(branch_verifier.logger).not_to receive(:fatal)

          expect { branch_verifier.execute }.to raise_error(described_class::FailedBranchError)
        end
      end

      context 'when the job is unable to complete and an error is raised' do
        before do
          allow(branch_verifier).to receive(:statuses).and_return(statuses)
          allow(ReleaseTools::Services::ComponentStatusService).to receive(:new)
            .and_raise(Gitlab::Error::Error)
        end

        it 'outputs manual instructions and raises an error' do
          log_message = "Project status check failed. If this job continues to fail, the status of the projects can be checked manually:\n\n- project_name - branch_name: link"

          expect(branch_verifier).to receive(:generate_link_list)
            .with(projects, versions, release_type)
            .and_yield('project_name', 'branch_name', 'link')

          expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new).with(
            job_type: 'Branches Status',
            status: :failed,
            release_type: :patch
          ).and_return(instance_double(ReleaseTools::Slack::ReleaseJobEndNotifier, send_notification: true))

          expect(branch_verifier.logger).to receive(:fatal).with(log_message, error: anything)

          expect { branch_verifier.execute }.to raise_error(Gitlab::Error::Error)
        end

        it 'does not log the failure message (info) and does not raise FailedBranchError' do
          expect(branch_verifier.logger).not_to receive(:info)

          expect { branch_verifier.execute }.not_to raise_error(described_class::FailedBranchError)
        end
      end
    end

    context "on internal release" do
      let(:release_type) { :internal }
      let(:versions) do
        [
          ReleaseTools::Version.new('15.2.1-internal1'),
          ReleaseTools::Version.new('15.1.2-internal1')
        ]
      end

      before do
        allow(branch_verifier).to receive(:projects).and_return(projects)
      end

      it "returns all branches are green on success status" do
        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '15-2-stable',
          release_type: :internal
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.first,
          branch: '15-1-stable',
          release_type: :internal
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '15-2-stable-ee',
          release_type: :internal
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
          project: projects.second,
          branch: '15-1-stable-ee',
          release_type: :internal
        ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'success'))

        expect(branch_verifier.logger).to receive(:info).with(/All branches are green/)
        expect(notifier).to receive(:send_notification)

        branch_verifier.execute
      end

      context 'when some statuses are not success and raises an exception' do
        it 'outputs the component versions that are not successful' do
          logger_output = "

❌ The following branches do not have green pipelines:

- gitlab-ce - 15-2-stable - failed: https://gitlab.com/gitlab-org/security/gitlab-foss/-/commits/15-2-stable
- gitlab-ce - 15-1-stable - failed: https://gitlab.com/gitlab-org/security/gitlab-foss/-/commits/15-1-stable
- gitlab-ee - 15-2-stable-ee - canceled: https://gitlab.com/gitlab-org/security/gitlab/-/commits/15-2-stable-ee
- gitlab-ee - 15-1-stable-ee - running: https://gitlab.com/gitlab-org/security/gitlab/-/commits/15-1-stable-ee"

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '15-2-stable',
            release_type: :internal
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.first,
            branch: '15-1-stable',
            release_type: :internal
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'failed'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '15-2-stable-ee',
            release_type: :internal
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'canceled'))

          expect(ReleaseTools::Services::ComponentStatusService).to receive(:new).with(
            project: projects.second,
            branch: '15-1-stable-ee',
            release_type: :internal
          ).and_return(instance_double(ReleaseTools::Services::ComponentStatusService, execute: 'running'))

          expect(branch_verifier.logger).to receive(:info).with(logger_output)
          expect(notifier).to receive(:send_notification)

          expect(branch_verifier.logger).not_to receive(:fatal)

          expect { branch_verifier.execute }.to raise_error(described_class::FailedBranchError)
        end
      end

      context 'when the job is unable to complete and an error is raised' do
        before do
          allow(branch_verifier).to receive(:statuses).and_return(statuses)
          allow(ReleaseTools::Services::ComponentStatusService).to receive(:new)
            .and_raise(StandardError)
        end

        it 'outputs manual instructions and raises an error' do
          log_message = "Project status check failed. If this job continues to fail, the status of the projects can be checked manually:\n\n- project_name - branch_name: link"

          expect(branch_verifier).to receive(:generate_link_list)
            .with(projects, versions, release_type)
            .and_yield('project_name', 'branch_name', 'link')

          expect(ReleaseTools::Slack::ReleaseJobEndNotifier).to receive(:new).with(
            job_type: 'Branches Status',
            status: :failed,
            release_type: :internal
          ).and_return(instance_double(ReleaseTools::Slack::ReleaseJobEndNotifier, send_notification: true))

          expect(branch_verifier.logger).to receive(:fatal).with(log_message, error: anything)

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

      it 'does not log the failure message (info) and does not raise FailedBranchError' do
        expect(branch_verifier.logger).not_to receive(:info)

        expect { branch_verifier.execute }.not_to raise_error(described_class::FailedBranchError)
      end
    end
  end
end
