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