spec/request/space_quotas_spec.rb
require 'spec_helper'
require 'request_spec_shared_examples'
module VCAP::CloudController
RSpec.describe 'space_quotas' do
let(:user) { VCAP::CloudController::User.make(guid: 'user-guid') }
let(:org) { VCAP::CloudController::Organization.make(guid: 'organization-guid') }
let!(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'space-quota-guid', organization: org) }
let(:space) { VCAP::CloudController::Space.make(guid: 'space-guid', organization: org, space_quota_definition: space_quota) }
let(:admin_header) { headers_for(user, scopes: %w[cloud_controller.admin]) }
describe 'GET /v3/space_quotas/:guid' do
let(:api_call) { ->(user_headers) { get "/v3/space_quotas/#{space_quota.guid}", nil, user_headers } }
context 'when the space quota is applied to the space where the current user has a role' do
let(:expected_codes_and_responses) do
h = Hash.new(code: 404)
expected_response = make_space_quota_json(space_quota)
h['admin'] = { code: 200, response_object: expected_response }
h['admin_read_only'] = { code: 200, response_object: expected_response }
h['global_auditor'] = { code: 200, response_object: expected_response }
h['space_developer'] = { code: 200, response_object: expected_response }
h['space_supporter'] = { code: 200, response_object: expected_response }
h['space_manager'] = { code: 200, response_object: expected_response }
h['space_auditor'] = { code: 200, response_object: expected_response }
h['org_manager'] = { code: 200, response_object: expected_response }
h
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
context 'when the space quota has no associated spaces' do
let(:api_call) { ->(user_headers) { get "/v3/space_quotas/#{unapplied_space_quota.guid}", nil, user_headers } }
let(:unapplied_space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org) }
let(:expected_codes_and_responses) do
h = Hash.new(code: 404)
h['admin'] = { code: 200, response_object: make_space_quota_json(unapplied_space_quota) }
h['admin_read_only'] = { code: 200, response_object: make_space_quota_json(unapplied_space_quota) }
h['global_auditor'] = { code: 200, response_object: make_space_quota_json(unapplied_space_quota) }
h['org_manager'] = { code: 200, response_object: make_space_quota_json(unapplied_space_quota) }
h
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
context 'when the space quota is owned by an org where the current user does not have a role' do
let(:api_call) { ->(user_headers) { get "/v3/space_quotas/#{other_space_quota.guid}", nil, user_headers } }
let(:other_org) { VCAP::CloudController::Organization.make }
let(:other_space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: other_org) }
let(:expected_codes_and_responses) do
h = Hash.new(code: 404)
h['admin'] = { code: 200, response_object: make_space_quota_json(other_space_quota) }
h['admin_read_only'] = { code: 200, response_object: make_space_quota_json(other_space_quota) }
h['global_auditor'] = { code: 200, response_object: make_space_quota_json(other_space_quota) }
h
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
context 'when the space quota does not exist' do
it 'returns a 404 with a helpful message' do
get '/v3/space_quotas/not-exist', nil, admin_header
expect(last_response).to have_status_code(404)
expect(last_response).to have_error_message('Space quota not found')
end
end
context 'when not logged in' do
it 'returns a 401 with a helpful message' do
get '/v3/space_quotas/not-exist', nil, {}
expect(last_response).to have_status_code(401)
expect(last_response).to have_error_message('Authentication error')
end
end
end
describe 'PATCH /v3/space_quotas/:guid' do
let(:params) do
{
name: 'don-quixote',
apps: {
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
per_app_tasks: 5,
log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
total_service_instances: 10,
total_service_keys: 20
},
routes: {
total_routes: 8,
total_reserved_ports: 4
}
}
end
let(:updated_space_quota_json) do
{
guid: space_quota.guid,
created_at: iso8601,
updated_at: iso8601,
name: 'don-quixote',
apps: {
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: nil,
per_app_tasks: 5,
log_rate_limit_in_bytes_per_second: 2000
},
services: {
paid_services_allowed: false,
total_service_instances: 10,
total_service_keys: 20
},
routes: {
total_routes: 8,
total_reserved_ports: 4
},
relationships: {
organization: {
data: { guid: space_quota.organization.guid }
},
spaces: {
data: [{ guid: space.guid }]
}
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/space_quotas/#{space_quota.guid}} },
organization: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{space_quota.organization.guid}} }
}
}
end
context 'permissions' do
let(:api_call) { ->(user_headers) { patch "/v3/space_quotas/#{space_quota.guid}", params.to_json, user_headers } }
let(:expected_codes_and_responses) do
h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
h['admin'] = { code: 200, response_object: updated_space_quota_json }
h['org_manager'] = { code: 200, response_object: updated_space_quota_json }
h['org_auditor'] = { code: 404 }
h['org_billing_manager'] = { code: 404 }
h['no_role'] = { code: 404 }
h
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
context 'when organization is suspended' do
let(:expected_codes_and_responses) do
h = super()
h['org_manager'] = { code: 403, errors: CF_ORG_SUSPENDED }
h
end
before do
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end
context 'when the space quota does not exist' do
it 'returns a 404 with a helpful message' do
patch '/v3/space_quotas/not-exist', params.to_json, admin_header
expect(last_response).to have_status_code(404)
expect(last_response).to have_error_message('Space quota not found')
end
end
context 'update partial values' do
let(:space_quota_to_update) do
VCAP::CloudController::SpaceQuotaDefinition.make(
organization: org,
guid: 'space_quota_to_update_guid',
name: 'update-me',
memory_limit: 8,
non_basic_services_allowed: true
)
end
let(:partial_params) do
{
name: 'don-quixote',
apps: {
per_app_tasks: 9,
total_memory_in_mb: nil
},
services: {
total_service_instances: 14,
paid_services_allowed: false
}
}
end
before do
patch "/v3/space_quotas/#{space_quota_to_update.guid}", partial_params.to_json, admin_header
end
it 'only updates the requested fields' do
expect(last_response).to have_status_code(200)
expect(space_quota_to_update.reload.app_task_limit).to eq(9)
expect(space_quota_to_update.reload.memory_limit).to eq(-1)
expect(space_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(space_quota_to_update.reload.total_services).to eq(14)
expect(space_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
context 'patching with empty params' do
it 'succeeds without changing the quota' do
patch "/v3/space_quotas/#{space_quota_to_update.guid}", {}, admin_header
expect(last_response).to have_status_code(200)
expect(space_quota_to_update.reload.app_task_limit).to eq(9)
expect(space_quota_to_update.reload.memory_limit).to eq(-1)
expect(space_quota_to_update.reload.log_rate_limit).to eq(-1)
expect(space_quota_to_update.reload.total_services).to eq(14)
expect(space_quota_to_update.reload.non_basic_services_allowed).to be_falsey
end
end
end
context 'when trying to update name to a pre-existing name' do
let!(:new_space_quota) { SpaceQuotaDefinition.make(organization: org) }
let(:params) do
{
name: space_quota.name
}
end
it 'returns 422' do
patch "/v3/space_quotas/#{new_space_quota.guid}", params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message("Space Quota '#{space_quota.name}' already exists.")
end
end
context 'when trying to update name with invalid params' do
let(:params) do
{
wat: 'idk'
}
end
it 'returns 422' do
patch "/v3/space_quotas/#{space_quota.guid}", params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message("Unknown field(s): 'wat'")
end
end
context 'when trying to set a log rate limit and there are apps with unlimited log rates' do
let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) }
let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) }
it 'returns 422' do
patch "/v3/space_quotas/#{space_quota.guid}", params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message(
"Current usage exceeds new quota values. This quota is applied to space '#{space.name}' which contains apps running with an unlimited log rate limit."
)
end
end
end
describe 'GET /v3/space_quotas' do
let(:api_call) { ->(user_headers) { get '/v3/space_quotas', nil, user_headers } }
it_behaves_like 'list_endpoint_with_common_filters' do
let(:resource_klass) { VCAP::CloudController::SpaceQuotaDefinition }
let(:headers) { admin_headers }
let(:api_call) do
->(headers, filters) { get "/v3/space_quotas?#{filters}", nil, headers }
end
end
context 'when listing space quotas without filters' do
let!(:unapplied_space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, guid: 'unapplied-space-quota') }
let(:other_org) { VCAP::CloudController::Organization.make }
let!(:other_space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: other_org, guid: 'other-space-quota') }
let(:expected_codes_and_responses) do
h = Hash.new(code: 200, response_objects: [])
h['admin'] = {
code: 200,
response_objects: contain_exactly(
make_space_quota_json(space_quota),
make_space_quota_json(other_space_quota),
make_space_quota_json(unapplied_space_quota)
)
}
h['admin_read_only'] = {
code: 200,
response_objects: contain_exactly(
make_space_quota_json(space_quota),
make_space_quota_json(other_space_quota),
make_space_quota_json(unapplied_space_quota)
)
}
h['global_auditor'] = {
code: 200,
response_objects: contain_exactly(
make_space_quota_json(space_quota),
make_space_quota_json(other_space_quota),
make_space_quota_json(unapplied_space_quota)
)
}
h['org_manager'] = {
code: 200,
response_objects: contain_exactly(
make_space_quota_json(space_quota),
make_space_quota_json(unapplied_space_quota)
)
}
h['space_manager'] = { code: 200, response_objects: [make_space_quota_json(space_quota)] }
h['space_auditor'] = { code: 200, response_objects: [make_space_quota_json(space_quota)] }
h['space_developer'] = { code: 200, response_objects: [make_space_quota_json(space_quota)] }
h['space_supporter'] = { code: 200, response_objects: [make_space_quota_json(space_quota)] }
h
end
it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS
end
context 'with filters' do
let!(:space_quota_2) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'second-guid', name: 'second-name', organization: org) }
let(:space_2) { VCAP::CloudController::Space.make(guid: 'space-2-guid', organization: org, space_quota_definition: space_quota_2) }
let!(:space_quota_3) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'third-guid', name: 'third-name', organization: org) }
let(:space_3) { VCAP::CloudController::Space.make(guid: 'space-3-guid', organization: org, space_quota_definition: space_quota_3) }
let(:org_alien) { VCAP::CloudController::Organization.make(guid: 'organization-alien-guid') }
let!(:space_quota_alien) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'space-quota-alien-guid', organization: org_alien) }
it 'returns the list of quotas filtered by names and guids' do
get "/v3/space_quotas?guids=#{space_quota.guid},second-guid&names=#{space_quota.name},third-name", nil, admin_header
expect(last_response).to have_status_code(200)
expect(parsed_response['resources'].length).to eq(1)
expect(parsed_response['resources'][0]['guid']).to eq(space_quota.guid)
end
it 'returns the list of quotas filtered by org guids' do
get "/v3/space_quotas?organization_guids=#{org_alien.guid}", nil, admin_header
expect(last_response).to have_status_code(200)
expect(
parsed_response['resources'].pluck('guid')
).to eq([space_quota_alien.guid])
end
it 'returns the list of quotas filtered by space guids' do
get "/v3/space_quotas?space_guids=#{space.guid},#{space_2.guid}", nil, admin_header
expect(last_response).to have_status_code(200)
expect(
parsed_response['resources'].pluck('guid')
).to eq([space_quota.guid, space_quota_2.guid])
end
end
context 'when the user is not logged in' do
it 'returns 401 for Unauthenticated requests' do
get '/v3/space_quotas', nil, base_json_headers
expect(last_response).to have_status_code(401)
end
end
context 'when the quota is applied to spaces that are not visible to the user' do
let!(:other_space) do
VCAP::CloudController::Space.make(
guid: 'other-space-guid',
organization: org,
space_quota_definition: space_quota
)
end
let(:expected_response) { make_space_quota_json(space_quota, [space]) }
it 'only shows the guids of spaces that the user has permissions to see' do
space_manager_header = set_user_with_header_as_role(role: 'space_manager', org: org, space: space, user: user)
get '/v3/space_quotas', nil, space_manager_header
expect(last_response).to have_status_code(200)
expect(parsed_response['resources'][0]).to match_json_response(expected_response)
end
end
end
describe 'POST /v3/space_quotas' do
let(:api_call) { ->(user_headers) { post '/v3/space_quotas', params.to_json, user_headers } }
let(:params) do
{
name: 'quota1',
relationships: {
organization: {
data: { guid: org.guid }
}
}
}
end
context 'specifying only the required params' do
let(:space_quota_json) do
{
guid: UUID_REGEX,
created_at: iso8601,
updated_at: iso8601,
name: params[:name],
apps: {
total_memory_in_mb: nil,
per_process_memory_in_mb: nil,
total_instances: nil,
per_app_tasks: nil,
log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
total_service_instances: nil,
total_service_keys: nil
},
routes: {
total_routes: nil,
total_reserved_ports: nil
},
relationships: {
organization: {
data: { guid: org.guid }
},
spaces: {
data: []
}
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/space_quotas/#{params[:guid]}} },
organization: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{org.guid}} }
}
}
end
let(:expected_codes_and_responses) do
h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
h['admin'] = {
code: 201,
response_object: space_quota_json
}
h['org_manager'] = {
code: 201,
response_object: space_quota_json
}
h
end
it 'creates a space_quota' do
expect do
api_call.call(admin_header)
end.to change(SpaceQuotaDefinition, :count).by 1
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
context 'when organization is suspended' do
let(:expected_codes_and_responses) do
h = super()
h['org_manager'] = { code: 403, errors: CF_ORG_SUSPENDED }
h
end
before do
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end
context 'passing empty limit objects' do
let(:params) do
{
name: 'quota1',
apps: {},
services: {},
routes: {},
relationships: {
organization: {
data: { guid: org.guid }
}
}
}
end
let(:space_quota_json) do
{
guid: UUID_REGEX,
created_at: iso8601,
updated_at: iso8601,
name: params[:name],
apps: {
total_memory_in_mb: nil,
per_process_memory_in_mb: nil,
total_instances: nil,
per_app_tasks: nil,
log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
total_service_instances: nil,
total_service_keys: nil
},
routes: {
total_routes: nil,
total_reserved_ports: nil
},
relationships: {
organization: {
data: { guid: org.guid }
},
spaces: {
data: []
}
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/space_quotas/#{params[:guid]}} },
organization: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{org.guid}} }
}
}
end
let(:expected_codes_and_responses) do
h = Hash.new(
code: 403
)
h['admin'] = {
code: 201,
response_object: space_quota_json
}
h['org_manager'] = {
code: 201,
response_object: space_quota_json
}
h
end
it 'creates a space_quota' do
expect do
api_call.call(admin_header)
end.to change(SpaceQuotaDefinition, :count).by 1
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
context 'specifying all possible params' do
let(:params) do
{
name: 'my-space-quota',
apps: {
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
per_app_tasks: 5,
log_rate_limit_in_bytes_per_second: 3000
},
services: {
paid_services_allowed: false,
total_service_instances: 11,
total_service_keys: 12
},
routes: {
total_routes: 47,
total_reserved_ports: 2
},
relationships: {
organization: {
data: { guid: org.guid }
},
spaces: {
data: [
{ guid: space.guid }
]
}
}
}
end
let(:expected_response) do
{
guid: UUID_REGEX,
created_at: iso8601,
updated_at: iso8601,
name: 'my-space-quota',
apps: {
total_memory_in_mb: 5120,
per_process_memory_in_mb: 1024,
total_instances: 10,
per_app_tasks: 5,
log_rate_limit_in_bytes_per_second: 3000
},
services: {
paid_services_allowed: false,
total_service_instances: 11,
total_service_keys: 12
},
routes: {
total_routes: 47,
total_reserved_ports: 2
},
relationships: {
organization: {
data: {
guid: org.guid
}
},
spaces: {
data: [
{ guid: space.guid }
]
}
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/space_quotas/#{params[:guid]}} },
organization: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{org.guid}} }
}
}
end
it 'creates a space quota with the requested limits' do
api_call.call(admin_header)
expect(last_response).to have_status_code(201)
expect(parsed_response).to match_json_response(expected_response)
end
end
context 'when the org guid is invalid' do
let(:params) do
{
name: 'quota-with-bad-org',
relationships: {
organization: {
data: { guid: 'not-real' }
}
}
}
end
it 'returns 422' do
post '/v3/space_quotas', params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message('Organization with guid \'not-real\' does not exist, or you do not have access to it.')
end
end
context 'when the space guid is invalid' do
let(:params) do
{
name: 'quota-with-bad-space',
relationships: {
organization: {
data: { guid: org.guid }
},
spaces: {
data: [
{ guid: 'not-real' }
]
}
}
}
end
it 'returns 422' do
post '/v3/space_quotas', params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message('Spaces with guids ["not-real"] do not exist within the organization specified, or you do not have access to them.')
end
end
context 'when the user is not logged in' do
it 'returns 401 for Unauthenticated requests' do
post '/v3/space_quotas', params.to_json, base_json_headers
expect(last_response).to have_status_code(401)
end
end
context 'when the params are invalid' do
let(:headers) { set_user_with_header_as_role(role: 'admin') }
context 'when provided invalid arguments' do
let(:params) do
{
name: 555
}
end
it 'returns 422' do
post '/v3/space_quotas', params.to_json, headers
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message('Name must be a string')
end
end
context 'with a pre-existing name' do
let(:params) do
{
name: 'double-trouble',
relationships: {
organization: {
data: { guid: org.guid }
}
}
}
end
it 'returns 422' do
post '/v3/space_quotas', params.to_json, headers
post '/v3/space_quotas', params.to_json, headers
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message("Space Quota 'double-trouble' already exists.")
end
end
end
end
describe 'POST /v3/space_quotas/:guid/relationships/spaces' do
let(:api_call) { ->(user_headers) { post "/v3/space_quotas/#{space_quota.guid}/relationships/spaces", params.to_json, user_headers } }
let(:other_space) { VCAP::CloudController::Space.make(organization: org, guid: 'other-space-guid') }
let(:params) do
{
data: [{ guid: other_space.guid }]
}
end
context 'when applying quota to a space' do
let(:data_json) do
{
data: a_collection_containing_exactly(
{ guid: space.guid },
{ guid: other_space.guid }
),
links: {
self: { href: "#{link_prefix}/v3/space_quotas/#{space_quota.guid}/relationships/spaces" }
}
}
end
let(:expected_codes_and_responses) do
h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
h['admin'] = { code: 200, response_object: data_json }
h['org_manager'] = { code: 200, response_object: data_json }
h['org_auditor'] = { code: 404 }
h['org_billing_manager'] = { code: 404 }
h['no_role'] = { code: 404 }
h
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
context 'when organization is suspended' do
let(:expected_codes_and_responses) do
h = super()
h['org_manager'] = { code: 403, errors: CF_ORG_SUSPENDED }
h
end
before do
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end
context 'when a space does not exist' do
let(:params) do
{
data: [
{ guid: 'not a real guid' }
]
}
end
it 'returns a 422 with a helpful message' do
post "/v3/space_quotas/#{space_quota.guid}/relationships/spaces", params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to have_error_message('Spaces with guids ["not a real guid"] do not exist, or you do not have access to them.')
end
end
context 'when a guid in the request body is the wrong type' do
let(:bad_params) do
{
data: [
{ guid: space.guid },
{ guid: 6 }
]
}
end
it 'returns a helpful error message' do
post "/v3/space_quotas/#{space_quota.guid}/relationships/spaces", bad_params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(parsed_response['errors'][0]['detail']).to eq('Invalid data type: Data[1] guid should be a string.')
end
end
context 'when the quota has a finite log rate limit and there are apps with unlimited log rates' do
let(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'space-quota-guid', organization: org, log_rate_limit: 100) }
let!(:other_space) { VCAP::CloudController::Space.make(guid: 'other-space-guid', organization: org, space_quota_definition: space_quota) }
let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: other_space) }
let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) }
it 'returns 422' do
post "/v3/space_quotas/#{space_quota.guid}/relationships/spaces", params.to_json, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message(
'Current usage exceeds new quota values. The space(s) being assigned this quota contain apps running with an unlimited log rate limit.'
)
end
end
end
describe 'DELETE /v3/space_quotas/:guid/relationships/spaces' do
let(:api_call) { ->(user_headers) { delete "/v3/space_quotas/#{space_quota.guid}/relationships/spaces/#{space.guid}", {}, user_headers } }
context 'when removing a space quota from a space' do
let(:expected_codes_and_responses) do
h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
h['admin'] = { code: 204 }
h['org_manager'] = { code: 204 }
h['org_auditor'] = { code: 404 }
h['org_billing_manager'] = { code: 404 }
h['no_role'] = { code: 404 }
h
end
let(:db_check) do
lambda do
expect(space_quota.reload.spaces.count).to eq(0)
expect(space.reload.space_quota_definition_guid).to be_nil
end
end
it_behaves_like 'permissions for delete endpoint', ALL_PERMISSIONS
context 'when organization is suspended' do
let(:expected_codes_and_responses) do
h = super()
h['org_manager'] = { code: 403, errors: CF_ORG_SUSPENDED }
h
end
before do
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end
context 'when the space does not exist' do
let(:fake_space_guid) { 'does-not-exist' }
it 'returns a helpful error message' do
delete "/v3/space_quotas/#{space_quota.guid}/relationships/spaces/#{fake_space_guid}", {}, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message("Unable to remove quota from space with guid 'does-not-exist'. Ensure the space quota is applied to this space.")
end
end
context 'when the space is not associated with the quota' do
let(:other_space) { VCAP::CloudController::Space.make(guid: 'not-related-space') }
it 'returns a helpful error message' do
delete "/v3/space_quotas/#{space_quota.guid}/relationships/spaces/#{other_space.guid}", {}, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to include_error_message("Unable to remove quota from space with guid 'not-related-space'. Ensure the space quota is applied to this space.")
end
end
end
describe 'DELETE /v3/space_quotas/:guid' do
context 'when deleting a space quota that is not applied to any spaces' do
let(:api_call) { ->(user_headers) { delete "/v3/space_quotas/#{unapplied_space_quota.guid}", {}, user_headers } }
let!(:unapplied_space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, guid: 'unapplied-space-quota') }
let(:expected_codes_and_responses) do
h = Hash.new(code: 404)
h['admin'] = { code: 202 }
h['org_manager'] = { code: 202 }
%w[admin_read_only global_auditor].each { |r| h[r] = { code: 403, errors: CF_NOT_AUTHORIZED } }
h
end
let(:db_check) do
lambda do
last_job = VCAP::CloudController::PollableJobModel.last
expect(last_response.headers['Location']).to match(%r{/v3/jobs/#{last_job.guid}})
expect(last_job.resource_type).to eq('space_quota')
get "/v3/jobs/#{last_job.guid}", nil, admin_header
expect(last_response).to have_status_code(200)
expect(parsed_response['operation']).to eq('space_quota.delete')
expect(parsed_response['links']['space_quota']['href']).to match(%r{/v3/space_quotas/#{unapplied_space_quota.guid}})
execute_all_jobs(expected_successes: 1, expected_failures: 0)
get "/v3/space_quotas/#{unapplied_space_quota.guid}", nil, admin_header
expect(last_response).to have_status_code(404)
end
end
it_behaves_like 'permissions for delete endpoint', ALL_PERMISSIONS
context 'when organization is suspended' do
let(:expected_codes_and_responses) do
h = super()
h['org_manager'] = { code: 403, errors: CF_ORG_SUSPENDED }
h
end
before do
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
end
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end
context 'when the space quota does not exist' do
let(:fake_space_quota_guid) { 'does-not-exist' }
it 'returns a 404 with a helpful error message' do
delete "/v3/space_quotas/#{fake_space_quota_guid}", {}, admin_header
expect(last_response).to have_status_code(404)
expect(last_response).to have_error_message('Space quota not found')
end
end
context 'when the space quota is still applied to a space' do
let!(:space) { VCAP::CloudController::Space.make(space_quota_definition: space_quota, organization: org) }
let(:api_call) { ->(user_headers) { delete "/v3/space_quotas/#{space_quota.guid}", {}, user_headers } }
let(:expected_codes_and_responses) do
h = Hash.new(code: 403)
h['admin'] = { code: 422 }
h['org_manager'] = { code: 422 }
h['org_auditor'] = { code: 404 }
h['org_billing_manager'] = { code: 404 }
h['no_role'] = { code: 404 }
h
end
let(:db_check) do
lambda do
get "/v3/space_quotas/#{space_quota.guid}", nil, admin_header
expect(last_response).to have_status_code(200)
end
end
it_behaves_like 'permissions for delete endpoint', ALL_PERMISSIONS
context 'when the user has sufficient permissions to delete a space quota' do
it 'returns a 422 with a helpful error message' do
delete "/v3/space_quotas/#{space_quota.guid}", {}, admin_header
expect(last_response).to have_status_code(422)
expect(last_response).to have_error_message('This quota is applied to one or more spaces. Remove this quota from all spaces before deleting.')
end
end
end
end
def make_space_quota_json(space_quota, associated_spaces=space_quota.spaces)
{
guid: space_quota.guid,
created_at: iso8601,
updated_at: iso8601,
name: space_quota.name,
apps: {
total_memory_in_mb: 20_480,
per_process_memory_in_mb: nil,
total_instances: nil,
per_app_tasks: 5,
log_rate_limit_in_bytes_per_second: nil
},
services: {
paid_services_allowed: true,
total_service_instances: 60,
total_service_keys: 600
},
routes: {
total_routes: 1000,
total_reserved_ports: nil
},
relationships: {
organization: {
data: { guid: space_quota.organization.guid }
},
spaces: {
data: associated_spaces.map { |space| { guid: space.guid } }
}
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/space_quotas/#{space_quota.guid}} },
organization: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{space_quota.organization.guid}} }
}
}
end
end
end