From 93791c999d73bb50a7dbdf40a59c1834f464d6ae Mon Sep 17 00:00:00 2001 From: kevgliss Date: Tue, 10 May 2016 13:43:26 -0700 Subject: [PATCH] Marsmallowing destinations (#311) --- lemur/destinations/schemas.py | 29 +++++ lemur/destinations/service.py | 11 +- lemur/destinations/views.py | 44 +++---- lemur/tests/test_destinations.py | 189 +++++++++++++------------------ 4 files changed, 124 insertions(+), 149 deletions(-) create mode 100644 lemur/destinations/schemas.py diff --git a/lemur/destinations/schemas.py b/lemur/destinations/schemas.py new file mode 100644 index 00000000..0ee49693 --- /dev/null +++ b/lemur/destinations/schemas.py @@ -0,0 +1,29 @@ +""" +.. module: lemur.destinations.schemas + :platform: unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. +.. moduleauthor:: Kevin Glisson +""" + +from marshmallow import fields +from lemur.common.schema import LemurInputSchema, LemurOutputSchema +from lemur.schemas import PluginSchema + + +class DestinationInputSchema(LemurInputSchema): + label = fields.String(required=True) + options = fields.Dict(required=True) + description = fields.String() + plugin = fields.Nested(PluginSchema, required=True) + + +class DestinationOutputSchema(LemurOutputSchema): + label = fields.String() + options = fields.Dict(dump_to='destination_options') + description = fields.String() + + +destination_input_schema = DestinationInputSchema() +destinations_output_schema = DestinationOutputSchema(many=True) +destination_output_schema = DestinationOutputSchema() diff --git a/lemur/destinations/service.py b/lemur/destinations/service.py index ed860614..14e24df9 100644 --- a/lemur/destinations/service.py +++ b/lemur/destinations/service.py @@ -86,10 +86,6 @@ def get_all(): def render(args): - sort_by = args.pop('sort_by') - sort_dir = args.pop('sort_dir') - page = args.pop('page') - count = args.pop('count') filt = args.pop('filter') certificate_id = args.pop('certificate_id', None) @@ -103,12 +99,7 @@ def render(args): terms = filt.split(';') query = database.filter(query, Destination, terms) - query = database.find_all(query, Destination, args) - - if sort_by and sort_dir: - query = database.sort(query, Destination, sort_by, sort_dir) - - return database.paginate(query, page, count) + return database.sort_and_page(query, Destination, args) def stats(**kwargs): diff --git a/lemur/destinations/views.py b/lemur/destinations/views.py index bc8799a1..f27d435e 100644 --- a/lemur/destinations/views.py +++ b/lemur/destinations/views.py @@ -7,34 +7,28 @@ .. moduleauthor:: Kevin Glisson """ from flask import Blueprint -from flask.ext.restful import Api, reqparse, fields +from flask.ext.restful import Api, reqparse from lemur.destinations import service from lemur.auth.service import AuthenticatedResource from lemur.auth.permissions import admin_permission -from lemur.common.utils import paginated_parser, marshal_items +from lemur.common.utils import paginated_parser + +from lemur.common.schema import validate_schema +from lemur.destinations.schemas import destinations_output_schema, destination_input_schema, destination_output_schema mod = Blueprint('destinations', __name__) api = Api(mod) -FIELDS = { - 'description': fields.String, - 'destinationOptions': fields.Raw(attribute='options'), - 'pluginName': fields.String(attribute='plugin_name'), - 'label': fields.String, - 'id': fields.Integer, -} - - class DestinationsList(AuthenticatedResource): """ Defines the 'destinations' endpoint """ def __init__(self): self.reqparse = reqparse.RequestParser() super(DestinationsList, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, destinations_output_schema) def get(self): """ .. http:get:: /destinations @@ -92,8 +86,8 @@ class DestinationsList(AuthenticatedResource): return service.render(args) @admin_permission.require(http_exception=403) - @marshal_items(FIELDS) - def post(self): + @validate_schema(destination_input_schema, destination_output_schema) + def post(self, data=None): """ .. http:post:: /destinations @@ -154,12 +148,7 @@ class DestinationsList(AuthenticatedResource): :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ - self.reqparse.add_argument('label', type=str, location='json', required=True) - self.reqparse.add_argument('plugin', type=dict, location='json', required=True) - self.reqparse.add_argument('description', type=str, location='json') - - args = self.reqparse.parse_args() - return service.create(args['label'], args['plugin']['slug'], args['plugin']['pluginOptions'], args['description']) + return service.create(data['label'], data['plugin']['slug'], data['plugin']['pluginOptions'], data['description']) class Destinations(AuthenticatedResource): @@ -167,7 +156,7 @@ class Destinations(AuthenticatedResource): self.reqparse = reqparse.RequestParser() super(Destinations, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, destination_output_schema) def get(self, destination_id): """ .. http:get:: /destinations/1 @@ -213,8 +202,8 @@ class Destinations(AuthenticatedResource): return service.get(destination_id) @admin_permission.require(http_exception=403) - @marshal_items(FIELDS) - def put(self, destination_id): + @validate_schema(destination_input_schema, destination_output_schema) + def put(self, destination_id, data=None): """ .. http:put:: /destinations/1 @@ -276,12 +265,7 @@ class Destinations(AuthenticatedResource): :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ - self.reqparse.add_argument('label', type=str, location='json', required=True) - self.reqparse.add_argument('plugin', type=dict, location='json', required=True) - self.reqparse.add_argument('description', type=str, location='json') - - args = self.reqparse.parse_args() - return service.update(destination_id, args['label'], args['plugin']['pluginOptions'], args['description']) + return service.update(destination_id, data['label'], data['plugin']['pluginOptions'], data['description']) @admin_permission.require(http_exception=403) def delete(self, destination_id): @@ -294,7 +278,7 @@ class CertificateDestinations(AuthenticatedResource): def __init__(self): super(CertificateDestinations, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, destination_output_schema) def get(self, certificate_id): """ .. http:get:: /certificates/1/destinations diff --git a/lemur/tests/test_destinations.py b/lemur/tests/test_destinations.py index 37da1299..0a52b54c 100644 --- a/lemur/tests/test_destinations.py +++ b/lemur/tests/test_destinations.py @@ -1,134 +1,105 @@ -from lemur.destinations.service import * # noqa +import pytest + from lemur.destinations.views import * # noqa -from json import dumps + +from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN -def test_crud(session): - destination = create('testdest', 'aws-destination', {}, description='destination1') - assert destination.id > 0 +def test_destination_input_schema(client, destination): + from lemur.destinations.schemas import DestinationInputSchema - destination = update(destination.id, 'testdest2', {}, 'destination2') - assert destination.label == 'testdest2' + input_data = { + 'label': 'destination1', + 'options': {}, + 'description': 'my destination', + 'active': True, + 'plugin': { + 'slug': 'aws-destination' + } + } - assert len(get_all()) == 1 + data, errors = DestinationInputSchema().load(input_data) - delete(1) - assert len(get_all()) == 0 + assert not errors -def test_destination_get(client): - assert client.get(api.url_for(Destinations, destination_id=1)).status_code == 401 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 404), + (VALID_ADMIN_HEADER_TOKEN, 404), + ('', 401) +]) +def test_destination_get(client, token, status): + assert client.get(api.url_for(Destinations, destination_id=1), headers=token).status_code == status -def test_destination_post(client): - assert client.post(api.url_for(Destinations, destination_id=1), data={}).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_destination_post_(client, token, status): + assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=token).status_code == status -def test_destination_put(client): - assert client.put(api.url_for(Destinations, destination_id=1), data={}).status_code == 401 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 403), + (VALID_ADMIN_HEADER_TOKEN, 400), + ('', 401) +]) +def test_destination_put(client, token, status): + assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=token).status_code == status -def test_destination_delete(client): - assert client.delete(api.url_for(Destinations, destination_id=1)).status_code == 401 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 403), + (VALID_ADMIN_HEADER_TOKEN, 200), + ('', 401) +]) +def test_destination_delete(client, token, status): + assert client.delete(api.url_for(Destinations, destination_id=1), headers=token).status_code == status -def test_destination_patch(client): - assert client.patch(api.url_for(Destinations, destination_id=1), data={}).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_destination_patch(client, token, status): + assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=token).status_code == status -VALID_USER_HEADER_TOKEN = { - 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 403), + (VALID_ADMIN_HEADER_TOKEN, 400), + ('', 401) +]) +def test_destination_list_post_(client, token, status): + assert client.post(api.url_for(DestinationsList), data={}, headers=token).status_code == status -def test_auth_destination_get(client): - assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 200), + (VALID_ADMIN_HEADER_TOKEN, 200), + ('', 401) +]) +def test_destination_list_get(client, token, status): + assert client.get(api.url_for(DestinationsList), headers=token).status_code == status -def test_auth_destination_post_(client): - assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_destination_list_delete(client, token, status): + assert client.delete(api.url_for(DestinationsList), headers=token).status_code == status -def test_auth_destination_put(client): - assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 - - -def test_auth_destination_delete(client): - assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 - - -def test_auth_destination_patch(client): - assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 - - -VALID_ADMIN_HEADER_TOKEN = { - 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} - - -def test_admin_destination_get(client): - assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - - -def test_admin_destination_post(client): - assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 - - -def test_admin_destination_put(client): - assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 - - -def test_admin_destination_delete(client): - assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - - -def test_admin_destination_patch(client): - assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 - - -def test_destinations_get(client): - assert client.get(api.url_for(DestinationsList)).status_code == 401 - - -def test_destinations_post(client): - assert client.post(api.url_for(DestinationsList), data={}).status_code == 401 - - -def test_destinations_put(client): - assert client.put(api.url_for(DestinationsList), data={}).status_code == 405 - - -def test_destinations_delete(client): - assert client.delete(api.url_for(DestinationsList)).status_code == 405 - - -def test_destinations_patch(client): - assert client.patch(api.url_for(DestinationsList), data={}).status_code == 405 - - -def test_auth_destinations_get(client): - assert client.get(api.url_for(DestinationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 - - -def test_auth_destinations_post(client): - assert client.post(api.url_for(DestinationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 - - -def test_admin_destinations_get(client): - resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) - assert resp.status_code == 200 - assert resp.json == {'items': [], 'total': 0} - - -def test_admin_destinations_crud(client): - assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 - data = {'plugin': {'slug': 'aws-destination', 'pluginOptions': {}}, 'label': 'test', 'description': 'test'} - resp = client.post(api.url_for(DestinationsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) - assert resp.status_code == 200 - assert client.get(api.url_for(Destinations, destination_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) - assert resp.status_code == 200 - assert resp.json['items'][0]['description'] == 'test' - assert client.delete(api.url_for(Destinations, destination_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) - assert resp.status_code == 200 - assert resp.json == {'items': [], 'total': 0} +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_destination_list_patch(client, token, status): + assert client.patch(api.url_for(DestinationsList), data={}, headers=token).status_code == status