spec/unit/jobs/runtime/buildpack_installer_factory_spec.rb
require 'spec_helper'
module VCAP::CloudController
module Jobs::Runtime
RSpec.describe BuildpackInstallerFactory do
describe '#plan' do
let(:name) { 'the-buildpack' }
let(:file) { 'the-file' }
let(:opts) { { enabled: true, locked: false, position: 1 } }
let(:factory) { BuildpackInstallerFactory.new }
let(:jobs) { factory.plan(name, buildpack_fields) }
before do
allow(Buildpacks::StackNameExtractor).to receive(:extract_from_file)
end
context 'when the manifest has one buildpack' do
let(:buildpack_fields) { [{ file: file, options: opts }] }
let(:single_buildpack_job) { jobs.first }
shared_examples_for 'passthrough parameters' do
it 'passes through buildpack name' do
expect(single_buildpack_job.name).to eq(name)
end
it 'passes through opts' do
expect(single_buildpack_job.options).to eq(opts)
end
it 'passes through file' do
expect(single_buildpack_job.file).to eq(file)
end
end
context 'when there is no matching buildpack record by name' do
context 'and there is a detected stack in the zipfile' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'detected stack' }] }
include_examples 'passthrough parameters'
it 'plans to create the record' do
expect(single_buildpack_job).to be_a(CreateBuildpackInstaller)
end
it 'sets the stack to the detected stack' do
expect(single_buildpack_job.stack_name).to eq('detected stack')
end
end
context 'and there is not a detected stack in the zipfile' do
include_examples 'passthrough parameters'
it 'plans to create the record' do
expect(single_buildpack_job).to be_a(CreateBuildpackInstaller)
end
it 'sets the stack to nil' do
expect(single_buildpack_job.stack_name).to be_nil
end
end
end
context 'and when there is a single existing buildpack that matches by name' do
context 'and when that buildpack record has a stack' do
let(:existing_stack) { Stack.make(name: 'existing stack') }
let!(:existing_buildpack) { Buildpack.make(name: name, stack: existing_stack.name, key: 'new_key', guid: 'the-guid') }
context 'and the buildpack zip has the same stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: existing_stack.name }] }
include_examples 'passthrough parameters'
it 'sets the stack to the matching stack' do
expect(single_buildpack_job.stack_name).to eq(existing_stack.name)
end
it 'plans on updating that record' do
expect(single_buildpack_job).to be_a(UpdateBuildpackInstaller)
end
it 'identifies the buildpack record to update' do
expect(single_buildpack_job.guid_to_upgrade).to eq(existing_buildpack.guid)
end
end
context 'and the buildpack zip has a different stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'manifest stack' }] }
include_examples 'passthrough parameters'
it 'plans on creating a new record' do
expect(single_buildpack_job).to be_a(CreateBuildpackInstaller)
end
it 'gives the record to the detected stack' do
expect(single_buildpack_job.stack_name).to eq 'manifest stack'
end
end
context 'and the manifest stack is nil' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: nil }] }
it 'errors' do
expect do
factory.plan(name, buildpack_fields)
end.to raise_error(BuildpackInstallerFactory::StacklessBuildpackIncompatibilityError)
end
end
end
context 'and that buildpack record has a nil stack' do
let!(:existing_buildpack) { Buildpack.make(name: name, stack: nil, key: 'new_key', guid: 'the-guid') }
context 'and the buildpack zip also has a nil stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: nil }] }
include_examples 'passthrough parameters'
it 'plans to update' do
expect(single_buildpack_job).to be_a(UpdateBuildpackInstaller)
end
it 'identifies the buildpack record to update' do
expect(single_buildpack_job.guid_to_upgrade).to eq(existing_buildpack.guid)
end
it 'leaves the stack nil' do
expect(single_buildpack_job.stack_name).to be_nil
end
end
context 'but the buildpack zip /has/ a stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'manifest stack' }] }
include_examples 'passthrough parameters'
it 'plans on updating it' do
expect(single_buildpack_job).to be_a(UpdateBuildpackInstaller)
end
it 'gives the record to the detected stack' do
expect(single_buildpack_job.stack_name).to eq 'manifest stack'
end
it 'identifies the buildpack record to update' do
expect(single_buildpack_job.guid_to_upgrade).to eq(existing_buildpack.guid)
end
end
end
end
context 'and when there are many existing buildpacks' do
let(:existing_stack) { Stack.make(name: 'existing stack') }
let!(:existing_buildpack) { Buildpack.make(name: name, stack: existing_stack.name, key: 'new_key', guid: 'the-guid') }
let(:another_existing_stack) { Stack.make(name: 'another existing stack') }
let!(:another_existing_buildpack) { Buildpack.make(name: name, stack: another_existing_stack.name, key: 'new_key', guid: 'another-guid') }
context 'and one matches the manifest stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: existing_stack.name }] }
include_examples 'passthrough parameters'
it 'sets the stack to the matching stack' do
expect(single_buildpack_job.stack_name).to eq(existing_stack.name)
end
it 'plans on updating that record' do
expect(single_buildpack_job).to be_a(UpdateBuildpackInstaller)
end
it 'identifies the buildpack record to update' do
expect(single_buildpack_job.guid_to_upgrade).to eq(existing_buildpack.guid)
end
end
context 'and the manifest stack is nil' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: nil }] }
it 'errors' do
expect do
factory.plan(name, buildpack_fields)
end.to raise_error(BuildpackInstallerFactory::StacklessBuildpackIncompatibilityError)
end
end
context 'and none match the manifest stack' do
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'manifest stack' }] }
include_examples 'passthrough parameters'
it 'plans on creating a new record' do
expect(single_buildpack_job).to be_a(CreateBuildpackInstaller)
end
it 'gives the record to the detected stack' do
expect(single_buildpack_job.stack_name).to eq 'manifest stack'
end
end
end
end
context 'when the manifest has multiple buildpack entries for one name, with different stacks' do
let(:another_file) { 'the-other-file' }
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'existing stack' }, { file: another_file, options: opts, stack: 'manifest stack' }] }
context 'and there are no matching Buildpacks' do
it 'plans to create all the Buildpacks' do
expect(jobs.length).to eq(2)
expect(jobs[0]).to be_a(CreateBuildpackInstaller)
expect(jobs[0].stack_name).to eq('existing stack')
expect(jobs[1]).to be_a(CreateBuildpackInstaller)
expect(jobs[1].stack_name).to eq('manifest stack')
end
end
context 'and there is only one matching Buildpack' do
let!(:existing_buildpack) { Buildpack.make(name: name, stack: nil, key: 'new_key', guid: 'the-guid') }
context 'and the Buildpack has a nil stack' do
context 'and the buildpack is not locked' do
it 'creates a job for each buildpack' do
expect(jobs.length).to eq(2)
end
it 'updates the Buildpack stack' do
expect(jobs[0]).to be_a(UpdateBuildpackInstaller)
expect(jobs[0].stack_name).to eq('existing stack')
expect(jobs[0].guid_to_upgrade).to eq(existing_buildpack.guid)
end
it 'creates new Buildpacks for the remaining manifest entries' do
expect(jobs[1]).to be_a(CreateBuildpackInstaller)
expect(jobs[1].stack_name).to eq('manifest stack')
end
end
context 'and the buildpack is locked' do
let!(:existing_buildpack) { Buildpack.make(name: name, stack: nil, key: 'new_key', guid: 'the-guid', locked: true) }
it 'raises' do
msg = "Attempt to install '#{name}' for multiple stacks failed. Buildpack '#{name}' cannot be locked during upgrade."
expect { factory.plan(name, buildpack_fields) }.to raise_error(
BuildpackInstallerFactory::LockedStacklessBuildpackUpgradeError, msg
)
end
end
end
context 'and the Buildpack has a non-nil stack' do
let(:existing_stack) { Stack.make(name: 'existing stack') }
let!(:existing_buildpack) { Buildpack.make(name: name, stack: existing_stack.name, key: 'new_key', guid: 'the-guid') }
it 'creates a job for each buildpack' do
expect(jobs.length).to eq(2)
end
it 'updates the Buildpack which has a matching stack in the manifest entry' do
expect(jobs[0]).to be_a(UpdateBuildpackInstaller)
expect(jobs[0].stack_name).to eq(existing_stack.name)
expect(jobs[0].guid_to_upgrade).to eq(existing_buildpack.guid)
end
it 'creates new Buildpacks for the remaining manifest entries' do
expect(jobs[1]).to be_a(CreateBuildpackInstaller)
expect(jobs[1].stack_name).to eq('manifest stack')
end
end
end
context 'and there are multiple matching Buildpacks' do
let(:existing_stack) { Stack.make(name: 'existing stack') }
let!(:existing_buildpack) do
Buildpack.make(name: name, stack: existing_stack.name, key:
'new_key', guid: 'the-guid')
end
let(:another_existing_stack) { Stack.make(name: 'another existing stack') }
let!(:another_existing_buildpack) { Buildpack.make(name: name, stack: another_existing_stack.name, key: 'a_different_key', guid: 'a-different-guid') }
let(:buildpack_fields) { [{ file: file, options: opts, stack: existing_stack.name }, { file: another_file, options: opts, stack: another_existing_stack.name }] }
context 'and none of them has a nil stack' do
it 'creates a job for each buildpack' do
expect(jobs.length).to eq(2)
end
it 'updates the Buildpacks which have matching stacks in their manifest entry' do
expect(jobs[0]).to be_a(UpdateBuildpackInstaller)
expect(jobs[0].stack_name).to eq(existing_stack.name)
expect(jobs[0].guid_to_upgrade).to eq(existing_buildpack.guid)
expect(jobs[1]).to be_a(UpdateBuildpackInstaller)
expect(jobs[1].stack_name).to eq(another_existing_stack.name)
expect(jobs[1].guid_to_upgrade).to eq(another_existing_buildpack.guid)
end
end
end
context 'when one of them is stackless' do
let(:buildpack_fields) { [{ file: file, options: opts }] }
before do
Stack.make(name: 'existing stack')
Buildpack.make(name: name, stack: 'existing stack')
Buildpack.make(name: name, stack: nil)
end
it 'raises' do
msg = "Attempt to install '#{name}' failed. Ensure that all buildpacks have a stack associated with them before upgrading."
expect { factory.plan(name, buildpack_fields) }.to raise_error(
BuildpackInstallerFactory::StacklessAndStackfulMatchingBuildpacksExistError, msg
)
end
end
end
context 'when the manifest has multiple buildpack entries for one name, with the same stack' do
let(:another_file) { 'the-other-file' }
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'stack' }, { file: another_file, options: opts, stack: 'stack' }] }
it 'raises a DuplicateInstall error' do
expect do
factory.plan(name, buildpack_fields)
end.to raise_error(BuildpackInstallerFactory::DuplicateInstallError)
end
end
context 'when the manifest has multiple buildpack entries for one name, neither specifying a stack' do
let(:another_file) { 'the-other-file' }
let(:buildpack_fields) { [{ file: file, options: opts, stack: nil }, { file: another_file, options: opts, stack: nil }] }
it 'raises a DuplicateInstall error' do
expect do
factory.plan(name, buildpack_fields)
end.to raise_error(BuildpackInstallerFactory::DuplicateInstallError)
end
end
context 'when the manifest has multiple buildpack entries for one name, one stackful and one stackless' do
let(:another_file) { 'the-other-file' }
let(:buildpack_fields) { [{ file: file, options: opts, stack: 'stack' }, { file: another_file, options: opts, stack: nil }] }
it 'raises a StacklessBuildpackIncompatibilityError error' do
expect do
factory.plan(name, buildpack_fields)
end.to raise_error(BuildpackInstallerFactory::StacklessBuildpackIncompatibilityError)
end
end
end
end
end
end