Put and patch methods implemented for components

Components implementation moved to separate module.
Tests for components decoupled from test_app.

Change-Id: I9a653f3211708ab071b146ba6cf8527b29a698f1
This commit is contained in:
Alexander Kislitsky 2016-08-08 17:35:27 +03:00
parent daa9536cd7
commit 042223382b
7 changed files with 276 additions and 143 deletions

View File

@ -21,6 +21,7 @@ from werkzeug import exceptions
from tuning_box import converters
from tuning_box import db
from tuning_box.library import components
from tuning_box import logger
from tuning_box.middleware import keystone
@ -30,19 +31,8 @@ api_errors = {
}
api = flask_restful.Api(errors=api_errors)
resource_definition_fields = {
'id': fields.Integer,
'name': fields.String,
'component_id': fields.Integer,
'content': fields.Raw,
}
component_fields = {
'id': fields.Integer,
'name': fields.String,
'resource_definitions': fields.List(
fields.Nested(resource_definition_fields)),
}
api.add_resource(components.ComponentsCollection, '/components')
api.add_resource(components.Component, '/components/<int:component_id>')
def with_transaction(f):
@ -54,38 +44,6 @@ def with_transaction(f):
return inner
@api.resource('/components')
class ComponentsCollection(flask_restful.Resource):
method_decorators = [flask_restful.marshal_with(component_fields)]
def get(self):
return db.Component.query.all()
@with_transaction
def post(self):
component = db.Component(name=flask.request.json['name'])
component.resource_definitions = []
for resdef_data in flask.request.json.get('resource_definitions'):
resdef = db.ResourceDefinition(name=resdef_data['name'],
content=resdef_data.get('content'))
component.resource_definitions.append(resdef)
db.db.session.add(component)
return component, 201
@api.resource('/components/<int:component_id>')
class Component(flask_restful.Resource):
method_decorators = [flask_restful.marshal_with(component_fields)]
def get(self, component_id):
return db.Component.query.get_or_404(component_id)
@with_transaction
def delete(self, component_id):
component = db.Component.query.get_or_404(component_id)
db.db.session.delete(component)
return None, 204
environment_fields = {
'id': fields.Integer,
'components': fields.List(fields.Integer(attribute='id')),

View File

@ -31,6 +31,15 @@ pk_type = db.Integer
pk = functools.partial(db.Column, pk_type, primary_key=True)
def with_transaction(f):
@functools.wraps(f)
def inner(*args, **kwargs):
with db.session.begin():
return f(*args, **kwargs)
return inner
def fk(cls, **kwargs):
return db.Column(pk_type, db.ForeignKey(cls.id), **kwargs)

View File

View File

@ -0,0 +1,88 @@
# 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,
'content': fields.Raw,
}
component_fields = {
'id': fields.Integer,
'name': fields.String,
'resource_definitions': fields.List(
fields.Nested(resource_definition_fields)),
}
class ComponentsCollection(flask_restful.Resource):
method_decorators = [flask_restful.marshal_with(component_fields)]
def get(self):
return db.Component.query.all()
@db.with_transaction
def post(self):
component = db.Component(name=flask.request.json['name'])
component.resource_definitions = []
for resdef_data in flask.request.json.get('resource_definitions'):
resdef = db.ResourceDefinition(name=resdef_data['name'],
content=resdef_data.get('content'))
component.resource_definitions.append(resdef)
db.db.session.add(component)
return component, 201
class Component(flask_restful.Resource):
method_decorators = [flask_restful.marshal_with(component_fields)]
def get(self, component_id):
return db.Component.query.get_or_404(component_id)
@db.with_transaction
def _perform_update(self, component_id):
component = db.Component.query.get_or_404(component_id)
update_by = flask.request.json
component.name = update_by.get('name', component.name)
resource_definitions = update_by.get('resource_definitions')
if resource_definitions is not None:
resources = []
for resource_data in resource_definitions:
resource = db.ResourceDefinition.query.filter_by(
id=resource_data.get('id')
).one()
resource.component_id = component.id
db.db.session.add(resource)
resources.append(resource)
component.resource_definitions = resources
def put(self, component_id):
return self.patch(component_id)
def patch(self, component_id):
self._perform_update(component_id)
return None, 204
@db.with_transaction
def delete(self, component_id):
component = db.Component.query.get_or_404(component_id)
db.db.session.delete(component)
return None, 204

View File

View File

@ -0,0 +1,172 @@
# 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 components
from tuning_box.tests.test_app import BaseTest
class TestComponents(BaseTest):
@property
def _component_json(self):
return {
'id': 7,
'name': 'component1',
'resource_definitions': [{
'id': 5,
'name': 'resdef1',
'component_id': 7,
'content': {'key': 'nsname.key'},
}],
}
def test_get_components_empty(self):
res = self.client.get('/components')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, [])
def test_get_components(self):
self._fixture()
res = self.client.get('/components')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, [self._component_json])
def test_get_one_component(self):
self._fixture()
res = self.client.get('/components/7')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, self._component_json)
def test_get_one_component_404(self):
res = self.client.get('/components/7')
self.assertEqual(res.status_code, 404)
def test_post_component(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
json['name'] = 'component2'
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 201)
json['id'] = 8
json['resource_definitions'][0]['component_id'] = json['id']
json['resource_definitions'][0]['id'] = 6
self.assertEqual(res.json, json)
self._assert_db_effect(db.Component, 8, components.component_fields,
json)
def test_post_component_conflict(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 409)
self._assert_not_in_db(db.Component, 8)
def test_post_component_conflict_propagate_exc(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 409)
self._assert_not_in_db(db.Component, 8)
def test_post_component_no_resdef_content(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
del json['resource_definitions'][0]['content']
json['name'] = 'component2'
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 201)
json['id'] = 8
json['resource_definitions'][0]['component_id'] = json['id']
json['resource_definitions'][0]['id'] = 6
json['resource_definitions'][0]['content'] = None
self.assertEqual(res.json, json)
self._assert_db_effect(db.Component, 8, components.component_fields,
json)
def test_delete_component(self):
self._fixture()
res = self.client.delete('/components/7')
self.assertEqual(res.status_code, 204)
self.assertEqual(res.data, b'')
self._assert_not_in_db(db.Component, 7)
def test_delete_component_404(self):
res = self.client.delete('/components/7')
self.assertEqual(res.status_code, 404)
def test_put_component_404(self):
res = self.client.put('/components/7')
self.assertEqual(res.status_code, 404)
def test_put_component(self):
self._fixture()
component_url = '/components/7'
initial_data = self._component_json
new_name = 'new_{0}'.format(initial_data['name'])
# Updating name
res = self.client.put(component_url, data={'name': new_name})
self.assertEqual(res.status_code, 204)
actual_component = self.client.get(component_url).json
self.assertEqual(new_name, actual_component['name'])
self.assertEqual(initial_data['resource_definitions'],
actual_component['resource_definitions'])
# Updating resource_definitions
res = self.client.put(component_url,
data={'resource_definitions': []})
self.assertEqual(res.status_code, 204)
actual_component = self.client.get(component_url).json
self.assertEqual([], actual_component['resource_definitions'])
# Restoring resource_definitions and name
res = self.client.put(
component_url,
data={'name': initial_data['name'],
'resource_definitions': initial_data['resource_definitions']}
)
self.assertEqual(res.status_code, 204)
actual_component = self.client.get(component_url).json
self.assertEqual(initial_data['name'],
actual_component['name'])
self.assertEqual(initial_data['resource_definitions'],
actual_component['resource_definitions'])
def test_put_component_ignore_changing_id(self):
self._fixture()
component_url = '/components/7'
initial_data = self._component_json
new_name = 'new_{0}'.format(initial_data['name'])
res = self.client.put(component_url,
data={'name': new_name, 'id': None,
'fake': 'xxxx'})
self.assertEqual(res.status_code, 204)
actual_component = self.client.get(component_url).json
self.assertEqual(new_name, actual_component['name'])
self.assertEqual(initial_data['resource_definitions'],
actual_component['resource_definitions'])

View File

@ -40,9 +40,10 @@ class Client(testing.FlaskClient):
return super(Client, self).open(*args, **kwargs)
class TestApp(base.TestCase):
class BaseTest(base.TestCase):
def setUp(self):
super(TestApp, self).setUp()
super(BaseTest, self).setUp()
self.app = app.build_app(configure_logging=False,
with_keystone=False)
self.app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///'
@ -72,19 +73,6 @@ class TestApp(base.TestCase):
environment.hierarchy_levels = hierarchy_levels
db.db.session.add(environment)
@property
def _component_json(self):
return {
'id': 7,
'name': 'component1',
'resource_definitions': [{
'id': 5,
'name': 'resdef1',
'component_id': 7,
'content': {'key': 'nsname.key'},
}],
}
def _assert_db_effect(self, model, key, fields, expected):
with self.app.app_context():
obj = model.query.get(key)
@ -97,90 +85,8 @@ class TestApp(base.TestCase):
obj = model.query.get(key)
self.assertIsNone(obj)
def test_get_components_empty(self):
res = self.client.get('/components')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, [])
def test_get_components(self):
self._fixture()
res = self.client.get('/components')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, [self._component_json])
def test_get_one_component(self):
self._fixture()
res = self.client.get('/components/7')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json, self._component_json)
def test_get_one_component_404(self):
res = self.client.get('/components/7')
self.assertEqual(res.status_code, 404)
def test_post_component(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
json['name'] = 'component2'
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 201)
json['id'] = 8
json['resource_definitions'][0]['component_id'] = json['id']
json['resource_definitions'][0]['id'] = 6
self.assertEqual(res.json, json)
self._assert_db_effect(db.Component, 8, app.component_fields, json)
def test_post_component_conflict(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 409)
self._assert_not_in_db(db.Component, 8)
def test_post_component_conflict_propagate_exc(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 409)
self._assert_not_in_db(db.Component, 8)
def test_post_component_no_resdef_content(self):
self._fixture() # Just for namespace
json = self._component_json
del json['id']
del json['resource_definitions'][0]['id']
del json['resource_definitions'][0]['component_id']
del json['resource_definitions'][0]['content']
json['name'] = 'component2'
res = self.client.post('/components', data=json)
self.assertEqual(res.status_code, 201)
json['id'] = 8
json['resource_definitions'][0]['component_id'] = json['id']
json['resource_definitions'][0]['id'] = 6
json['resource_definitions'][0]['content'] = None
self.assertEqual(res.json, json)
self._assert_db_effect(db.Component, 8, app.component_fields, json)
def test_delete_component(self):
self._fixture()
res = self.client.delete('/components/7')
self.assertEqual(res.status_code, 204)
self.assertEqual(res.data, b'')
self._assert_not_in_db(db.Component, 7)
def test_delete_component_404(self):
res = self.client.delete('/components/7')
self.assertEqual(res.status_code, 404)
class TestApp(BaseTest):
def test_get_environments_empty(self):
res = self.client.get('/environments')