From 03b9cd3a03685a4620e87a3f1bdfb64c49eba9fc Mon Sep 17 00:00:00 2001 From: Alexander Kislitsky Date: Fri, 12 Aug 2016 18:45:45 +0300 Subject: [PATCH] CRUD operations implemented for resource_definition Change-Id: I22bc4c7ab0d6acb34f3dd6ed10041a582df8d03b --- tuning_box/app.py | 9 ++ tuning_box/library/components.py | 13 +- tuning_box/library/resource_definitions.py | 89 ++++++++++ .../library/test_resource_definitions.py | 152 ++++++++++++++++++ 4 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 tuning_box/library/resource_definitions.py create mode 100644 tuning_box/tests/library/test_resource_definitions.py diff --git a/tuning_box/app.py b/tuning_box/app.py index 0762aef..0ccac5f 100644 --- a/tuning_box/app.py +++ b/tuning_box/app.py @@ -23,6 +23,7 @@ from tuning_box import db from tuning_box import errors from tuning_box.library import components from tuning_box.library import environments +from tuning_box.library import resource_definitions from tuning_box import logger from tuning_box.middleware import keystone @@ -36,6 +37,14 @@ api = flask_restful.Api(errors=api_errors) api.add_resource(components.ComponentsCollection, '/components') api.add_resource(components.Component, '/components/') +api.add_resource( + resource_definitions.ResourceDefinitionsCollection, + '/resource_definitions', +) +api.add_resource( + resource_definitions.ResourceDefinition, + '/resource_definition/' +) api.add_resource(environments.EnvironmentsCollection, '/environments') api.add_resource( environments.Environment, diff --git a/tuning_box/library/components.py b/tuning_box/library/components.py index ec42aa4..1adf89a 100644 --- a/tuning_box/library/components.py +++ b/tuning_box/library/components.py @@ -17,19 +17,14 @@ from flask_restful import fields from tuning_box import db from tuning_box import library - -resource_definition_fields = { - 'id': fields.Integer, - 'name': fields.String, - 'component_id': fields.Integer, - 'content': fields.Raw, -} +from tuning_box.library import resource_definitions component_fields = { 'id': fields.Integer, 'name': fields.String, - 'resource_definitions': fields.List( - fields.Nested(resource_definition_fields)), + 'resource_definitions': fields.List(fields.Nested( + resource_definitions.resource_definition_fields + )) } diff --git a/tuning_box/library/resource_definitions.py b/tuning_box/library/resource_definitions.py new file mode 100644 index 0000000..bb48d66 --- /dev/null +++ b/tuning_box/library/resource_definitions.py @@ -0,0 +1,89 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import flask +import flask_restful +from flask_restful import fields + +from tuning_box import db + +resource_definition_fields = { + 'id': fields.Integer, + 'name': fields.String, + 'component_id': fields.Integer(default=None), + 'content': fields.Raw, +} + + +class ResourceDefinitionsCollection(flask_restful.Resource): + method_decorators = [ + flask_restful.marshal_with(resource_definition_fields) + ] + + def get(self): + query = db.ResourceDefinition.query + if 'component_id' in flask.request.args: + component_id = flask.request.args.get('component_id') + component_id = component_id or None + query = query.filter( + db.ResourceDefinition.component_id == component_id + ) + return query.all() + + @db.with_transaction + def post(self): + data = dict() + for field_name in resource_definition_fields.keys(): + data[field_name] = flask.request.json.get(field_name, None) + resource_definition = db.ResourceDefinition(**data) + db.db.session.add(resource_definition) + return resource_definition, 201 + + +class ResourceDefinition(flask_restful.Resource): + method_decorators = [ + flask_restful.marshal_with(resource_definition_fields)] + + def get(self, resource_definition_id): + return db.ResourceDefinition.query.get_or_404(resource_definition_id) + + @db.with_transaction + def _perform_update(self, resource_definition_id): + res_definition = db.ResourceDefinition.query.get_or_404( + resource_definition_id) + update_by = flask.request.json + skip_fields = ('id', ) + + for field_name in resource_definition_fields.keys(): + + if field_name in skip_fields: + continue + if field_name in update_by: + setattr( + res_definition, field_name, + update_by.get(field_name) + ) + + def put(self, resource_definition_id): + return self.patch(resource_definition_id) + + def patch(self, resource_definition_id): + self._perform_update(resource_definition_id) + return None, 204 + + @db.with_transaction + def delete(self, resource_definition_id): + res_definition = db.ResourceDefinition.query.get_or_404( + resource_definition_id) + db.db.session.delete(res_definition) + return None, 204 diff --git a/tuning_box/tests/library/test_resource_definitions.py b/tuning_box/tests/library/test_resource_definitions.py new file mode 100644 index 0000000..7a49847 --- /dev/null +++ b/tuning_box/tests/library/test_resource_definitions.py @@ -0,0 +1,152 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tuning_box import db +from tuning_box.library import resource_definitions +from tuning_box.tests.test_app import BaseTest + + +class TestResourceDefinitions(BaseTest): + + collection_url = '/resource_definitions' + object_url = '/resource_definition/{0}' + + @property + def _resource_json(self): + return { + 'id': 5, + 'name': 'resdef1', + 'component_id': 7, + 'content': {'key': 'nsname.key'}, + } + + def test_post_resource_definition(self): + data = self._resource_json + data['component_id'] = None + + res = self.client.post(self.collection_url, data=data) + self.assertEqual(201, res.status_code) + data['id'] = res.json['id'] + + self.assertEqual(data, res.json) + self._assert_db_effect( + db.ResourceDefinition, + res.json['id'], + resource_definitions.resource_definition_fields, + data + ) + + def test_get_resource_definitions_empty(self): + res = self.client.get(self.collection_url) + self.assertEqual(res.status_code, 200) + self.assertEqual(res.json, []) + + def test_get_definitions(self): + self._fixture() + res = self.client.get(self.collection_url) + self.assertEqual(200, res.status_code) + self.assertEqual(1, len(res.json)) + self.assertItemsEqual(self._resource_json, res.json[0]) + + def test_get_definitions_filtration(self): + self._fixture() + + resource_data = { + 'name': 'resdef2', + 'content': {'key': 'service.key'}, + } + + res = self.client.post(self.collection_url, data=resource_data) + self.assertEqual(201, res.status_code) + resource_data = res.json + + component_id = self._resource_json['component_id'] + res = self.client.get(self.collection_url, + query_string={'component_id': component_id}) + self.assertEqual(200, res.status_code) + self.assertNotIn(resource_data['id'], (d['id'] for d in res.json)) + + res = self.client.get(self.collection_url + '?component_id=') + self.assertEqual(200, res.status_code) + self.assertFalse(any(d['component_id'] for d in res.json)) + self.assertIn(resource_data['id'], (d['id'] for d in res.json)) + + res = self.client.get(self.collection_url) + self.assertEqual(200, res.status_code) + self.assertIn(resource_data['id'], (d['id'] for d in res.json)) + self.assertIn(self._resource_json['id'], (d['id'] for d in res.json)) + + def test_get_one_resource_definition(self): + self._fixture() + res_id = self._resource_json['id'] + res = self.client.get(self.object_url.format(res_id)) + self.assertEqual(200, res.status_code) + self.assertItemsEqual(self._resource_json, res.json) + + def test_get_one_resource_definition_404(self): + res_id = self._resource_json['id'] + res = self.client.get( + self.object_url.format(res_id)) + self.assertEqual(res.status_code, 404) + + def test_delete_resource_definition(self): + self._fixture() + res_id = self._resource_json['id'] + res = self.client.delete(self.object_url.format(res_id)) + self.assertEqual(res.status_code, 204) + self.assertEqual(res.data, b'') + self._assert_not_in_db(db.ResourceDefinition, res_id) + + def test_delete_resource_definition_404(self): + res_id = self._resource_json['id'] + res = self.client.delete(self.object_url.format(res_id)) + self.assertEqual(res.status_code, 404) + + def test_put_resource_definition_404(self): + res_id = self._resource_json['id'] + res = self.client.delete(self.object_url.format(res_id)) + self.assertEqual(res.status_code, 404) + + def test_put_resource_definition(self): + self._fixture() + res_id = self._resource_json['id'] + + data = self._resource_json + data['name'] = 'new_{0}'.format(data['name']) + data['component_id'] = None + data['content'] = {'x': 'y'} + + res = self.client.put(self.object_url.format(res_id), + data=data) + self.assertEqual(204, res.status_code) + actual_res_def = self.client.get(self.object_url.format(res_id)).json + self.assertItemsEqual(data, actual_res_def) + + # Restoring resource_definition values + res = self.client.put( + self.object_url.format(res_id), + data=self._resource_json + ) + self.assertEqual(204, res.status_code) + actual_res_def = self.client.get(self.object_url.format(res_id)).json + self.assertItemsEqual(self._resource_json, actual_res_def) + + def test_put_resource_definition_ignore_changing_id(self): + self._fixture() + res_id = self._resource_json['id'] + + data = self._resource_json + data['id'] = None + res = self.client.put(self.object_url.format(res_id), data=data) + self.assertEqual(204, res.status_code) + actual_res_def = self.client.get(self.object_url.format(res_id)).json + self.assertItemsEqual(self._resource_json, actual_res_def)