diff --git a/app/models/services/service_plan.rb b/app/models/services/service_plan.rb index 101b6605f74..557969f103f 100644 --- a/app/models/services/service_plan.rb +++ b/app/models/services/service_plan.rb @@ -6,11 +6,11 @@ class ServicePlan < Sequel::Model add_association_dependencies service_plan_visibilities: :destroy - export_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :active + export_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :active, :create_instance_schema export_attributes_from_methods bindable: :bindable? - import_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable + import_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :create_instance_schema strip_attributes :name diff --git a/app/presenters/v2/presenter_provider.rb b/app/presenters/v2/presenter_provider.rb index 275cdc7c8f1..7584f316788 100644 --- a/app/presenters/v2/presenter_provider.rb +++ b/app/presenters/v2/presenter_provider.rb @@ -32,3 +32,4 @@ def self.presenters require_relative 'service_instance_presenter' require_relative 'space_presenter' require_relative 'organization_presenter' +require_relative 'service_plan_presenter' diff --git a/app/presenters/v2/service_plan_presenter.rb b/app/presenters/v2/service_plan_presenter.rb new file mode 100644 index 00000000000..6b6652a4ed5 --- /dev/null +++ b/app/presenters/v2/service_plan_presenter.rb @@ -0,0 +1,46 @@ +module CloudController + module Presenters + module V2 + class ServicePlanPresenter < BasePresenter + extend PresenterProvider + + present_for_class 'VCAP::CloudController::ServicePlan' + + def entity_hash(controller, plan, opts, depth, parents, orphans=nil) + entity = DefaultPresenter.new.entity_hash(controller, plan, opts, depth, parents, orphans) + + schemas = present_schemas(plan) + entity.merge!(schemas) + entity.delete('create_instance_schema') + + entity + end + + private + + def present_schemas(plan) + create_instance_schema = parse_schema(plan.create_instance_schema) + { + 'schemas' => { + 'service_instance' => { + 'create' => { + 'parameters' => create_instance_schema + } + } + } + } + end + + def parse_schema(schema) + return {} unless schema + + begin + JSON.parse(schema) + rescue JSON::ParserError + {} + end + end + end + end + end +end diff --git a/db/migrations/20170602113045_add_instance_create_schema_to_service_plans.rb b/db/migrations/20170602113045_add_instance_create_schema_to_service_plans.rb new file mode 100644 index 00000000000..7624e22df03 --- /dev/null +++ b/db/migrations/20170602113045_add_instance_create_schema_to_service_plans.rb @@ -0,0 +1,5 @@ +Sequel.migration do + change do + add_column :service_plans, :create_instance_schema, :text, null: true + end +end diff --git a/lib/services/service_brokers/service_manager.rb b/lib/services/service_brokers/service_manager.rb index c2851dd655b..a560b2439f6 100644 --- a/lib/services/service_brokers/service_manager.rb +++ b/lib/services/service_brokers/service_manager.rb @@ -64,7 +64,8 @@ def update_or_create_plans(catalog) free: catalog_plan.free, bindable: catalog_plan.bindable, active: true, - extra: catalog_plan.metadata ? catalog_plan.metadata.to_json : nil + extra: catalog_plan.metadata ? catalog_plan.metadata.to_json : nil, + create_instance_schema: catalog_plan.schemas.create_instance ? catalog_plan.schemas.create_instance.to_json : nil }) @services_event_repository.with_service_plan_event(plan) do plan.save(changed: true) diff --git a/lib/services/service_brokers/v2.rb b/lib/services/service_brokers/v2.rb index 69f31b708bb..54ab723306e 100644 --- a/lib/services/service_brokers/v2.rb +++ b/lib/services/service_brokers/v2.rb @@ -4,6 +4,7 @@ module VCAP::Services::ServiceBrokers::V2 end require 'services/service_brokers/v2/catalog' require 'services/service_brokers/v2/catalog_service' require 'services/service_brokers/v2/catalog_plan' +require 'services/service_brokers/v2/catalog_schemas' require 'services/service_brokers/v2/http_client' require 'services/service_brokers/v2/client' require 'services/service_brokers/v2/orphan_mitigator' diff --git a/lib/services/service_brokers/v2/catalog_plan.rb b/lib/services/service_brokers/v2/catalog_plan.rb index a020e028c91..c0a1166820d 100644 --- a/lib/services/service_brokers/v2/catalog_plan.rb +++ b/lib/services/service_brokers/v2/catalog_plan.rb @@ -2,8 +2,7 @@ module VCAP::Services::ServiceBrokers::V2 class CatalogPlan include CatalogValidationHelper - attr_reader :broker_provided_id, :name, :description, :metadata, :catalog_service, :errors, :free, :bindable - + attr_reader :broker_provided_id, :name, :description, :metadata, :catalog_service, :errors, :free, :bindable, :schemas def initialize(catalog_service, attrs) @catalog_service = catalog_service @broker_provided_id = attrs['id'] @@ -13,6 +12,7 @@ def initialize(catalog_service, attrs) @errors = VCAP::Services::ValidationErrors.new @free = attrs['free'].nil? ? true : attrs['free'] @bindable = attrs['bindable'] + @schemas = CatalogSchemas.new(attrs['schemas']) end def cc_plan @@ -22,6 +22,7 @@ def cc_plan def valid? return @valid if defined? @valid validate! + validate_schemas! @valid = errors.empty? end @@ -38,6 +39,10 @@ def validate! validate_bool!(:bindable, bindable) if bindable end + def validate_schemas! + errors.add_nested(schemas, schemas.errors) unless schemas.valid? + end + def human_readable_attr_name(name) { broker_provided_id: 'Plan id', @@ -46,6 +51,7 @@ def human_readable_attr_name(name) metadata: 'Plan metadata', free: 'Plan free', bindable: 'Plan bindable', + schemas: 'Plan schemas', }.fetch(name) { raise NotImplementedError } end end diff --git a/lib/services/service_brokers/v2/catalog_schemas.rb b/lib/services/service_brokers/v2/catalog_schemas.rb new file mode 100644 index 00000000000..8353f237931 --- /dev/null +++ b/lib/services/service_brokers/v2/catalog_schemas.rb @@ -0,0 +1,38 @@ +module VCAP::Services::ServiceBrokers::V2 + class CatalogSchemas + attr_reader :errors, :create_instance + + def initialize(attrs) + @errors = VCAP::Services::ValidationErrors.new + validate_and_populate_create_instance(attrs) + end + + def valid? + errors.empty? + end + + private + + def validate_and_populate_create_instance(attrs) + return unless attrs + unless attrs.is_a? Hash + errors.add("Schemas must be a hash, but has value #{attrs.inspect}") + return + end + + path = [] + ['service_instance', 'create', 'parameters'].each do |key| + path += [key] + attrs = attrs[key] + return nil unless attrs + + unless attrs.is_a? Hash + errors.add("Schemas #{path.join('.')} must be a hash, but has value #{attrs.inspect}") + return nil + end + end + + @create_instance = attrs + end + end +end diff --git a/lib/services/service_brokers/validation_errors_formatter.rb b/lib/services/service_brokers/validation_errors_formatter.rb index 0d2856692b9..7e17158afa1 100644 --- a/lib/services/service_brokers/validation_errors_formatter.rb +++ b/lib/services/service_brokers/validation_errors_formatter.rb @@ -1,6 +1,7 @@ module VCAP::Services::ServiceBrokers class ValidationErrorsFormatter INDENT = ' '.freeze + def format(validation_errors) message = "\n" validation_errors.messages.each { |e| message += "#{e}\n" } @@ -9,19 +10,30 @@ def format(validation_errors) message += "Service #{service.name}\n" service_errors.messages.each do |error| - message += "#{INDENT}#{error}\n" + message += indent + "#{error}\n" end service_errors.nested_errors.each do |plan, plan_errors| next if plan_errors.empty? - message += "#{INDENT}Plan #{plan.name}\n" + message += indent + "Plan #{plan.name}\n" plan_errors.messages.each do |error| - message += "#{INDENT}#{INDENT}#{error}\n" + message += indent(2) + "#{error}\n" + end + + plan_errors.nested_errors.each do |schema, schema_errors| + message += indent(2) + "Schemas\n" + schema_errors.messages.each do |error| + message += indent(3) + "#{error}\n" + end end end end message end + + def indent(num=1) + INDENT.to_s * num + end end end diff --git a/spec/acceptance/broker_api_compatibility/broker_api_v2.13_spec.rb b/spec/acceptance/broker_api_compatibility/broker_api_v2.13_spec.rb new file mode 100644 index 00000000000..c5e7b997476 --- /dev/null +++ b/spec/acceptance/broker_api_compatibility/broker_api_v2.13_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +RSpec.describe 'Service Broker API integration' do + describe 'v2.13' do + include VCAP::CloudController::BrokerApiHelper + + let(:create_instance_schema) { {} } + let(:schemas) { + { + 'service_instance' => { + 'create' => { + 'parameters' => create_instance_schema + } + } + } + } + + let(:catalog) { default_catalog(plan_schemas: schemas) } + + before do + setup_cc + setup_broker(catalog) + @broker = VCAP::CloudController::ServiceBroker.find guid: @broker_guid + end + + context 'when a broker catalog defines plan schemas' do + let(:create_instance_schema) { + { + '$schema' => 'http://example.com/schema', + 'type' => 'object' + } + } + + it 'is responds with the schema for a service plan entry' do + get("/v2/service_plans/#{@plan_guid}", + {}.to_json, + json_headers(admin_headers)) + + parsed_body = MultiJson.load(last_response.body) + expect(parsed_body['entity']['schemas']).to eq schemas + end + end + + context 'when the broker catalog defines a plan without schemas' do + it 'responds with an empty schema' do + get("/v2/service_plans/#{@large_plan_guid}", + {}.to_json, + json_headers(admin_headers)) + + parsed_body = MultiJson.load(last_response.body) + expect(parsed_body['entity']['schemas']).to eq({ 'service_instance' => { 'create' => { 'parameters' => {} } } }) + end + end + + context 'when the broker catalog has an invalid schema' do + before do + update_broker(default_catalog(plan_schemas: { 'service_instance' => { 'create' => true } })) + end + + it 'returns an error' do + parsed_body = MultiJson.load(last_response.body) + + expect(parsed_body['code']).to eq(270012) + expect(parsed_body['description']).to include('Schemas service_instance.create must be a hash, but has value true') + end + end + end +end diff --git a/spec/api/api_version_spec.rb b/spec/api/api_version_spec.rb index 8ba22c64235..029dc6c072e 100644 --- a/spec/api/api_version_spec.rb +++ b/spec/api/api_version_spec.rb @@ -2,7 +2,7 @@ require 'vcap/digester' RSpec.describe 'Stable API warning system', api_version_check: true do - API_FOLDER_CHECKSUM = '9256f850ec5715e833d756dbc84d15a7d118bac8'.freeze + API_FOLDER_CHECKSUM = '768d0cd5ab9b32809f63c7e6ede60ff101ca431e'.freeze it 'tells the developer if the API specs change' do api_folder = File.expand_path('..', __FILE__) diff --git a/spec/api/documentation/events_api_spec.rb b/spec/api/documentation/events_api_spec.rb index 194a756c7c9..9f785dc5e37 100644 --- a/spec/api/documentation/events_api_spec.rb +++ b/spec/api/documentation/events_api_spec.rb @@ -613,15 +613,16 @@ example 'List Service Plan Create Events' do new_plan = VCAP::CloudController::ServicePlan.new( - guid: 'guid', - name: 'plan-name', - service: test_service, - description: 'A plan', - unique_id: 'guid', - free: true, - public: true, - active: true, - bindable: true + guid: 'guid', + name: 'plan-name', + service: test_service, + description: 'A plan', + unique_id: 'guid', + free: true, + public: true, + active: true, + bindable: true, + create_instance_schema: '{}' ) service_event_repository.with_service_plan_event(new_plan) do new_plan.save @@ -638,15 +639,16 @@ actee_name: new_plan.name, space_guid: '', metadata: { - 'name' => new_plan.name, - 'free' => new_plan.free, - 'description' => new_plan.description, - 'service_guid' => new_plan.service.guid, - 'extra' => new_plan.extra, - 'unique_id' => new_plan.unique_id, - 'public' => new_plan.public, - 'active' => new_plan.active, - 'bindable' => new_plan.bindable + 'name' => new_plan.name, + 'free' => new_plan.free, + 'description' => new_plan.description, + 'service_guid' => new_plan.service.guid, + 'extra' => new_plan.extra, + 'unique_id' => new_plan.unique_id, + 'public' => new_plan.public, + 'active' => new_plan.active, + 'bindable' => new_plan.bindable, + 'create_instance_schema' => new_plan.create_instance_schema } } end diff --git a/spec/api/documentation/service_plans_api_spec.rb b/spec/api/documentation/service_plans_api_spec.rb index 0e82ca38ecd..43577ed3cd9 100644 --- a/spec/api/documentation/service_plans_api_spec.rb +++ b/spec/api/documentation/service_plans_api_spec.rb @@ -11,8 +11,10 @@ field :guid, 'The guid of the service plan', required: false field :public, 'A boolean describing that the plan is visible to the all users', required: false, default: true - standard_model_list(:service_plans, VCAP::CloudController::ServicePlansController) - standard_model_get(:service_plans, nested_attributes: [:service]) + expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] + [:schemas] + + standard_model_list(:service_plans, VCAP::CloudController::ServicePlansController, export_attributes: expected_attributes) + standard_model_get(:service_plans, export_attributes: expected_attributes) standard_model_delete(:service_plans) put '/v2/service_plans' do @@ -20,7 +22,7 @@ client.put "/v2/service_plans/#{guid}", fields_json(public: false), headers expect(status).to eq(201) - standard_entity_response parsed_response, :service_plans + standard_entity_response parsed_response, :service_plans, expected_attributes: expected_attributes end end end diff --git a/spec/api/documentation/services_api_spec.rb b/spec/api/documentation/services_api_spec.rb index 8b6bb47aaf7..e8298aecaf1 100644 --- a/spec/api/documentation/services_api_spec.rb +++ b/spec/api/documentation/services_api_spec.rb @@ -72,7 +72,8 @@ VCAP::CloudController::ServicePlan.make(service: service) end - standard_model_list :service_plan, VCAP::CloudController::ServicePlansController, outer_model: :service + expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] + [:schemas] + standard_model_list :service_plan, VCAP::CloudController::ServicePlansController, { outer_model: :service, export_attributes: expected_attributes } end end end diff --git a/spec/support/broker_api_helper.rb b/spec/support/broker_api_helper.rb index 34bfb84997b..62dbb17da2e 100644 --- a/spec/support/broker_api_helper.rb +++ b/spec/support/broker_api_helper.rb @@ -27,7 +27,7 @@ def stub_catalog_fetch(broker_response_status=200, catalog=nil) body: catalog.to_json) end - def default_catalog(plan_updateable: false, requires: []) + def default_catalog(plan_updateable: false, requires: [], plan_schemas: {}) { services: [ { @@ -41,8 +41,10 @@ def default_catalog(plan_updateable: false, requires: []) { id: 'plan1-guid-here', name: 'small', - description: 'A small shared database with 100mb storage quota and 10 connections' - }, { + description: 'A small shared database with 100mb storage quota and 10 connections', + schemas: plan_schemas + }, + { id: 'plan2-guid-here', name: 'large', description: 'A large dedicated database with 10GB storage quota, 512MB of RAM, and 100 connections' diff --git a/spec/unit/controllers/services/service_plans_controller_spec.rb b/spec/unit/controllers/services/service_plans_controller_spec.rb index b993c6f2ed8..9955efd180e 100644 --- a/spec/unit/controllers/services/service_plans_controller_spec.rb +++ b/spec/unit/controllers/services/service_plans_controller_spec.rb @@ -30,9 +30,9 @@ module VCAP::CloudController unique_id: { type: 'string' }, public: { type: 'bool', default: true }, service_guid: { type: 'string', required: true }, - service_instance_guids: { type: '[string]' } + service_instance_guids: { type: '[string]' }, } - ) + ) end it do @@ -46,7 +46,7 @@ module VCAP::CloudController unique_id: { type: 'string' }, public: { type: 'bool' }, service_guid: { type: 'string' }, - service_instance_guids: { type: '[string]' } + service_instance_guids: { type: '[string]' }, }) end end @@ -171,6 +171,19 @@ module VCAP::CloudController expect(entity['service_guid']).to eq service_plan.service.guid end + it 'returns an empty schema for service instance creation' do + set_current_user_as_admin + + get "/v2/service_plans/#{service_plan.guid}" + + expect(last_response.status).to eq 200 + + schemas = decoded_response.fetch('entity')['schemas'] + + expect(schemas).to_not be_nil + expect(schemas).to eq({ 'service_instance' => { 'create' => { 'parameters' => {} } } }) + end + context 'when the plan does not set bindable' do let(:service_plan) { ServicePlan.make(bindable: nil) } diff --git a/spec/unit/lib/services/service_brokers/service_manager_spec.rb b/spec/unit/lib/services/service_brokers/service_manager_spec.rb index 79c1faf34f3..1ad10d96d6e 100644 --- a/spec/unit/lib/services/service_brokers/service_manager_spec.rb +++ b/spec/unit/lib/services/service_brokers/service_manager_spec.rb @@ -32,6 +32,9 @@ module VCAP::Services::ServiceBrokers 'redirect_uri' => 'http://example.com' } end + let(:plan_schemas_hash) do + { 'schemas' => { 'service_instance' => { 'create' => { 'parameters' => { '$schema': 'example.com/schema' } } } } } + end let(:catalog_hash) do { @@ -52,7 +55,7 @@ module VCAP::Services::ServiceBrokers 'description' => plan_description, 'free' => false, 'bindable' => true, - }.merge(plan_metadata_hash) + }.merge(plan_metadata_hash).merge(plan_schemas_hash) ] }.merge(service_metadata_hash) ] @@ -158,6 +161,7 @@ module VCAP::Services::ServiceBrokers 'public' => service_plan.public, 'active' => service_plan.active, 'bindable' => true, + 'create_instance_schema' => '{"$schema":"example.com/schema"}', }) end @@ -192,6 +196,7 @@ module VCAP::Services::ServiceBrokers expect(plan.name).to eq(plan_name) expect(plan.description).to eq(plan_description) expect(JSON.parse(plan.extra)).to eq({ 'cost' => '0.0' }) + expect(plan.create_instance_schema).to eq('{"$schema":"example.com/schema"}') expect(plan.free).to be false end @@ -223,6 +228,36 @@ module VCAP::Services::ServiceBrokers end end + context 'when the catalog service plan schemas is empty' do + let(:plan_schemas_hash) { { 'schemas' => nil } } + + it 'leaves the plan schemas field as nil' do + service_manager.sync_services_and_plans(catalog) + plan = VCAP::CloudController::ServicePlan.last + expect(plan.create_instance_schema).to be_nil + end + end + + context 'when the catalog service plan has no schemas key' do + let(:plan_schemas_hash) { {} } + + it 'leaves the plan schemas field as nil' do + service_manager.sync_services_and_plans(catalog) + plan = VCAP::CloudController::ServicePlan.last + expect(plan.create_instance_schema).to be_nil + end + end + + context 'when the catalog service plan has no create schema' do + let(:plan_schemas_hash) { { 'schemas' => { 'service_instance' => nil } } } + + it 'leaves the plan schemas field as nil' do + service_manager.sync_services_and_plans(catalog) + plan = VCAP::CloudController::ServicePlan.last + expect(plan.create_instance_schema).to be_nil + end + end + context 'when a service already exists' do let!(:service) do VCAP::CloudController::Service.make( @@ -280,7 +315,8 @@ module VCAP::Services::ServiceBrokers service: service, unique_id: plan_id, free: true, - bindable: false + bindable: false, + create_instance_schema: nil ) end @@ -289,6 +325,7 @@ module VCAP::Services::ServiceBrokers expect(plan.description).to_not eq(plan_description) expect(plan.free).to be true expect(plan.bindable).to be false + expect(plan.create_instance_schema).to be_nil expect { service_manager.sync_services_and_plans(catalog) @@ -299,6 +336,7 @@ module VCAP::Services::ServiceBrokers expect(plan.description).to eq(plan_description) expect(plan.free).to be false expect(plan.bindable).to be true + expect(plan.create_instance_schema).to eq('{"$schema":"example.com/schema"}') end it 'creates service audit events for each service plan updated' do @@ -323,6 +361,7 @@ module VCAP::Services::ServiceBrokers 'extra' => '{"cost":"0.0"}', 'bindable' => true, 'free' => false, + 'create_instance_schema' => '{"$schema":"example.com/schema"}', }) end diff --git a/spec/unit/lib/services/service_brokers/v2/catalog_plan_spec.rb b/spec/unit/lib/services/service_brokers/v2/catalog_plan_spec.rb index 98e3f92be74..43d25e84363 100644 --- a/spec/unit/lib/services/service_brokers/v2/catalog_plan_spec.rb +++ b/spec/unit/lib/services/service_brokers/v2/catalog_plan_spec.rb @@ -10,7 +10,8 @@ def build_valid_plan_attrs(opts={}) 'name' => opts[:name] || 'service-plan-name', 'description' => opts[:description] || 'The description of the service plan', 'free' => opts.fetch(:free, true), - 'bindable' => opts[:bindable] + 'bindable' => opts[:bindable], + 'schemas' => opts[:schemas] || {} } end @@ -117,6 +118,12 @@ def build_valid_plan_attrs(opts={}) attrs = build_valid_plan_attrs(metadata: ['list', 'of', 'strings']) expect(CatalogPlan.new(instance_double(VCAP::CloudController::ServiceBroker), attrs).valid?).to be false end + + it 'is false if plan schemas has errors' do + attrs = build_valid_plan_attrs(schemas: { 'service_instance' => 1 }) + plan = CatalogPlan.new(instance_double(VCAP::CloudController::ServiceBroker), attrs) + expect(plan.valid?).to be false + end end end diff --git a/spec/unit/lib/services/service_brokers/v2/catalog_schemas_spec.rb b/spec/unit/lib/services/service_brokers/v2/catalog_schemas_spec.rb new file mode 100644 index 00000000000..4ee906cc405 --- /dev/null +++ b/spec/unit/lib/services/service_brokers/v2/catalog_schemas_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +module VCAP::Services::ServiceBrokers::V2 + RSpec.describe CatalogSchemas do + describe 'initializing' do + let(:create_instance_schema) { { '$schema': 'example.com/schema' } } + let(:attrs) { { 'service_instance' => { 'create' => { 'parameters' => create_instance_schema } } } } + subject { CatalogSchemas.new(attrs) } + + its(:create_instance) { should eq create_instance_schema } + its(:errors) { should be_empty } + its(:valid?) { should be true } + + context 'when attrs have nil value' do + { + 'Schemas': nil, + 'Schemas service_instance': { 'service_instance' => nil }, + 'Schemas service_instance.create': { 'service_instance' => { 'create' => nil } }, + 'Schemas service_instance.create.parameters': { 'service_instance' => { 'create' => { 'parameters' => nil } } }, + }.each do |name, test| + context "for property #{name}" do + let(:attrs) { test } + + its(:create_instance) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + + context 'when attrs have a missing key' do + { + 'Schemas': {}, + 'Schemas service_instance': { 'service_instance' => {} }, + 'Schemas service_instance.create': { 'service_instance' => { 'create' => {} } }, + }.each do |name, test| + context "for property #{name}" do + let(:attrs) { test } + + its(:create_instance) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + + context 'when attrs have an invalid type' do + { + 'Schemas': true, + 'Schemas service_instance': { 'service_instance' => true }, + 'Schemas service_instance.create': { 'service_instance' => { 'create' => true } }, + 'Schemas service_instance.create.parameters': { 'service_instance' => { 'create' => { 'parameters' => true } } }, + }.each do |name, test| + context "for property #{name}" do + let(:attrs) { test } + + its(:create_instance) { should be_nil } + its('errors.messages') { should have(1).items } + its('errors.messages.first') { should eq "#{name} must be a hash, but has value true" } + its(:valid?) { should be false } + end + end + end + end + end +end diff --git a/spec/unit/lib/services/service_brokers/validation_errors_formatter_spec.rb b/spec/unit/lib/services/service_brokers/validation_errors_formatter_spec.rb index 96a66b403b0..61a9e564c54 100644 --- a/spec/unit/lib/services/service_brokers/validation_errors_formatter_spec.rb +++ b/spec/unit/lib/services/service_brokers/validation_errors_formatter_spec.rb @@ -7,19 +7,26 @@ module VCAP::Services::ServiceBrokers let(:service_broker) { instance_double(VCAP::CloudController::ServiceBroker) } let(:service_1) { V2::CatalogService.new(service_broker, 'name' => 'service-1') } let(:service_2) { V2::CatalogService.new(service_broker, 'name' => 'service-2') } - let(:plan_123) { V2::CatalogPlan.new(service_2, 'name' => 'plan-123') } + let(:plan_1) { V2::CatalogPlan.new(service_2, 'name' => 'plan-1') } let(:service_3) { V2::CatalogService.new(service_broker, 'name' => 'service-3') } + let(:service_4) { V2::CatalogService.new(service_broker, 'name' => 'service-4') } + let(:plan_2) { V2::CatalogPlan.new(service_2, 'name' => 'plan-2') } + let(:schemas) { V2::CatalogSchemas.new('schemas' => {}) } before do errors.add('Service ids must be unique') errors.add_nested(service_1). - add('Service id must be a string, but has value 123') + add('Service id must be a string, but has value foo') errors.add_nested(service_2). add('Plan ids must be unique'). - add_nested(plan_123). - add('Plan name must be a string, but has value 123') + add_nested(plan_1). + add('Plan name must be a string, but has value 1') errors.add_nested(service_3). add('At least one plan is required') + errors.add_nested(service_4). + add_nested(plan_2). + add_nested(schemas). + add('Schemas must be valid') end it 'builds a formatted string' do @@ -29,13 +36,17 @@ module VCAP::Services::ServiceBrokers Service ids must be unique Service service-1 - Service id must be a string, but has value 123 + Service id must be a string, but has value foo Service service-2 Plan ids must be unique - Plan plan-123 - Plan name must be a string, but has value 123 + Plan plan-1 + Plan name must be a string, but has value 1 Service service-3 At least one plan is required + Service service-4 + Plan plan-2 + Schemas + Schemas must be valid HEREDOC end end diff --git a/spec/unit/models/services/service_plan_spec.rb b/spec/unit/models/services/service_plan_spec.rb index 13e95d4abd7..d1bf4242498 100644 --- a/spec/unit/models/services/service_plan_spec.rb +++ b/spec/unit/models/services/service_plan_spec.rb @@ -45,8 +45,8 @@ module VCAP::CloudController end describe 'Serialization' do - it { is_expected.to export_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :active } - it { is_expected.to import_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable } + it { is_expected.to export_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :active, :create_instance_schema } + it { is_expected.to import_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :create_instance_schema } end describe '#save' do diff --git a/spec/unit/presenters/v2/service_plan_presenter_spec.rb b/spec/unit/presenters/v2/service_plan_presenter_spec.rb new file mode 100644 index 00000000000..280c96b402b --- /dev/null +++ b/spec/unit/presenters/v2/service_plan_presenter_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +module CloudController::Presenters::V2 + RSpec.describe ServicePlanPresenter do + let(:controller) { 'controller' } + let(:opts) { {} } + let(:depth) { 'depth' } + let(:parents) { 'parents' } + let(:orphans) { 'orphans' } + let(:relations_presenter) { instance_double(RelationsPresenter, to_hash: relations_hash) } + let(:relations_hash) { { 'relationship_url' => 'http://relationship.example.com' } } + subject { described_class.new } + + describe '#entity_hash' do + before do + set_current_user_as_admin + end + + let(:service_plan) do + VCAP::CloudController::ServicePlan.make(create_instance_schema: create_instance_schema) + end + + let(:create_instance_schema) { nil } + + before do + allow(RelationsPresenter).to receive(:new).and_return(relations_presenter) + end + + it 'returns the service plan entity' do + expect(subject.entity_hash(controller, service_plan, opts, depth, parents, orphans)).to eq( + { + 'active' => true, + 'bindable' => true, + 'description' => service_plan.description, + 'extra' => nil, + 'free' => false, + 'name' => service_plan.name, + 'public' => true, + 'relationship_url' => 'http://relationship.example.com', + 'schemas' => { 'service_instance' => { 'create' => { 'parameters' => {} } } }, + 'service_guid' => service_plan.service_guid, + 'unique_id' => service_plan.unique_id + } + ) + end + + context 'when the plan create_instance_schema is nil' do + let(:create_instance_schema) { nil } + it 'returns an empty schema in the correct format' do + expect(subject.entity_hash(controller, service_plan, opts, depth, parents, orphans)).to include( + { + 'schemas' => { 'service_instance' => { 'create' => { 'parameters' => {} } } }, + } + ) + end + end + + context 'when the plan create_instance_schema is valid json' do + schema = { '$schema' => 'example.com/schema' } + let(:create_instance_schema) { schema.to_json } + it 'returns the service plan entity with the schema in the correct format' do + expect(subject.entity_hash(controller, service_plan, opts, depth, parents, orphans)).to include( + { + 'schemas' => { 'service_instance' => { 'create' => { 'parameters' => schema } } }, + } + ) + end + end + + context 'when the plan create_instance_schema is invalid json' do + let(:create_instance_schema) { '{' } + it 'returns an empty schema in the correct format' do + expect(subject.entity_hash(controller, service_plan, opts, depth, parents, orphans)).to include( + { + 'schemas' => { 'service_instance' => { 'create' => { 'parameters' => {} } } }, + } + ) + end + end + end + end +end