spec/lib/gdk/package_helper_spec.rb (264 lines of code) (raw):

# frozen_string_literal: true require_relative '../../../lib/gdk/package_config' RSpec.describe GDK::PackageHelper do let(:package) { :test_package } let(:package_version) { '1.0.0' } let(:package_config) do { package_name: 'test_package', project_path: '/path/to/project', upload_path: '/path/to/upload', download_paths: ['/path/to/download'], platform_specific: platform_specific } end let(:package_helper) { described_class.new(package: package, token: 'test_token') } let(:platform) { 'linux' } let(:architecture) { 'amd64' } before do allow(GDK::VersionManager).to receive(:fetch).with(:test_package).and_return(package_version) stub_const('GDK::PackageConfig::PROJECTS', { test_package: package_config }) allow(GDK::Machine).to receive_messages(platform: platform, architecture: architecture) end describe '#initialize' do let(:platform_specific) { false } context 'when project_id is not provided' do it 'uses the default GDK_PROJECT_ID' do expect(package_helper.project_id).to eq(GDK::PackageHelper::GDK_PROJECT_ID) end end context 'when project_id is provided' do let(:package_helper) { described_class.new(package: package, project_id: '12345', token: 'test_token') } it 'sets instance variables correctly' do expect(package_helper.package_name).to eq('test_package') expect(package_helper.package_path).to eq('test_package.tar.gz') expect(package_helper.package_version).to eq('1.0.0') expect(package_helper.project_path).to eq(Pathname.new('/path/to/project')) expect(package_helper.upload_path).to eq(Pathname.new('/path/to/upload')) expect(package_helper.download_paths).to eq([Pathname.new('/path/to/download')]) expect(package_helper.platform_specific).to be(false) expect(package_helper.project_id).to eq('12345') expect(package_helper.token).to eq('test_token') end end end describe '#create_package' do let(:platform_specific) { false } let(:directory) { instance_double(Pathname, directory?: true, to_s: 'test_directory', stat: instance_double(File::Stat, mode: 0o755)) } let(:first_file) { instance_double(Pathname, directory?: false) } let(:second_file) { instance_double(Pathname, directory?: false) } let(:tar_double) { instance_double(Gem::Package::TarWriter) } it 'creates a package' do allow(File).to receive(:open).with('test_package.tar.gz', 'wb').and_yield(instance_double(File)) allow(Zlib::GzipWriter).to receive(:wrap).and_yield(instance_double(Zlib::GzipWriter)) allow(Gem::Package::TarWriter).to receive(:new).and_yield(tar_double) allow(package_helper.upload_path).to receive(:find).and_yield(directory).and_yield(first_file).and_yield(second_file) expect(tar_double).to receive(:mkdir).with('test_directory', any_args) expect(package_helper).to receive(:add_file_to_tar).twice expect(GDK::Output).to receive(:success).with(include('Package created at')) package_helper.create_package end it 'raises an error when an unexpected error occurs' do allow(File).to receive(:open).with('test_package.tar.gz', 'wb').and_raise(StandardError, 'Error message') expect { package_helper.create_package }.to raise_error(StandardError, /Package creation failed: Error message/) end end describe '#upload_package' do let(:platform_specific) { false } let(:request) { instance_double(Net::HTTP::Put) } let(:http) { instance_double(Net::HTTP) } let(:response) { instance_double(Net::HTTPSuccess, is_a?: true) } let(:stubbed_env) { nil } let(:package_versions) { %w[1.0.0 latest] } let(:package_name) { "test_package" } let(:base_uri) { "https://gitlab.com/api/v4/projects/74823/packages/generic/#{package_name}" } let(:versioned_uri) { URI.parse("#{base_uri}/1.0.0/test_package.tar.gz") } let(:latest_uri) { URI.parse("#{base_uri}/latest/test_package.tar.gz") } before do stub_const('ENV', stubbed_env) if stubbed_env [versioned_uri, latest_uri].each do |uri| allow(Net::HTTP::Put).to receive(:new).with(uri).and_return(request) end allow(request).to receive(:[]=).with('JOB-TOKEN', 'test_token') allow(request).to receive(:body=).with('package content') allow(File).to receive(:read).and_call_original allow(File).to receive(:read).with('test_package.tar.gz').and_return('package content') allow(http).to receive(:request).and_return(response) allow(Net::HTTP).to receive(:start).and_yield(http).and_return(response) allow(package_helper).to receive(:create_package) end shared_examples 'uploads packages successfully' do it 'uploads both the versioned and latest packages' do expect(package_helper).to receive(:create_package) expect(request).to receive(:[]=).with('JOB-TOKEN', 'test_token').twice expect(request).to receive(:body=).with('package content').twice expect(response).to receive(:is_a?).with(Net::HTTPSuccess).and_return(true).twice expect(GDK::Output).to receive(:success).with(include('Package uploaded successfully')).twice package_helper.upload_package end end context 'when uploading both versioned and latest packages' do include_examples 'uploads packages successfully' end it 'raises an error when create_package fails' do allow(package_helper).to receive(:create_package).and_raise(StandardError, /Package creation failed: Error message/) expect { package_helper.upload_package }.to raise_error(StandardError, /Package creation failed: Error message/) end it 'raises an error when upload fails' do allow(response).to receive(:is_a?).with(Net::HTTPSuccess).and_return(false) allow(response).to receive(:body).and_return('Error message') expect(package_helper).to receive(:create_package) expect { package_helper.upload_package }.to raise_error(RuntimeError, /Upload failed for version '1.0.0': Error message/) end context 'when platform_specific is true' do let(:platform_specific) { true } let(:package_name) { "test_package-linux-amd64" } let(:base_uri) { "https://gitlab.com/api/v4/projects/74823/packages/generic/#{package_name}" } let(:versioned_uri) { URI.parse("#{base_uri}/1.0.0/test_package.tar.gz") } let(:latest_uri) { URI.parse("#{base_uri}/latest/test_package.tar.gz") } before do [versioned_uri, latest_uri].each do |uri| allow(Net::HTTP::Put).to receive(:new).with(uri).and_return(request) end end context 'and OS and architecture are supported' do include_examples 'uploads packages successfully' end context 'when BUILD_ARCH is set to arm64' do let(:stubbed_env) { { 'BUILD_ARCH' => 'arm64' } } let(:package_name) { "test_package-linux-arm64" } let(:base_uri) { "https://gitlab.com/api/v4/projects/74823/packages/generic/#{package_name}" } let(:versioned_uri) { URI.parse("#{base_uri}/1.0.0/test_package.tar.gz") } let(:latest_uri) { URI.parse("#{base_uri}/latest/test_package.tar.gz") } before do [versioned_uri, latest_uri].each do |uri| allow(Net::HTTP::Put).to receive(:new).with(uri).and_return(request) end end include_examples 'uploads packages successfully' end end end describe '#download_package' do let(:base_uri) { "https://gitlab.com/api/v4/projects/74823/packages/generic/#{expected_package_name}" } let(:versioned_uri) { URI.parse("#{base_uri}/1.0.0/test_package.tar.gz") } let(:latest_uri) { URI.parse("#{base_uri}/latest/test_package.tar.gz") } let(:response) { instance_double(Net::HTTPSuccess) } let(:current_sha) { 'abc123' } let(:stored_sha) { 'def456' } let(:fixture_path) { File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'package.tar.gz') } let(:content) { File.read(fixture_path) } let(:tmpdir) { Dir.mktmpdir } before do package_config[:download_paths] = [tmpdir] allow(package_helper).to receive(:sha_file_root).and_return(File.join(tmpdir, 'sha_file_root')) allow(package_helper).to receive_messages(current_commit_sha: current_sha, stored_commit_sha: stored_sha) allow(File).to receive(:write).and_call_original allow(File).to receive(:write).with('test_package.tar.gz', content) allow(File).to receive(:open).and_call_original allow(File).to receive(:open).with('test_package.tar.gz', 'rb').and_yield(StringIO.new(content)) end shared_examples 'downloads the package successfully' do before do stub_request(:get, versioned_uri.to_s).to_return(body: content) end it 'downloads the package' do expect(GDK::Output).to receive(:info).with(include('Downloading package from')) expect(GDK::Output).to receive(:success).with(include('Package downloaded successfully')) expect(GDK::Output).to receive(:success).with(include('Package extracted successfully')) package_helper.download_package expect(Dir.children(tmpdir)).to match_array(%w[hello-world.sh sha_file_root]) sha_file_path = File.join(tmpdir, 'sha_file_root', '.cache') expect(Dir.children(sha_file_path)).to eq([expected_commit_sha_name]) expect(File.read(File.join(sha_file_path, expected_commit_sha_name))).to eq(current_sha) end end context 'when platform_specific is true' do let(:platform_specific) { true } context 'and OS and architecture are supported' do include_examples 'downloads the package successfully' end context 'and OS and architecture are not supported' do let(:platform) { 'unsupported_os' } let(:architecture) { 'unsupported_arch' } it 'does not download the package' do expect(GDK::Output).to receive(:info).with(include('Unsupported OS or architecture detected')) expect(Net::HTTP).not_to receive(:get_response).with(versioned_uri) package_helper.download_package end end end context 'when platform_specific is false' do let(:platform_specific) { false } let(:platform) { 'unsupported_os' } let(:architecture) { 'unsupported_arch' } include_examples 'downloads the package successfully' end context 'when current commit SHA is different from stored SHA' do let(:platform_specific) { false } include_examples 'downloads the package successfully' end context 'when current commit SHA matches stored SHA' do let(:platform_specific) { false } let(:current_sha) { stored_sha } it 'does not download the package' do expect(GDK::Output).to receive(:success).with(include('No changes detected')) expect(Net::HTTP).not_to receive(:get_response) expect(package_helper).not_to receive(:extract_package) expect(File).not_to receive(:write) package_helper.download_package end end context 'when download fails' do let(:platform_specific) { false } before do stub_request(:get, versioned_uri.to_s).to_return(status: 502, body: 'Error message') end it 'raises an error' do expect(GDK::Output).to receive(:info).with(include('Downloading package from')) expect { package_helper.download_package }.to raise_error(RuntimeError, /Download failed: Error message/) end end context 'when extract_package fails', :hide_output do let(:platform_specific) { false } before do stub_request(:get, versioned_uri.to_s).to_return(body: content) end it 'raises an error' do allow(package_helper).to receive(:extract_package).and_raise(StandardError, 'Error message') expect { package_helper.download_package }.to raise_error(StandardError, /Error message/) end end context 'when requested version is not found but the latest version is available', :hide_output do let(:platform_specific) { false } before do stub_request(:get, versioned_uri.to_s).to_return(status: 404, body: 'not found') stub_request(:get, latest_uri.to_s).to_return(body: content) end it 'downloads the latest package successfully' do package_helper.download_package expect(Dir.children(tmpdir)).to match_array(%w[hello-world.sh sha_file_root]) end end context 'when both version and latest downloads fail', :hide_output do let(:platform_specific) { false } before do stub_request(:get, versioned_uri.to_s).to_return(status: 404, body: 'not here') stub_request(:get, latest_uri.to_s).to_return(status: 500, body: 'server error') end it 'raises an error' do expect { package_helper.download_package }.to raise_error(RuntimeError, /server/) end end end def stub_tar_reader tar_reader = instance_double(Gem::Package::TarReader) allow(Gem::Package::TarReader).to receive(:new).and_yield(tar_reader) allow(tar_reader).to receive(:each).and_yield(instance_double(Gem::Package::TarReader::Entry, full_name: 'fake/path/to/file')) end def expected_package_name platform_specific ? 'test_package-linux-amd64' : 'test_package' end def expected_commit_sha_name platform_specific ? '.test_package_linux_amd64_commit_sha' : '.test_package_commit_sha' end end