From 24154654f1f7a726f1550d17d6d121ab60190059 Mon Sep 17 00:00:00 2001 From: Sven Krieger <37476281+svkrieger@users.noreply.github.com> Date: Wed, 10 Jan 2024 07:53:15 +0100 Subject: [PATCH] Enhance CATS-SB - The behaviour for a fetch service instance can now be controlled with `fetch_service_instance` - The behaviour for a fetch service binding can now be controlled with `fetch_service_instance` - The behaviour for a fetch service instance last operation can now be controlled with `fetch_service_instance_last_operation` - The behaviour for a fetch service binding last operation can now be controlled with `fetch_service_binding_last_operation` - Improved error responses in case a service instance or service binding does not exist but the resource or its last operation is being fetched - Improved overriding the response json with configuration --- assets/service_broker/data.json | 37 +- assets/service_broker/service_broker.rb | 159 +++- .../spec/service_broker_spec.rb | 715 +++++++++++++++++- 3 files changed, 874 insertions(+), 37 deletions(-) diff --git a/assets/service_broker/data.json b/assets/service_broker/data.json index 64b4392a4..b60ee75f9 100644 --- a/assets/service_broker/data.json +++ b/assets/service_broker/data.json @@ -121,7 +121,7 @@ "body": {} } }, - "fetch": { + "fetch_service_instance_last_operation": { "default": { "in_progress": { "sleep_seconds": 0, @@ -139,6 +139,20 @@ } } }, + "fetch_service_instance": { + "default": { + "sleep_seconds": 0, + "status": 200, + "body": {} + } + }, + "fetch_service_binding": { + "default": { + "sleep_seconds": 0, + "status": 200, + "body": {} + } + }, "update": { "fake-async-plan-guid": { "sleep_seconds": 0, @@ -198,9 +212,28 @@ "status": 200, "body": {} } + }, + "fetch_service_binding_last_operation": { + "default": { + "in_progress": { + "sleep_seconds": 0, + "status": 200, + "body": { + "state": "in progress" + } + }, + "finished": { + "sleep_seconds": 0, + "status": 200, + "body": { + "state": "succeeded" + } + } + } } }, "service_instances": {}, "service_bindings": {}, - "max_fetch_service_instance_requests": 1 + "max_fetch_service_instance_requests": 1, + "max_fetch_service_binding_requests": 1 } diff --git a/assets/service_broker/service_broker.rb b/assets/service_broker/service_broker.rb index d9c87e160..8037aad0c 100755 --- a/assets/service_broker/service_broker.rb +++ b/assets/service_broker/service_broker.rb @@ -53,6 +53,39 @@ def to_json(opts={}) end end +class ServiceBinding + attr_reader :binding_data, :instance_id, :fetch_count, :deleted + + def initialize(opts={}) + @binding_data = opts.fetch(:binding_data) + @instance_id = opts.fetch(:instance_id) + @fetch_count = opts.fetch(:fetch_count, 0) + @deleted = opts.fetch(:deleted, false) + end + + def plan_id + @binding_data['plan_id'] + end + + def delete! + @deleted = true + @fetch_count = 0 + self + end + + def increment_fetch_count + @fetch_count += 1 + end + + def to_json(opts={}) + { + binding_data: binding_data, + fetch_count: fetch_count, + deleted: deleted + }.to_json(opts) + end +end + class DataSource attr_reader :data @@ -64,6 +97,10 @@ def max_fetch_service_instance_requests @data['max_fetch_service_instance_requests'] || 1 end + def max_fetch_service_binding_requests + @data['max_fetch_service_binding_requests'] || 1 + end + def service_instance_by_id(cc_id) @data['service_instances'][cc_id] end @@ -78,15 +115,23 @@ def create_service_instance(cc_id, json_data) service_instance end + def service_binding_by_id(cc_id) + @data['service_bindings'][cc_id] + end + def create_service_binding(instance_id, binding_id, binding_data) - @data['service_instances'][binding_id] = { - 'binding_data' => binding_data, - 'instance_id' => instance_id, - } + service_binding = ServiceBinding.new( + binding_data: binding_data, + instance_id: instance_id + ) + + @data['service_bindings'][binding_id] = service_binding + + service_binding end def delete_service_binding(binding_id) - @data['service_instances'].delete(binding_id) + @data['service_bindings'].delete(binding_id) end def merge!(data) @@ -225,7 +270,7 @@ def cf_respond_with_api_info_location(cf_api_info_location) respond_with_behavior($datasource.behavior_for_type(:provision, service_instance.plan_id), params['accepts_incomplete']) end - # fetch service instance + # fetch service instance last operation get '/v2/service_instances/:id/last_operation/?' do |id| service_instance = $datasource.service_instance_by_id(id) if service_instance @@ -237,7 +282,7 @@ def cf_respond_with_api_info_location(cf_api_info_location) state = 'in_progress' end - behavior = $datasource.behavior_for_type('fetch', plan_id)[state] + behavior = $datasource.behavior_for_type('fetch_service_instance_last_operation', plan_id)[state] sleep behavior['sleep_seconds'] status behavior['status'] @@ -246,22 +291,42 @@ def cf_respond_with_api_info_location(cf_api_info_location) else log_response(status, behavior['raw_body']) end - else - status 200 - log_response(status, { - state: 'failed', - description: "Broker could not find service instance by the given id #{id}", - }.to_json) + else # service instance does not exist + status 410 + log_response(status, "Broker could not find service instance by the given id #{id}") end end - # fetch service binding + # fetch service binding last operation get '/v2/service_instances/:instance_id/service_bindings/:binding_id/last_operation/?' do |instance_id, binding_id| - status 200 - log_response(status, { - state: 'succeeded', - description: '100%', - }.to_json) + service_binding = $datasource.service_binding_by_id(binding_id) + if service_binding + if service_binding.instance_id == instance_id + plan_id = service_binding.plan_id + + if service_binding.increment_fetch_count > $datasource.max_fetch_service_binding_requests + state = 'finished' + else + state = 'in_progress' + end + + behavior = $datasource.behavior_for_type('fetch_service_binding_last_operation', plan_id)[state] + sleep behavior['sleep_seconds'] + status behavior['status'] + + if behavior['body'] + log_response(status, behavior['body'].to_json) + else + log_response(status, behavior['raw_body']) + end + else # service binding is not associated with the given instance_id + status 410 + log_response(status, "Broker could not find the service binding `#{binding_id}` for service instance `#{instance_id}`") + end + else # service binding does not exist + status 410 + log_response(status, "Broker could not find the service binding `#{binding_id}` for service instance `#{instance_id}`") + end end # update service instance @@ -294,32 +359,68 @@ def cf_respond_with_api_info_location(cf_api_info_location) content_type :json json_body = JSON.parse(request.body.read) - service_binding = $datasource.create_service_binding(instance_id, binding_id, json_body) - respond_with_behavior($datasource.behavior_for_type(:bind, service_binding['binding_data']['plan_id']), params[:accepts_incomplete]) + service_instance = $datasource.service_instance_by_id(instance_id) + if service_instance + service_binding = $datasource.create_service_binding(instance_id, binding_id, json_body) + respond_with_behavior($datasource.behavior_for_type(:bind, service_binding.plan_id), params[:accepts_incomplete]) + else + status 400 + log_response(status, "Broker could not find service instance by the given id #{instance_id}") + end end # delete service binding delete '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id| content_type :json - service_binding = $datasource.delete_service_binding(binding_id) + service_binding = $datasource.service_binding_by_id(binding_id) if service_binding - respond_with_behavior($datasource.behavior_for_type(:unbind, service_binding['binding_data']['plan_id']), params[:accepts_incomplete]) + service_binding.delete! + respond_with_behavior($datasource.behavior_for_type(:unbind, service_binding.plan_id), params[:accepts_incomplete]) else respond_with_behavior($datasource.behavior_for_type(:unbind, nil), params[:accepts_incomplete]) end end + # fetch service instance get '/v2/service_instances/:instance_id' do |instance_id| - status 200 - log_response(status, JSON.pretty_generate($datasource.data['service_instances'][instance_id].provision_data)) + service_instance = $datasource.service_instance_by_id(instance_id) + if service_instance + behaviour = $datasource.behavior_for_type(:fetch_service_instance, service_instance.plan_id).clone + + provision_data = service_instance.provision_data.clone + if behaviour["body"] + behaviour["body"] = provision_data.merge!(behaviour["body"]) + end + + respond_with_behavior(behaviour) + else # service instance does not exist + status 404 + log_response(status, "Broker could not find service instance by the given id #{instance_id}") + end end + # fetch service binding get '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id| - binding_data = $datasource.data['service_instances'][binding_id]['binding_data'] - response_body = $datasource.behavior_for_type(:fetch_service_binding, binding_data['plan_id']) - response_body['body'].merge!(binding_data) - respond_with_behavior(response_body) + service_binding = $datasource.service_binding_by_id(binding_id) + if service_binding + if service_binding.instance_id == instance_id + behaviour = $datasource.behavior_for_type(:fetch_service_binding, service_binding.plan_id).clone + + binding_data = service_binding.binding_data.clone + if behaviour["body"] + behaviour["body"] = binding_data.merge!(behaviour["body"]) + end + + respond_with_behavior(behaviour) + else # service binding is not associated with the given instance_id + status 404 + log_response(status, "Broker could not find the service binding `#{binding_id}` for service instance `#{instance_id}`") + end + else # service binding does not exist + status 404 + log_response(status, "Broker could not find the service binding `#{binding_id}` for service instance `#{instance_id}`") + end end get '/config/all/?' do diff --git a/assets/service_broker/spec/service_broker_spec.rb b/assets/service_broker/spec/service_broker_spec.rb index 5e0f875f3..511764122 100644 --- a/assets/service_broker/spec/service_broker_spec.rb +++ b/assets/service_broker/spec/service_broker_spec.rb @@ -30,6 +30,27 @@ end end + describe 'GET /v2/service_instances/:id' do + context 'service instance exists' do + before do + put '/v2/service_instances/fake-guid', {service_id: 'fake-service', plan_id: 'fake-plan'}.to_json + end + + it 'returns 200 with the provisioning data in the body ' do + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({service_id: 'fake-service', plan_id: 'fake-plan'}.to_json) + end + end + context 'service instance does not exist' do + it 'returns 404' do + get '/v2/service_instances/non-existent' + expect(last_response.status).to eq(404) + expect(last_response.body).to eq("Broker could not find service instance by the given id non-existent") + end + end + end + describe 'PUT /v2/service_instances/:id' do it 'returns 200 with an empty JSON body' do put '/v2/service_instances/fakeIDThough', {}.to_json @@ -201,6 +222,227 @@ end end + describe 'GET /v2/service_instances/:id/service_bindings/:id' do + context 'service binding exists' do + before do + put '/v2/service_instances/fake-guid', {service_id: 'fake-service', plan_id: 'fake-plan'}.to_json + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-plan'}.to_json + end + + it 'returns 200 with the provisioning data in the body ' do + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan'}.to_json) + end + + context 'service binding is not associated with given service instance' do + it 'returns 404' do + get '/v2/service_instances/non-existent/service_bindings/binding-guid' + expect(last_response.status).to eq(404) + expect(last_response.body).to eq("Broker could not find the service binding `binding-guid` for service instance `non-existent`") + end + end + end + context 'service binding does not exist' do + it 'returns 404' do + get '/v2/service_instances/non-existent/service_bindings/non-existent-binding' + expect(last_response.status).to eq(404) + expect(last_response.body).to eq("Broker could not find the service binding `non-existent-binding` for service instance `non-existent`") + end + end + end + + describe 'PUT /v2/service_instances/:id/service_bindings/:id' do + before do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + bind: { + 'fake-plan-guid' => { + sleep_seconds: 0, + status: 201, + body: {} + }, + default: { + sleep_seconds: 0, + status: 202, + body: {} + } + } + } + }.to_json + + post '/config', config + + put '/v2/service_instances/fake-guid', {service_id: 'fake-service', plan_id: 'fake-plan-guid'}.to_json + end + + it 'returns 201 with an empty JSON body' do + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-plan-guid'}.to_json + expect(last_response.status).to eq(201) + expect(JSON.parse(last_response.body)).to be_empty + end + + context 'service instance does not exist' do + it 'returns 400' do + put '/v2/service_instances/non-existent/service_bindings/binding-guid', {plan_id: 'fake-plan-guid'}.to_json + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('Broker could not find service instance by the given id non-existent') + end + end + + context 'when the plan is configured as async_only' do + before do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + bind: { + 'fake-async-plan-guid' => { + sleep_seconds: 0, + async_only: true, + status: 202, + body: {} + }, + default: { + sleep_seconds: 0, + status: 202, + body: {} + } + } + } + }.to_json + + post '/config', config + + put '/v2/service_instances/fake-guid?accepts_incomplete=true', {service_id: 'fake-service', plan_id: 'fake-async-plan-guid'}.to_json + end + + + context 'request is for an async plan' do + it 'returns as usual if it does include accepts_incomplete' do + put '/v2/service_instances/fake-guid/service_bindings/binding-guid?accepts_incomplete=true', {plan_id: 'fake-async-plan-guid'}.to_json + + expect(last_response.status).to eq(202) + end + + it 'rejects request if it does not include accepts_incomplete' do + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-async-plan-guid'}.to_json + + expect(last_response.status).to eq(422) + expect(last_response.body).to eq( + { + 'error' => 'AsyncRequired', + 'description' => 'This service plan requires client support for asynchronous service operations.' + }.to_json + ) + end + end + + end + end + + describe 'DELETE /v2/service_instances/:id/service_bindings/:id' do + before do + put '/v2/service_instances/fake-guid?accepts_incomplete=true', {plan_id: 'fake-async-plan-guid'}.to_json + put '/v2/service_instances/fake-guid/service_bindings/binding-guid?accepts_incomplete=true', {plan_id: 'fake-async-plan-guid'}.to_json + end + + context 'when the plan is configured as async_only' do + before do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + unbind: { + 'fake-async-plan-guid' => { + sleep_seconds: 0, + async_only: true, + status: 202, + body: {} + }, + default: { + sleep_seconds: 0, + status: 202, + body: {} + } + } + } + }.to_json + + post '/config', config + end + + + context 'request is for an async plan' do + it 'returns as usual if it does include accepts_incomplete' do + delete '/v2/service_instances/fake-guid/service_bindings/binding-guid?accepts_incomplete=true' + + expect(last_response.status).to eq(202) + end + + it 'rejects request if it does not include accepts_incomplete' do + delete '/v2/service_instances/fake-guid/service_bindings/binding-guid' + + expect(last_response.status).to eq(422) + expect(last_response.body).to eq( + { + 'error' => 'AsyncRequired', + 'description' => 'This service plan requires client support for asynchronous service operations.' + }.to_json + ) + end + end + end + end + + describe 'GET /v2/service_instances/:id/last_operation' do + before do + put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid'}.to_json + end + + it 'should return 200 and the current status in the body' do + get '/v2/service_instances/fake-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({state: "in progress"}.to_json) + end + + context 'service instance does not exist' do + it 'should return 410 - gone' do + get '/v2/service_instances/non-existent/last_operation' + expect(last_response.status).to eq(410) + expect(last_response.body).to eq("Broker could not find service instance by the given id non-existent") + end + end + end + + describe 'GET /v2/service_instances/:id/service_bindings/:id/last_operation' do + before do + put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid'}.to_json + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-plan-guid'}.to_json + end + + it 'should return 200 and the current status in the body' do + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({state: "in progress"}.to_json) + end + + context 'service binding does not exist' do + it 'should return 410 - gone' do + get '/v2/service_instances/fake-guid/service_bindings/non-existent/last_operation' + expect(last_response.status).to eq(410) + expect(last_response.body).to eq("Broker could not find the service binding `non-existent` for service instance `fake-guid`") + end + end + + context 'service binding is not associated with given service instance' do + it 'should return 410 - gone' do + get '/v2/service_instances/non-existent/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(410) + expect(last_response.body).to eq("Broker could not find the service binding `binding-guid` for service instance `non-existent`") + end + end + end + describe 'cf api info location' do api_not_known_error = JSON.pretty_generate({ "error" => true, @@ -350,7 +592,7 @@ def unbind end end - context 'for a fetch operation' do + context 'for a fetch service instance last operation' do before do put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid'}.to_json end @@ -359,7 +601,7 @@ def unbind config = { max_fetch_service_instance_requests: 1, behaviors: { - fetch: { + fetch_service_instance_last_operation: { default: { in_progress: { status: 200, @@ -391,7 +633,7 @@ def unbind config = { max_fetch_service_instance_requests: 1, behaviors: { - fetch: { + fetch_service_instance_last_operation: { default: { in_progress: { status: 200, @@ -423,7 +665,7 @@ def unbind config = { max_fetch_service_instance_requests: 1, behaviors: { - fetch: { + fetch_service_instance_last_operation: { default: { in_progress: { status: 200, @@ -459,7 +701,7 @@ def unbind config = { max_fetch_service_instance_requests: 2, behaviors: { - fetch: { + fetch_service_instance_last_operation: { default: { in_progress: { status: 200, @@ -495,7 +737,7 @@ def unbind config = { max_fetch_service_instance_requests: 1, behaviors: { - fetch: { + fetch_service_instance_last_operation: { 'fake-plan-guid' => { in_progress: { status: 200, @@ -536,6 +778,467 @@ def unbind end end + context 'for a fetch service binding last operation' do + before do + put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid'}.to_json + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-plan-guid'}.to_json + end + + it 'should change the response using a json body' do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + fetch_service_binding_last_operation: { + default: { + in_progress: { + status: 200, + sleep_seconds: 0, + body: {} + }, + finished: { + status: 400, + sleep_seconds: 0, + body: { foo: :bar } + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('{}') + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq({ foo: :bar }.to_json) + end + + it 'should change the response using an invalid json body' do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + fetch_service_binding_last_operation: { + default: { + in_progress: { + status: 200, + sleep_seconds: 0, + raw_body: 'cheese' + }, + finished: { + status: 400, + sleep_seconds: 0, + raw_body: 'cake' + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq 'cheese' + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq 'cake' + end + + it 'should cause the action to sleep' do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + fetch_service_binding_last_operation: { + default: { + in_progress: { + status: 200, + sleep_seconds: 1.1, + body: {} + }, + finished: { + status: 200, + sleep_seconds: 0.6, + body: { } + } + } + } + } + }.to_json + + post '/config', config + + expect do + Timeout::timeout(1) do + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + end + end.to raise_error(TimeoutError) + + expect do + Timeout::timeout(0.5) do + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + end + end.to raise_error(TimeoutError) + end + + it 'honors max_fetch_service_bindings_request' do + config = { + max_fetch_service_binding_requests: 2, + behaviors: { + fetch_service_binding_last_operation: { + default: { + in_progress: { + status: 200, + sleep_seconds: 0, + body: {} + }, + finished: { + status: 400, + sleep_seconds: 0, + body: { foo: :bar } + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('{}') + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('{}') + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq({ foo: :bar }.to_json) + end + + it 'can be customized on a per-plan basis' do + config = { + max_fetch_service_binding_requests: 1, + behaviors: { + fetch_service_binding_last_operation: { + 'fake-plan-guid' => { + in_progress: { + status: 200, + sleep_seconds: 0, + body: { foo: 'bar' } + }, + finished: { + status: 201, + sleep_seconds: 0, + body: { foo: 'baz' } + } + }, + default: { + in_progress: { + status: 200, + sleep_seconds: 0, + body: {} + }, + finished: { + status: 400, + sleep_seconds: 0, + body: { foo: :bar } + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({ foo: 'bar' }.to_json) + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid/last_operation' + expect(last_response.status).to eq(201) + expect(last_response.body).to eq({ foo: 'baz' }.to_json) + end + end + + context 'for a fetch service instance' do + before do + put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}}.to_json + end + + it 'should return the provision data and merge in the body from config' do + config = { + behaviors: { + fetch_service_instance: { + default: { + status: 200, + sleep_seconds: 0, + body: {} + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}}.to_json) + end + + it 'should return an empty response if no body provided in the config' do + config = { + behaviors: { + fetch_service_instance: { + default: { + status: 200, + sleep_seconds: 0 + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to be_empty + end + + it 'should overwrite provision data with configured behaviour' do + config = { + behaviors: { + fetch_service_instance: { + default: { + status: 200, + sleep_seconds: 0, + body: { + context: { + organization_guid: "some-new-org-guid" + } + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-new-org-guid'}}.to_json) + end + + it 'should change the response using an invalid json body' do + config = { + behaviors: { + fetch_service_instance: { + default: { + status: 200, + sleep_seconds: 0, + raw_body: 'cheese' + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq 'cheese' + end + + it 'should cause the action to sleep' do + config = { + behaviors: { + fetch_service_instance: { + default: { + status: 200, + sleep_seconds: 1.1, + body: {} + } + } + } + }.to_json + + post '/config', config + + expect do + Timeout::timeout(1) do + get '/v2/service_instances/fake-guid' + end + end.to raise_error(TimeoutError) + end + + it 'can be customized on a per-plan basis' do + config = { + behaviors: { + fetch_service_instance: { + 'fake-plan-guid' => { + status: 200, + sleep_seconds: 0, + body: { foo: 'bar' } + }, + default: { + status: 200, + sleep_seconds: 0, + body: {} + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}, foo: 'bar'}.to_json) + end + end + + context 'for a fetch service binding' do + before do + put '/v2/service_instances/fake-guid', {plan_id: 'fake-plan-guid'}.to_json + put '/v2/service_instances/fake-guid/service_bindings/binding-guid', {plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}}.to_json + + end + + it 'should return the provision data and merge in the body from config' do + config = { + behaviors: { + fetch_service_binding: { + default: { + status: 200, + sleep_seconds: 0, + body: {} + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}}.to_json) + end + + it 'should return an empty response if no body provided in the config' do + config = { + behaviors: { + fetch_service_binding: { + default: { + status: 200, + sleep_seconds: 0 + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to be_empty + end + + it 'should overwrite provision data with configured behaviour' do + config = { + behaviors: { + fetch_service_binding: { + default: { + status: 200, + sleep_seconds: 0, + body: { + context: { + organization_guid: "some-new-org-guid" + } + } + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-new-org-guid'}}.to_json) + end + + it 'should change the response using an invalid json body' do + config = { + behaviors: { + fetch_service_binding: { + default: { + status: 200, + sleep_seconds: 0, + raw_body: 'cheese' + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq 'cheese' + end + + it 'should cause the action to sleep' do + config = { + behaviors: { + fetch_service_binding: { + default: { + status: 200, + sleep_seconds: 1.1, + body: {} + } + } + } + }.to_json + + post '/config', config + + expect do + Timeout::timeout(1) do + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + end + end.to raise_error(TimeoutError) + end + + it 'can be customized on a per-plan basis' do + config = { + behaviors: { + fetch_service_binding: { + 'fake-plan-guid' => { + status: 200, + sleep_seconds: 0, + body: { foo: 'bar' } + }, + default: { + status: 200, + sleep_seconds: 0, + body: {} + } + } + } + }.to_json + + post '/config', config + + get '/v2/service_instances/fake-guid/service_bindings/binding-guid' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq({plan_id: 'fake-plan-guid', context: {organization_guid: 'some-org-guid'}, foo: 'bar'}.to_json) + end + end + it 'should allow resetting the configuration to its defaults' do get '/config' data = last_response.body