spec/unit/lib/services/service_brokers/v2/catalog_spec.rb
require 'spec_helper'
module VCAP::Services::ServiceBrokers::V2
RSpec.describe Catalog do
let(:broker) { VCAP::CloudController::ServiceBroker.make }
def service_entry(opts={})
{
'id' => opts[:id] || Sham.guid,
'name' => opts[:name] || Sham.name,
'description' => Sham.description,
'bindable' => true,
'tags' => %w[magical webscale],
'plans' => opts[:plans] || [plan_entry]
}
end
def plan_entry(opts={})
{
'id' => opts[:id] || Sham.guid,
'name' => opts[:name] || Sham.name,
'description' => Sham.description
}
end
let(:catalog) { Catalog.new(broker, catalog_hash) }
def build_service(attrs={})
@index ||= 0
@index += 1
{
'id' => @index.to_s,
'name' => @index.to_s,
'description' => 'the service description',
'bindable' => true,
'tags' => ['tag1'],
'metadata' => { 'foo' => 'bar' },
'plans' => [
{
'id' => @index.to_s,
'name' => @index.to_s,
'description' => 'the plan description',
'metadata' => { 'foo' => 'bar' }
}
]
}.merge(attrs)
end
describe 'validations' do
context "when the catalog's services include errors" do
let(:catalog_hash) do
{
'services' => [
service_entry,
service_entry(id: 123),
service_entry(plans: [plan_entry(id: 'plan-id'), plan_entry(id: 'plan-id', name: 123)]),
service_entry(plans: [])
]
}
end
specify '#valid? returns false' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.nested_errors).not_to be_empty
end
end
context 'when two services in the catalog have the same id' do
let(:catalog_hash) do
{
'services' => [build_service('id' => '1'), build_service('id' => '1')]
}
end
it 'gives an error' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to include('Service ids must be unique')
end
end
context 'unique service names' do
context 'when two services in the catalog have the same name' do
let(:catalog_hash) do
{
'services' => [
build_service('id' => '1', 'name' => 'my-service'),
build_service('id' => '2', 'name' => 'my-service')
]
}
end
it 'gives an error' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to include('Service names must be unique within a broker')
end
end
context 'when the broker is being created and has not yet been persisted' do
let(:broker) do
VCAP::CloudController::ServiceBroker.new(name: 'not-persisted')
end
let(:catalog_hash) do
{
'services' => [build_service('id' => '1')]
}
end
it 'is does not check for preexistent services' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be true
end
end
context 'when a service in the catalog has the same name as a preexistent one for same broker' do
context 'when ids provided by the broker are different' do
context 'when there are service instances for a plan of that offering' do
let(:new_catalog_hash) do
{
'services' => [
build_service('id' => '1', 'name' => 'clashing-service-name'),
build_service('id' => '2', 'name' => 'clashing-service-name2')
]
}
end
let(:broker) do
broker = VCAP::CloudController::ServiceBroker.make
old_service = VCAP::CloudController::Service.make(label: 'clashing-service-name', service_broker: broker)
old_plan = VCAP::CloudController::ServicePlan.make(service: old_service)
VCAP::CloudController::ManagedServiceInstance.make(service_plan: old_plan)
old_service = VCAP::CloudController::Service.make(label: 'clashing-service-name2', service_broker: broker)
old_plan = VCAP::CloudController::ServicePlan.make(service: old_service)
VCAP::CloudController::ManagedServiceInstance.make(service_plan: old_plan)
broker
end
it 'is invalid' do
catalog = Catalog.new(broker, new_catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to include(
include('Service names must be unique within a broker') &
include('clashing-service-name2') &
include('clashing-service-name') &
end_with('already exist')
)
end
end
context 'when there are no service instances for a plan of that offering' do
let(:new_catalog_hash) do
{
'services' => [build_service('id' => '1', 'name' => 'clashing-service-name')]
}
end
let(:broker) { VCAP::CloudController::ServiceBroker.make }
let(:old_service) { VCAP::CloudController::Service.make(label: 'clashing-service-name', service_broker: broker) }
let!(:old_plan) { VCAP::CloudController::ServicePlan.make(service: old_service) }
it 'is valid' do
catalog = Catalog.new(broker, new_catalog_hash)
expect(catalog.valid?).to be true
end
end
end
context 'when ids provided by the broker are the same' do
let(:broker) { VCAP::CloudController::ServiceBroker.make }
let(:old_service) { VCAP::CloudController::Service.make(label: 'clashing-service-name', service_broker: broker) }
let(:new_catalog_hash) do
{
'services' => [build_service('id' => old_service.unique_id, 'name' => old_service.label)]
}
end
let(:old_plan) { VCAP::CloudController::ServicePlan.make(service: old_service) }
let!(:instance) { VCAP::CloudController::ManagedServiceInstance.make(service_plan: old_plan) }
it 'is valid' do
catalog = Catalog.new(broker, new_catalog_hash)
expect(catalog.valid?).to be true
end
end
end
context 'when a service in the catalog has the same name as a service from a different broker' do
let(:catalog_hash) do
{
'services' => [build_service('id' => '1'), build_service('id' => '2')]
}
end
let(:broker) { VCAP::CloudController::ServiceBroker.make }
let(:another_broker) do
broker = VCAP::CloudController::ServiceBroker.make
old_service = VCAP::CloudController::Service.make(name: '1', service_broker: broker)
old_plan = VCAP::CloudController::ServicePlan.make(service: old_service)
VCAP::CloudController::ManagedServiceInstance.make(service_plan: old_plan)
broker
end
it 'is valid' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be true
end
end
end
context 'when two services in the catalog have the same dashboard_client id' do
let(:catalog_hash) do
{
'services' => [
build_service('dashboard_client' => {
'id' => 'client-1',
'secret' => 'secret',
'redirect_uri' => 'http://example.com/client-1'
}),
build_service('dashboard_client' => {
'id' => 'client-1',
'secret' => 'secret2',
'redirect_uri' => 'http://example.com/client-2'
})
]
}
end
it 'gives an error' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to include('Service dashboard_client id must be unique')
end
end
context "when a service's dashboard_client attribute is not a hash" do
let(:catalog_hash) do
{ 'services' => [build_service('dashboard_client' => 1)] }
end
it 'gives an error' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
end
end
context 'when there are multiple services without a dashboard_client' do
let(:catalog_hash) do
{ 'services' => [build_service, build_service] }
end
it 'does not give a uniqueness error on dashboard_client id' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be true
end
end
context 'when there are multiple services with a nil dashboard_client id' do
let(:catalog_hash) do
{
'services' => [
build_service('dashboard_client' => { 'id' => nil }),
build_service('dashboard_client' => { 'id' => nil })
]
}
end
it 'is invalid, but not due to uniqueness constraints' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to eq []
end
end
context 'when there are multiple services with an empty id' do
let(:catalog_hash) do
{ 'services' => [build_service('id' => nil), build_service('id' => nil)] }
end
it 'is invalid, but not due to uniqueness constraints' do
catalog = Catalog.new(broker, catalog_hash)
expect(catalog.valid?).to be false
expect(catalog.errors.messages).to eq []
end
end
context 'when there are both service validation problems and uniqueness problems' do
let(:catalog_hash) do
{
'services' => [
build_service('id' => 'service-1', 'dashboard_client' => { 'id' => 'client-1' }),
build_service('id' => 'service-1', 'dashboard_client' => { 'id' => 'client-1' })
]
}
end
let(:catalog) { Catalog.new(broker, catalog_hash) }
it 'is not valid' do
expect(catalog).not_to be_valid
end
it 'has validation errors on the service' do
catalog.valid?
expect(catalog.errors.nested_errors).not_to be_empty
end
it 'has a validation error for duplicate service ids' do
catalog.valid?
expect(catalog.errors.messages).to include('Service ids must be unique')
end
it 'has a validation error for duplicate dashboard_client ids' do
catalog.valid?
expect(catalog.errors.messages).to include('Service dashboard_client id must be unique')
end
end
end
describe 'incompatibilities' do
context 'when the catalog has no services with route forwarding or volume mounts' do
let(:catalog_hash) do
{
'services' => [
build_service('requires' => []),
build_service('requires' => [])
]
}
end
context 'when the CF config has route forwarding and volume mounts disabled' do
before do
TestConfig.config[:volume_services_enabled] = false
TestConfig.config[:route_services_enabled] = false
end
it 'is compatible and there are no compatibility errors' do
expect(catalog.compatible?).to be(true)
expect(catalog.incompatibility_errors.messages).to be_empty
end
end
end
context 'when the catalog has a services with route forwarding and volume mounts' do
let(:catalog_hash) do
{
'services' => [
build_service('requires' => []),
build_service('requires' => %w[route_forwarding]),
build_service('requires' => %w[volume_mount]),
build_service('requires' => %w[route_forwarding volume_mount])
]
}
end
context 'when the CF config has route forwarding and volume mounts enabled' do
before do
TestConfig.config[:volume_services_enabled] = true
TestConfig.config[:route_services_enabled] = true
end
it 'is compatible and there are no compatibility errors' do
expect(catalog.compatible?).to be(true)
expect(catalog.incompatibility_errors.messages).to be_empty
end
end
context 'when the CF config has route forwarding disabled' do
before do
TestConfig.config[:volume_services_enabled] = true
TestConfig.config[:route_services_enabled] = false
end
it 'is not compatible and there are few compatibility errors' do
expect(catalog.compatible?).to be(false)
expect(catalog.incompatibility_errors.messages).to eq([
'Service 2 is declared to be a route service but support for route services is disabled.',
'Service 4 is declared to be a route service but support for route services is disabled.'
])
end
end
context 'when the CF config has volume mounts disabled' do
before do
TestConfig.config[:volume_services_enabled] = false
TestConfig.config[:route_services_enabled] = true
end
it 'is not compatible and there are few compatibility errors' do
expect(catalog.compatible?).to be(false)
expect(catalog.incompatibility_errors.messages).to eq([
'Service 3 is declared to be a volume mount service but support for volume mount services is disabled.',
'Service 4 is declared to be a volume mount service but support for volume mount services is disabled.'
])
end
end
context 'when the CF config has route forwarding and volume mounts disabled' do
before do
TestConfig.config[:volume_services_enabled] = false
TestConfig.config[:route_services_enabled] = false
end
it 'is not compatible and there are few compatibility errors' do
expect(catalog.compatible?).to be(false)
expect(catalog.incompatibility_errors.messages).to eq([
'Service 2 is declared to be a route service but support for route services is disabled.',
'Service 3 is declared to be a volume mount service but support for volume mount services is disabled.',
'Service 4 is declared to be a route service but support for route services is disabled.',
'Service 4 is declared to be a volume mount service but support for volume mount services is disabled.'
])
end
end
end
end
end
end