require 'spec_helper'
require 'gitlab/package_repository'

RSpec.describe PackageRepository do
  let(:repo) { PackageRepository.new }

  before do
    allow(ENV).to receive(:[]).and_call_original
  end

  describe :repository_for_rc do
    context 'on master' do
      # Example:
      # on non stable branch: 8.1.0+rc1.ce.0-1685-gd2a2c51
      # on tag: 8.12.0+rc1.ee.0
      before do
        allow(IO).to receive(:popen).with(%w[git describe]).and_return("8.12.0+rc1.ee.0\n")
      end

      it { expect(repo.repository_for_rc).to eq 'unstable' }
    end

    context 'on stable branch' do
      # Example:
      # on non stable branch: 8.12.8+ce.0-1-gdac92d4
      # on tag: 8.12.8+ce.0
      before do
        allow(IO).to receive(:popen).with(%w[git describe]).and_return("8.12.8+ce.0\n")
      end

      it { expect(repo.repository_for_rc).to eq nil }
    end
  end

  describe :target do
    shared_examples 'with an override repository' do
      context 'with repository override' do
        before do
          set_all_env_variables
        end

        it 'uses the override repository' do
          expect(repo.target).to eq('super-stable-1234')
        end
      end
    end

    shared_examples 'with raspberry pi repo' do
      context 'with raspberry pi repo' do
        before do
          set_raspi_env_variable
        end

        it 'uses the raspberry pi repository' do
          expect(repo.target).to eq('raspi')
        end
      end
    end

    context 'on non-stable branch' do
      before do
        unset_all_env_variables
        allow(IO).to receive(:popen).with(%w[git describe]).and_return("8.1.0+rc1.ce.0-1685-gd2a2c51\n")
      end

      it 'prints unstable' do
        expect(repo.target).to eq('unstable')
      end

      it_behaves_like 'with an override repository'
      it_behaves_like 'with raspberry pi repo'
    end

    context 'on a stable branch' do
      before do
        unset_all_env_variables
        allow(IO).to receive(:popen).with(%w[git describe]).and_return("8.12.8+ce.0-1-gdac92d4\n")
      end

      context 'when EE' do
        before do
          allow(File).to receive(:read).with('VERSION').and_return("1.2.3-ee\n")
        end

        it 'prints gitlab-ee' do
          expect(repo.target).to eq('gitlab-ee')
        end

        it_behaves_like 'with an override repository'
        it_behaves_like 'with raspberry pi repo'
      end

      context 'when CE' do
        before do
          stub_is_ee(false)
          allow(File).to receive(:read).with('VERSION').and_return("1.2.3\n")
        end

        it 'prints gitlab-ce' do
          expect(repo.target).to eq('gitlab-ce')
        end

        it_behaves_like 'with an override repository'
        it_behaves_like 'with raspberry pi repo'
      end
    end
  end

  describe :validate do
    context 'with artifacts available' do
      before do
        allow(Build::Info::Package).to receive(:file_list).and_return(['pkg/el-6/gitlab-ce.rpm'])
      end

      it 'in dry run mode prints the checksum commands' do
        expect { repo.validate(true) }.to output("sha256sum -c pkg/el-6/gitlab-ce.rpm.sha256\n").to_stdout
      end

      it 'raises an exception when there is a mismatch' do
        expect(repo).to receive(:verify_checksum).with('pkg/el-6/gitlab-ce.rpm.sha256', true).and_return(false)

        expect { repo.validate(true) }.to raise_error(%r{Aborting, package .* has an invalid checksum!})
      end
    end

    context 'with artifacts unavailable' do
      before do
        allow(Build::Info::Package).to receive(:file_list).and_return([])
      end

      it 'prints nothing' do
        expect { repo.validate(true) }.to output('').to_stdout
      end
    end
  end

  describe '#upload' do
    describe 'with staging repository' do
      context 'when upload user is not specified' do
        before do
          unset_all_env_variables
        end

        it 'prints a message and aborts' do
          expect { repo.upload('my-staging-repository', true) }.to output(%r{Owner of the repository to which packages are being uploaded not specified}).to_stdout
        end
      end

      context 'with specified upload user' do
        before do
          stub_env_var('PACKAGECLOUD_USER', "gitlab")
        end

        context 'with artifacts available' do
          before do
            allow(Build::Info::Package).to receive(:file_list).and_return(['pkg/el-9/gitlab-ce.rpm'])
          end

          it 'in dry run mode prints the upload commands' do
            expect { repo.upload('my-staging-repository', true) }.to output(%r{Uploading...\n}).to_stdout
            expect { repo.upload('my-staging-repository', true) }.to output(%r{bin/package_cloud push gitlab/my-staging-repository/ol/9 pkg/el-9/gitlab-ce.rpm --url=https://packages.gitlab.com\n}).to_stdout
            expect { repo.upload('my-staging-repository', true) }.to output(%r{bin/package_cloud push gitlab/my-staging-repository/el/9 pkg/el-9/gitlab-ce.rpm --url=https://packages.gitlab.com\n}).to_stdout
          end

          it 'retries upload if it fails' do
            allow(repo).to receive(:`).and_return('504 Gateway Timeout')
            allow(repo).to receive(:child_process_status).and_return(1)
            allow(repo).to receive(:validate).and_return(nil)

            expect(repo).to receive(:`).exactly(10).times

            expect { repo.upload('my-staging-repository', false) }.to raise_error(described_class::PackageUploadError)
          end

          context 'with OpenSUSE Leap 15.6 artifact' do
            before do
              allow(Build::Info::Package).to receive(:file_list).and_return(['pkg/opensuse-15.6_aarch64/gitlab-ce.rpm'])
            end

            it 'uploads the package to SLES and Leap repositories' do
              expect { repo.upload('my-staging-repository', true) }.to output(%r{Uploading...\n}).to_stdout
              expect { repo.upload('my-staging-repository', true) }.to output(%r{bin/package_cloud push gitlab/my-staging-repository/opensuse/15.6 pkg/opensuse-15.6_aarch64/gitlab-ce.rpm --url=https://packages.gitlab.com\n}).to_stdout
              expect { repo.upload('my-staging-repository', true) }.to output(%r{bin/package_cloud push gitlab/my-staging-repository/sles/15.6 pkg/opensuse-15.6_aarch64/gitlab-ce.rpm --url=https://packages.gitlab.com\n}).to_stdout
            end
          end
        end

        context 'with artifacts unavailable' do
          before do
            allow(Dir).to receive(:glob).with("pkg/**/*.{deb,rpm}").and_return([])
          end

          it 'prints a message and aborts' do
            expect { repo.upload('my-staging-repository', true) }.to raise_exception(%r{No packages found for upload. Are artifacts available?})
          end
        end
      end
    end

    describe "with production repository" do
      context 'with artifacts available' do
        before do
          stub_env_var('PACKAGECLOUD_USER', "gitlab")
          allow(Dir).to receive(:glob).with("pkg/**/*.{deb,rpm}").and_return(['pkg/ubuntu-focal/gitlab.deb'])
        end

        context 'for stable release' do
          before do
            stub_env_var('PACKAGECLOUD_REPO', nil)
            stub_env_var('RASPBERRY_REPO', nil)
            allow_any_instance_of(PackageRepository).to receive(:repository_for_rc).and_return(nil)
          end

          context 'of EE' do
            before do
              stub_is_ee(true)
            end

            it 'in dry run mode prints the upload commands' do
              expect { repo.upload(nil, true) }.to output(%r{Uploading...\n}).to_stdout
              expect { repo.upload(nil, true) }.to output(%r{bin/package_cloud push gitlab/gitlab-ee/ubuntu/focal pkg/ubuntu-focal/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
            end

            context 'for arm64 packages' do
              before do
                allow(Dir).to receive(:glob).with("pkg/**/*.{deb,rpm}").and_return(['pkg/ubuntu-focal_aarch64/gitlab.deb'])
              end

              it 'drops the architecture suffix from repo path' do
                expect { repo.upload(nil, true) }.to output(%r{Uploading...\n}).to_stdout
                expect { repo.upload(nil, true) }.to output(%r{bin/package_cloud push gitlab/gitlab-ee/ubuntu/focal pkg/ubuntu-focal_aarch64/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
              end
            end

            context 'for fips packages' do
              before do
                allow(Dir).to receive(:glob).with("pkg/**/*.{deb,rpm}").and_return(['pkg/ubuntu-focal_fips/gitlab.deb'])
              end

              it 'drops the fips suffix from repo path' do
                expect { repo.upload(nil, true) }.to output(%r{Uploading...\n}).to_stdout
                expect { repo.upload(nil, true) }.to output(%r{bin/package_cloud push gitlab/gitlab-ee/ubuntu/focal pkg/ubuntu-focal_fips/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
              end
            end
          end

          context 'of CE' do
            before do
              stub_is_ee(nil)
            end

            it 'in dry run mode prints the upload commands' do
              expect { repo.upload(nil, true) }.to output(%r{Uploading...\n}).to_stdout
              expect { repo.upload(nil, true) }.to output(%r{bin/package_cloud push gitlab/gitlab-ce/ubuntu/focal pkg/ubuntu-focal/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
            end
          end
        end

        context 'for nightly release' do
          before do
            set_nightly_env_variable
            allow_any_instance_of(PackageRepository).to receive(:repository_for_rc).and_return(nil)
          end

          it 'in dry run mode prints the upload commands' do
            expect { repo.upload(Gitlab::Util.get_env('STAGING_REPO'), true) }.to output(%r{Uploading...\n}).to_stdout
            expect { repo.upload(Gitlab::Util.get_env('STAGING_REPO'), true) }.to output(%r{bin/package_cloud push gitlab/nightly-builds/ubuntu/focal pkg/ubuntu-focal/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
          end
        end

        context 'for raspbian release' do
          before do
            set_raspi_env_variable
            allow_any_instance_of(PackageRepository).to receive(:repository_for_rc).and_return(nil)
          end

          it 'in dry run mode prints the upload commands' do
            expect { repo.upload(nil, true) }.to output(%r{Uploading...\n}).to_stdout
            expect { repo.upload(nil, true) }.to output(%r{bin/package_cloud push gitlab/raspi/ubuntu/focal pkg/ubuntu-focal/gitlab.deb --url=https://packages.gitlab.com\n}).to_stdout
          end
        end
      end
    end

    describe 'when artifacts contain unexpected files' do
      before do
        stub_env_var('PACKAGECLOUD_USER', "gitlab")
        set_all_env_variables
        allow(Dir).to receive(:glob).with("pkg/**/*.{deb,rpm}").and_return(['pkg/ubuntu-focal/gitlab.deb', 'pkg/ubuntu-focal/testing/gitlab.deb'])
      end

      it 'raises an exception' do
        expect { repo.upload(nil, true) }.to raise_exception(%r{Found unexpected contents in the directory:})
      end
    end
  end

  def unset_all_env_variables
    stub_env_var('PACKAGECLOUD_TOKEN', nil)
    stub_env_var('PACKAGECLOUD_USER', nil)
    stub_env_var('PACKAGECLOUD_REPO', nil)
    stub_env_var('RASPBERRY_REPO', nil)
    stub_env_var('STAGING_REPO', nil)
  end

  def set_all_env_variables
    stub_env_var("PACKAGECLOUD_REPO", "super-stable-1234")
    stub_env_var("RASPBERRY_REPO", "raspi")
  end

  def set_nightly_env_variable
    stub_env_var("PACKAGECLOUD_REPO", "")
    stub_env_var("RASPBERRY_REPO", "")
    stub_env_var("STAGING_REPO", "nightly-builds")
  end

  def set_raspi_env_variable
    stub_env_var("PACKAGECLOUD_REPO", "")
    stub_env_var("RASPBERRY_REPO", "raspi")
  end
end
