Overriding flask.g in flask unit test - python

I'm trying to write some unit tests for my flask app that uses OpenID for authentication. Since it seems like there's no way to log in the test client via OpenID (I asked this question but it hasn't received any responses: Flask OpenID unittest), I was thinking of overriding g.user in my test, so I tried the code snippet from http://flask.pocoo.org/docs/testing/#faking-resources-and-context and it works as expected.
Unfortunately, when using flask-login, g.user is overridden in the before_request wrapper that sets
g.user = current_user
current_user is anonymous, so my test case is broken. One fix is to execute the before_request wrapper code only when in test mode but it seems lame that you need to add test-specific logic in production code. I've tried messing around with the request context too but g.user still gets overridden eventually. Any ideas for a clean way to solve this?

The official documentation has an example in "Faking Resources and Context":
You first need to make sure that g.user is only set if it does not exist yet. You can do this using getattr. Here's a slightly modified example:
#APP.before_request
def set_user():
user = getattr(g, 'user', None)
if user is None:
g.user = concrete_implementation()
return user
By using getattr we give ourselves the chance to "inject" something during testing. If we would not do this, we would overwrite the variable again with the concrete implementation after the unit-tests injected the value.
The next thing we do is hook into the appcontext_pushed event and set the g.user value with a testable value. This happens before the before_request hook. So by the time that is called getattr will return our test-value and the concrete implementation is skipped:
from contextlib import contextmanager
from flask import appcontext_pushed, g
#contextmanager
def user_set(app, user):
def handler(sender, **kwargs):
g.user = user
with appcontext_pushed.connected_to(handler, app):
yield
And with this little helper we can inject something whenever we need to use a testable value:
def test_user_me():
with user_set(app, 'our-testable-user'):
c = app.test_client()
resp = c.get('/protected-resource')
assert resp.data == '...'

Based on this other question in a Flask unit-test, how can I mock objects on the request-global `g` object? what worked for me is the following:
In my app I refactored the login logic that is contained in the before_request into a separate function. Then, I patched that function in my tests so that it returns the specific user I want to use for a bunch of tests. The before_request is still run with the tests but by patching the function it invokes I can now avoid the actual login process.
I am not sure it is the cleanest way but I think it is better than adding test-only logic to your before_request; it is just a refactoring.

Related

Trying to pass an argument to a function, can get through the ''#app.route'' in python flask

I am trying to pass an argument to a function I made into app.route.
In the following code, I create 2 pages, 1 Homepage and 1 Admin page.
I want to permit or deny access to the /admin page depending if I have the parameter ADMINpass set to False or True in my python code directly.
I understand that in this particular situation, it will work, as I don't need to pass the argument, but if I define my admin page in another subscript and import it in mainscript, it will tell me that ''admin takes exactly 1 argument (0 given). So I want this function to work for whatever case.
I don't really worry about security for now as I am testing things.
Thanks alot and apologies if a similar question has already been posted. Here is the code:
###User section, importing parameters
ADMINpass=False
###End of user section
##Main code
#app.route('/') #Home page
def homepage():
return 'Hello, main page here'
#app.route('/admin') #Admin page that I want to access
def admin(ADMINpass): #Trying to pass the argument here will not work, probably because of the app.route
if ADMINpass==False: #If admin not activated in code, redirect to homepage
a=redirect(url_for('homepage'))
if ADMINpass == True: #If admin is activated, permit access
print('This is the admin page')
if __name__== '__main__':
app.run()
There are several issues here.
The #app.route decorator is a wrapper around the admin() function. It provides the extra functionality that does route registration (the thing that allows the '/admin' string to be an endpoint), request handling (you could specify HTTP methods like 'POST' with it), and more (see Flask docs for route()).
So if you did this:
#app.route('/admin') #Admin page that I want to access
def admin(ADMINpass): #Trying to pass the argument here will not work, probably because of the app.route
Flask expects you to use this for parameterising the endpoint itself:
#app.route('/endpoint/<something>')
def func(something):
return something
$ curl localhost:5000/endpoint/value_of_something
value_of_something
I would suggest Miguel Grinberg's guide to decorators for more info on decorators with a focus on Flask.
Coming back to your issue, you could do this (but read on to see why I think you shouldn't):
ADMINpass=False
#app.route('/endpoint')
def admin():
global ADMINpass
return f'ADMINpass is {ADMINpass}'
The problem with global variables is that they could be changed at any point in some other piece of code. This is generally a good way to introduce mistakes in your code and make your code less maintainable and more difficult to test.
With servers, the problem is more fundamental - what if your code grew to look something like this?
ADMINpass=False
#app.route('/endpoint')
def admin():
global ADMINpass
ADMINpass = not ADMINpass
return f'ADMINpass is {ADMINpass}'
Every request (which may be coming from different clients) would change it, so its value is nearly impossible to reason about:
$ curl localhost:5000/endpoint
ADMINpass is False
$ curl localhost:5000/endpoint
ADMINpass is True
$ curl localhost:5000/endpoint
ADMINpass is False
You might be looking for Flask session objects. The values stored in the session object are specific to each client because its implementation is based on cookies. If it's an authentication mechanism implementation you're going towards, this may be exactly what you need.
from flask import session
app.something = 'value_of_something_else'
#app.route('/endpoint')
def admin():
return app.something
$ curl localhost:5000/endpoint
value_of_something_else
If a single static value is what you need, one way to overcome the errors you're likely getting when trying to use the variable would be to use a function you can always call:
def admin_pass():
return False
#app.route('/endpoint')
def admin():
if not admin_pass():
...

Unittest sensitive_post_parameters decorator in django view

I have a view to create new users in my django project.
I am applying the #sensitive_post_parameters decorator to that view to make sure the password isn't logged if there is an unhandled exception or something like that (as indicated in the comments in the source code https://docs.djangoproject.com/en/2.0/_modules/django/views/decorators/debug/).
When I proceed to test the view, I would like to make sure that this protection of the sensitive information is still in place (that I didn't delete the decorator to the function by mistake or something).
I am aware, since the decorator is applied to my function, I can't test it directly from the view tests.
But, for example, with the #login_required decorator, I can test its effects with assertRedirects (as explained here How to test if a view is decorated with "login_required" (Django)).
I have been searching for a way to do that, but I can't find one that works.
I thought of something like this:
def test_senstive_post_parameters(self):
request = RequestFactory().post('create_user', data={})
my_sensitive_parameters = ['password']
self.assertEqual(
request.sensitive_post_parameters,
my_senstive_parameters
)
but that gives me an
AttributeError: 'WSGIRequest' object has no attribute 'sensitive_post_parameters'
Any help would be appreciated.
Even it is telling me I shouldn't be attempting to test this, though I would really like to, as it is seems like an important behaviour that I should make sure remains in my code as it is later modified.
You have created a request using RequestFactory, but you have not actually used it. To test the effect of your view you need to import the view and call it.
from myapp.views import create_user
def test_senstive_post_parameters(self):
request = RequestFactory().post('create_user', data={})
response = create_user(request)
my_sensitive_parameters = ['password']
self.assertEqual(
request.sensitive_post_parameters,
my_senstive_parameters
)

Unable to test if method was called with Mock by Celery

I am having an issue while testing if Celery task was executed successfully. My task calls another reusable function that sends request to another server:
#shared_task
def my_task():
send_request_to_server()
I am using mock to check if function send_request_to_server() was called. Task is triggered via Django Admin changelist action, so the final test looks like this:
#override_settings(CELERY_TASK_ALWAYS_EAGER=True)
#mock.patch(‘helpers.send_request_to_server’)
def my_test(self, mocked_function):
change_url = reverse('admin:app_model_changelist')
response = self.client.post(change_url, {'action': ‘mark’, '_selected_action': [1]}, follow=True)
self.assertTrue(mocked_function.called)
I am 100% percent sure, that this test at some point calls send_request_to_server() function, since this function also creates a file and it is quite easy to notice that. But mocked_function attribute “called” still holds value of False. Also I am quite certain, that the mock decorator path is correct, since simplified test passes without any problems:
#override_settings(CELERY_TASK_ALWAYS_EAGER=True)
#mock.patch(‘helpers.send_request_to_server’)
def my_test(self, mocked_function):
send_request_to_server()
self.assertTrue(mocked_function.called)
Even if the function is called, what could cause the False value of mocked_function.called? Maybe it has something to do with multiple threads? Thank you!

python pytest for testing the requests and response

I am a beginner to using pytest in python and trying to write test cases for the following method which get the user address when correct Id is passed else rises custom error BadId.
def get_user_info(id: str, host='127.0.0.1', port=3000 ) -> str:
uri = 'http://{}:{}/users/{}'.format(host,port,id)
result = Requests.get(uri).json()
address = result.get('user',{}).get('address',None)
if address:
return address
else:
raise BadId
Can someone help me with this and also can you suggest me what are the best resources for learning pytest? TIA
Your test regimen might look something like this.
First I suggest creating a fixture to be used in your various method tests. The fixture sets up an instance of your class to be used in your tests rather than creating the instance in the test itself. Keeping tasks separated in this way helps to make your tests both more robust and easier to read.
from my_package import MyClass
import pytest
#pytest.fixture
def a_test_object():
return MyClass()
You can pass the test object to your series of method tests:
def test_something(a_test_object):
# do the test
However if your test object requires some resources during setup (such as a connection, a database, a file, etc etc), you can mock it instead to avoid setting up the resources for the test. See this talk for some helpful info on how to do that.
By the way: if you need to test several different states of the user defined object being created in your fixture, you'll need to parametrize your fixture. This is a bit of a complicated topic, but the documentation explains fixture parametrization very clearly.
The other thing you need to do is make sure any .get calls to Requests are intercepted. This is important because it allows your tests to be run without an internet connection, and ensures they do not fail as a result of a bad connection, which is not the thing you are trying to test.
You can intercept Requests.get by using the monkeypatch feature of pytest. All that is required is to include monkeypatch as an input parameter to the test regimen functions.
You can employ another fixture to accomplish this. It might look like this:
import Requests
import pytest
#pytest.fixture
def patched_requests(monkeypatch):
# store a reference to the old get method
old_get = Requests.get
def mocked_get(uri, *args, **kwargs):
'''A method replacing Requests.get
Returns either a mocked response object (with json method)
or the default response object if the uri doesn't match
one of those that have been supplied.
'''
_, id = uri.split('/users/', 1)
try:
# attempt to get the correct mocked json method
json = dict(
with_address1 = lambda: {'user': {'address': 123}},
with_address2 = lambda: {'user': {'address': 456}},
no_address = lambda: {'user': {}},
no_user = lambda: {},
)[id]
except KeyError:
# fall back to default behavior
obj = old_get(uri, *args, **kwargs)
else:
# create a mocked requests object
mock = type('MockedReq', (), {})()
# assign mocked json to requests.json
mock.json = json
# assign obj to mock
obj = mock
return obj
# finally, patch Requests.get with patched version
monkeypatch.setattr(Requests, 'get', mocked_get)
This looks complicated until you understand what is happening: we have simply made some mocked json objects (represented by dictionaries) with pre-determined user ids and addresses. The patched version of Requests.get simply returns an object- of type MockedReq- with the corresponding mocked .json() method when its id is requested.
Note that Requests will only be patched in tests that actually use the above fixture, e.g.:
def test_something(patched_requests):
# use patched Requests.get
Any test that does not use patched_requests as an input parameter will not use the patched version.
Also note that you could monkeypatch Requests within the test itself, but I suggest doing it separately. If you are using other parts of the requests API, you may need to monkeypatch those as well. Keeping all of this stuff separate is often going to be easier to understand than including it within your test.
Write your various method tests next. You'll need a different test for each aspect of your method. In other words, you will usually write a different test for the instance in which your method succeeds, and another one for testing when it fails.
First we test method success with a couple test cases.
#pytest.mark.parametrize('id, result', [
('with_address1', 123),
('with_address2', 456),
])
def test_get_user_info_success(patched_requests, a_test_object, id, result):
address = a_test_object.get_user_info(id)
assert address == result
Next we can test for raising the BadId exception using the with pytest.raises feature. Note that since an exception is raised, there is not a result input parameter for the test function.
#pytest.mark.parametrize('id', [
'no_address',
'no_user',
])
def test_get_user_info_failure(patched_requests, a_test_object, id):
from my_package import BadId
with pytest.raises(BadId):
address = a_test_object.get_user_info(id)
As posted in my comment, here also are some additional resources to help you learn more about pytest:
link
link
Also be sure to check out Brian Okken's book and Bruno Oliveira's book. They are both very helpful for learning pytest.

Flask: How do I get the returned value from a function that uses #app.route decorator?

So I'm pretty new to Flask and I'm trying to make my mind around one thing. So, if I understand well when you write a function within a Flask app and you use the #app.route decorator in that function, it only runs when you hit that path/url.
I have a small oauth app written in Flask that goes through all the authorization flow and then it return the token.
My question is how do I get that token from the #decorated function? For example, lets say I have something like this:
#app.route(/token/)
def getToken(code): #code from the callback url.
#/Stuff to get the Token/
#/**********************/
return token
If I hit the (/token/) url-path the function returns the token. But now I need to get that token and use it in another function to write and read from the API I just got the token from. My initial thought was doing this:
token = getToken(code)
But if I do that, I get this error:
RuntimeError: working outside of request context
So again, my question is, how do I get the token so I can pass it as a parameter to other functions.
Extract the token generation code into a separate function, so that you can call it from anywhere, including the view function. It's a good practice to keep the application logic away from the view, and it also helps with unit testing.
I assume your route includes a placeholder for code, which you skipped:
def generateToken(code):
#/Stuff to get the Token/
#/**********************/
return token
#app.route('/token/<string:code>')
def getToken(code):
return generateToken(code)
Just keep in mind that generateToken shouldn't depend on the request object. If you need any request data (e.g. HTTP header), you should pass it explicitly in arguments. Otherwise you will get the "working outside of request context" exception you mentioned.
It is possible to call request-dependent views directly, but you need to mock the request object, which is a bit tricky. Read the request context documentation to learn more.
not sure what the context is. You could just call the method.
from yourmodule import get_token
def yourmethod():
token = get_token()
Otherwise, you could use the requests library in order to retrieve the data from the route
>>> import requests
>>> response = requests.get('www.yoursite.com/yourroute/')
>>> print response.text
If you're looking for unittests, Flask comes with a mock client
def test_get_token():
resp = self.app.get('/yourroute')
# do something with resp.data

Categories