|
218 | 218 | get "/v3/jobs/#{job.guid}", nil, space_dev_headers |
219 | 219 |
|
220 | 220 | expect(last_response).to have_status_code(200) |
221 | | - expect(parsed_response['guid']). to eq(job.guid) |
| 221 | + expect(parsed_response['guid']).to eq(job.guid) |
222 | 222 | end |
223 | 223 |
|
224 | 224 | describe 'the pollable job' do |
|
650 | 650 | [ |
651 | 651 | expected_json( |
652 | 652 | binding_guid: route_binding_1.guid, |
653 | | - route_service_url: route_service_url, |
654 | | - service_instance_guid: service_instance_1.guid, |
655 | | - route_guid: route.guid, |
656 | | - last_operation_type: 'create', |
657 | | - last_operation_state: 'successful', |
| 653 | + route_service_url: route_service_url, |
| 654 | + service_instance_guid: service_instance_1.guid, |
| 655 | + route_guid: route.guid, |
| 656 | + last_operation_type: 'create', |
| 657 | + last_operation_state: 'successful', |
658 | 658 | ), |
659 | 659 | expected_json( |
660 | 660 | binding_guid: route_binding_2.guid, |
|
867 | 867 | end |
868 | 868 | end |
869 | 869 |
|
| 870 | + describe 'DELETE /v3/service_route_bindings/:guid' do |
| 871 | + let(:api_call) { lambda { |user_headers| delete "/v3/service_route_bindings/#{guid}", nil, user_headers } } |
| 872 | + |
| 873 | + context 'route binding exists' do |
| 874 | + let(:route) { VCAP::CloudController::Route.make(space: space) } |
| 875 | + let(:binding) do |
| 876 | + VCAP::CloudController::RouteBinding.new.save_with_new_operation( |
| 877 | + { service_instance: service_instance, route: route, route_service_url: route_service_url }, |
| 878 | + { type: 'create', state: 'successful' } |
| 879 | + ) |
| 880 | + end |
| 881 | + let(:guid) { binding.guid } |
| 882 | + |
| 883 | + context 'user-provided service instance' do |
| 884 | + let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space, route_service_url: route_service_url) } |
| 885 | + |
| 886 | + let(:expected_codes_and_responses) { responses_for_space_restricted_delete_endpoint } |
| 887 | + let(:db_check) { |
| 888 | + lambda do |
| 889 | + expect(VCAP::CloudController::RouteBinding.all).to be_empty |
| 890 | + end |
| 891 | + } |
| 892 | + |
| 893 | + it_behaves_like 'permissions for delete endpoint', ALL_PERMISSIONS |
| 894 | + end |
| 895 | + |
| 896 | + context 'managed service instance' do |
| 897 | + let(:service_offering) { VCAP::CloudController::Service.make(requires: ['route_forwarding']) } |
| 898 | + let(:service_plan) { VCAP::CloudController::ServicePlan.make(service: service_offering) } |
| 899 | + let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: service_plan) } |
| 900 | + |
| 901 | + let(:expected_codes_and_responses) { responses_for_space_restricted_async_delete_endpoint } |
| 902 | + let(:db_check) { lambda {} } |
| 903 | + let(:job) { VCAP::CloudController::PollableJobModel.last } |
| 904 | + |
| 905 | + it_behaves_like 'permissions for delete endpoint', ALL_PERMISSIONS |
| 906 | + |
| 907 | + it 'responds with a job resource' do |
| 908 | + api_call.call(space_dev_headers) |
| 909 | + expect(last_response).to have_status_code(202) |
| 910 | + expect(last_response.headers['Location']).to end_with("/v3/jobs/#{job.guid}") |
| 911 | + |
| 912 | + expect(job.state).to eq(VCAP::CloudController::PollableJobModel::PROCESSING_STATE) |
| 913 | + expect(job.operation).to eq('service_route_bindings.delete') |
| 914 | + expect(job.resource_guid).to eq(binding.guid) |
| 915 | + expect(job.resource_type).to eq('service_route_binding') |
| 916 | + |
| 917 | + get "/v3/jobs/#{job.guid}", nil, space_dev_headers |
| 918 | + |
| 919 | + expect(last_response).to have_status_code(200) |
| 920 | + expect(parsed_response['guid']).to eq(job.guid) |
| 921 | + end |
| 922 | + |
| 923 | + describe 'the pollable job' do |
| 924 | + let(:broker_base_url) { service_instance.service_broker.broker_url } |
| 925 | + let(:broker_unbind_url) { "#{broker_base_url}/v2/service_instances/#{service_instance.guid}/service_bindings/#{binding.guid}" } |
| 926 | + let(:route_service_url) { 'https://route_service_url.com' } |
| 927 | + let(:broker_status_code) { 200 } |
| 928 | + let(:broker_response) { {} } |
| 929 | + let(:query) do |
| 930 | + { |
| 931 | + service_id: service_instance.service_plan.service.unique_id, |
| 932 | + plan_id: service_instance.service_plan.unique_id, |
| 933 | + } |
| 934 | + end |
| 935 | + |
| 936 | + before do |
| 937 | + api_call.call(space_dev_headers) |
| 938 | + expect(last_response).to have_status_code(202) |
| 939 | + |
| 940 | + stub_request(:delete, broker_unbind_url). |
| 941 | + with(query: query). |
| 942 | + to_return(status: broker_status_code, body: broker_response.to_json, headers: {}) |
| 943 | + end |
| 944 | + |
| 945 | + it 'sends an unbind request with the right arguments to the service broker' do |
| 946 | + execute_all_jobs(expected_successes: 1, expected_failures: 0) |
| 947 | + |
| 948 | + expect( |
| 949 | + a_request(:delete, broker_unbind_url). |
| 950 | + with( |
| 951 | + query: query, |
| 952 | + ) |
| 953 | + ).to have_been_made.once |
| 954 | + end |
| 955 | + |
| 956 | + context 'when the unbind completes synchronously' do |
| 957 | + it 'removes the binding' do |
| 958 | + execute_all_jobs(expected_successes: 1, expected_failures: 0) |
| 959 | + |
| 960 | + expect(VCAP::CloudController::RouteBinding.all).to be_empty |
| 961 | + end |
| 962 | + |
| 963 | + it 'completes the job' do |
| 964 | + execute_all_jobs(expected_successes: 1, expected_failures: 0) |
| 965 | + |
| 966 | + expect(job.state).to eq(VCAP::CloudController::PollableJobModel::COMPLETE_STATE) |
| 967 | + end |
| 968 | + end |
| 969 | + |
| 970 | + context 'when the broker returns a failure' do |
| 971 | + let(:broker_status_code) { 418 } |
| 972 | + let(:broker_response) { 'nope' } |
| 973 | + |
| 974 | + it 'does not remove the binding' do |
| 975 | + execute_all_jobs(expected_successes: 0, expected_failures: 1) |
| 976 | + |
| 977 | + expect(VCAP::CloudController::RouteBinding.all).not_to be_empty |
| 978 | + end |
| 979 | + |
| 980 | + it 'puts the error details in the job' do |
| 981 | + execute_all_jobs(expected_successes: 0, expected_failures: 1) |
| 982 | + |
| 983 | + expect(job.state).to eq(VCAP::CloudController::PollableJobModel::FAILED_STATE) |
| 984 | + expect(job.cf_api_error).not_to be_nil |
| 985 | + error = YAML.safe_load(job.cf_api_error) |
| 986 | + expect(error['errors'].first['code']).to eq(10009) |
| 987 | + expect(error['errors'].first['detail']). |
| 988 | + to include('The service broker rejected the request. Status Code: 418 I\'m a Teapot, Body: "nope"') |
| 989 | + end |
| 990 | + end |
| 991 | + end |
| 992 | + end |
| 993 | + end |
| 994 | + |
| 995 | + context 'no route binding' do |
| 996 | + let(:guid) { 'no-such-route-binding' } |
| 997 | + |
| 998 | + it 'fails with the correct error' do |
| 999 | + api_call.call(space_dev_headers) |
| 1000 | + |
| 1001 | + expect(last_response).to have_status_code(404) |
| 1002 | + expect(parsed_response['errors']).to include( |
| 1003 | + include({ |
| 1004 | + 'detail' => 'Service route binding not found', |
| 1005 | + 'title' => 'CF-ResourceNotFound', |
| 1006 | + 'code' => 10010, |
| 1007 | + }) |
| 1008 | + ) |
| 1009 | + end |
| 1010 | + end |
| 1011 | + end |
| 1012 | + |
870 | 1013 | let(:user) { VCAP::CloudController::User.make } |
871 | 1014 | let(:org) { VCAP::CloudController::Organization.make } |
872 | 1015 | let(:space) { VCAP::CloudController::Space.make(organization: org) } |
@@ -921,7 +1064,7 @@ def bind_service_to_route(service_instance, route) |
921 | 1064 | route_service_url = service_instance.route_service_url |
922 | 1065 | VCAP::CloudController::RouteBinding.new.save_with_new_operation( |
923 | 1066 | { service_instance: service_instance, route: route, route_service_url: route_service_url }, |
924 | | - { type: 'create', state: 'successful' } |
| 1067 | + { type: 'create', state: 'successful' } |
925 | 1068 | ) |
926 | 1069 | end |
927 | 1070 | end |
0 commit comments