From a7b912fa3b48c9b94e7669cd385ec6c6ade86f7d Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 15:33:15 +0300 Subject: [PATCH 01/14] BackendMixin: allow specifying a backend class directly --- colibris/conf/backends.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/colibris/conf/backends.py b/colibris/conf/backends.py index 494ca16..1fb2c93 100644 --- a/colibris/conf/backends.py +++ b/colibris/conf/backends.py @@ -20,13 +20,18 @@ def configure(cls, settings): cls._instance = None try: - backend_path = settings.pop('backend') + backend = settings.pop('backend') except KeyError: return # Backend class not specified - cls._class = utils.import_member(backend_path) - logger.debug('%s: using class %s', cls.__name__, backend_path) + if isinstance(backend, str): + cls._class = utils.import_member(backend) + logger.debug('%s: using class %s', cls.__name__, backend) + + else: + cls._class = backend + logger.debug('%s: using class %s', cls.__name__, backend.__name__) @classmethod def get_instance(cls): From 6075c268467073cd9662c2e838b538d5a9ff4483 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 15:33:58 +0300 Subject: [PATCH 02/14] Unit tests: generalize view test http client fixtures --- tests/conftest.py | 1 + tests/unit/fixtures.py | 40 ++++++++++++++++++ tests/unit/views/test_api_view.py | 28 +++++-------- tests/unit/views/test_model_view.py | 63 +++++++++++++---------------- 4 files changed, 81 insertions(+), 51 deletions(-) create mode 100644 tests/unit/fixtures.py diff --git a/tests/conftest.py b/tests/conftest.py index 938d0e9..8805ce1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,2 +1,3 @@ from .functional.fixtures import * +from .unit.fixtures import * diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py new file mode 100644 index 0000000..4c02f22 --- /dev/null +++ b/tests/unit/fixtures.py @@ -0,0 +1,40 @@ + +from .permissions.fixtures import * + + +@pytest.fixture +def http_client_maker(aiohttp_client): + + def client(authentication_backend=None, authorization_backend=None, + routes=None, middlewares=None): + + if middlewares is None: + middlewares = [ + handle_errors_json, + handle_auth + ] + + app = web.Application(middlewares=middlewares) + routes = routes or [] + + for path, view in routes: + app.router.add_route('*', path, view) + + if not authentication_backend: + authentication_backend = 'colibris.authentication.null.NullBackend' + + if not authorization_backend: + authorization_backend = 'colibris.authorization.null.NullBackend' + + authentication.AuthenticationBackend.configure({ + 'backend': authentication_backend + }) + + if authorization_backend: + authorization.AuthorizationBackend.configure({ + 'backend': authorization_backend + }) + + return aiohttp_client(app) + + return client diff --git a/tests/unit/views/test_api_view.py b/tests/unit/views/test_api_view.py index 91af7a8..1423766 100644 --- a/tests/unit/views/test_api_view.py +++ b/tests/unit/views/test_api_view.py @@ -1,10 +1,10 @@ import pytest -from aiohttp import web, hdrs +from aiohttp import web from marshmallow import Schema, fields -from colibris.middleware.errors import handle_errors_json from colibris.views import APIView +from colibris.middleware.errors import handle_errors_json class ItemSchema(Schema): @@ -34,18 +34,12 @@ async def post(self): @pytest.fixture -def http_client(loop, aiohttp_client): - middlewares = [ - handle_errors_json - ] - - app = web.Application(middlewares=middlewares) - app.router.add_route('*', '/items', ItemsView) - - return loop.run_until_complete(aiohttp_client(app)) +async def api_views_http_client(http_client_maker): + return await http_client_maker(middlewares=[handle_errors_json], + routes=[('/items', ItemsView)]) -async def test_get(http_client): +async def test_get(api_views_http_client): sent_args = { 'count': '222' } @@ -53,7 +47,7 @@ async def test_get(http_client): 'count': 222 } - response = await http_client.request(hdrs.METH_GET, '/items', params=sent_args) + response = await api_views_http_client.get('/items', params=sent_args) assert response.status == 200 @@ -63,7 +57,7 @@ async def test_get(http_client): assert expected_args == query -async def test_post_body(http_client): +async def test_post_body(api_views_http_client): sent_data = { 'name': 'Risus Fusce', 'info': 'Egestas Lorem Sit Fringilla', @@ -75,7 +69,7 @@ async def test_post_body(http_client): 'count': 22, } - response = await http_client.request(hdrs.METH_POST, '/items', json=sent_data) + response = await api_views_http_client.post('/items', json=sent_data) assert response.status == 200 @@ -85,7 +79,7 @@ async def test_post_body(http_client): assert expected_data == body -async def test_post_query(http_client): +async def test_post_query(api_views_http_client): sent_data = { 'count': '22', } @@ -93,7 +87,7 @@ async def test_post_query(http_client): 'count': 22, } - response = await http_client.request(hdrs.METH_POST, '/items', params=sent_data) + response = await api_views_http_client.post('/items', params=sent_data) assert response.status == 200 diff --git a/tests/unit/views/test_model_view.py b/tests/unit/views/test_model_view.py index f83af9c..6bb90d8 100644 --- a/tests/unit/views/test_model_view.py +++ b/tests/unit/views/test_model_view.py @@ -1,5 +1,5 @@ import pytest -from aiohttp import web, hdrs + from peewee import SqliteDatabase from colibris.middleware.errors import handle_errors_json @@ -49,33 +49,28 @@ def database(): @pytest.fixture -def http(loop, aiohttp_client): - middlewares = [ - handle_errors_json - ] - - app = web.Application(middlewares=middlewares) - app.router.add_route('*', '/paginated-items', ItemsPaginatedView) - app.router.add_route('*', '/items', ItemsView) - app.router.add_route('*', '/items/{id}', ItemView) - return loop.run_until_complete(aiohttp_client(app)) +async def model_view_http_client(http_client_maker): + return await http_client_maker(middlewares=[handle_errors_json], + routes=[('/paginated-items', ItemsPaginatedView), + ('/items', ItemsView), + ('/items/{id}', ItemView)]) class TestList: - async def test_get_items(self, database, http): - response = await http.request(hdrs.METH_GET, '/items') + async def test_get_items(self, database, model_view_http_client): + response = await model_view_http_client.get('/items') assert response.status == 200 data = await response.json() assert isinstance(data, list) - async def test_get_pagination_default_page_and_size(self, database, http): + async def test_get_pagination_default_page_and_size(self, database, model_view_http_client): item__name = 'Ligula Egestas Fermentum' item_info = 'Ridiculus Fermentum Quam Porta' Item.create(name=item__name, info=item_info) - response = await http.request(hdrs.METH_GET, '/paginated-items') + response = await model_view_http_client.get('/paginated-items') assert response.status == 200 data = await response.json() @@ -86,7 +81,7 @@ async def test_get_pagination_default_page_and_size(self, database, http): assert 'page' in data assert 'page_size' in data - async def test_get_pagination_different_page(self, database, http): + async def test_get_pagination_different_page(self, database, model_view_http_client): item_info = 'Ridiculus Fermentum Quam Porta' first_item_name = 'Ligula Egestas Fermentum' second_item_name = 'Euismod Ipsum Vulputate' @@ -96,7 +91,7 @@ async def test_get_pagination_different_page(self, database, http): Item.create(name=second_item_name, info=item_info) Item.create(name=third_item_name, info=item_info) - response = await http.request(hdrs.METH_GET, '/paginated-items', params={'page_size': 1, 'page': 1}) + response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 1}) assert response.status == 200 data = await response.json() @@ -106,28 +101,28 @@ async def test_get_pagination_different_page(self, database, http): assert data['page_size'] == 1 assert data['results'][0]['name'] == third_item_name - response = await http.request(hdrs.METH_GET, '/paginated-items', params={'page_size': 1, 'page': 2}) + response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 2}) assert response.status == 200 data = await response.json() assert data['results'][0]['name'] == second_item_name - response = await http.request(hdrs.METH_GET, '/paginated-items', params={'page_size': 1, 'page': 3}) + response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 3}) assert response.status == 200 data = await response.json() assert data['results'][0]['name'] == first_item_name - response = await http.request(hdrs.METH_GET, '/paginated-items', params={'page_size': 1, 'page': 4}) + response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 4}) assert response.status == 200 data = await response.json() assert data['results'] == [] class TestCreate: - async def test_create_item(self, database, http): + async def test_create_item(self, database, model_view_http_client): item_name = 'Commodo Nibh' item_info = 'Cras Lorem Purus Etiam Venenatis' - response = await http.request(hdrs.METH_POST, '/items', json={'name': item_name, 'info': item_info}) + response = await model_view_http_client.post('/items', json={'name': item_name, 'info': item_info}) assert response.status == 201 @@ -135,8 +130,8 @@ async def test_create_item(self, database, http): assert item.name == item_name - async def test_create_item_validation(self, database, http): - response = await http.request(hdrs.METH_POST, '/items') + async def test_create_item_validation(self, database, model_view_http_client): + response = await model_view_http_client.post('/items') assert response.status == 400 data = await response.json() @@ -145,14 +140,14 @@ async def test_create_item_validation(self, database, http): class TestUpdate: - async def test_update_item(self, database, http): + async def test_update_item(self, database, model_view_http_client): item_initial_name = 'Ligula Egestas Fermentum' item_updated_name = 'Cursus Inceptos' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_initial_name, info=item_info) - response = await http.request(hdrs.METH_PATCH, '/items/{}'.format(item.id), json={'name': item_updated_name}) + response = await model_view_http_client.patch('/items/{}'.format(item.id), json={'name': item_updated_name}) assert response.status == 200 @@ -160,40 +155,40 @@ async def test_update_item(self, database, http): assert item.id == updated_item.id - async def test_update_item_validation(self, database, http): + async def test_update_item_validation(self, database, model_view_http_client): item_initial_name = 'Nibh Lorem Amet Aenean' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_initial_name, info=item_info) - response = await http.request(hdrs.METH_PATCH, '/items/{}'.format(item.id), json={'name': None}) + response = await model_view_http_client.patch('/items/{}'.format(item.id), json={'name': None}) assert response.status == 400 data = await response.json() assert 'name' in data['details'] - async def test_update_item_not_found(self, database, http): - response = await http.request(hdrs.METH_PATCH, '/items/11011', json={'name': 'Egestas Fringilla'}) + async def test_update_item_not_found(self, database, model_view_http_client): + response = await model_view_http_client.patch('/items/11011', json={'name': 'Egestas Fringilla'}) assert response.status == 404 class TestDestroy: - async def test_delete_item(self, database, http): + async def test_delete_item(self, database, model_view_http_client): item_name = 'Sit Lorem' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_name, info=item_info) - response = await http.request(hdrs.METH_DELETE, '/items/{}'.format(item.id)) + response = await model_view_http_client.delete('/items/{}'.format(item.id)) assert response.status == 204 items = Item.select().where(Item.name == item_name) assert items.count() == 0 - async def test_delete_item_not_found(self, database, http): - response = await http.request(hdrs.METH_DELETE, '/items/111111') + async def test_delete_item_not_found(self, database, model_view_http_client): + response = await model_view_http_client.delete('/items/111111') assert response.status == 404 From f5318eda2d70d484694b1e9a9b28289cb3bf6d25 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 16:11:43 +0300 Subject: [PATCH 03/14] Unit tests: add view permissions tests --- tests/unit/fixtures.py | 9 ++- .../unit/permissions/test_view_permissions.py | 72 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/unit/permissions/test_view_permissions.py diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py index 4c02f22..c4d01ce 100644 --- a/tests/unit/fixtures.py +++ b/tests/unit/fixtures.py @@ -1,5 +1,12 @@ -from .permissions.fixtures import * +import pytest + +from aiohttp import web + +from colibris import authentication +from colibris import authorization +from colibris.middleware.errors import handle_errors_json +from colibris.middleware.auth import handle_auth @pytest.fixture diff --git a/tests/unit/permissions/test_view_permissions.py b/tests/unit/permissions/test_view_permissions.py new file mode 100644 index 0000000..637c5ee --- /dev/null +++ b/tests/unit/permissions/test_view_permissions.py @@ -0,0 +1,72 @@ + +from aiohttp import web + +from colibris import authorization +from colibris import views + + +DUMMY_PERMISSION = 'dummy_permission' + + +class DummyView(views.View): + async def get(self): + return web.json_response({'message': 'dummy'}) + + +class DummyViewWithClassPermission(DummyView): + required_permissions = {DUMMY_PERMISSION} + + +class DummyViewWithMethodPermission(DummyView): + @authorization.require_permission(DUMMY_PERMISSION) + async def get(self): + return await super().get() + + +class PermissionAuthorizationBackend(authorization.AuthorizationBackend): + def get_actual_permissions(self, account, method, path): + return {DUMMY_PERMISSION} + + +class NoPermissionAuthorizationBackend(authorization.AuthorizationBackend): + def get_actual_permissions(self, account, method, path): + return () + + +async def test_default_view_permissions(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyView)]) + response = await client.get('/dummy') + + assert response.status == 200 + + +async def test_class_required_permissions_fulfilled(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)], + authorization_backend=PermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 200 + + +async def test_class_required_permissions_not_fulfilled(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)], + authorization_backend=NoPermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 403 + + +async def test_method_required_permissions_fulfilled(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)], + authorization_backend=PermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 200 + + +async def test_method_required_permissions_not_fulfilled(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)], + authorization_backend=NoPermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 403 From 9abb989052001edab2349e3a236e05f2743be029 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 16:42:17 +0300 Subject: [PATCH 04/14] Unit tests: add more view permissions tests --- tests/unit/fixtures.py | 2 ++ tests/unit/permissions/__init__.py | 0 tests/unit/permissions/fixtures.py | 3 ++ .../unit/permissions/test_view_permissions.py | 28 ++++++++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 tests/unit/permissions/__init__.py create mode 100644 tests/unit/permissions/fixtures.py diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py index c4d01ce..ff3c073 100644 --- a/tests/unit/fixtures.py +++ b/tests/unit/fixtures.py @@ -8,6 +8,8 @@ from colibris.middleware.errors import handle_errors_json from colibris.middleware.auth import handle_auth +from .permissions.fixtures import * + @pytest.fixture def http_client_maker(aiohttp_client): diff --git a/tests/unit/permissions/__init__.py b/tests/unit/permissions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/permissions/fixtures.py b/tests/unit/permissions/fixtures.py new file mode 100644 index 0000000..0aabbea --- /dev/null +++ b/tests/unit/permissions/fixtures.py @@ -0,0 +1,3 @@ + +DUMMY_PERMISSION = 'dummy_permission' +ANOTHER_PERMISSION = 'another_permission' diff --git a/tests/unit/permissions/test_view_permissions.py b/tests/unit/permissions/test_view_permissions.py index 637c5ee..51f9603 100644 --- a/tests/unit/permissions/test_view_permissions.py +++ b/tests/unit/permissions/test_view_permissions.py @@ -4,8 +4,7 @@ from colibris import authorization from colibris import views - -DUMMY_PERMISSION = 'dummy_permission' +from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION class DummyView(views.View): @@ -33,6 +32,11 @@ def get_actual_permissions(self, account, method, path): return () +class WrongPermissionAuthorizationBackend(authorization.AuthorizationBackend): + def get_actual_permissions(self, account, method, path): + return {ANOTHER_PERMISSION} + + async def test_default_view_permissions(http_client_maker): client = await http_client_maker(routes=[('/dummy', DummyView)]) response = await client.get('/dummy') @@ -48,7 +52,7 @@ async def test_class_required_permissions_fulfilled(http_client_maker): assert response.status == 200 -async def test_class_required_permissions_not_fulfilled(http_client_maker): +async def test_class_required_permissions_no_permissions(http_client_maker): client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)], authorization_backend=NoPermissionAuthorizationBackend) response = await client.get('/dummy') @@ -56,6 +60,14 @@ async def test_class_required_permissions_not_fulfilled(http_client_maker): assert response.status == 403 +async def test_class_required_permissions_wrong_permissions(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)], + authorization_backend=WrongPermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 403 + + async def test_method_required_permissions_fulfilled(http_client_maker): client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)], authorization_backend=PermissionAuthorizationBackend) @@ -64,9 +76,17 @@ async def test_method_required_permissions_fulfilled(http_client_maker): assert response.status == 200 -async def test_method_required_permissions_not_fulfilled(http_client_maker): +async def test_method_required_permissions_no_permissions(http_client_maker): client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)], authorization_backend=NoPermissionAuthorizationBackend) response = await client.get('/dummy') assert response.status == 403 + + +async def test_method_required_permissions_wrong_permissions(http_client_maker): + client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)], + authorization_backend=WrongPermissionAuthorizationBackend) + response = await client.get('/dummy') + + assert response.status == 403 From f4ac2aee4ba2c8381c7e04a1babcf7be968461eb Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 17:07:00 +0300 Subject: [PATCH 05/14] Unit tests: add Permissions class tests --- colibris/authorization/permissions.py | 2 +- tests/unit/permissions/fixtures.py | 1 + .../permissions/test_permissions_class.py | 67 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/unit/permissions/test_permissions_class.py diff --git a/colibris/authorization/permissions.py b/colibris/authorization/permissions.py index fcf4185..4170c54 100644 --- a/colibris/authorization/permissions.py +++ b/colibris/authorization/permissions.py @@ -24,7 +24,7 @@ def verify(self, actual_permissions): permissions = self.or_set & actual_permissions if len(permissions) == 0: - raise PermissionNotMet(permissions.pop()) + raise PermissionNotMet(list(self.or_set)[0]) def __str__(self): s = '' diff --git a/tests/unit/permissions/fixtures.py b/tests/unit/permissions/fixtures.py index 0aabbea..85e4ca5 100644 --- a/tests/unit/permissions/fixtures.py +++ b/tests/unit/permissions/fixtures.py @@ -1,3 +1,4 @@ DUMMY_PERMISSION = 'dummy_permission' ANOTHER_PERMISSION = 'another_permission' +YET_ANOTHER_PERMISSION = 'yet_another_permission' diff --git a/tests/unit/permissions/test_permissions_class.py b/tests/unit/permissions/test_permissions_class.py new file mode 100644 index 0000000..5329d94 --- /dev/null +++ b/tests/unit/permissions/test_permissions_class.py @@ -0,0 +1,67 @@ + +import pytest + +from colibris.authorization import permissions + +from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION + + +def test_conjunction(): + p = permissions.Permissions(and_set={DUMMY_PERMISSION, ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({DUMMY_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({ANOTHER_PERMISSION}) + + p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION}) + + +def test_disjunction(): + p = permissions.Permissions(or_set={DUMMY_PERMISSION, ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({}) + + p.verify({DUMMY_PERMISSION}) + p.verify({ANOTHER_PERMISSION}) + p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION}) + + +def test_conjunction_disjunction(): + p = permissions.Permissions(and_set={DUMMY_PERMISSION, ANOTHER_PERMISSION}, + or_set={DUMMY_PERMISSION, YET_ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({DUMMY_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({YET_ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({DUMMY_PERMISSION, YET_ANOTHER_PERMISSION}) + + with pytest.raises(permissions.PermissionNotMet): + p.verify({ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION}) + + p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION}) + p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION}) + + +def test_combine(): + p1 = permissions.Permissions(and_set={DUMMY_PERMISSION}, or_set={ANOTHER_PERMISSION}) + p2 = permissions.Permissions(and_set={ANOTHER_PERMISSION}, or_set={YET_ANOTHER_PERMISSION}) + + c = p1.combine(p2) + assert c.and_set == {DUMMY_PERMISSION, ANOTHER_PERMISSION} + assert c.or_set == {ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION} From a16dfeb93ca965eac685ce1295745b2d2de7e7cd Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 24 Jul 2019 17:48:35 +0300 Subject: [PATCH 06/14] tox.ini: remove duplicate pytest args --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 596a457..dda5ed1 100644 --- a/tox.ini +++ b/tox.ini @@ -9,9 +9,8 @@ ignore = E129,W504 changedir = tests deps = pytest requests -commands = pytest -v -p aiohttp.pytest_plugin {posargs} +commands = pytest {posargs} [pytest] -norecursedirs = dummy-skeleton testpaths = tests addopts = -v -p aiohttp.pytest_plugin From b0b50470475efc5d5d52098e66198a146d5f231a Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Thu, 25 Jul 2019 13:50:35 +0300 Subject: [PATCH 07/14] Ensure AuthorizationBackend.verify() is always called with set of perms --- colibris/authorization/base.py | 3 +-- colibris/authorization/null.py | 2 +- colibris/authorization/permissions.py | 2 -- colibris/authorization/rights.py | 2 +- colibris/authorization/role.py | 4 ++-- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/colibris/authorization/base.py b/colibris/authorization/base.py index 572b861..3a48504 100644 --- a/colibris/authorization/base.py +++ b/colibris/authorization/base.py @@ -7,5 +7,4 @@ def get_actual_permissions(self, account, method, path): raise NotImplementedError def authorize(self, account, method, path, handler, required_permissions): - actual_permissions = self.get_actual_permissions(account, method, path) - required_permissions.verify(actual_permissions) + required_permissions.verify(self.get_actual_permissions(account, method, path)) diff --git a/colibris/authorization/null.py b/colibris/authorization/null.py index 9ed66c2..3cc005f 100644 --- a/colibris/authorization/null.py +++ b/colibris/authorization/null.py @@ -12,4 +12,4 @@ def authorize(self, account, method, path, handler, required_permissions): return True def get_actual_permissions(self, account, method, path): - return () + return set() diff --git a/colibris/authorization/permissions.py b/colibris/authorization/permissions.py index 8d699ec..5a22549 100644 --- a/colibris/authorization/permissions.py +++ b/colibris/authorization/permissions.py @@ -11,8 +11,6 @@ def combine(self, permissions): return Permissions(self.and_set | permissions.and_set, self.or_set | permissions.or_set) def verify(self, actual_permissions): - actual_permissions = set(actual_permissions) - # Verify permissions in and set for p in self.and_set: if p not in actual_permissions: diff --git a/colibris/authorization/rights.py b/colibris/authorization/rights.py index a3b71ca..f185a3c 100644 --- a/colibris/authorization/rights.py +++ b/colibris/authorization/rights.py @@ -27,4 +27,4 @@ def get_actual_permissions(self, account, method, path): # Gather all rights from all entries for the given account rights = self.model.select().where(self.get_account_field() == account) - return ['{}:{}'.format(self.get_resource(r), self.get_operation(r)) for r in rights] + return {'{}:{}'.format(self.get_resource(r), self.get_operation(r)) for r in rights} diff --git a/colibris/authorization/role.py b/colibris/authorization/role.py index 2708db2..0e8dfcc 100644 --- a/colibris/authorization/role.py +++ b/colibris/authorization/role.py @@ -19,7 +19,7 @@ def get_role(self, account): def get_actual_permissions(self, account, method, path): role = self.get_role(account) - actual_permissions = [role] + actual_permissions = {role} try: index = self.order.index(role) @@ -27,6 +27,6 @@ def get_actual_permissions(self, account, method, path): except ValueError: index = 0 - actual_permissions += self.order[:index] + actual_permissions.update(self.order[:index]) return actual_permissions From dd66afd07b725cdf05ce3f41d62626fda3bbb7b7 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Thu, 25 Jul 2019 13:54:29 +0300 Subject: [PATCH 08/14] Tests: add role authorization backend tests --- .../__init__.py | 0 tests/unit/authorization/fixtures.py | 10 ++++++++++ .../test_permissions_class.py | 0 tests/unit/authorization/test_role_backend.py | 20 +++++++++++++++++++ .../test_view_permissions.py | 0 tests/unit/fixtures.py | 2 +- tests/unit/permissions/fixtures.py | 4 ---- 7 files changed, 31 insertions(+), 5 deletions(-) rename tests/unit/{permissions => authorization}/__init__.py (100%) create mode 100644 tests/unit/authorization/fixtures.py rename tests/unit/{permissions => authorization}/test_permissions_class.py (100%) create mode 100644 tests/unit/authorization/test_role_backend.py rename tests/unit/{permissions => authorization}/test_view_permissions.py (100%) delete mode 100644 tests/unit/permissions/fixtures.py diff --git a/tests/unit/permissions/__init__.py b/tests/unit/authorization/__init__.py similarity index 100% rename from tests/unit/permissions/__init__.py rename to tests/unit/authorization/__init__.py diff --git a/tests/unit/authorization/fixtures.py b/tests/unit/authorization/fixtures.py new file mode 100644 index 0000000..62ff025 --- /dev/null +++ b/tests/unit/authorization/fixtures.py @@ -0,0 +1,10 @@ + +import types + + +DUMMY_PERMISSION = 'dummy_permission' +ANOTHER_PERMISSION = 'another_permission' +YET_ANOTHER_PERMISSION = 'yet_another_permission' + +DUMMY_ACCOUNT = types.SimpleNamespace(role=DUMMY_PERMISSION) +ANOTHER_ACCOUNT = types.SimpleNamespace(role=ANOTHER_PERMISSION) diff --git a/tests/unit/permissions/test_permissions_class.py b/tests/unit/authorization/test_permissions_class.py similarity index 100% rename from tests/unit/permissions/test_permissions_class.py rename to tests/unit/authorization/test_permissions_class.py diff --git a/tests/unit/authorization/test_role_backend.py b/tests/unit/authorization/test_role_backend.py new file mode 100644 index 0000000..6639f06 --- /dev/null +++ b/tests/unit/authorization/test_role_backend.py @@ -0,0 +1,20 @@ + +from colibris.authorization.role import RoleBackend + +from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION, DUMMY_ACCOUNT, ANOTHER_ACCOUNT + + +def test_extract_role(): + backend = RoleBackend(role_field='role') + assert backend.get_role(account=DUMMY_ACCOUNT) == DUMMY_PERMISSION + + +def test_actual_permissions_simple(): + backend = RoleBackend(role_field='role') + assert backend.get_actual_permissions(account=DUMMY_ACCOUNT, method='GET', path='/') == {DUMMY_PERMISSION} + + +def test_actual_permissions_order(): + backend = RoleBackend(role_field='role', order=[DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION]) + permissions = backend.get_actual_permissions(account=ANOTHER_ACCOUNT, method='GET', path='/') + assert permissions == {DUMMY_PERMISSION, ANOTHER_PERMISSION} diff --git a/tests/unit/permissions/test_view_permissions.py b/tests/unit/authorization/test_view_permissions.py similarity index 100% rename from tests/unit/permissions/test_view_permissions.py rename to tests/unit/authorization/test_view_permissions.py diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py index ff3c073..98622bd 100644 --- a/tests/unit/fixtures.py +++ b/tests/unit/fixtures.py @@ -8,7 +8,7 @@ from colibris.middleware.errors import handle_errors_json from colibris.middleware.auth import handle_auth -from .permissions.fixtures import * +from .authorization.fixtures import * @pytest.fixture diff --git a/tests/unit/permissions/fixtures.py b/tests/unit/permissions/fixtures.py deleted file mode 100644 index 85e4ca5..0000000 --- a/tests/unit/permissions/fixtures.py +++ /dev/null @@ -1,4 +0,0 @@ - -DUMMY_PERMISSION = 'dummy_permission' -ANOTHER_PERMISSION = 'another_permission' -YET_ANOTHER_PERMISSION = 'yet_another_permission' From f74e426269c680f65bc54b651b314ccda81b9358 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Fri, 26 Jul 2019 10:34:26 +0300 Subject: [PATCH 09/14] Auth model backends: allow specifying the model class directly --- colibris/authentication/model.py | 5 ++++- colibris/authorization/model.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/colibris/authentication/model.py b/colibris/authentication/model.py index 24e92ef..8fec46f 100644 --- a/colibris/authentication/model.py +++ b/colibris/authentication/model.py @@ -6,7 +6,10 @@ class ModelBackend(AuthenticationBackend): def __init__(self, model, active_field=None, inactive_field=None, **kwargs): - self.model = utils.import_member(model) + self.model = model + if isinstance(model, str): + self.model = utils.import_member(self.model) + self.active_field = active_field self.inactive_field = inactive_field diff --git a/colibris/authorization/model.py b/colibris/authorization/model.py index 992cee7..b00d4ba 100644 --- a/colibris/authorization/model.py +++ b/colibris/authorization/model.py @@ -6,7 +6,10 @@ class ModelBackend(AuthorizationBackend): def __init__(self, model, account_field, **kwargs): - self.model = utils.import_member(model) + self.model = model + if isinstance(model, str): + self.model = utils.import_member(self.model) + self.account_field = account_field super().__init__(**kwargs) From 31feadf93ffd2522321702573d1ceea7a6fe0470 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Fri, 26 Jul 2019 11:29:33 +0300 Subject: [PATCH 10/14] Unit tests: generalize database fixture --- tests/unit/fixtures.py | 21 +++++++++++++++++++++ tests/unit/views/test_model_view.py | 7 ++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/unit/fixtures.py b/tests/unit/fixtures.py index 98622bd..108459d 100644 --- a/tests/unit/fixtures.py +++ b/tests/unit/fixtures.py @@ -5,6 +5,7 @@ from colibris import authentication from colibris import authorization +from colibris import persist from colibris.middleware.errors import handle_errors_json from colibris.middleware.auth import handle_auth @@ -47,3 +48,23 @@ def client(authentication_backend=None, authorization_backend=None, return aiohttp_client(app) return client + + +@pytest.fixture +def database_maker(): + + def database(models, **settings): + # Use in-memory SQLite db by default + settings.setdefault('backend', 'colibris.persist.backends.SQLiteBackend') + if settings['backend'] == 'colibris.persist.backends.SQLiteBackend': + settings.setdefault('database', ':memory:') + + persist.DatabaseBackend.configure(settings) + db = persist.get_database() + db.connect() + persist.models.set_database(db) + db.create_tables(models) + + return db + + return database diff --git a/tests/unit/views/test_model_view.py b/tests/unit/views/test_model_view.py index 6bb90d8..08ad340 100644 --- a/tests/unit/views/test_model_view.py +++ b/tests/unit/views/test_model_view.py @@ -41,11 +41,8 @@ class ItemView(RetrieveUpdateDestroyModelView): @pytest.fixture -def database(): - db = SqliteDatabase(':memory:') - db.bind(MODELS) - db.connect() - db.create_tables(MODELS) +def database(database_maker): + return database_maker(models=MODELS) @pytest.fixture From 87b4eaa141aa85b8c7a32b19e1074971bcb80a67 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Fri, 26 Jul 2019 12:18:38 +0300 Subject: [PATCH 11/14] Unit tests: fix some authorization test cases --- tests/unit/authorization/test_permissions_class.py | 6 +++--- tests/unit/authorization/test_view_permissions.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/authorization/test_permissions_class.py b/tests/unit/authorization/test_permissions_class.py index 5329d94..65faddd 100644 --- a/tests/unit/authorization/test_permissions_class.py +++ b/tests/unit/authorization/test_permissions_class.py @@ -10,7 +10,7 @@ def test_conjunction(): p = permissions.Permissions(and_set={DUMMY_PERMISSION, ANOTHER_PERMISSION}) with pytest.raises(permissions.PermissionNotMet): - p.verify({}) + p.verify(set()) with pytest.raises(permissions.PermissionNotMet): p.verify({DUMMY_PERMISSION}) @@ -25,7 +25,7 @@ def test_disjunction(): p = permissions.Permissions(or_set={DUMMY_PERMISSION, ANOTHER_PERMISSION}) with pytest.raises(permissions.PermissionNotMet): - p.verify({}) + p.verify(set()) p.verify({DUMMY_PERMISSION}) p.verify({ANOTHER_PERMISSION}) @@ -37,7 +37,7 @@ def test_conjunction_disjunction(): or_set={DUMMY_PERMISSION, YET_ANOTHER_PERMISSION}) with pytest.raises(permissions.PermissionNotMet): - p.verify({}) + p.verify(set()) with pytest.raises(permissions.PermissionNotMet): p.verify({DUMMY_PERMISSION}) diff --git a/tests/unit/authorization/test_view_permissions.py b/tests/unit/authorization/test_view_permissions.py index 51f9603..433ba51 100644 --- a/tests/unit/authorization/test_view_permissions.py +++ b/tests/unit/authorization/test_view_permissions.py @@ -13,7 +13,7 @@ async def get(self): class DummyViewWithClassPermission(DummyView): - required_permissions = {DUMMY_PERMISSION} + permissions = {DUMMY_PERMISSION} class DummyViewWithMethodPermission(DummyView): @@ -29,7 +29,7 @@ def get_actual_permissions(self, account, method, path): class NoPermissionAuthorizationBackend(authorization.AuthorizationBackend): def get_actual_permissions(self, account, method, path): - return () + return set() class WrongPermissionAuthorizationBackend(authorization.AuthorizationBackend): From ecc000201d184c20a6d1433204bdd3ba3a2d99cf Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Fri, 26 Jul 2019 12:18:59 +0300 Subject: [PATCH 12/14] Unit tests: add authorization rights backend tests --- .../unit/authorization/test_rights_backend.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/unit/authorization/test_rights_backend.py diff --git a/tests/unit/authorization/test_rights_backend.py b/tests/unit/authorization/test_rights_backend.py new file mode 100644 index 0000000..39c149b --- /dev/null +++ b/tests/unit/authorization/test_rights_backend.py @@ -0,0 +1,57 @@ + +import pytest + +from colibris.authorization.rights import RightsBackend +from colibris import persist + + +USERNAME1 = 'username1' +USERNAME2 = 'username2' + +RESOURCE = 'resource' +OPERATION = 'operation' +PERMISSION = '{}:{}'.format(RESOURCE, OPERATION) + + +class User(persist.Model): + username = persist.CharField() + + +class Right(persist.Model): + user = persist.ForeignKeyField(User) + resource = persist.CharField() + operation = persist.CharField() + + +@pytest.fixture +def database(database_maker): + return database_maker(models=[User, Right]) + + +@pytest.fixture +def user1(database): + return User.create(username=USERNAME1) + + +@pytest.fixture +def user2(database): + return User.create(username=USERNAME2) + + +@pytest.fixture +def right(user1): + return Right.create(user=user1, resource=RESOURCE, operation=OPERATION) + + +@pytest.fixture +def backend(): + return RightsBackend(model=Right, account_field='user', + resource_field='resource', operation_field='operation') + + +def test_allowed(backend, user1, right): + assert backend.get_actual_permissions(account=user1, method='GET', path='/') == {PERMISSION} + + +def test_forbidden(backend, user2, right): + assert backend.get_actual_permissions(account=user2, method='GET', path='/') == set() From ef486c003498c26e2e98590b0dd857df9faf2c6d Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Tue, 30 Jul 2019 17:06:19 +0300 Subject: [PATCH 13/14] Tests: simplify fixture names --- tests/unit/views/test_api_view.py | 2 +- tests/unit/views/test_model_view.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/views/test_api_view.py b/tests/unit/views/test_api_view.py index 1423766..3245b67 100644 --- a/tests/unit/views/test_api_view.py +++ b/tests/unit/views/test_api_view.py @@ -34,7 +34,7 @@ async def post(self): @pytest.fixture -async def api_views_http_client(http_client_maker): +async def http_client(http_client_maker): return await http_client_maker(middlewares=[handle_errors_json], routes=[('/items', ItemsView)]) diff --git a/tests/unit/views/test_model_view.py b/tests/unit/views/test_model_view.py index 08ad340..0c5aefb 100644 --- a/tests/unit/views/test_model_view.py +++ b/tests/unit/views/test_model_view.py @@ -1,7 +1,5 @@ import pytest -from peewee import SqliteDatabase - from colibris.middleware.errors import handle_errors_json from colibris.pagination import PageNumberPagination from colibris.schemas import ModelSchema @@ -9,6 +7,8 @@ from colibris import persist from colibris.views.generic import ListCreateModelView, RetrieveUpdateDestroyModelView +from ..fixtures import database_maker, http_client_maker + class Item(persist.Model): name = persist.CharField() @@ -46,7 +46,7 @@ def database(database_maker): @pytest.fixture -async def model_view_http_client(http_client_maker): +async def http_client(http_client_maker): return await http_client_maker(middlewares=[handle_errors_json], routes=[('/paginated-items', ItemsPaginatedView), ('/items', ItemsView), From bdccfaf74d22dad041e79510570c11d1ee582405 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Tue, 30 Jul 2019 17:32:11 +0300 Subject: [PATCH 14/14] Tests: actually use new fixture names --- tests/unit/views/test_api_view.py | 12 ++++---- tests/unit/views/test_model_view.py | 48 ++++++++++++++--------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/unit/views/test_api_view.py b/tests/unit/views/test_api_view.py index 3245b67..cc549fc 100644 --- a/tests/unit/views/test_api_view.py +++ b/tests/unit/views/test_api_view.py @@ -39,7 +39,7 @@ async def http_client(http_client_maker): routes=[('/items', ItemsView)]) -async def test_get(api_views_http_client): +async def test_get(http_client): sent_args = { 'count': '222' } @@ -47,7 +47,7 @@ async def test_get(api_views_http_client): 'count': 222 } - response = await api_views_http_client.get('/items', params=sent_args) + response = await http_client.get('/items', params=sent_args) assert response.status == 200 @@ -57,7 +57,7 @@ async def test_get(api_views_http_client): assert expected_args == query -async def test_post_body(api_views_http_client): +async def test_post_body(http_client): sent_data = { 'name': 'Risus Fusce', 'info': 'Egestas Lorem Sit Fringilla', @@ -69,7 +69,7 @@ async def test_post_body(api_views_http_client): 'count': 22, } - response = await api_views_http_client.post('/items', json=sent_data) + response = await http_client.post('/items', json=sent_data) assert response.status == 200 @@ -79,7 +79,7 @@ async def test_post_body(api_views_http_client): assert expected_data == body -async def test_post_query(api_views_http_client): +async def test_post_query(http_client): sent_data = { 'count': '22', } @@ -87,7 +87,7 @@ async def test_post_query(api_views_http_client): 'count': 22, } - response = await api_views_http_client.post('/items', params=sent_data) + response = await http_client.post('/items', params=sent_data) assert response.status == 200 diff --git a/tests/unit/views/test_model_view.py b/tests/unit/views/test_model_view.py index 0c5aefb..67b6668 100644 --- a/tests/unit/views/test_model_view.py +++ b/tests/unit/views/test_model_view.py @@ -7,8 +7,6 @@ from colibris import persist from colibris.views.generic import ListCreateModelView, RetrieveUpdateDestroyModelView -from ..fixtures import database_maker, http_client_maker - class Item(persist.Model): name = persist.CharField() @@ -54,20 +52,20 @@ async def http_client(http_client_maker): class TestList: - async def test_get_items(self, database, model_view_http_client): - response = await model_view_http_client.get('/items') + async def test_get_items(self, database, http_client): + response = await http_client.get('/items') assert response.status == 200 data = await response.json() assert isinstance(data, list) - async def test_get_pagination_default_page_and_size(self, database, model_view_http_client): + async def test_get_pagination_default_page_and_size(self, database, http_client): item__name = 'Ligula Egestas Fermentum' item_info = 'Ridiculus Fermentum Quam Porta' Item.create(name=item__name, info=item_info) - response = await model_view_http_client.get('/paginated-items') + response = await http_client.get('/paginated-items') assert response.status == 200 data = await response.json() @@ -78,7 +76,7 @@ async def test_get_pagination_default_page_and_size(self, database, model_view_h assert 'page' in data assert 'page_size' in data - async def test_get_pagination_different_page(self, database, model_view_http_client): + async def test_get_pagination_different_page(self, database, http_client): item_info = 'Ridiculus Fermentum Quam Porta' first_item_name = 'Ligula Egestas Fermentum' second_item_name = 'Euismod Ipsum Vulputate' @@ -88,7 +86,7 @@ async def test_get_pagination_different_page(self, database, model_view_http_cli Item.create(name=second_item_name, info=item_info) Item.create(name=third_item_name, info=item_info) - response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 1}) + response = await http_client.get('/paginated-items', params={'page_size': 1, 'page': 1}) assert response.status == 200 data = await response.json() @@ -98,28 +96,28 @@ async def test_get_pagination_different_page(self, database, model_view_http_cli assert data['page_size'] == 1 assert data['results'][0]['name'] == third_item_name - response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 2}) + response = await http_client.get('/paginated-items', params={'page_size': 1, 'page': 2}) assert response.status == 200 data = await response.json() assert data['results'][0]['name'] == second_item_name - response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 3}) + response = await http_client.get('/paginated-items', params={'page_size': 1, 'page': 3}) assert response.status == 200 data = await response.json() assert data['results'][0]['name'] == first_item_name - response = await model_view_http_client.get('/paginated-items', params={'page_size': 1, 'page': 4}) + response = await http_client.get('/paginated-items', params={'page_size': 1, 'page': 4}) assert response.status == 200 data = await response.json() assert data['results'] == [] class TestCreate: - async def test_create_item(self, database, model_view_http_client): + async def test_create_item(self, database, http_client): item_name = 'Commodo Nibh' item_info = 'Cras Lorem Purus Etiam Venenatis' - response = await model_view_http_client.post('/items', json={'name': item_name, 'info': item_info}) + response = await http_client.post('/items', json={'name': item_name, 'info': item_info}) assert response.status == 201 @@ -127,8 +125,8 @@ async def test_create_item(self, database, model_view_http_client): assert item.name == item_name - async def test_create_item_validation(self, database, model_view_http_client): - response = await model_view_http_client.post('/items') + async def test_create_item_validation(self, database, http_client): + response = await http_client.post('/items') assert response.status == 400 data = await response.json() @@ -137,14 +135,14 @@ async def test_create_item_validation(self, database, model_view_http_client): class TestUpdate: - async def test_update_item(self, database, model_view_http_client): + async def test_update_item(self, database, http_client): item_initial_name = 'Ligula Egestas Fermentum' item_updated_name = 'Cursus Inceptos' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_initial_name, info=item_info) - response = await model_view_http_client.patch('/items/{}'.format(item.id), json={'name': item_updated_name}) + response = await http_client.patch('/items/{}'.format(item.id), json={'name': item_updated_name}) assert response.status == 200 @@ -152,40 +150,40 @@ async def test_update_item(self, database, model_view_http_client): assert item.id == updated_item.id - async def test_update_item_validation(self, database, model_view_http_client): + async def test_update_item_validation(self, database, http_client): item_initial_name = 'Nibh Lorem Amet Aenean' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_initial_name, info=item_info) - response = await model_view_http_client.patch('/items/{}'.format(item.id), json={'name': None}) + response = await http_client.patch('/items/{}'.format(item.id), json={'name': None}) assert response.status == 400 data = await response.json() assert 'name' in data['details'] - async def test_update_item_not_found(self, database, model_view_http_client): - response = await model_view_http_client.patch('/items/11011', json={'name': 'Egestas Fringilla'}) + async def test_update_item_not_found(self, database, http_client): + response = await http_client.patch('/items/11011', json={'name': 'Egestas Fringilla'}) assert response.status == 404 class TestDestroy: - async def test_delete_item(self, database, model_view_http_client): + async def test_delete_item(self, database, http_client): item_name = 'Sit Lorem' item_info = 'Ridiculus Fermentum Quam Porta' item = Item.create(name=item_name, info=item_info) - response = await model_view_http_client.delete('/items/{}'.format(item.id)) + response = await http_client.delete('/items/{}'.format(item.id)) assert response.status == 204 items = Item.select().where(Item.name == item_name) assert items.count() == 0 - async def test_delete_item_not_found(self, database, model_view_http_client): - response = await model_view_http_client.delete('/items/111111') + async def test_delete_item_not_found(self, database, http_client): + response = await http_client.delete('/items/111111') assert response.status == 404