spec/unit/models/services/service_plan_spec.rb
require 'spec_helper'
module VCAP::CloudController
RSpec.describe ServicePlan, type: :model do
it { is_expected.to have_timestamp_columns }
describe 'Associations' do
it { is_expected.to have_associated :service }
it { is_expected.to have_associated :service_instances, class: ManagedServiceInstance }
it { is_expected.to have_associated :service_plan_visibilities, { test_instance: ServicePlan.make(public: false) } }
it { is_expected.to have_associated :labels, class: ServicePlanLabelModel }
it { is_expected.to have_associated :annotations, class: ServicePlanAnnotationModel }
end
describe 'Validations' do
it { is_expected.to validate_presence :name, message: 'is required' }
it { is_expected.to validate_presence :free, message: 'is required' }
it { is_expected.to validate_presence :description, message: 'is required' }
it { is_expected.to validate_presence :service, message: 'is required' }
it { is_expected.to strip_whitespace :name }
context 'when the unique_id is not unique across different services' do
let(:existing_service_plan) { ServicePlan.make }
let(:service_plan) { ServicePlan.make(unique_id: existing_service_plan.unique_id, service: Service.make) }
it 'is valid' do
expect(service_plan).to be_valid
end
end
context 'for plans belonging to private brokers' do
it 'does not allow the plan to be public' do
space = Space.make
private_broker = ServiceBroker.make(space:)
service = Service.make service_broker: private_broker
expect do
ServicePlan.make service: service, public: true
end.to raise_error Sequel::ValidationFailed, 'public may not be true for plans belonging to private service brokers'
end
end
end
describe 'Serialization' do
it 'exports these attributes' do
expect(subject).to export_attributes :name,
:free,
:description,
:service_guid,
:extra,
:unique_id,
:public,
:bindable,
:plan_updateable,
:maximum_polling_duration,
:maintenance_info,
:active,
:create_instance_schema,
:update_instance_schema,
:create_binding_schema
end
it 'imports these attributes' do
expect(subject).to import_attributes :name,
:free,
:description,
:service_guid,
:extra,
:unique_id,
:public,
:bindable,
:plan_updateable,
:maximum_polling_duration,
:maintenance_info,
:create_instance_schema,
:update_instance_schema,
:create_binding_schema
end
end
describe '#save' do
context 'before_filters' do
it 'defaults public to true if a value is not supplied' do
service = Service.make
expect(ServicePlan.make(service: service, public: false).public).to be(false)
expect(ServicePlan.make(service: service, public: true).public).to be(true)
expect(ServicePlan.make(service:).public).to be(true)
end
it 'defaults to false if a value is not supplied but a private broker is' do
space = Space.make
private_broker = ServiceBroker.make space_id: space.id, space_guid: space.guid
service = Service.make service_broker: private_broker
expect(ServicePlan.make(service: service, public: false).public).to be(false)
expect(ServicePlan.make(service:).public).to be(false)
end
end
context 'on create' do
context 'when no unique_id is set' do
let(:attrs) { { unique_id: nil } }
it 'generates guid for the unique_id' do
plan = ServicePlan.make(attrs)
expect(plan.unique_id).to be_a_guid
end
end
context 'when a unique_id is set' do
let(:attrs) { { unique_id: Sham.guid } }
it 'persists the given unique_id' do
plan = ServicePlan.make(attrs)
expect(plan.unique_id).to eq(attrs[:unique_id])
end
end
context 'when a plan with the same name has already been added for this service' do
let(:service) { Service.make(label: 'my-service') }
before { ServicePlan.make(name: 'dumbo', service_id: service.id) }
it 'throws a useful error' do
expect { ServicePlan.make(name: 'dumbo', service_id: service.id) }.
to raise_exception('Plan names must be unique within a service. Service my-service already has a plan named dumbo')
end
end
end
context 'on update' do
let(:plan) { ServicePlan.make }
context 'when the unique_id is unset' do
before { plan.unique_id = nil }
it 'does not generate a unique_id' do
expect do
plan.save
rescue StandardError
nil
end.not_to change(plan, :unique_id)
end
it 'raises a validation error' do
expect do
plan.save
end.to raise_error(Sequel::ValidationFailed)
end
end
end
end
describe '#destroy' do
let(:service_plan) { ServicePlan.make(public: false) }
it 'destroys associated dependencies' do
service_plan_visibility = ServicePlanVisibility.make(service_plan:)
service_plan_label = ServicePlanLabelModel.make(resource_guid: service_plan.guid, key_name: 'flavor', value: 'pear')
service_plan_annotation = ServicePlanAnnotationModel.make(resource_guid: service_plan.guid, key_name: 'colour', value: 'purple')
service_plan.destroy
expect(ServicePlanVisibility.where(id: service_plan_visibility.id)).to be_empty
expect(ServiceOfferingLabelModel.where(id: service_plan_label.id)).to be_empty
expect(ServicePlanAnnotationModel.where(id: service_plan_annotation.id)).to be_empty
end
it 'cannot be destroyed if associated service_instances exist' do
service_plan = ServicePlan.make
ManagedServiceInstance.make(service_plan:)
expect do
service_plan.destroy
end.to raise_error Sequel::DatabaseError, /foreign key/
end
end
describe '.plan_ids_from_private_brokers' do
let(:organization) { Organization.make }
let(:space_1) { Space.make(organization: organization, id: Space.count + 9998) }
let(:space_2) { Space.make(organization: organization, id: Space.count + 9999) }
let(:user) { User.make }
let(:broker_1) { ServiceBroker.make(space: space_1) }
let(:broker_2) { ServiceBroker.make(space: space_2) }
let(:service_1) { Service.make(service_broker: broker_1) }
let(:service_2) { Service.make(service_broker: broker_2) }
let!(:service_plan_1) { ServicePlan.make(service: service_1, public: false) }
let!(:service_plan_2) { ServicePlan.make(service: service_2, public: false) }
before do
organization.add_user user
space_1.add_developer user
space_2.add_manager user
end
it 'returns plans from private service brokers in all the spaces the user has roles in' do
expect(ServicePlan.plan_ids_from_private_brokers(user).select_map(:service_plans__id)).to(contain_exactly(service_plan_1.id, service_plan_2.id))
end
it "doesn't return plans for private services in spaces the user doesn't have roles in" do
broker = ServiceBroker.make
service = Service.make(service_broker: broker)
plan = ServicePlan.make(service: service, public: false)
expect(ServicePlan.plan_ids_from_private_brokers(user).select_map(:service_plans__id)).not_to include plan
end
end
describe '.plan_ids_for_visible_service_instances' do
context 'when the service plans have service instances associated with them' do
let(:organization) { Organization.make }
let(:space) { Space.make(organization:) }
let(:other_space) { Space.make(organization:) }
let(:user) { User.make }
let(:broker) { ServiceBroker.make }
let(:service) { Service.make(service_broker: broker) }
let(:service_plan) { ServicePlan.make(service: service, public: true, active: true) }
let(:non_public_plan) { ServicePlan.make(service: service, public: false, active: true) }
let(:inactive_plan) { ServicePlan.make(service: service, public: true, active: false) }
let(:other_plan) { ServicePlan.make(service: service, public: true, active: true) }
let!(:service_instance) { ManagedServiceInstance.make(service_plan:, space:) }
let!(:service_instance2) { ManagedServiceInstance.make(service_plan: non_public_plan, space: space) }
let!(:service_instance3) { ManagedServiceInstance.make(service_plan: inactive_plan, space: space) }
let!(:user_provided_service_instance) { UserProvidedServiceInstance.make(space:) }
let!(:other_service_instance) { ManagedServiceInstance.make(service_plan: other_plan, space: other_space) }
before do
organization.add_user user
space.add_developer user
end
context 'when the service instances are in spaces that the user has a role in' do
it 'returns all plans regardless of active or public' do
expect(ServicePlan.plan_ids_for_visible_service_instances(user).select_map(:service_plans__id)).
to contain_exactly(service_plan.id, non_public_plan.id, inactive_plan.id)
end
end
context 'when the service instances are in spaces that the user does NOT have a role in' do
it 'does not return service plans associated with that service instance' do
expect(ServicePlan.plan_ids_for_visible_service_instances(user).select_map(:service_plans__id)).not_to include(other_plan.id)
end
end
end
end
describe '.organization_visible' do
it 'returns plans that are visible to the organization' do
hidden_private_plan = ServicePlan.make(public: false)
visible_public_plan = ServicePlan.make(public: true)
visible_private_plan = ServicePlan.make(public: false)
inactive_public_plan = ServicePlan.make(public: true, active: false)
organization = Organization.make
ServicePlanVisibility.make(organization: organization, service_plan: visible_private_plan)
visible = ServicePlan.organization_visible(organization).all
expect(visible).to include(visible_public_plan)
expect(visible).to include(visible_private_plan)
expect(visible).not_to include(hidden_private_plan)
expect(visible).not_to include(inactive_public_plan)
end
end
describe '.space_visible' do
it 'returns plans that are visible to the space' do
hidden_private_plan = ServicePlan.make(public: false)
visible_public_plan = ServicePlan.make(public: true)
visible_private_plan = ServicePlan.make(public: false)
inactive_public_plan = ServicePlan.make(public: true, active: false)
organization = Organization.make
space = Space.make(organization:)
ServicePlanVisibility.make(organization: organization, service_plan: visible_private_plan)
space_scoped_broker1 = ServiceBroker.make(space:)
space_scoped_broker1_service = Service.make(service_broker: space_scoped_broker1)
space_scoped_broker1_plan = ServicePlan.make(service: space_scoped_broker1_service)
space_scoped_broker1_plan_inactive = ServicePlan.make(service: space_scoped_broker1_service, active: false)
space_scoped_broker2 = ServiceBroker.make(space: Space.make)
space_scoped_broker2_service = Service.make(service_broker: space_scoped_broker2)
space_scoped_broker2_plan = ServicePlan.make(service: space_scoped_broker2_service)
visible = ServicePlan.space_visible(space).all
expect(visible).to include(visible_public_plan)
expect(visible).to include(visible_private_plan)
expect(visible).not_to include(hidden_private_plan)
expect(visible).not_to include(inactive_public_plan)
expect(visible).to include(space_scoped_broker1_plan)
expect(visible).not_to include(space_scoped_broker1_plan_inactive)
expect(visible).not_to include(space_scoped_broker2_plan)
end
end
describe '#visible_in_space?' do
it 'returns true when included in .space_visible set' do
visible_private_plan = ServicePlan.make(public: false)
organization = Organization.make
space = Space.make(organization:)
ServicePlanVisibility.make(organization: organization, service_plan: visible_private_plan)
visible = ServicePlan.space_visible(space).all
expect(visible).to include(visible_private_plan)
expect(visible_private_plan.visible_in_space?(space)).to be true
end
it 'returns false when not included in .space_visible set' do
hidden_private_plan = ServicePlan.make(public: false)
organization = Organization.make
space = Space.make(organization:)
visible = ServicePlan.space_visible(space).all
expect(visible).not_to include(hidden_private_plan)
expect(hidden_private_plan.visible_in_space?(space)).to be false
end
end
describe '#bindable?' do
let(:service_plan) { ServicePlan.make(service: service, bindable: plan_bindable) }
context 'when the plan does not specify if it is bindable' do
let(:plan_bindable) { nil }
context 'and the service is bindable' do
let(:service) { Service.make(bindable: true) }
specify { expect(service_plan).to be_bindable }
end
context 'and the service is unbindable' do
let(:service) { Service.make(bindable: false) }
specify { expect(service_plan).not_to be_bindable }
end
end
context 'when the plan is explicitly set to not be bindable' do
let(:plan_bindable) { false }
context 'and the service is bindable' do
let(:service) { Service.make(bindable: true) }
specify { expect(service_plan).not_to be_bindable }
end
context 'and the service is unbindable' do
let(:service) { Service.make(bindable: false) }
specify { expect(service_plan).not_to be_bindable }
end
end
context 'when the plan is explicitly set to be bindable' do
let(:plan_bindable) { true }
context 'and the service is bindable' do
let(:service) { Service.make(bindable: true) }
specify { expect(service_plan).to be_bindable }
end
context 'and the service is unbindable' do
let(:service) { Service.make(bindable: false) }
specify { expect(service_plan).to be_bindable }
end
end
end
describe '#plan_updateable?' do
let(:service_plan) { ServicePlan.make(service:, plan_updateable:) }
context 'when the plan does not specify if it is updateable' do
let(:plan_updateable) { nil }
context 'and the service is plan_updateable' do
let(:service) { Service.make(plan_updateable: true) }
specify { expect(service_plan).to be_plan_updateable }
end
context 'and the service is not plan_updateable' do
let(:service) { Service.make(plan_updateable: false) }
specify { expect(service_plan).not_to be_plan_updateable }
end
end
context 'when the plan is explicitly set to not be updateable' do
let(:plan_updateable) { false }
context 'and the service is plan_updateable' do
let(:service) { Service.make(plan_updateable: true) }
specify { expect(service_plan).not_to be_plan_updateable }
end
context 'and the service is not plan_updateable' do
let(:service) { Service.make(plan_updateable: false) }
specify { expect(service_plan).not_to be_plan_updateable }
end
end
context 'when the plan is explicitly set to be updateable' do
let(:plan_updateable) { true }
context 'and the service is updateable' do
let(:service) { Service.make(plan_updateable: true) }
specify { expect(service_plan).to be_plan_updateable }
end
context 'and the service is not updateable' do
let(:service) { Service.make(plan_updateable: false) }
specify { expect(service_plan).to be_plan_updateable }
end
end
context 'when updateable is nil' do
let(:plan_updateable) { nil }
context 'and the service updateable is also nil' do
let(:service) { Service.make(plan_updateable: nil) }
specify { expect(service_plan.plan_updateable?).to be(false) }
end
end
end
describe '#broker_space_scoped?' do
it 'returns true if the plan belongs to a service that belongs to a private broker' do
space = Space.make
broker = ServiceBroker.make(space:)
service = Service.make service_broker: broker
plan = ServicePlan.make(service:)
expect(plan).to be_broker_space_scoped
end
it 'returns false if the plan belongs to a service that belongs to a public broker' do
plan = ServicePlan.make
expect(plan).not_to be_broker_space_scoped
end
end
describe '.visibility_type' do
it 'returns "public" for public plans' do
plan = ServicePlan.make(public: true)
expect(plan.visibility_type).to eq('public')
end
it 'returns "admin" for private plans' do
plan = ServicePlan.make(public: false)
expect(plan.visibility_type).to eq('admin')
end
it 'returns "space" for plans from space-scoped brokers' do
plan = ServicePlan.make(service: Service.make(service_broker: ServiceBroker.make(space: Space.make)))
expect(plan.visibility_type).to eq('space')
end
it 'returns "organization" for org restricted plans' do
plan = ServicePlanVisibility.make(
service_plan: ServicePlan.make(public: false),
organization: Organization.make
).service_plan
expect(plan.visibility_type).to eq('organization')
end
end
end
end