Search code examples
djangodjango-testingtastypie

Django/Tastypie test resource fails when a NotFound exception is raised by TastyPie


I have a Django app with APIs provided by Tastypie and all works fine on my local and live servers, however I get an error when I try to write a test to call a specific API .

Here is a CURL request (to my local Django server running on Apache):

curl --dump-header - -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: ApiKey user:025b513834656753db4bd7b32d66ea2e50ea08f3" -X POST --data '{"digest":"123456789123456789"}'  "http://localhost/python/api/v1/tracker/"

and this is the (expected) response:

HTTP/1.1 404 NOT FOUND
Date: Mon, 01 Jul 2013 18:15:53 GMT
Server: Apache/2.2.22 (Ubuntu)
Vary: Accept-Language,Cookie
Content-Language: en-us
Transfer-Encoding: chunked
Content-Type: application/json

{
    "error_message": "", 
    "traceback": "Traceback (most recent call last):\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 217, in wrapper\n    response = callback(request, *args, **kwargs)\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 459, in dispatch_list\n    return self.dispatch('list', request, **kwargs)\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 491, in dispatch\n    response = method(request, **kwargs)\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 1357, in post_list\n    updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 2150, in obj_create\n    return self.save(bundle)\n\n  File \"/home/alex/data/development/home_alexlittle_net/venv/lib/python2.7/site-packages/tastypie/resources.py\", line 2281, in save\n    self.is_valid(bundle)\n\n  File \"/home/alex/data/development/django-oppia/oppia/api/resources.py\", line 205, in is_valid\n    raise NotFound\n\nNotFound\n"

However, when I run the following test resource case:

class TrackerResourceTest(ResourceTestCase): 
    fixtures = ['user.json', 'oppia.json']  

    def setUp(self):
        super(TrackerResourceTest, self).setUp()
        self.username = 'user'
        user = User.objects.get(username=self.username)
        api_key = ApiKey.objects.get(user = user)
        self.api_key = api_key.key
        self.url = '/api/v1/tracker/'

    def get_credentials(self):
        return self.create_apikey(username=self.username, api_key=self.api_key)

    def test_post_digest_not_found(self):
        data = {
            'digest': '123456789123456789',
        }
        resp = self.api_client.post(self.url, format='json', data=data, authentication=self.get_credentials())
        self.assertHttpNotFound(resp)

I get the following error:

======================================================================
ERROR: test_post_digest_not_found (oppia.tests.TrackerResourceTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/alex/data/development/django-oppia/oppia/tests.py", line 459, in test_post_digest_not_found
    resp = self.api_client.post(self.url, format='json', data=data, authentication=self.get_credentials())
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/tastypie/test.py", line 96, in post
    return self.client.post(uri, **kwargs)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 463, in post
    response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 297, in post
    return self.request(**r)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 406, in request
    response = self.handler(environ)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/client.py", line 111, in __call__
    response = self.get_response(request)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/core/handlers/base.py", line 178, in get_response
    response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/core/handlers/base.py", line 224, in handle_uncaught_exception
    return callback(request, **param_dict)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/utils/decorators.py", line 91, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/defaults.py", line 41, in server_error
    return http.HttpResponseServerError(template.render(Context({})))
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 140, in render
    return self._render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 124, in render
    return compiled_parent._render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 156, in render
    return self.render_template(self.template, context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/loader_tags.py", line 138, in render_template
    output = template.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 140, in render
    return self._render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/test/utils.py", line 65, in instrumented_test_render
    return self.nodelist.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 830, in render
    bit = self.render_node(node, context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/debug.py", line 74, in render_node
    return node.render(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/templatetags/i18n.py", line 50, in render
    langs = self.languages.resolve(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 728, in resolve
    value = self._resolve_lookup(context)
  File "/home/alex/data/development/home_alexlittle_net/venv/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/template/base.py", line 771, in _resolve_lookup
    (bit, current))  # missing attribute
VariableDoesNotExist: Failed lookup for key [LANGUAGES] in u"[{'False': False, 'None': None, 'True': True}, {}, {}]"

So it seems that it's not picking up the fact that there has been a NotFound exception raised. The NotFound exception is raised by my API Resource is_valid method:

def is_valid(self, bundle, request=None):
        exists = False
        try:
            activity = Activity.objects.get(digest=bundle.obj.digest)
            exists = True
        except Activity.DoesNotExist:
            pass

        try:
            media = Media.objects.get(digest=bundle.obj.digest)
            exists = True
        except Media.DoesNotExist:
            pass
        if not exists:
            raise NotFound()

I'm struggling to figure out why I get such a cryptic error message - seems to be looking at the Django templates (?), but since this is with the TastyPie API, this part of the app doesn't use the templating. For info I get the same response from the curl request on my local server whether I have the DEBUG setting set as True or False (as I'd expect).

I'm not clear if this is because there is something wrong in how I've written either the test case or my API resource?

Any help/pointers much appreciated - I'd really like to get the tests working properly.


Solution

  • I've now figured out where I was going wrong... I had made a couple of mistakes:

    Firstly the is_valid wasn't in a separate Validation class, it is was a method on the TrackerResource class. I've now moved it to being in it's own Validation class, and referenced this as validation = TrackerValidation() in my TrackerResource.

    Secondly, I had the is_valid returning a NotFound error, rather than allowing it to return a set of error messages - this now means that if incorrect data is sent it flags up a 400 Bad Request, rather than the 404 Not Found.

    My TrackerValidation class now looks like:

    class TrackerValidation(Validation):
        def is_valid(self, bundle, request=None):
            exists = False
            errors = {}
            try:
                activity = Activity.objects.get(digest=bundle.obj.digest)
                exists = True
            except Activity.DoesNotExist:
                pass
    
            try:
                media = Media.objects.get(digest=bundle.obj.digest)
                exists = True
            except Media.DoesNotExist:
                pass
            if not exists:
                errors['digest'] = [_(u'Digest not found')]
            return errors
    

    With this I now get consistent results between curl requests to my local site, and in the testing framework.