I'm writing some unit tests for a Django project, and I was wondering if its possible (or necessary?) to test some of the decorators that I wrote for it.
Here is an example of a decorator that I wrote:
class login_required(object):
def __init__(self, f):
self.f = f
def __call__(self, *args):
request = args[0]
if request.user and request.user.is_authenticated():
return self.f(*args)
return redirect('/login')
Simply:
from nose.tools import assert_equal
from mock import Mock
class TestLoginRequired(object):
def test_no_user(self):
func = Mock()
decorated_func = login_required(func)
request = prepare_request_without_user()
response = decorated_func(request)
assert not func.called
# assert response is redirect
def test_bad_user(self):
func = Mock()
decorated_func = login_required(func)
request = prepare_request_with_non_authenticated_user()
response = decorated_func(request)
assert not func.called
# assert response is redirect
def test_ok(self):
func = Mock(return_value='my response')
decorated_func = login_required(func)
request = prepare_request_with_ok_user()
response = decorated_func(request)
func.assert_called_with(request)
assert_equal(response, 'my response')
The mock library helps here.
A decorator like this might be tested simply thanks to duck-typing. Just supply a mock object to the call function, that seems to hold and act as a request, and see if you get the expected behaviour.
When it is necessary to use unit tests is quite individual i'd say. The example you give contain such basic code that one might say that it isn't necessary. But then again, the cost of testing a class like this is equally low.
Example for Django's UnitTest
class TestCaseExample(TestCase):
def test_decorator(self):
request = HttpRequest()
# Set the required properties of your request
function = lambda x: x
decorator = login_required(function)
response = decorator(request)
self.assertRedirects(response)
In general, the approach I've utilized is the following:
Set up your request.
Create a dummy function to allow the decorator magic to happen (lambda). This is where you can control the number of arguments that will eventually be passed into the decorator.
Conduct an assertion based on your decorator's response.
For those looking for a django type decorator test, this is how I ran tests on my custom django decorator:
common/decorators.py
from functools import wraps
from django.http import Http404
def condition_passes_test(test_func, fail_msg=''):
"""
Decorator for views that checks that a condition passes the given test,
raising a 404 if condition fails
"""
def decorator(view_func):
#wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if test_func():
return view_func(request, *args, **kwargs)
else:
raise Http404(fail_msg)
return _wrapped_view
return decorator
The test:
import django
from django.test import TestCase
from django.http import Http404
from django.http import HttpResponse
from django.test import RequestFactory
from common import decorators
class TestCommonDecorators(TestCase):
def shortDescription(self):
return None
def test_condition_passes_test1(self):
"""
Test case where we raise a 404 b/c test function returns False
"""
def func():
return False
#decorators.condition_passes_test(func)
def a_view(request):
return HttpResponse('a response for request {}'.format(request))
request = RequestFactory().get('/a/url')
with self.assertRaises(django.http.response.Http404):
a_view(request)
def test_condition_passes_test2(self):
"""
Test case where we get 200 b/c test function returns True
"""
def func():
return True
#decorators.condition_passes_test(func)
def a_view(request):
return HttpResponse('a response for request {}'.format(request))
request = RequestFactory().get('/app_health/z')
request = a_view(request)
self.assertEquals(request.status_code, 200)
NOTE: I Am using it as such on my view where my_test_function returns True or False:
#method_decorator(condition_passes_test(my_test_function, fail_msg="Container view disabled"), name='dispatch')
Related
I'm developing Flask application and I have a decorator called login_required which checks if the user is logged in, and sends the current user to the next function.
def login_required(function):
#wraps(function)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'a valid token is missing'}), 401
try:
data = jwt.decode(token, app.secret_key)
current_user = User.query.filter_by(username=data['username']).first()
except:
return jsonify({'message': 'token is invalid'}), 401
return function(current_user, *args, **kwargs)
return decorator
So, the callback function is declared like this.
#blueprint.route('/settings', methods=['GET'])
#login_required
def settings(current_user):
# here I check if the current_user is admin or not
...
# function body
Now I want to implement an admin_required decorator which depends on login_required decorator, to be easy using it within functions like this.
#blueprint.route('/settings', methods=['GET'])
#admin_required
def settings(current_user):
...
# function body
How can I achieve this?
So you can create your functionality like this
def decorator1(function):
def fun(*args, **kwargs):
user = function(*args, **kwargs)
print("here")
return user
return fun
def decorator2(function):
def fun(*args, **kwargs):
# firslty i will decorate function with decorator1
decor1 = decorator1(function)
decor1_result = decor1(*args, **kwargs)
# do operation with above result in your case now check if user is admin
print("here in decor2")
return decor1_result + 5
return fun
#decorator2
def test_function(a,b):
return a+b
# Now you want to access a+b and then add c to it
I have included comments for better understanding.
In your case decorator1 will be login_required decorator
and decorator2 will be admin_check decorator. So while creating admin check decorator you can access login_required decorator inside it.
I use tornado and a jwt decorator as below:
def jwtauth(handler_class):
"""
Tornado JWT Auth Decorator
"""
def wrap_execute(handler_execute):
def require_auth(handler, kwargs):
auth = handler.request.headers.get(AUTHORIZATION_HEADER)
if auth:
parts = auth.split()
if not is_valid_header(parts):
return_header_error(handler)
token = parts[1]
try:
result = jwt.decode(
token,
SECRET_KEY,
options=jwt_options
)
except Exception as err:
return_auth_error(handler, str(err))
else:
handler._transforms = []
handler.write(MISSING_AUTHORIZATION_KEY)
handler.finish()
return result
def _execute(self, transforms, *args, **kwargs):
try:
result = require_auth(self, kwargs)
except Exception:
return False
return handler_execute(self, transforms, *args, **kwargs)
return _execute
handler_class._execute = wrap_execute(handler_class._execute)
return handler_class
#jwtauth
class MyHandler(tornado.web.RequestHandler):
def post(self):
unit = json.loads(self.request.body.decode('utf-8'))
# TODO:
# get the result from jwtauth decorator and use it here
print(result) # The result from jwtauth
Now, I'd like to get the jwt decode result and pass into MyHandler for further verification. Can I do it? I checked most of the comment that I can pass the parameter to a decorator but I cannot get from it. Is is possible to pass the jwtauth result to my function?
A class decorator takes your class and spits out a new version of your class (usually with some features added to it). In this case, the #jwtauth decorator takes your class and spits out a new class that makes sure that each request is checked for a valid JWT token in the authorization header. tornado.web.RequestHandler._execute internally calls post. The current behavior is that if the JWT token auth fails, then post will never be called.
In short, you probably want to just raise an error below instead of returning False.
try:
result = require_auth(self, kwargs)
except Exception:
return False
If you need to add more logic about what kind of error to raise, then you probably want to pass that into the decorator along with the class.
I would like to mimic the behavior of csrf_exempt(see here and here). The problem with following the CSRF middleware way of doing it is that it uses from django.utils.deprecation import MiddlewareMixin, which is deprecated. Is there a way I can do this using the recommended way of creating middleware in Django 1.11? I am also aware of from django.utils.decorators import decorator_from_middleware but that's the opposite of what I want. Anybody have any ideas?
Edit: The following works but is not the prettiest, recommendations?
class MyMiddleware(object):
def __init(self, get_response):
# initialization
def process_view(self, request, view_func, view_args, view_kwargs):
if getattr(view_func, "my_middleware_exempt", False):
request.exempt = True
return None
# Perform request logic
def __call__(self, request):
# __call__ has to be included, even though I don't want it.
response = self.get_response(request)
# If I wanted to perform logic after the request,
# I would have to check the flag I set on the request
# in process_view
if not request.exempt:
# perform logic
return response
def exempt_decorator(f):
#wraps(f)
def decorator(*args, **kwargs):
return f(*args, **kwargs)
decorator.my_middleware_exempt = True
return decorator
I think my problem is related on the way I structured my pyramid project.
What I want to accomplish is to make my code runs on all views, I don't want to paste same codes on all views. Its like I will include the code in all views by just simply calling it. This is my code.
my wizard module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
import json
class WizardView:
def __init__(self, request):
self.request = request
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
my bill module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
class BillView:
def __init__(self, request):
self.request = request
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
As you can see I have to paste this code twice (this code checks if session exist, if not, then redirect user to login page)
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
I've tried to search and I think what I need is some sort of auto loader? How can I apply this on pyramid? Or should I stick with this process?
If you wish to return exactly the same thing from both (many) views, then the best way is to use inheritance.
class GenericView:
def __init__(self, request):
self.request = request
def generic_response(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
Use generic_response in WizardView and BillView
class WizardView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
return self.generic_response()
class BillView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
return self.generic_response()
If you want to just check (and redirect) when session does not exists and otherwise proceed normally you could use custom exceptions and corresponding views.
First define custom exception
class SessionNotPresent(Exception):
pass
And view for this exception
#view_config(context=SessionNotPresent)
def handle_no_session(context, request):
# context is our custom exception, request is normal request
request.route_url('login')
return HTTPFound(location=url)
Then just check if session exists in parent constructor
class SessionView:
def __init__(self, request):
self.request = request
if not self.request.session:
raise SessionNotPresent() # Pyramid will delegate request handilng to handle_no_request
In views simply extend SessionView and non existing sessions will be handeled by handle_no_session.
class WizardView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
class BillView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
You could easily add additional parameters to exception (redirect_url, ...)
For exception handling see http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/pylons/exceptions.html
You can use events: http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/events.html
Example:
from pyramid.events import subscriber, NewRequest
#subscriber(NewRequest)
def check_session(event):
if not event.request.session:
raise HTTPFound(location=event.request.route_path('login'))
I have a python module security.py which defines a decorator authorized().
I want to test the decorator. The decorator will receive a flask request header.
The decorator is sth like this:
def authorized():
def _authorized(wrapped_func):
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
return _authorized
I want to mock the flask request header using a #patch decorator.The test I wrote is sth like this:
#patch('security.request.headers', Mock(side_effect=lambda *args, **kwargs: MockHeaders({})))
def test_no_authorization_token_in_header(self):
#security.authorized()
def decorated_func(token='abc'):
return access_token
result = decorated_func()
self.assertEqual(result, None)
class MockHeaders(object):
def __init__(self, json_data):
self.json_data=json_data
but I always get the following error:
name = 'request'
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
How should I do it right?
Mock the whole request object to avoid triggering the context lookup:
#patch('security.request')
and build up the mock from there:
#patch('security.request')
def test_no_authorization_token_in_header(self, mock_request):
mock_request.headers= {}
#security.authorized()
def decorated_func(token='abc'):
return token
self.assertRaises(Abort):
result = decorated_func()
Since a missing token results in an Abort exception being raised, you should explicitly test for that. Note that the request.headers attribute is not called anywhere, so side_effect or return_value attributes don't apply here.
I ignored MockHeaders altogether; your decorator is not using json_data and your implementation is lacking a __contains__ method, so in tests wouldn't work on that. A plain dictionary suffices for the current code-under-test.
Side note: authorized is a decorator factory, but it doesn't take any parameters. It'd be clearer if you didn't use a factory there at all. You should also use functools.wraps() to ensure that any metadata other decorators add are properly propagated:
from functools import wraps
def authorized(wrapped_func):
#wraps(wrapped_func)
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
then use the decorator directly (so no call):
#security.authorized
def decorated_func(token='abc'):
return access_token