Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic Object Definition CRUD #15

Merged
merged 3 commits into from
Aug 31, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions app/controllers/api/generic_object_definitions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Api
class GenericObjectDefinitionsController < BaseController
def show
object = fetch_generic_object_definition(@req.c_id)
render_resource(:generic_object_definitions, object)
end

def create_resource(_type, _id, data)
klass = collection_class(:generic_object_definitions)
klass.create!(data.deep_symbolize_keys)
rescue => err
raise BadRequestError, "Failed to create new generic object definition - #{err}"
end

def edit_resource(_type, id, data)
id ||= data['name']
go_def = fetch_generic_object_definition(id)
updated_data = data['resource'].try(:deep_symbolize_keys) || data.deep_symbolize_keys
go_def.update_attributes!(updated_data) if data.present?
go_def
rescue => err
raise BadRequestError, "Failed to update generic object definition - #{err}"
end

def delete_resource(_type, id, data = {})
id ||= data['name']
go_def = fetch_generic_object_definition(id)
go_def.destroy!
rescue => err
raise BadRequestError, "Failed to delete generic object definition - #{err}"
end

private

def fetch_generic_object_definition(id)
klass = collection_class(:generic_object_definitions)
go_def = klass.find_by(:name => id) || klass.find(id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably want logic separation, i.e. check if id is compressed_id? to do a klass.find otherwise do the find_by :name, (just in case someone named one as a numeric string).

go_def = Rbac.filtered_object(go_def, :user => User.current_user, :class => klass)
raise ForbiddenError, "Access to Generic Object Definition id: #{id} is forbidden" unless go_def
go_def
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add Rbac.filtered_object before returning it.

end
end
29 changes: 29 additions & 0 deletions config/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,35 @@
:get:
- :name: read
:identifier: floating_ip_show
:generic_object_definitions:
:description: Generic Object Definitions
:options:
- :collection
:verbs: *gpd
:klass: GenericObjectDefinition
:collection_actions:
:get:
- :name: read
:identifier: generic_object_definition_show_list
:post:
- :name: create
:identifier: generic_object_definition_new
- :name: edit
:identifier: generic_object_definition_edit
- :name: delete
:identifier: generic_object_definition_delete
:resource_actions:
:get:
- :name: read
:identifier: generic_object_definition_view
:post:
- :name: edit
:identifier: generic_object_definition_edit
- :name: delete
:identifier: generic_object_definition_delete
:delete:
- :name: delete
:identifier: generic_object_definition_delete
:groups:
:description: Groups
:identifier: rbac_group
Expand Down
312 changes: 312 additions & 0 deletions spec/requests/generic_object_definitions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
# rubocop:disable Style/WordArray
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling as I prefer that the sample requests not use word arrays to better align with how the request will look.

RSpec.describe 'GenericObjectDefinitions API' do
let(:object_def) { FactoryGirl.create(:generic_object_definition, :name => 'foo') }

describe 'GET /api/generic_object_definitions' do
it 'does not list object definitions without an appropriate role' do
api_basic_authorize

run_get(generic_object_definitions_url)

expect(response).to have_http_status(:forbidden)
end

it 'lists all generic object definitions with an appropriate role' do
api_basic_authorize collection_action_identifier(:generic_object_definitions, :read, :get)
object_def_href = generic_object_definitions_url(object_def.compressed_id)

run_get(generic_object_definitions_url)

expected = {
'count' => 1,
'subcount' => 1,
'name' => 'generic_object_definitions',
'resources' => [
hash_including('href' => a_string_matching(object_def_href))
]
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end
end

describe 'GET /api/generic_object_definitions/:id' do
it 'does not let you query object definitions without an appropriate role' do
api_basic_authorize

run_get(generic_object_definitions_url(object_def.compressed_id))

expect(response).to have_http_status(:forbidden)
end

it 'can query an object definition by its id' do
api_basic_authorize action_identifier(:generic_object_definitions, :read, :resource_actions, :get)

run_get(generic_object_definitions_url(object_def.id))

expected = {
'id' => object_def.compressed_id,
'name' => object_def.name
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end

it 'can query an object definition by its name' do
api_basic_authorize action_identifier(:generic_object_definitions, :read, :resource_actions, :get)

run_get(generic_object_definitions_url(object_def.name))

expected = {
'id' => object_def.compressed_id,
'name' => object_def.name
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end

it 'raises a record not found error if no object definition is found' do
api_basic_authorize action_identifier(:generic_object_definitions, :read, :resource_actions, :get)

run_get(generic_object_definitions_url('bar'))

expect(response).to have_http_status(:not_found)
end
end

describe 'POST /api/generic_object_definitions' do
it 'can create a new generic_object_definition' do
api_basic_authorize collection_action_identifier(:generic_object_definitions, :create)

object_definition = {
'name' => 'LoadBalancer',
'description' => 'LoadBalancer description',
'properties' => {
'attributes' => {
'address' => 'string',
'last_restart' => 'datetime'
},
'associations' => {
'vms' => 'Vm',
'services' => 'Service'
},
'methods' => [
'add_vm',
'remove_vm'
]
}
}
run_post(generic_object_definitions_url, object_definition)

expect(response).to have_http_status(:ok)
expect(response.parsed_body['results'].first).to include(object_definition)
end

it 'cannot create an invalid generic_object_definition' do
api_basic_authorize collection_action_identifier(:generic_object_definitions, :create)

request = {
'name' => 'foo',
'description' => 'LoadBalancer description',
'properties' => {
'attributes' => {
'last_restart' => 'date'
}
}
}
run_post(generic_object_definitions_url, request)

expected = {
'error' => a_hash_including(
'kind' => 'bad_request',
'message' => a_string_including('Failed to create new generic object definition - Validation failed')
)
}
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to include(expected)
end

it 'can edit generic_object_definitions by id, name, or href' do
object_def2 = FactoryGirl.create(:generic_object_definition, :name => 'foo 2')
object_def3 = FactoryGirl.create(:generic_object_definition, :name => 'foo 3')
api_basic_authorize collection_action_identifier(:generic_object_definitions, :edit)

request = {
'action' => 'edit',
'resources' => [
{ 'name' => object_def.name, 'resource' => { 'name' => 'updated 1' } },
{ 'id' => object_def2.compressed_id, 'resource' => { 'name' => 'updated 2' }},
{ 'href' => generic_object_definitions_url(object_def3.compressed_id), 'resource' => { 'name' => 'updated 3' }}
]
}
run_post(generic_object_definitions_url, request)

expected = {
'results' => a_collection_including(
a_hash_including('id' => object_def.compressed_id, 'name' => 'updated 1'),
a_hash_including('id' => object_def2.compressed_id, 'name' => 'updated 2'),
a_hash_including('id' => object_def3.compressed_id, 'name' => 'updated 3')
)
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end
end

describe 'POST /api/generic_object_definitions/:id' do
it 'can update an object definition by name' do
api_basic_authorize action_identifier(:generic_object_definitions, :edit)

request = {
'action' => 'edit',
'name' => 'LoadBalancer Updated',
'description' => 'LoadBalancer description Updated',
'properties' => {
'attributes' => {
'last_updated' => 'string',
},
'associations' => {
'vms' => 'Vm',
'services' => 'Service'
},
'methods' => [
'add_vm',
'remove_vm',
'new_method'
]
}
}
run_post(generic_object_definitions_url(object_def.name), request)

expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(request.except('action'))
end

it 'can update an object definition by id' do
api_basic_authorize action_identifier(:generic_object_definitions, :edit)

request = {
'action' => 'edit',
'name' => 'LoadBalancer Updated',
'description' => 'LoadBalancer description Updated',
'properties' => {
'attributes' => {
'last_updated' => 'string',
},
'associations' => {
'vms' => 'Vm',
'services' => 'Service'
},
'methods' => [
'add_vm',
'remove_vm',
'new_method'
]
}
}
run_post(generic_object_definitions_url(object_def.compressed_id), request)

expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(request.except('action'))
end

it 'cannot update an object with bad data' do
api_basic_authorize action_identifier(:generic_object_definitions, :edit)

request = {
'action' => 'edit',
'properties' => {
'attributes' => {
'last_updated' => 'date',
}
}
}
run_post(generic_object_definitions_url(object_def.compressed_id), request)

expected = {
'error' => a_hash_including(
'kind' => 'bad_request',
'message' => a_string_including('Failed to update generic object definition - Validation failed')
)
}
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to include(expected)
end

it 'can delete an object definition by name' do
api_basic_authorize action_identifier(:generic_object_definitions, :delete)

run_post(generic_object_definitions_url(object_def.name), :action => 'delete')

expect(response).to have_http_status(:ok)
end

it 'can delete an object definition by id' do
api_basic_authorize action_identifier(:generic_object_definitions, :delete)

run_post(generic_object_definitions_url(object_def.compressed_id), :action => 'delete')

expect(response).to have_http_status(:ok)
end

it 'will not delete a generic_object_definition if it is in use' do
api_basic_authorize action_identifier(:generic_object_definitions, :delete, :resource_actions, :delete)
object_def.create_object(:name => 'foo object')

run_post(generic_object_definitions_url(object_def.name), :action => 'delete')

expected = {
'error' => a_hash_including(
'kind' => 'bad_request',
'message' => a_string_including('Failed to delete generic object definition')
)
}
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to include(expected)
end

it 'can delete generic_object_definition in bulk by name, id, or href' do
object_def2 = FactoryGirl.create(:generic_object_definition, :name => 'foo 2')
object_def3 = FactoryGirl.create(:generic_object_definition, :name => 'foo 3')
api_basic_authorize collection_action_identifier(:generic_object_definitions, :delete)

request = {
'action' => 'delete',
'resources' => [
{ 'name' => object_def.name },
{ 'id' => object_def2.compressed_id},
{ 'href' => generic_object_definitions_url(object_def3.compressed_id)}
]
}
run_post(generic_object_definitions_url, request)

expected = {
'results' => a_collection_including(
a_hash_including('id' => object_def.compressed_id),
a_hash_including('id' => object_def2.compressed_id),
a_hash_including('id' => object_def3.compressed_id)
)
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end
end

describe 'DELETE /api/generic_object_definitions/:id' do
it 'can delete a generic_object_definition by id' do
api_basic_authorize action_identifier(:generic_object_definitions, :delete, :resource_actions, :delete)

run_delete(generic_object_definitions_url(object_def.compressed_id))

expect(response).to have_http_status(:no_content)
end

it 'can delete a generic_object_definition by name' do
api_basic_authorize action_identifier(:generic_object_definitions, :delete, :resource_actions, :delete)

run_delete(generic_object_definitions_url(object_def.name))

expect(response).to have_http_status(:no_content)
end
end
end