From 2bca4c38cb2f863c68a7b848634f77510d634cc1 Mon Sep 17 00:00:00 2001 From: Sam Gunaratne Date: Fri, 4 Aug 2017 10:50:30 +0100 Subject: [PATCH] Add service broker create binding schema [#145580773] Signed-off-by: Alex Blease --- app/models/services/service_plan.rb | 25 +- app/presenters/v2/service_plan_presenter.rb | 9 +- ..._binding_create_schema_to_service_plans.rb | 5 + .../service_brokers/service_manager.rb | 3 +- .../service_brokers/v2/catalog_schemas.rb | 50 ++-- .../broker_api_v2.13_spec.rb | 85 +++++- spec/acceptance/service_broker_spec.rb | 271 +++++++++--------- spec/api/api_version_spec.rb | 2 +- spec/api/documentation/events_api_spec.rb | 6 +- .../documentation/service_plans_api_spec.rb | 2 +- spec/api/documentation/services_api_spec.rb | 2 +- .../services/service_plans_controller_spec.rb | 12 +- .../service_brokers/service_manager_spec.rb | 25 +- .../v2/catalog_schemas_spec.rb | 217 +++++++++++--- .../unit/models/services/service_plan_spec.rb | 6 +- .../v2/service_plan_presenter_spec.rb | 97 ++++++- 16 files changed, 604 insertions(+), 213 deletions(-) create mode 100644 db/migrations/20170724090255_add_binding_create_schema_to_service_plans.rb diff --git a/app/models/services/service_plan.rb b/app/models/services/service_plan.rb index 6e74d6d8c6e..ad8d421fb45 100644 --- a/app/models/services/service_plan.rb +++ b/app/models/services/service_plan.rb @@ -6,11 +6,32 @@ class ServicePlan < Sequel::Model add_association_dependencies service_plan_visibilities: :destroy - export_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :active, :create_instance_schema, :update_instance_schema + export_attributes :name, + :free, + :description, + :service_guid, + :extra, + :unique_id, + :public, + :bindable, + :active, + :create_instance_schema, + :update_instance_schema, + :create_binding_schema export_attributes_from_methods bindable: :bindable? - import_attributes :name, :free, :description, :service_guid, :extra, :unique_id, :public, :bindable, :create_instance_schema, :update_instance_schema + import_attributes :name, + :free, + :description, + :service_guid, + :extra, + :unique_id, + :public, + :bindable, + :create_instance_schema, + :update_instance_schema, + :create_binding_schema strip_attributes :name diff --git a/app/presenters/v2/service_plan_presenter.rb b/app/presenters/v2/service_plan_presenter.rb index cefe543f00a..ec37e2b4526 100644 --- a/app/presenters/v2/service_plan_presenter.rb +++ b/app/presenters/v2/service_plan_presenter.rb @@ -13,6 +13,7 @@ def entity_hash(controller, plan, opts, depth, parents, orphans=nil) entity.merge!(schemas) entity.delete('create_instance_schema') entity.delete('update_instance_schema') + entity.delete('create_binding_schema') entity end @@ -22,6 +23,7 @@ def entity_hash(controller, plan, opts, depth, parents, orphans=nil) def present_schemas(plan) create_instance_schema = parse_schema(plan.create_instance_schema) update_instance_schema = parse_schema(plan.update_instance_schema) + create_binding_schema = parse_schema(plan.create_binding_schema) { 'schemas' => { 'service_instance' => { @@ -29,7 +31,12 @@ def present_schemas(plan) 'parameters' => create_instance_schema }, 'update' => { - 'parameters' => update_instance_schema + 'parameters' => update_instance_schema + } + }, + 'service_binding' => { + 'create' => { + 'parameters' => create_binding_schema } } } diff --git a/db/migrations/20170724090255_add_binding_create_schema_to_service_plans.rb b/db/migrations/20170724090255_add_binding_create_schema_to_service_plans.rb new file mode 100644 index 00000000000..7a77171ba88 --- /dev/null +++ b/db/migrations/20170724090255_add_binding_create_schema_to_service_plans.rb @@ -0,0 +1,5 @@ +Sequel.migration do + change do + add_column :service_plans, :create_binding_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 aa30621abcd..a7815f0dfbe 100644 --- a/lib/services/service_brokers/service_manager.rb +++ b/lib/services/service_brokers/service_manager.rb @@ -66,7 +66,8 @@ def update_or_create_plans(catalog) active: true, extra: catalog_plan.metadata.try(:to_json), create_instance_schema: catalog_plan.schemas.create_instance.try(:to_json), - update_instance_schema: catalog_plan.schemas.update_instance.try(:to_json) + update_instance_schema: catalog_plan.schemas.update_instance.try(:to_json), + create_binding_schema: catalog_plan.schemas.create_binding.try(:to_json) }) @services_event_repository.with_service_plan_event(plan) do plan.save(changed: true) diff --git a/lib/services/service_brokers/v2/catalog_schemas.rb b/lib/services/service_brokers/v2/catalog_schemas.rb index 6f0ddbb5edd..acdd318aa32 100644 --- a/lib/services/service_brokers/v2/catalog_schemas.rb +++ b/lib/services/service_brokers/v2/catalog_schemas.rb @@ -1,20 +1,33 @@ module VCAP::Services::ServiceBrokers::V2 class CatalogSchemas - attr_reader :errors, :create_instance, :update_instance + attr_reader :errors, :create_instance, :update_instance, :create_binding def initialize(schemas) @errors = VCAP::Services::ValidationErrors.new return unless schemas - return unless validate_structure(schemas, []) - service_instance_path = ['service_instance'] - return unless validate_structure(schemas, service_instance_path) - create_schema = get_method('create', schemas) - @create_instance = Schema.new(create_schema) if create_schema + setup_instance_schemas(schemas) + setup_binding_schemas(schemas) + end + + def setup_instance_schemas(schemas) + path = ['service_instance'] + if validate_structure(schemas, path) + create_schema = get_method_params(path + ['create'], schemas) + @create_instance = Schema.new(create_schema) if create_schema + + update_schema = get_method_params(path + ['update'], schemas) + @update_instance = Schema.new(update_schema) if update_schema + end + end - update_schema = get_method('update', schemas) - @update_instance = Schema.new(update_schema) if update_schema + def setup_binding_schemas(schemas) + path = ['service_binding'] + if validate_structure(schemas, path) + binding_schema = get_method_params(path + ['create'], schemas) + @create_binding = Schema.new(binding_schema) if binding_schema + end end def valid? @@ -28,6 +41,10 @@ def valid? add_schema_validation_errors(update_instance.errors, 'service_instance.update.parameters') end + if create_binding && !create_binding.validate + add_schema_validation_errors(create_binding.errors, 'service_binding.create.parameters') + end + errors.empty? end @@ -52,14 +69,18 @@ def validate_structure(schemas, path) true end - def get_method(method, schema) - path = ['service_instance', method] + def get_method_params(path, schema) return unless validate_structure(schema, path) - path = ['service_instance', method, 'parameters'] + path << 'parameters' return unless validate_structure(schema, path) - schema['service_instance'][method]['parameters'] + schema.dig(*path) + end + + def add_schema_type_error_msg(path, value) + path = path.empty? ? '' : " #{path.join('.')}" + errors.add("Schemas#{path} must be a hash, but has value #{value.inspect}") end def add_schema_validation_errors(schema_errors, path) @@ -69,10 +90,5 @@ def add_schema_validation_errors(schema_errors, path) end end end - - def add_schema_type_error_msg(path, value) - path = path.empty? ? '' : " #{path.join('.')}" - errors.add("Schemas#{path} must be a hash, but has value #{value.inspect}") - 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 index aea784d29e4..4a619e02483 100644 --- a/spec/acceptance/broker_api_compatibility/broker_api_v2.13_spec.rb +++ b/spec/acceptance/broker_api_compatibility/broker_api_v2.13_spec.rb @@ -6,6 +6,7 @@ let(:create_instance_schema) { { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } } let(:update_instance_schema) { { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } } + let(:create_binding_schema) { { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } } let(:schemas) { { 'service_instance' => { @@ -15,6 +16,11 @@ 'update' => { 'parameters' => update_instance_schema } + }, + 'service_binding' => { + 'create' => { + 'parameters' => create_binding_schema + } } } } @@ -28,7 +34,7 @@ end context 'when a broker catalog defines a service instance' do - context 'with a create plan schema' do + context 'with a valid create schema' do let(:create_instance_schema) { { '$schema' => 'http://json-schema.org/draft-04/schema#', @@ -55,7 +61,7 @@ end end - context 'with an update plan schema' do + context 'with a valid update schema' do let(:update_instance_schema) { { '$schema' => 'http://json-schema.org/draft-04/schema#', @@ -82,7 +88,7 @@ end end - context 'with an invalid create plan schema' do + context 'with an invalid create schema' do before do update_broker(default_catalog(plan_schemas: { 'service_instance' => { 'create' => true } })) end @@ -95,7 +101,7 @@ end end - context 'with an invalid update plan schema' do + context 'with an invalid update schema' do before do update_broker(default_catalog(plan_schemas: { 'service_instance' => { 'update' => true } })) end @@ -108,5 +114,76 @@ end end end + + context 'when a broker catalog defines a service binding' do + context 'with a valid create schema' do + let(:create_binding_schema) { + { + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object' + } + } + + it '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) + create_schema = parsed_body['entity']['schemas']['service_binding']['create'] + expect(create_schema).to eq( + { + 'parameters' => + { + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object' + } + } + ) + end + end + + context 'with an invalid create schema' do + before do + update_broker(default_catalog(plan_schemas: { 'service_binding' => { '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_binding.create must be a hash, but has value true') + end + end + end + + context 'when the broker catalog defines a plan without plan 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' => {} + }, + 'update' => { + 'parameters' => {} + } + }, + 'service_binding' => { + 'create' => { + 'parameters' => {} + } + } + } + ) + end + end end end diff --git a/spec/acceptance/service_broker_spec.rb b/spec/acceptance/service_broker_spec.rb index baf9b0d731a..a4f8069c813 100644 --- a/spec/acceptance/service_broker_spec.rb +++ b/spec/acceptance/service_broker_spec.rb @@ -82,7 +82,7 @@ def build_service(attrs={}) }.merge(attrs) end - describe 'adding a service broker' do + describe 'on registration' do context 'when a service has no plans' do before do stub_catalog_fetch(200, { @@ -301,167 +301,177 @@ def build_service(attrs={}) end end - context 'when a service instance schema' do - ['create', 'update'].each do |service_instance_type| - context 'is not present' do - { - "#{service_instance_type} is nil": { 'service_instance' => { service_instance_type => nil } }, - "#{service_instance_type} is nil": { 'service_instance' => { service_instance_type => { 'parameters' => nil } } }, - "#{service_instance_type} is empty object": { 'service_instance' => { service_instance_type => {} } }, - }.each do |desc, schema| - context "#{desc} #{schema}" do - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end - it 'responds with invalid' do - post('/v2/service_brokers', { + context 'when a schema' do + [ + { type: 'service_instance', actions: ['create', 'update'] }, + { type: 'service_binding', actions: ['create'] }, + ].each do |test| + test[:actions].each do |schema_action| + context "of type #{test[:type]} and action #{schema_action} is not present" do + { + "#{schema_action} is nil": { test[:type] => { schema_action => nil } }, + "#{schema_action} is nil": { test[:type] => { schema_action => { 'parameters' => nil } } }, + "#{schema_action} is empty object": { test[:type] => { schema_action => {} } }, + }.each do |desc, schema| + context "#{desc} #{schema}" do + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end + it 'succeeds' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) + }.to_json, admin_headers) - expect(last_response.status).to eql(201) + expect(last_response.status).to eql(201) + end end end end - end - - context 'has an invalid type' do - { - "service_instance.#{service_instance_type}": { 'service_instance' => { service_instance_type => true } }, - "service_instance.#{service_instance_type}.parameters": { 'service_instance' => { service_instance_type => { 'parameters' => true } } }, - }.each do |path, schema| - context "operator receives an error about #{path} #{schema}" do - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end - it 'responds with invalid' do - post('/v2/service_brokers', { + context "of type #{test[:type]} and action #{schema_action} has an invalid type" do + { + "#{test[:type]}.#{schema_action}": { (test[:type]).to_s => { schema_action => true } }, + "#{test[:type]}.#{schema_action}.parameters": { (test[:type]).to_s => { schema_action => { 'parameters' => true } } }, + }.each do |path, schema| + + context "operator receives an error about #{path} #{schema}" do + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end + it 'rejects the request' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) - - expect(last_response.status).to eql(502) - expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ - "Service MySQL\n" \ - " Plan small\n" \ - " Schemas\n" \ - " Schemas #{path} must be a hash, but has value true\n") + }.to_json, admin_headers) + + expect(last_response.status).to eql(502) + expect(decoded_response['code']).to eql(270012) + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ + "Service MySQL\n" \ + " Plan small\n" \ + " Schemas\n" \ + " Schemas #{path} must be a hash, but has value true\n") + end end end end - end - context 'does not conform to JSON Schema Draft 04' do - let(:path) { "service_instance.#{service_instance_type}.parameters" } - let(:schema) { { 'service_instance' => { service_instance_type => { 'parameters' => { 'type': 'object', 'properties': true } } } } } + context "of type #{test[:type]} and action #{schema_action} does not conform to JSON Schema Draft 04" do + let(:path) { "#{test[:type]}.#{schema_action}.parameters" } + let(:schema) { { (test[:type]).to_s => { schema_action => { 'parameters' => { 'type': 'object', 'properties': true } } } } } - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end - it 'responds with invalid' do - post('/v2/service_brokers', { + it 'rejects the request' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) - - expect(last_response.status).to eql(502) - expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ - "Service MySQL\n" \ - " Plan small\n" \ - " Schemas\n" \ - " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/properties' " \ - "of type boolean did not match the following type: object in schema http://json-schema.org/draft-04/schema#\n") + }.to_json, admin_headers) + + expect(last_response.status).to eql(502) + expect(decoded_response['code']).to eql(270012) + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ + "Service MySQL\n" \ + " Plan small\n" \ + " Schemas\n" \ + " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/properties' " \ + "of type boolean did not match the following type: object in schema http://json-schema.org/draft-04/schema#\n") + end end - end - context 'does not conform to JSON Schema Draft 04 with multiple problems' do - let(:path) { "service_instance.#{service_instance_type}.parameters" } - let(:schema) { { 'service_instance' => { service_instance_type => { 'parameters' => { 'type': 'object', 'properties': true, 'anyOf': true } } } } } + context "of type #{test[:type]} and action #{schema_action} does not conform to JSON Schema Draft 04 with multiple problems" do + let(:path) { "#{test[:type]}.#{schema_action}.parameters" } + let(:schema) { { (test[:type]).to_s => { schema_action => { 'parameters' => { 'type': 'object', 'properties': true, 'anyOf': true } } } } } - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end - it 'responds with invalid' do - post('/v2/service_brokers', { + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end + it 'responds with invalid' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) - - expect(last_response.status).to eql(502) - expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ - "Service MySQL\n" \ - " Plan small\n" \ - " Schemas\n" \ - " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/properties' " \ - "of type boolean did not match the following type: object in schema http://json-schema.org/draft-04/schema#\n"\ - " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/anyOf' " \ - "of type boolean did not match the following type: array in schema http://json-schema.org/draft-04/schema#\n") + }.to_json, admin_headers) + + expect(last_response.status).to eql(502) + expect(decoded_response['code']).to eql(270012) + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ + "Service MySQL\n" \ + " Plan small\n" \ + " Schemas\n" \ + " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/properties' " \ + "of type boolean did not match the following type: object in schema http://json-schema.org/draft-04/schema#\n"\ + " Schema #{path} is not valid. Must conform to JSON Schema Draft 04: The property '#/anyOf' " \ + "of type boolean did not match the following type: array in schema http://json-schema.org/draft-04/schema#\n") + end end - end - context 'has an external schema' do - let(:path) { "service_instance.#{service_instance_type}.parameters" } - let(:schema) { { 'service_instance' => { service_instance_type => { 'parameters' => { '$schema': 'http://example.com/schema', 'type': 'object' } } } } } + context "of type #{test[:type]} and action #{schema_action} has an external schema" do + let(:path) { "#{test[:type]}.#{schema_action}.parameters" } + let(:schema) { { (test[:type]).to_s => { schema_action => { 'parameters' => { '$schema': 'http://example.com/schema', 'type': 'object' } } } } } - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end - it 'responds with invalid' do - post('/v2/service_brokers', { + it 'responds with invalid' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) - - expect(last_response.status).to eql(502) - expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ - "Service MySQL\n" \ - " Plan small\n" \ - " Schemas\n" \ - " Schema #{path} is not valid. Custom meta schemas are not supported.\n" - ) + }.to_json, admin_headers) + + expect(last_response.status).to eql(502) + expect(decoded_response['code']).to eql(270012) + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ + "Service MySQL\n" \ + " Plan small\n" \ + " Schemas\n" \ + " Schema #{path} is not valid. Custom meta schemas are not supported.\n" + ) + end end - end - context 'has an external uri reference' do - let(:path) { "service_instance.#{service_instance_type}.parameters" } - let(:schema) { { 'service_instance' => { service_instance_type => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/ref' } } } } } + context "of type #{test[:type]} and action #{schema_action} has an external uri reference" do + let(:path) { "#{test[:type]}.#{schema_action}.parameters" } + let(:schema) { { (test[:type]).to_s => { schema_action => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/ref' } } } } } - before do - stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) - end - it 'responds with invalid' do - post('/v2/service_brokers', { + before do + stub_catalog_fetch(200, default_catalog(plan_schemas: schema)) + end + it 'responds with invalid' do + post('/v2/service_brokers', { name: 'some-guid', broker_url: 'http://broker-url', auth_username: 'username', auth_password: 'password' - }.to_json, admin_headers) - - expect(last_response.status).to eql(502) - expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ - "Service MySQL\n" \ - " Plan small\n" \ - " Schemas\n" \ - " Schema #{path} is not valid. No external references are allowed: Read of URI at http://example.com/ref refused\n" - ) + }.to_json, admin_headers) + + expect(last_response.status).to eql(502) + expect(decoded_response['code']).to eql(270012) + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ + "Service MySQL\n" \ + " Plan small\n" \ + " Schemas\n" \ + " Schema #{path} is not valid. No external references are allowed: Read of URI at http://example.com/ref refused\n" + ) + end end end end @@ -471,8 +481,11 @@ def build_service(attrs={}) let(:schema) { { 'service_instance' => { - 'create' => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/ref' } }, - 'update' => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/ref' } } + 'create' => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/create' } }, + 'update' => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/update' } } + }, + 'service_binding' => { + 'create' => { 'parameters' => { 'type': 'object', '$ref': 'http://example.com/binding' } }, } } } @@ -483,21 +496,23 @@ def build_service(attrs={}) it 'reports all issues' do post('/v2/service_brokers', { - name: 'some-guid', - broker_url: 'http://broker-url', - auth_username: 'username', - auth_password: 'password' + name: 'some-guid', + broker_url: 'http://broker-url', + auth_username: 'username', + auth_password: 'password' }.to_json, admin_headers) expect(last_response.status).to eql(502) expect(decoded_response['code']).to eql(270012) - expect(decoded_response['description']).to eql("Service broker catalog is invalid: \n" \ + expect(decoded_response['description']).to eql( + "Service broker catalog is invalid: \n" \ "Service MySQL\n" \ " Plan small\n" \ " Schemas\n" \ - " Schema service_instance.create.parameters is not valid. No external references are allowed: Read of URI at http://example.com/ref refused\n" \ - " Schema service_instance.update.parameters is not valid. No external references are allowed: Read of URI at http://example.com/ref refused\n" - ) + " Schema service_instance.create.parameters is not valid. No external references are allowed: Read of URI at http://example.com/create refused\n" \ + " Schema service_instance.update.parameters is not valid. No external references are allowed: Read of URI at http://example.com/update refused\n" \ + " Schema service_binding.create.parameters is not valid. No external references are allowed: Read of URI at http://example.com/binding refused\n" + ) end end end @@ -762,7 +777,7 @@ def build_service(attrs={}) Warning: Service plans are missing from the broker's catalog (http://#{stubbed_broker_host}/v2/catalog) but can not be removed from Cloud Foundry while instances exist. The plans have been deactivated to prevent users from attempting to provision new instances of these plans. The broker should continue to support bind, unbind, and delete for existing instances; if these operations fail contact your broker provider. #{service_name} small -HEREDOC + HEREDOC end end diff --git a/spec/api/api_version_spec.rb b/spec/api/api_version_spec.rb index 6bdfda22eac..6cec7562351 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 = '5d13e2d3b8855457ad523efceb8cee63e94bbda8'.freeze + API_FOLDER_CHECKSUM = '9a4720033354f38ccd572307341c5e930e6e7315'.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 7d21884d148..a3d90221891 100644 --- a/spec/api/documentation/events_api_spec.rb +++ b/spec/api/documentation/events_api_spec.rb @@ -623,7 +623,8 @@ active: true, bindable: true, create_instance_schema: '{}', - update_instance_schema: '{}' + update_instance_schema: '{}', + create_binding_schema: '{}' ) service_event_repository.with_service_plan_event(new_plan) do new_plan.save @@ -650,7 +651,8 @@ 'active' => new_plan.active, 'bindable' => new_plan.bindable, 'create_instance_schema' => new_plan.create_instance_schema, - 'update_instance_schema' => new_plan.update_instance_schema + 'update_instance_schema' => new_plan.update_instance_schema, + 'create_binding_schema' => new_plan.create_binding_schema } } end diff --git a/spec/api/documentation/service_plans_api_spec.rb b/spec/api/documentation/service_plans_api_spec.rb index bc6adc90d9a..6619a45dbcb 100644 --- a/spec/api/documentation/service_plans_api_spec.rb +++ b/spec/api/documentation/service_plans_api_spec.rb @@ -11,7 +11,7 @@ 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 - expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] - [:update_instance_schema] + [:schemas] + expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] - [:update_instance_schema] - [:create_binding_schema] + [:schemas] standard_model_list(:service_plans, VCAP::CloudController::ServicePlansController, export_attributes: expected_attributes) standard_model_get(:service_plans, export_attributes: expected_attributes) diff --git a/spec/api/documentation/services_api_spec.rb b/spec/api/documentation/services_api_spec.rb index a807a754db5..e7d4cd1d540 100644 --- a/spec/api/documentation/services_api_spec.rb +++ b/spec/api/documentation/services_api_spec.rb @@ -72,7 +72,7 @@ VCAP::CloudController::ServicePlan.make(service: service) end - expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] - [:update_instance_schema] + [:schemas] + expected_attributes = VCAP::CloudController::ServicePlan.new.export_attrs - [:create_instance_schema] - [:update_instance_schema] - [:create_binding_schema] + [:schemas] standard_model_list :service_plan, VCAP::CloudController::ServicePlansController, { outer_model: :service, export_attributes: expected_attributes } end end diff --git a/spec/unit/controllers/services/service_plans_controller_spec.rb b/spec/unit/controllers/services/service_plans_controller_spec.rb index 075692ae94c..f66283afb7a 100644 --- a/spec/unit/controllers/services/service_plans_controller_spec.rb +++ b/spec/unit/controllers/services/service_plans_controller_spec.rb @@ -181,7 +181,17 @@ module VCAP::CloudController schemas = decoded_response.fetch('entity')['schemas'] expect(schemas).to_not be_nil - expect(schemas).to eq({ 'service_instance' => { 'create' => { 'parameters' => {} }, 'update' => { 'parameters' => {} } } }) + expect(schemas).to eq( + { + 'service_instance' => { + 'create' => { 'parameters' => {} }, + 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } + } + } + ) end context 'when the plan does not set bindable' do 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 3d42e8c8ab0..99bc8e2c134 100644 --- a/spec/unit/lib/services/service_brokers/service_manager_spec.rb +++ b/spec/unit/lib/services/service_brokers/service_manager_spec.rb @@ -46,6 +46,13 @@ module VCAP::Services::ServiceBrokers 'type' => 'object' } } + }, + 'service_binding' => { + 'create' => { + 'parameters' => { + '$schema' => 'http://json-schema.org/draft-04/schema', 'type' => 'object' + } + } } } } @@ -135,7 +142,7 @@ module VCAP::Services::ServiceBrokers expect(event.actee_name).to eq(service_name) expect(event.space_guid).to eq('') expect(event.organization_guid).to eq('') - expect(event.metadata).to include({ + expect(event.metadata).to eq({ 'service_broker_guid' => service.service_broker.guid, 'unique_id' => service_id, 'provider' => service.provider, @@ -166,7 +173,7 @@ module VCAP::Services::ServiceBrokers expect(event.actee_name).to eq(plan_name) expect(event.space_guid).to eq('') expect(event.organization_guid).to eq('') - expect(event.metadata).to include({ + expect(event.metadata).to eq({ 'name' => service_plan.name, 'free' => service_plan.free, 'description' => service_plan.description, @@ -174,10 +181,11 @@ module VCAP::Services::ServiceBrokers 'extra' => '{"cost":"0.0"}', 'unique_id' => service_plan.unique_id, 'public' => service_plan.public, - 'active' => service_plan.active, 'bindable' => true, + 'active' => service_plan.active, 'create_instance_schema' => '{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}', - 'update_instance_schema' => '{"type":"object"}' + 'update_instance_schema' => '{"type":"object"}', + 'create_binding_schema' => '{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}' }) end @@ -214,6 +222,7 @@ module VCAP::Services::ServiceBrokers expect(JSON.parse(plan.extra)).to eq({ 'cost' => '0.0' }) expect(plan.create_instance_schema).to eq('{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}') expect(plan.update_instance_schema).to eq('{"type":"object"}') + expect(plan.create_binding_schema).to eq('{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}') expect(plan.free).to be false end @@ -253,6 +262,7 @@ module VCAP::Services::ServiceBrokers plan = VCAP::CloudController::ServicePlan.last expect(plan.create_instance_schema).to be_nil expect(plan.update_instance_schema).to be_nil + expect(plan.create_binding_schema).to be_nil end end @@ -264,6 +274,7 @@ module VCAP::Services::ServiceBrokers plan = VCAP::CloudController::ServicePlan.last expect(plan.create_instance_schema).to be_nil expect(plan.update_instance_schema).to be_nil + expect(plan.create_binding_schema).to be_nil end end @@ -275,6 +286,7 @@ module VCAP::Services::ServiceBrokers plan = VCAP::CloudController::ServicePlan.last expect(plan.create_instance_schema).to be_nil expect(plan.update_instance_schema).to be_nil + expect(plan.create_binding_schema).to be_nil end end @@ -348,6 +360,7 @@ module VCAP::Services::ServiceBrokers expect(plan.bindable).to be false expect(plan.create_instance_schema).to be_nil expect(plan.update_instance_schema).to be_nil + expect(plan.create_binding_schema).to be_nil expect { service_manager.sync_services_and_plans(catalog) @@ -360,6 +373,7 @@ module VCAP::Services::ServiceBrokers expect(plan.bindable).to be true expect(plan.create_instance_schema).to eq('{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}') expect(plan.update_instance_schema).to eq('{"type":"object"}') + expect(plan.create_binding_schema).to eq('{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}') end it 'creates service audit events for each service plan updated' do @@ -385,7 +399,8 @@ module VCAP::Services::ServiceBrokers 'bindable' => true, 'free' => false, 'create_instance_schema' => '{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}', - 'update_instance_schema' => '{"type":"object"}' + 'update_instance_schema' => '{"type":"object"}', + 'create_binding_schema' => '{"$schema":"http://json-schema.org/draft-04/schema","type":"object"}' }) 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 index 07b4f03d607..32c0e6467be 100644 --- a/spec/unit/lib/services/service_brokers/v2/catalog_schemas_spec.rb +++ b/spec/unit/lib/services/service_brokers/v2/catalog_schemas_spec.rb @@ -9,20 +9,35 @@ module VCAP::Services::ServiceBrokers::V2 catalog_schema end + context 'when schemas are not set' do + { + 'Schemas is nil': nil, + 'Schemas is empty': {}, + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + its(:create_instance) { should be_nil } + its(:update_instance) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + context 'service instance' do context 'when catalog has schemas' do let(:schemas) { { 'service_instance' => {} } } - context 'when schemas is not set' do + context 'when schemas are not set' do { - 'Schemas is nil': nil, - 'Schemas is empty': {}, - 'Schemas service_instance is nil': { 'service_instance' => nil }, - 'Schemas service_instance is empty': { 'service_instance' => {} }, + 'Schemas service_instance is nil': { 'service_instance' => nil }, + 'Schemas service_instance is empty': { 'service_instance' => {} }, }.each do |name, test| context "for property #{name}" do let(:schemas) { test } + its(:create_instance) { should be_nil } its(:update_instance) { should be_nil } its(:errors) { should be_empty } its(:valid?) { should be true } @@ -32,8 +47,8 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas is invalid' do { - 'Schemas': true, - 'Schemas service_instance': { 'service_instance' => true } + 'Schemas': true, + 'Schemas service_instance': { 'service_instance' => true } }.each do |name, test| context "for property #{name}" do let(:schemas) { test } @@ -55,8 +70,8 @@ module VCAP::Services::ServiceBrokers::V2 context 'and it has nil value' do { - 'Schemas service_instance.create': { 'service_instance' => { 'create' => nil } }, - 'Schemas service_instance.create.parameters': { 'service_instance' => { 'create' => { 'parameters' => 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(:schemas) { test } @@ -71,11 +86,11 @@ module VCAP::Services::ServiceBrokers::V2 context 'when the create instance schema is not valid' do let(:schemas) { { - 'service_instance' => { - 'create' => { - 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object', 'properties' => true } - } + 'service_instance' => { + 'create' => { + 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object', 'properties' => true } } + } } } let(:path) { 'service_instance.create.parameters' } @@ -87,7 +102,7 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas have a missing key' do { - 'Schemas service_instance.create': { 'service_instance' => { 'create' => {} } }, + 'Schemas service_instance.create': { 'service_instance' => { 'create' => {} } }, }.each do |name, test| context "for property #{name}" do let(:schemas) { test } @@ -101,8 +116,8 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas have an invalid type' do { - 'Schemas service_instance.create': { 'service_instance' => { 'create' => true } }, - 'Schemas service_instance.create.parameters': { 'service_instance' => { 'create' => { 'parameters' => 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(:schemas) { test } @@ -119,11 +134,11 @@ module VCAP::Services::ServiceBrokers::V2 let(:path) { 'service_instance.create.parameters' } let(:schemas) { { - 'service_instance' => { - 'create' => { - 'parameters' => 'https://example.com/hax0r' - } + 'service_instance' => { + 'create' => { + 'parameters' => 'https://example.com/hax0r' } + } } } @@ -138,11 +153,11 @@ module VCAP::Services::ServiceBrokers::V2 context 'and the schema structure is valid' do let(:schemas) { { - 'service_instance' => { - 'update' => { - 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } - } + 'service_instance' => { + 'update' => { + 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } } + } } } @@ -151,8 +166,8 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas have nil value' do { - 'Schemas service_instance.update': { 'service_instance' => { 'update' => nil } }, - 'Schemas service_instance.update.parameters': { 'service_instance' => { 'update' => { 'parameters' => nil } } }, + 'Schemas service_instance.update': { 'service_instance' => { 'update' => nil } }, + 'Schemas service_instance.update.parameters': { 'service_instance' => { 'update' => { 'parameters' => nil } } }, }.each do |name, test| context "for property #{name}" do let(:schemas) { test } @@ -167,11 +182,11 @@ module VCAP::Services::ServiceBrokers::V2 context 'when the update instance schema is not valid' do let(:schemas) { { - 'service_instance' => { - 'update' => { - 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object', 'properties' => true } - } + 'service_instance' => { + 'update' => { + 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object', 'properties' => true } } + } } } let(:path) { 'service_instance.update.parameters' } @@ -183,7 +198,7 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas have a missing key' do { - 'Schemas service_instance.update': { 'service_instance' => { 'update' => {} } }, + 'Schemas service_instance.update': { 'service_instance' => { 'update' => {} } }, }.each do |name, test| context "for property #{name}" do let(:schemas) { test } @@ -197,8 +212,8 @@ module VCAP::Services::ServiceBrokers::V2 context 'when schemas have an invalid type' do { - 'Schemas service_instance.update': { 'service_instance' => { 'update' => true } }, - 'Schemas service_instance.update.parameters': { 'service_instance' => { 'update' => { 'parameters' => true } } }, + 'Schemas service_instance.update': { 'service_instance' => { 'update' => true } }, + 'Schemas service_instance.update.parameters': { 'service_instance' => { 'update' => { 'parameters' => true } } }, }.each do |name, test| context "for property #{name}" do let(:schemas) { test } @@ -211,17 +226,15 @@ module VCAP::Services::ServiceBrokers::V2 end end - # TODO: Look into schema path is not valid error tests - context 'when schemas has a potentially dangerous uri' do let(:path) { 'service_instance.update.parameters' } let(:schemas) { { - 'service_instance' => { - 'update' => { - 'parameters' => 'https://example.com/hax0r' - } + 'service_instance' => { + 'update' => { + 'parameters' => 'https://example.com/hax0r' } + } } } @@ -232,6 +245,132 @@ module VCAP::Services::ServiceBrokers::V2 end end end + + context 'service binding' do + context 'when catalog has schemas' do + let(:schemas) { { 'service_binding' => {} } } + + context 'when schema is not set' do + { + 'Schemas service_binding is nil': { 'service_binding' => nil }, + 'Schemas service_binding is empty': { 'service_binding' => {} }, + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + its(:create_instance) { should be_nil } + its(:update_instance) { should be_nil } + its(:create_binding) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + + context 'when schemas is invalid' do + { + 'Schemas': true, + 'Schemas service_binding': { 'service_binding' => true } + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + 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 + + context 'when catalog has a create schema' do + context 'and the schema structure is valid' do + let(:schemas) { { 'service_binding' => { 'create' => { 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object' } } } } } + + its(:create_binding) { should be_a(Schema) } + end + + context 'and it has nil value' do + { + 'Schemas service_binding.create': { 'service_binding' => { 'create' => nil } }, + 'Schemas service_binding.create.parameters': { 'service_binding' => { 'create' => { 'parameters' => nil } } }, + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + its(:create_binding) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + + context 'when the create binding schema is not valid' do + let(:schemas) { + { + 'service_binding' => { + 'create' => { + 'parameters' => { '$schema' => 'http://json-schema.org/draft-04/schema#', 'type' => 'object', 'properties' => true } + } + } + } + } + let(:path) { 'service_binding.create.parameters' } + + its('errors.messages') { should have(1).items } + its('errors.messages.first') { should match "Schema #{path} is not valid" } + its(:valid?) { should be false } + end + + context 'when schemas have a missing key' do + { + 'Schemas service_binding.create': { 'service_binding' => { 'create' => {} } }, + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + its(:create_binding) { should be_nil } + its(:errors) { should be_empty } + its(:valid?) { should be true } + end + end + end + + context 'when schemas have an invalid type' do + { + 'Schemas service_binding.create': { 'service_binding' => { 'create' => true } }, + 'Schemas service_binding.create.parameters': { 'service_binding' => { 'create' => { 'parameters' => true } } }, + }.each do |name, test| + context "for property #{name}" do + let(:schemas) { test } + + its(:create_binding) { 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 + + context 'when schemas has a potentially dangerous uri' do + let(:path) { 'service_binding.create.parameters' } + let(:schemas) { + { + 'service_binding' => { + 'create' => { + 'parameters' => 'https://example.com/hax0r' + } + } + } + } + + its(:create_binding) { should be_nil } + its('errors.messages') { should have(1).items } + its('errors.messages.first') { should eq "Schemas #{path} must be a hash, but has value \"https://example.com/hax0r\"" } + its(:valid?) { should be false } + end + end + end end end end diff --git a/spec/unit/models/services/service_plan_spec.rb b/spec/unit/models/services/service_plan_spec.rb index 69f78c06859..02b113bf3ed 100644 --- a/spec/unit/models/services/service_plan_spec.rb +++ b/spec/unit/models/services/service_plan_spec.rb @@ -56,7 +56,8 @@ module VCAP::CloudController :bindable, :active, :create_instance_schema, - :update_instance_schema + :update_instance_schema, + :create_binding_schema end it 'imports these attributes' do @@ -69,7 +70,8 @@ module VCAP::CloudController :public, :bindable, :create_instance_schema, - :update_instance_schema + :update_instance_schema, + :create_binding_schema end end diff --git a/spec/unit/presenters/v2/service_plan_presenter_spec.rb b/spec/unit/presenters/v2/service_plan_presenter_spec.rb index 21505d43aa2..4bce80f54f4 100644 --- a/spec/unit/presenters/v2/service_plan_presenter_spec.rb +++ b/spec/unit/presenters/v2/service_plan_presenter_spec.rb @@ -18,11 +18,13 @@ module CloudController::Presenters::V2 let(:service_plan) do VCAP::CloudController::ServicePlan.make(create_instance_schema: create_instance_schema, - update_instance_schema: update_instance_schema) + update_instance_schema: update_instance_schema, + create_binding_schema: create_binding_schema) end let(:create_instance_schema) { nil } let(:update_instance_schema) { nil } + let(:create_binding_schema) { nil } before do allow(RelationsPresenter).to receive(:new).and_return(relations_presenter) @@ -41,8 +43,11 @@ module CloudController::Presenters::V2 'relationship_url' => 'http://relationship.example.com', 'schemas' => { 'service_instance' => { - 'create' => { 'parameters' => {} }, - 'update' => { 'parameters' => {} } + 'create' => { 'parameters' => {} }, + 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } } }, 'service_guid' => service_plan.service_guid, @@ -51,9 +56,10 @@ module CloudController::Presenters::V2 ) end - context 'when the plan create_instance_schema and update_instance_schema are nil' do + context 'when the plan create_instance_schema, update_instance_schema and create_binding_schema are nil' do let(:create_instance_schema) { nil } let(:update_instance_schema) { nil } + let(:create_binding_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( { @@ -61,6 +67,9 @@ module CloudController::Presenters::V2 'service_instance' => { 'create' => { 'parameters' => {} }, 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } } } } @@ -73,7 +82,17 @@ module CloudController::Presenters::V2 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 }, 'update' => { 'parameters' => {} } } } } + { + 'schemas' => { + 'service_instance' => { + 'create' => { 'parameters' => schema }, + 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } + } + } + } ) end end @@ -82,7 +101,17 @@ module CloudController::Presenters::V2 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' => {} }, 'update' => { 'parameters' => {} } } } } + { + 'schemas' => { + 'service_instance' => { + 'create' => { 'parameters' => {} }, + 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } + } + } + } ) end end @@ -92,7 +121,23 @@ module CloudController::Presenters::V2 let(:update_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' => {} }, 'update' => { 'parameters' => schema } } } } + { + 'schemas' => { + 'service_instance' => { + 'create' => { + 'parameters' => {} + }, + 'update' => { + 'parameters' => schema + } + }, + 'service_binding' => { + 'create' => { + 'parameters' => {} + } + } + } + } ) end end @@ -101,7 +146,43 @@ module CloudController::Presenters::V2 let(:update_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' => {} }, 'update' => { 'parameters' => {} } } } } + { + 'schemas' => { + 'service_instance' => { + 'create' => { 'parameters' => {} }, + 'update' => { 'parameters' => {} } + }, + 'service_binding' => { + 'create' => { 'parameters' => {} } + } + } + } + ) + end + end + + context 'when the plan create_binding_schema is valid json' do + schema = { '$schema' => 'example.com/schema' } + let(:create_binding_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' => {} + }, + 'update' => { + 'parameters' => {} + } + }, + 'service_binding' => { + 'create' => { + 'parameters' => schema + } + } + } + } ) end end