|
1389 | 1389 | end |
1390 | 1390 | end |
1391 | 1391 |
|
| 1392 | + describe 'PATCH /v3/service_plans/:guid' do |
| 1393 | + let(:offering) { VCAP::CloudController::Service.make(requires: ['route_forwarding'], bindings_retrievable: true) } |
| 1394 | + let(:plan) { VCAP::CloudController::ServicePlan.make(service: offering) } |
| 1395 | + let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: plan, route_service_url: route_service_url) } |
| 1396 | + let(:route) { VCAP::CloudController::Route.make(space: space) } |
| 1397 | + let(:app_model) { VCAP::CloudController::AppModel.make(space: space) } |
| 1398 | + let(:binding) { bind_service_to_route(service_instance, route) } |
| 1399 | + let(:guid) { binding.guid } |
| 1400 | + let(:labels) { { potato: 'sweet' } } |
| 1401 | + let(:annotations) { { style: 'mashed', amount: 'all' } } |
| 1402 | + let(:update_request_body) { |
| 1403 | + { |
| 1404 | + metadata: { |
| 1405 | + labels: labels, |
| 1406 | + annotations: annotations |
| 1407 | + } |
| 1408 | + } |
| 1409 | + } |
| 1410 | + |
| 1411 | + let(:api_call) { lambda { |user_headers| patch "/v3/service_route_bindings/#{guid}", update_request_body.to_json, user_headers } } |
| 1412 | + |
| 1413 | + it 'can update labels and annotations' do |
| 1414 | + api_call.call(admin_headers) |
| 1415 | + expect(last_response).to have_status_code(200) |
| 1416 | + expect(parsed_response.deep_symbolize_keys).to include(update_request_body) |
| 1417 | + end |
| 1418 | + |
| 1419 | + context 'when some labels are invalid' do |
| 1420 | + let(:labels) { { potato: 'sweet invalid potato' } } |
| 1421 | + |
| 1422 | + it 'returns a proper failure' do |
| 1423 | + api_call.call(admin_headers) |
| 1424 | + |
| 1425 | + expect(last_response).to have_status_code(422) |
| 1426 | + expect(parsed_response['errors'][0]['detail']).to match(/Metadata [\w\s]+ error/) |
| 1427 | + end |
| 1428 | + end |
| 1429 | + |
| 1430 | + context 'when some annotations are invalid' do |
| 1431 | + let(:annotations) { { '/style' => 'sweet invalid style' } } |
| 1432 | + |
| 1433 | + it 'returns a proper failure' do |
| 1434 | + api_call.call(admin_headers) |
| 1435 | + |
| 1436 | + expect(last_response).to have_status_code(422) |
| 1437 | + expect(parsed_response['errors'][0]['detail']).to match(/Metadata [\w\s]+ error/) |
| 1438 | + end |
| 1439 | + end |
| 1440 | + |
| 1441 | + context 'when the route binding does not exist' do |
| 1442 | + let(:guid) { 'moonlight-sonata' } |
| 1443 | + |
| 1444 | + it 'returns a not found error' do |
| 1445 | + api_call.call(admin_headers) |
| 1446 | + expect(last_response).to have_status_code(404) |
| 1447 | + end |
| 1448 | + end |
| 1449 | + |
| 1450 | + context 'when the route binding is being created' do |
| 1451 | + before do |
| 1452 | + binding.save_with_new_operation( |
| 1453 | + {}, |
| 1454 | + { type: 'create', state: 'in progress', broker_provided_operation: 'some-info' } |
| 1455 | + ) |
| 1456 | + end |
| 1457 | + |
| 1458 | + before do |
| 1459 | + api_call.call(admin_headers) |
| 1460 | + expect(last_response).to have_status_code(200) |
| 1461 | + binding.reload |
| 1462 | + end |
| 1463 | + |
| 1464 | + it 'can still update metadata' do |
| 1465 | + expect(binding).to have_labels({ prefix: nil, key: 'potato', value: 'sweet' }) |
| 1466 | + expect(binding).to have_annotations({ prefix: nil, key: 'style', value: 'mashed' }, { prefix: nil, key: 'amount', value: 'all' }) |
| 1467 | + end |
| 1468 | + |
| 1469 | + it 'does not update last operation' do |
| 1470 | + expect(binding.last_operation.type).to eq('create') |
| 1471 | + expect(binding.last_operation.state).to eq('in progress') |
| 1472 | + expect(binding.last_operation.broker_provided_operation).to eq('some-info') |
| 1473 | + end |
| 1474 | + end |
| 1475 | + |
| 1476 | + context 'when the route binding is being deleted' do |
| 1477 | + before do |
| 1478 | + binding.save_with_new_operation( |
| 1479 | + {}, |
| 1480 | + { type: 'delete', state: 'in progress', broker_provided_operation: 'some-info' } |
| 1481 | + ) |
| 1482 | + end |
| 1483 | + |
| 1484 | + it 'responds with a 422' do |
| 1485 | + api_call.call(admin_headers) |
| 1486 | + expect(last_response).to have_status_code(422) |
| 1487 | + end |
| 1488 | + |
| 1489 | + it 'does not update last operation' do |
| 1490 | + api_call.call(admin_headers) |
| 1491 | + binding.reload |
| 1492 | + expect(binding.last_operation.type).to eq('delete') |
| 1493 | + expect(binding.last_operation.state).to eq('in progress') |
| 1494 | + expect(binding.last_operation.broker_provided_operation).to eq('some-info') |
| 1495 | + end |
| 1496 | + end |
| 1497 | + |
| 1498 | + context 'permissions' do |
| 1499 | + let(:response_object) { |
| 1500 | + expected_json( |
| 1501 | + binding_guid: binding.guid, |
| 1502 | + route_service_url: route_service_url, |
| 1503 | + service_instance_guid: service_instance.guid, |
| 1504 | + route_guid: route.guid, |
| 1505 | + last_operation_type: 'create', |
| 1506 | + last_operation_state: 'successful', |
| 1507 | + include_params_link: service_instance.managed_instance?, |
| 1508 | + metadata: { |
| 1509 | + labels: labels, |
| 1510 | + annotations: annotations |
| 1511 | + } |
| 1512 | + ) |
| 1513 | + } |
| 1514 | + |
| 1515 | + let(:expected_codes_and_responses) do |
| 1516 | + Hash.new(code: 403).tap do |h| |
| 1517 | + h['admin'] = { code: 200, response_object: response_object } |
| 1518 | + h['space_developer'] = { code: 200, response_object: response_object } |
| 1519 | + h['no_role'] = { code: 404 } |
| 1520 | + h['org_auditor'] = { code: 404 } |
| 1521 | + h['org_billing_manager'] = { code: 404 } |
| 1522 | + end |
| 1523 | + end |
| 1524 | + |
| 1525 | + it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS |
| 1526 | + end |
| 1527 | + end |
| 1528 | + |
1392 | 1529 | let(:user) { VCAP::CloudController::User.make } |
1393 | 1530 | let(:org) { VCAP::CloudController::Organization.make } |
1394 | 1531 | let(:space) { VCAP::CloudController::Space.make(organization: org) } |
|
0 commit comments