Python nose test failing on JSON response - python

This is the method in ReportRunner class in report_runner.py in my Flask-Restful app:
class ReportRunner(object):
def __init__(self):
pass
def setup_routes(self, app):
app.add_url_rule("/run_report", view_func=self.run_report)
def request_report(self, key):
# code #
def key_exists(self, key):
# code #
def run_report(self):
key = request.args.get("key", "")
if self.key_exists(key):
self.request_report(report_type, key)
return jsonify(message = "Success! Your report has been created.")
else:
response = jsonify({"message": "Error => report key not found on server."})
response.status_code = 404
return response
and the nose test calls the URL associated with that route
def setUp(self):
self.setup_flask()
self.controller = Controller()
self.report_runner = ReportRunner()
self.setup_route(self.report_runner)
def test_run_report(self):
rr = Report(key = "daily_report")
rr.save()
self.controller.override(self.report_runner, "request_report")
self.controller.expectAndReturn(self.report_runner.request_report("daily_report"), True )
self.controller.replay()
response = self.client.get("/run_report?key=daily_report")
assert_equals({"message": "Success! Your report has been created."}, response.json)
assert_equals(200, response.status_code)
and the test was failing with the following message:
AttributeError: 'Response' object has no attribute 'json'
but according to the docs it seems that this is how you do it. Do I change the return value from the method, or do I need to structure the test differently?
The test is now passing written like this:
json_response = json.loads(response.data)
assert_equals("Success! Your report has been created.", json_response["message"])
but I'm not clear on the difference between the two approaches.

According to Flask API Response object doesn't have attribute json (it's Request object that has it). So, that's why you get exception. Instead, it has generic method get_data() that returns the string representation of response body.
json_response = json.loads(response.get_data())
assert_equals("Success! Your report has been created.", json_response.get("message", "<no message>"))
So, it's close to what you have except:
get_data() is suggested instead of data as API says: This should not be used and will eventually get deprecated.
reading value from dictionary with get() to not generate exception if key is missing but get correct assert about missing message.
Check this Q&A also.

Related

How to call a normal python function along with header information without using requests

Details of application:
UI: Angular
Backend: Python Flask (using Swagger)
Database: MongoDB
We have a few backend python methods which will be called from the UI side to do CURD operations on the database.
Each of the methods has a decorator which will check the header information to ensure that only a genuine person can call the methods.
From the UI side when these API's are called, this authorization decorator is not creating any problem and a proper response is returned to the UI (as we are passing the header information also to the request)
But now we are writing unit test cases for the API's. Here each test case will call the backend method and because of the authorization decorator, I am getting errors and not able to proceed. How can I handle this issue?
backend_api.py
--------------
from commonlib.auth import require_auth
#require_auth
def get_records(record_id):
try:
record_details = records_coll.find_one({"_id": ObjectId(str(record_id))})
if record_details is not None:
resp = jsonify({"msg": "Found Record", "data": str(record_details)})
resp.status_code = 200
return resp
else:
resp = jsonify({"msg": "Record not found"})
resp.status_code = 404
return resp
except Exception as ex:
resp = jsonify({"msg": "Exception Occured",'Exception Details': ex}))
resp.status_code = 500
return resp
commonlib/auth.py
-----------------
### some lines of code here
def require_auth(func):
"""
Decorator that can be added to a function to check for authorization
"""
def wrapper(*args, **kwargs):
print(*args,**kwargs)
username = get_username()
security_log = {
'loginId': username,
'securityProtocol': _get_auth_type(),
}
try:
if username is None:
raise SecurityException('Authorization header or cookie not found')
if not is_auth_valid():
raise SecurityException('Authorization header or cookie is invalid')
except SecurityException as ex:
log_security(result='DENIED', message=str(ex))
unauthorized(str(ex))
return func(*args, **kwargs)
return wrapper
test_backend_api.py
-------------------
class TestBackendApi(unittest.TestCase):
### some lines of code here
#mock.patch("pymongo.collection.Collection.find_one", side_effect=[projects_json])
def test_get_records(self, mock_call):
from backend_api import get_records
ret_resp = get_records('61729c18afe7a83268c6c9b8')
final_response = ret_resp.get_json()
message1 = "return response status code is not 200"
self.assertEqual(ret_resp.status_code, 200, message1)
Error snippet :
---------------
E RuntimeError: Working outside of request context.
E
E This typically means that you attempted to use functionality that needed
E an active HTTP request. Consult the documentation on testing for
E information about how to avoid this problem.

How to set the return value of a get request in an unit test?

I am trying to set the return value of a get request in python in order to do a unit test, which tests if the post request is called with the correct arguments. Assume I have the following code to test
# main.py
import requests
from django.contrib.auth.models import User
def function_with_get():
client = requests.session()
some_data = str(client.get('https://cool_site.com').content)
return some_data
def function_to_test(data):
for user in User.objects.all():
if user.username in data:
post_data = dict(data=user.username)
else:
post_data = dict(data='Not found')
client.post('https://not_cool_site.com', data=post_data)
#test.py
from unittest import mock
from unittest import TestCase
from main import function_with_get, function_to_test
class Testing(TestCase):
#mock.patch('main.requests.session')
def test_which_fails_because_of_get(self, mock_sess):
mock_sess.get[0].return_value.content = 'User1'
data = function_with_get()
function_to_test(data)
assertIn('Not Found', mock_sess.retrun_value.post.call_args_list[1])
This, sadly, does not work and I have also tried to set it without content, however, I get an error AttributeError: 'str' object has no attribute 'content'
What would be the correct way to set the return_value of the get request, so that I can test the arguments of the post request?
I think you almost have it, except you're missing the return value for session() - because session is instantiated to create the client instance. I think you can drop the [0] too.
Try:
mock_sess.return_value.get.return_value.content = 'User1'
Try with .text because this should work for strings.
s = requests.Session()
s.get('https://httpbin.org/cookies/ set/sessioncookie/123456789')
r = s.get('https://httpbin.org/ cookies')
print(r.text)
http://docs.python-requests.org/en/master/user/advanced/

how to test the result of an API without requesting it

I've got a function like:
def request_API(request_url): #To test
fail_request = -1
response = requests.get(request_url)
if response.ok:
infos = json.loads(response.text)
if infos.has_key("movie"):
return infos["movie"]
if infos.has_key("tvseries"):
return infos["tvseries"]
print "Allocine API Request Error"
return fail_request
I did a test like:
def test_should_fail_to_request(self):
#GIVEN
response = json.dumps({"error":{"code":0,"$":"No result"}})
requests.get = mock.MagicMock(return_value=response)
#WHEN
response = my_mod.request_allocine_API("") #requests mocked so we don't need an URL
#THEN
self.assertEqual(response, -1, "There should be an error in the API")
But I've got an error:
AttributeError: 'str' object has no attribute 'ok'
I know it come from the fact that when I mock request.get I return a JSON. My question is what is the proper way to do it. Have I to recreate an object requests or is there more simple way to do so.
You are mocking requests.get, which normally returns an Response object, to instead return a plain string. Try having it return an Response object instead:
from mock import patch
from requests import Response
def test_should_fail_to_request(self):
mock_response = Response()
mock_response.status_code = 404
mock_response.body = json.dumps({"error":{"code":0,"$":"No result"}})
with patch('requests.get', return_value=mock_response):
response = my_mod.request_allocine_API("")
self.assertEqual(response, -1, "There should be an error in the API")
I use requests-mock library which works well.
the document is in : https://requests-mock.readthedocs.org/en/latest/
The best feature is supporting for regex.

Defining request and response objects for webapp2 handlers on GAE python

I already have a REST API with GAE python built using webapp2. I was looking at protorpc messages used in protorpc and Cloud Enpoints and really like how I can define the request and responses. Is there a way to incorporate that into my webapp2 handlers?
Firstly I use a decorator on the webapp2 method. I define the decorator as follows*:
# Takes webapp2 request (self.request on baseHandler) and converts to defined protoRpc object
def jsonMethod(requestType, responseType, http_method='GET'):
"""
NB: if both URL and POST parameters are used, do not use 'required=True' values in the protorpc Message definition
as this will fail on one of the sets of parms
"""
def jsonMethodHandler(handler):
def jsonMethodInner(self, **kwargs):
requestObject = getJsonRequestObject(self, requestType, http_method, kwargs)
logging.info(u'request={0}'.format(requestObject))
response = handler(self, requestObject) # Response object
if response:
# Convert response to Json
responseJson = protojson.encode_message(response)
else:
responseJson = '{}'
logging.info(u'response json={0}'.format(responseJson))
if self.response.headers:
self.response.headers['Content-Type'] = 'application/json'
if responseJson:
self.response.write(responseJson)
self.response.write('')
return jsonMethodInner
return jsonMethodHandler
The jsonMethod decorator uses a protorpc message for 'requestType' and 'responseType'.
I have constrained the http_method to be either GET, POST or DELETE for a method; you may wish to change this.
Note that this decorator must be applied to instance methods on a webapp2.RequestHandler class (see the example below) as it needs to access the webapp2 request and response objects.
The protorpc message is populated in getJsonRequestObject():
def getJsonRequestObject(self, requestType, http_method, kwargs):
"kwargs: URL keywords eg: /api/test/<key:\d+> => key"
"request.GET: used for 'GET' URL query string arguments"
"request.body: used for 'POST' or 'DELETE' form fields"
logging.info(u'URL parameters: {0}'.format(kwargs))
if http_method == 'POST' or http_method == 'DELETE':
requestJson = self.request.body
if requestJson == None:
requestJson = '' # Cater for no body (eg: IE10)
try:
logging.info("Content type = {}".format(self.request.content_type))
logRequest = requestJson if len(requestJson) < 1024 else requestJson[0:1024]
try:
logging.info(u'Request JSON: {0}'.format(logRequest))
except:
logging.info("Cannot log request JSON (invalid character?)")
postRequestObject = protojson.decode_message(requestType, requestJson)
except:
logError()
raise
if self.request.query_string:
# combine POST and GET parameters [GET query string overrides POST field]
getRequestObject = protourlencode.decode_message(requestType, self.request.query_string)
requestObject = combineRequestObjects(requestType, getRequestObject, postRequestObject)
else:
requestObject = postRequestObject
elif http_method == 'GET':
logging.info(u'Query strings: {0}'.format(self.request.query_string))
requestObject = protourlencode.decode_message(requestType, self.request.query_string)
logging.info(u'Request object: {0}'.format(requestObject))
else:
raise ValidationException(u'Invalid HTTP method: {0}'.format(http_method))
if len(kwargs) > 0:
#Combine URL keywords (kwargs) with requestObject
queryString = urllib.urlencode(kwargs)
keywordRequestObject = protourlencode.decode_message(requestType, queryString)
requestObject = combineRequestObjects(requestType, requestObject, keywordRequestObject)
return requestObject
getJsonRequestObject() handles GET, POST and webapp2 URL arguments (note: these are entered as kwargs).
combineRequestObjects() combines two objects of the requestType message:
def combineRequestObjects(requestType, request1, request2):
"""
Combines two objects of requestType; Note that request2 values will override request1 values if duplicated
"""
members = inspect.getmembers(requestType, lambda a:not(inspect.isroutine(a)))
members = [m for m in members if not m[0].startswith('_')]
for key, value in members:
val = getattr(request2, key)
if val:
setattr(request1, key, val)
return request1
Finally, a decorated webapp2 method example:
from protorpc import messages, message_types
class FileSearchRequest(messages.Message):
"""
A combination of file metadata and file information
"""
filename = messages.StringField(1)
startDateTime = message_types.DateTimeField(2)
endDateTime = message_types.DateTimeField(3)
class ListResponse(messages.Message):
"""
List of strings response
"""
items = messages.StringField(1, repeated=True)
...
class FileHandler(webapp2.RequestHandler):
#jsonMethod(FileSearchRequest, ListResponse, http_method='POST')
def searchFiles(self, request):
# Can now use request.filename etc
...
return ListResponse(items=items)
Hopefully this will give you some idea of how to go about implementing your own webapp2/protorpc framework.
You can also check and see how Cloud Endpoints is implementing their protorpc message handling. You may also need to dive into the protorpc code itself.
Please note that I have attempted to simplify my existing implementation, so you may come across various issues that you will need to address in your implementation.
In addition methods like 'logError()' and classes like 'ValidationException' are non-standard, so you will need to replace them as you see fit.
You may also wish to remove the logging at some point.

Python unittest mock an API key

I'm writing unit tests for the Client class of client.py, which queries an API. Each test instantiates the client with c = client.Client("apikey"). Running one test at a time works fine, but running them all (e.g. with py.test) I get a 401: "Exception: Response 401: Unauthorized Access. Requests must contain a valid api-key."
I have a valid API key but this should not be included in the unit tests. I would appreciate an explanation of why "apikey" works for only one query. More specifically, how can I mock out the calls to the API? Below is an example unit test:
def testGetContextReturnFields(self):
c = client.Client("apikey")
contexts = c.getContext("foo")
assert(isinstance(contexts[0]["context_label"], str))
assert(contexts[0]["context_id"] == 0)
Separate out the tests for API calls and for the Client.getContext() method. For explicitly testing the API calls, patch a request object...
import client
import httpretty
import requests
from mock import Mock, patch
...
def testGetQueryToAPI(self):
"""
Tests the client can send a 'GET' query to the API, asserting we receive
an HTTP status code reflecting successful operation.
"""
# Arrange: patch the request in client.Client._queryAPI().
with patch.object(requests, 'get') as mock_get:
mock_get.return_value = mock_response = Mock()
mock_response.status_code = 200
# Act:
c = client.Client()
response = c._queryAPI("path", 'GET', {}, None, {})
# Assert:
self.assertEqual(response.status_code, 200)
# Repeat the same test for 'POST' queries.
And for testing getContext(), mock out the HTTP with httpretty...
#httpretty.activate
def testGetContextReturnFields(self):
"""
Tests client.getContext() for a sample term.
Asserts the returned object contains the corrcet fields and have contents as
expected.
"""
# Arrange: mock JSON response from API, mock out the API endpoint we expect
# to be called.
mockResponseString = getMockApiData("context_foo.json")
httpretty.register_uri(httpretty.GET,
"http://this.is.the.url/query",
body=mockResponseString,
content_type="application/json")
# Act: create the client object we'll be testing.
c = client.Client()
contexts = c.getContext("foo")
# Assert: check the result object.
self.assertTrue(isinstance(contexts, list),
"Returned object is not of type list as expected.")
self.assertTrue(("context_label" and "context_id") in contexts[0],
"Data structure returned by getContext() does not contain"
" the required fields.")
self.assertTrue(isinstance(contexts[0]["context_label"], str),
"The \'context_label\' field is not of type string.")
self.assertEqual(contexts[0]["context_id"], 0,
"The top context does not have ID of zero.")

Categories

Resources