From d2f45b1c2076cbe81845080cb53a9518a076e612 Mon Sep 17 00:00:00 2001 From: David Ignjic Date: Tue, 15 Jun 2021 22:04:41 +0200 Subject: [PATCH] Enable documented global decorators - tests for global and resource level decorators --- flask_combo_jsonapi/api.py | 5 +++ tests/test_sqlalchemy_data_layer.py | 60 +++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/flask_combo_jsonapi/api.py b/flask_combo_jsonapi/api.py index f12764b..957a0c8 100644 --- a/flask_combo_jsonapi/api.py +++ b/flask_combo_jsonapi/api.py @@ -91,6 +91,11 @@ def route(self, resource, view, *urls, **kwargs): resource.view = view url_rule_options = kwargs.get('url_rule_options') or dict() + if hasattr(resource, 'decorators'): + resource.decorators += self.decorators + else: + resource.decorators = self.decorators + view_func = resource.as_view(view) if 'blueprint' in kwargs: diff --git a/tests/test_sqlalchemy_data_layer.py b/tests/test_sqlalchemy_data_layer.py index 934e0a4..550e63e 100644 --- a/tests/test_sqlalchemy_data_layer.py +++ b/tests/test_sqlalchemy_data_layer.py @@ -4,11 +4,12 @@ from sqlalchemy import create_engine, Column, Integer, DateTime, String, ForeignKey from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base -from flask import Blueprint, make_response, json +from flask import Blueprint, make_response, json, request from marshmallow_jsonapi.flask import Schema, Relationship from marshmallow import Schema as MarshmallowSchema from marshmallow_jsonapi import fields from marshmallow import ValidationError +from werkzeug.exceptions import Unauthorized from flask_combo_jsonapi import Api, ResourceList, ResourceDetail, ResourceRelationship, JsonApiException from flask_combo_jsonapi.pagination import add_pagination_links @@ -231,9 +232,26 @@ def address(session, address_model): @pytest.fixture(scope="module") -def dummy_decorator(): +def custom_auth_decorator(): def deco(f): def wrapper_f(*args, **kwargs): + auth = request.headers.get("auth", None) + if auth == '123': + raise Unauthorized() + return f(*args, **kwargs) + + return wrapper_f + + yield deco + + +@pytest.fixture(scope="module") +def custom_auth_decorator_2(): + def deco(f): + def wrapper_f(*args, **kwargs): + auth = request.headers.get("auth", None) + if auth == '1234': + raise Unauthorized() return f(*args, **kwargs) return wrapper_f @@ -405,7 +423,7 @@ def before_delete_object_(self, obj, view_kwargs): @pytest.fixture(scope="module") -def person_list(session, person_model, dummy_decorator, person_schema, before_create_object): +def person_list(session, person_model, person_schema, before_create_object): class PersonList(ResourceList): schema = person_schema data_layer = { @@ -413,8 +431,6 @@ class PersonList(ResourceList): "session": session, "methods": {"before_create_object": before_create_object}, } - get_decorators = [dummy_decorator] - post_decorators = [dummy_decorator] get_schema_kwargs = dict() post_schema_kwargs = dict() @@ -459,7 +475,8 @@ class PersonList(ResourceList): @pytest.fixture(scope="module") -def person_detail(session, person_model, dummy_decorator, person_schema, before_update_object, before_delete_object): +def person_detail(session, person_model, person_schema, before_update_object, before_delete_object, + custom_auth_decorator_2): class PersonDetail(ResourceDetail): schema = person_schema data_layer = { @@ -468,25 +485,19 @@ class PersonDetail(ResourceDetail): "url_field": "person_id", "methods": {"before_update_object": before_update_object, "before_delete_object": before_delete_object}, } - get_decorators = [dummy_decorator] - patch_decorators = [dummy_decorator] - delete_decorators = [dummy_decorator] get_schema_kwargs = dict() patch_schema_kwargs = dict() delete_schema_kwargs = dict() + decorators = (custom_auth_decorator_2,) yield PersonDetail @pytest.fixture(scope="module") -def person_computers(session, person_model, dummy_decorator, person_schema): +def person_computers(session, person_model, person_schema): class PersonComputersRelationship(ResourceRelationship): schema = person_schema data_layer = {"session": session, "model": person_model, "url_field": "person_id"} - get_decorators = [dummy_decorator] - post_decorators = [dummy_decorator] - patch_decorators = [dummy_decorator] - delete_decorators = [dummy_decorator] yield PersonComputersRelationship @@ -566,7 +577,7 @@ class ComputerList(ResourceList): @pytest.fixture(scope="module") -def computer_detail(session, computer_model, dummy_decorator, computer_schema): +def computer_detail(session, computer_model, computer_schema): class ComputerDetail(ResourceDetail): schema = computer_schema data_layer = {"model": computer_model, "session": session} @@ -576,7 +587,7 @@ class ComputerDetail(ResourceDetail): @pytest.fixture(scope="module") -def computer_owner(session, computer_model, dummy_decorator, computer_schema): +def computer_owner(session, computer_model, computer_schema): class ComputerOwnerRelationship(ResourceRelationship): schema = computer_schema data_layer = {"session": session, "model": computer_model} @@ -628,6 +639,7 @@ def register_routes( client, app, api_blueprint, + custom_auth_decorator, person_list, person_detail, person_computers, @@ -643,7 +655,7 @@ def register_routes( string_json_attribute_person_detail, string_json_attribute_person_list, ): - api = Api(blueprint=api_blueprint) + api = Api(blueprint=api_blueprint, decorators=(custom_auth_decorator,)) api.route(person_list, "person_list", "/persons") api.route(person_list_custom_qs_manager, "person_list_custom_qs_manager", "/persons_qs") api.route(person_detail, "person_detail", "/persons/") @@ -941,6 +953,7 @@ def test_post_list_nested(client, register_routes, computer): assert response.status_code == 201 assert json.loads(response.get_data())["data"]["attributes"]["tags"][0]["key"] == "k1" + def test_post_list_nested_field(client, register_routes): """ Test a schema contains a nested field is correctly serialized and deserialized @@ -991,6 +1004,19 @@ def test_get_detail(client, register_routes, person): assert response.status_code == 200 +def test_get_detail_custom_auth_decorator_global(client, register_routes, person): + with client: + response = client.get("/persons/" + str(person.person_id), content_type="application/vnd.api+json", + headers={'auth': '123'}) + assert response.status_code == 401 + +def test_get_detail_custom_auth_decorator_resource_level(client, register_routes, person): + with client: + response = client.get("/persons/" + str(person.person_id), content_type="application/vnd.api+json", + headers={'auth': '1234'}) + assert response.status_code == 401 + + def test_patch_detail(client, register_routes, computer, person): payload = { "data": {