[snowy] [Test] Add initial REST API unit tests and fix allowed methods in handlers



commit 0503a80fe16202ea6545f33b820b3239e841da4f
Author: Sandy Armstrong <sanfordarmstrong gmail com>
Date:   Sun Sep 6 17:32:35 2009 -0700

    [Test] Add initial REST API unit tests and fix allowed methods in handlers
    
    Run tests with `python manage.py test api`.
    
    The 4 *BadMethods tests are expected to fail at this point.

 api/fixtures/basic.json |    9 ++
 api/handlers.py         |   10 +-
 api/tests.py            |  329 +++++++++++++++++++++++++++++++++++++++++++++++
 test_settings.py        |    7 +
 4 files changed, 350 insertions(+), 5 deletions(-)
---
diff --git a/api/fixtures/basic.json b/api/fixtures/basic.json
new file mode 100644
index 0000000..b14281e
--- /dev/null
+++ b/api/fixtures/basic.json
@@ -0,0 +1,9 @@
+[{"pk": 1, "model": "auth.user", "fields": {"username": "admin", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": true, "last_login": "2009-07-22 13:18:30", "groups": [], "user_permissions": [], "password": "sha1$5a2b0$db6f412d1b56d44e13d1588442bd604d15d4310b", "email": "admin snowy com", "date_joined": "2009-07-22 13:18:04"}},
+ 
+{"pk": 2, "model": "auth.user", "fields": {"username": "test1", "first_name": "John", "last_name": "Doe", "is_active": true, "is_superuser": false, "is_staff": false, "last_login": "2009-07-22 19:44:43", "groups": [], "user_permissions": [], "password": "sha1$05fc9$6cc1e54c73ad45bfbd35b9a5bf6d6c53b4452f86", "email": "john doe com", "date_joined": "2009-07-22 19:43:27"}},
+
+{"pk": 1, "model": "accounts.userprofile", "fields": {"latest_sync_rev": -1, "current_sync_uuid": "5ec1f08a-19f1-416a-b086-ff22f6f7c9e8", "user": 1, "language": null}},
+
+{"pk": 2, "model": "accounts.userprofile", "fields": {"latest_sync_rev": -1, "current_sync_uuid": "1886ae92-6c46-43e8-86c0-bb74df89f66c", "user": 2, "language": null}},
+
+{"pk": 1, "model": "piston.consumer", "fields": {"status": "accepted", "name": "Tomboy", "secret": "1234567", "user": null, "key": "abcdefg", "description": "Tomboy"}}]
diff --git a/api/handlers.py b/api/handlers.py
index a3aa19c..183dbc3 100644
--- a/api/handlers.py
+++ b/api/handlers.py
@@ -54,7 +54,7 @@ class catch_and_return(object):
 
 # http://domain/api/1.0
 class RootHandlerAnonymous(AnonymousBaseHandler):
-    allow_methods = ('GET')
+    allowed_methods = ('GET',)
 
     def read(self, request):
         kwargs = {'username': request.user.username}
@@ -62,7 +62,7 @@ class RootHandlerAnonymous(AnonymousBaseHandler):
 
 # http://domain/api/1.0
 class RootHandler(BaseHandler):
-    allow_methods = ('GET')
+    allowed_methods = ('GET',)
     anonymous = RootHandlerAnonymous
 
     def read(self, request):
@@ -84,7 +84,7 @@ def basic_root():
 
 # http://domain/api/1.0/user
 class UserHandler(AnonymousBaseHandler):
-    allow_methods = ('GET',)
+    allowed_methods = ('GET',)
 
     @catch_and_return(ObjectDoesNotExist, rc.NOT_HERE)
     def read(self, request, username):
@@ -106,7 +106,7 @@ class UserHandler(AnonymousBaseHandler):
 
 # http://domain/api/1.0/user/notes
 class NotesHandler(BaseHandler):
-    allow_methods = ('GET', 'PUT')
+    allowed_methods = ('GET', 'PUT')
 
     @catch_and_return(ObjectDoesNotExist, rc.NOT_HERE)
     def read(self, request, username):
@@ -189,7 +189,7 @@ class NotesHandler(BaseHandler):
 
 # http://domain/api/1.0/user/notes/id
 class NoteHandler(BaseHandler):
-    allow_methods = ('GET',)
+    allowed_methods = ('GET',)
     model = Note
 
     @catch_and_return(ObjectDoesNotExist, rc.NOT_HERE)
diff --git a/api/models.py b/api/models.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/tests.py b/api/tests.py
new file mode 100644
index 0000000..6f9b15c
--- /dev/null
+++ b/api/tests.py
@@ -0,0 +1,329 @@
+import urllib
+import base64
+
+# Use simplejson or Python 2.6 json, prefer simplejson.
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+from django.test import TestCase
+from django.test.client import Client
+from django.contrib.auth.models import User
+from django.utils.functional import curry
+
+from piston import oauth
+from piston.models import Consumer, Token
+from piston.forms import OAuthAuthenticationForm
+
+from snowy import settings
+from snowy.notes.models import Note
+
+class OAuthRequester:
+    signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+
+    def __init__ (self, test_case, username, password, consumer):
+        self.SERVER_NAME = 'testserver'
+        self.consumer = consumer
+        self.test_case = test_case
+        
+        # Much of this method comes straight from Piston's unit tests
+        # TODO: Fix copyright, then!
+        oaconsumer = oauth.OAuthConsumer(self.consumer.key,
+                                         self.consumer.secret)
+
+        # Get a request key...
+        url = 'http://' + self.SERVER_NAME + '/oauth/request_token/'
+        request = oauth.OAuthRequest.from_consumer_and_token(oaconsumer,
+                                                             http_url=url)
+        request.sign_request(self.signature_method, oaconsumer, None)
+
+        response = test_case.client.get('/oauth/request_token/',
+                                        request.parameters)
+        oatoken = oauth.OAuthToken.from_string(response.content)
+
+        token = Token.objects.get(key=oatoken.key, token_type=Token.REQUEST)
+        test_case.assertEqual(token.secret, oatoken.secret)
+
+        # Simulate user authentication...
+        test_case.failUnless(test_case.client.login(username=username,
+                                                    password=password))
+        url = 'http://' + self.SERVER_NAME + '/oauth/authenticate/'
+        c_url = 'http://printer.example.com/request_token_ready'
+        request = oauth.OAuthRequest.from_token_and_callback(token=oatoken,
+                                                             callback=c_url,
+                                                             http_url=url)
+        request.sign_request(self.signature_method, oaconsumer, oatoken)
+        
+        # Faking form submission seems to not work, so approve token manually
+        token.is_approved = True
+        token.user = User.objects.get(username=username)
+        token.save()
+
+        # Obtain access token...
+        url = 'http://' + self.SERVER_NAME + '/oauth/access_token/'
+        request = oauth.OAuthRequest.from_consumer_and_token(oaconsumer,
+                                                             token=oatoken,
+                                                             http_url=url)
+        request.sign_request(self.signature_method, oaconsumer, oatoken)
+        response = test_case.client.get('/oauth/access_token/',
+                                        request.parameters)
+
+        oa_atoken = oauth.OAuthToken.from_string(response.content)
+        atoken = Token.objects.get(key=oa_atoken.key, token_type=Token.ACCESS)
+        test_case.assertEqual(atoken.secret, oa_atoken.secret)
+
+        self.oa_atoken = oa_atoken
+
+    def build_request(self, abs_uri, method):
+        url = 'http://' + self.SERVER_NAME + abs_uri
+        oaconsumer = oauth.OAuthConsumer(self.consumer.key,
+                                         self.consumer.secret)
+        request = oauth.OAuthRequest.from_consumer_and_token(oaconsumer,
+                                                             token=self.oa_atoken,
+                                                             http_url=url,
+                                                             http_method=method)
+        request.sign_request(self.signature_method, oaconsumer, self.oa_atoken)
+        return request
+
+    def build_auth_header(self, request):
+        return request.to_header(realm='Snowy')['Authorization']
+
+    def get(self, abs_uri):
+        request = self.build_request(abs_uri, 'GET')
+        auth = self.build_auth_header(request)
+        return self.test_case.client.get(abs_uri,
+                                         HTTP_AUTHORIZATION=auth)
+
+    def put(self, abs_uri, json):
+        request = self.build_request(abs_uri, 'PUT')
+        auth = self.build_auth_header(request)
+        return self.test_case.client.put(abs_uri, data=json,
+                                         content_type='application/json',
+                                         HTTP_AUTHORIZATION=auth)
+
+    def post(self, abs_uri, json):
+        request = self.build_request(abs_uri, 'POST')
+        auth = self.build_auth_header(request)
+        return self.test_case.client.post(abs_uri, data=json,
+                                          content_type='application/json',
+                                          HTTP_AUTHORIZATION=auth)
+
+    def delete(self, abs_uri):
+        request = self.build_request(abs_uri, 'DELETE')
+        auth = self.build_auth_header(request)
+        return self.test_case.client.delete(abs_uri,
+                                            HTTP_AUTHORIZATION=auth)
+
+
+class ApiTestCase(TestCase):
+    fixtures = ['basic.json']
+
+    def setUp(self):
+        # Although client.put now exists in Django 1.1, it is unusable for us:
+        # http://code.djangoproject.com/ticket/11371
+        # So, we override it with our own working version:
+        self.client.put = curry(self.client.post, REQUEST_METHOD='PUT')
+        # TODO: Use standard consumer?
+        self.consumer = Consumer(name='Test Consumer', description='Test',
+                                 status='accepted')
+        self.consumer.key="123"
+        self.consumer.secret="123"
+        self.consumer.save()
+        self.admin_requester = OAuthRequester(self, 'admin', 'admin',
+                                              self.consumer)
+        self.test1_requester = OAuthRequester(self, 'test1', 'test1',
+                                              self.consumer)
+
+    def tearDown(self):
+        self.consumer.delete()
+
+    def testUser(self):
+        # Test a user with missing fields
+        response = self.client.get('/api/1.0/admin/')
+        self.assertEqual(response.status_code, 200)
+        # TODO: Genericize URL stuff
+        self.assertEqual(json.loads(response.content),
+                         {
+                            'user-name': 'admin',
+                            'last-name': '',
+                            'notes-ref': {
+                                'href': 'http://example.com/admin/notes/',
+                                'api-ref':
+                                    'http://example.com/api/1.0/admin/notes/'
+                            },
+                            'current-sync-guid':
+                                '5ec1f08a-19f1-416a-b086-ff22f6f7c9e8',
+                            'first-name': '',
+                            'latest-sync-revision': -1
+                         })
+
+        # Test a user with all fields, with and without auth
+        for client in self.client, self.test1_requester:
+            response = client.get('/api/1.0/test1/')
+            self.assertEqual(response.status_code, 200)
+            self.assertEqual(json.loads(response.content),
+                             {'user-name': 'test1',
+                              'last-name': 'Doe',
+                              'notes-ref': {
+                                'href': 'http://example.com/test1/notes/',
+                                'api-ref':
+                                    'http://example.com/api/1.0/test1/notes/'
+                              },
+                              'current-sync-guid':
+                                '1886ae92-6c46-43e8-86c0-bb74df89f66c',
+                              'first-name': 'John',
+                              'latest-sync-revision': -1
+                             })
+
+
+    def testUserBadMethods(self):
+        # PUT/POST/DELETE are not allowed
+        response = self.admin_requester.put('/api/1.0/admin/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.post('/api/1.0/admin/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.delete('/api/1.0/admin/')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+
+
+    def testRootNoAuth(self):
+        # Test w/out auth
+        response = self.client.get('/api/1.0/')
+        self.assertEqual(response.status_code,200)
+        self.assertEqual(json.loads(response.content),
+                         {
+                            'api-version': '1.0',
+                            'oauth_access_token_url': 'http://example.com/oauth/access_token/',
+                            'oauth_authorize_url': 'http://example.com/oauth/authenticate/',
+                            'oauth_request_token_url': 'http://example.com/oauth/request_token/'
+                         })
+
+    def testRootWithAuth(self):
+        # Test w/ auth (admin user)
+        response = self.admin_requester.get('/api/1.0/')
+        self.assertEqual(response.status_code,200)
+        self.assertEqual(json.loads(response.content),
+                         {
+                            'api-version': '1.0',
+                            'user-ref': {
+                                'href': 'http://example.com/admin/',
+                                'api-ref': 'http://example.com/api/1.0/admin/'
+                            },
+                            'oauth_access_token_url': 'http://example.com/oauth/access_token/',
+                            'oauth_authorize_url': 'http://example.com/oauth/authenticate/',
+                            'oauth_request_token_url': 'http://example.com/oauth/request_token/'
+                         })
+
+        # Test w/ auth (test1 user)
+        response = self.test1_requester.get('/api/1.0/')
+        self.assertEqual(response.status_code,200)
+        self.assertEqual(json.loads(response.content),
+                         {
+                            'api-version': '1.0',
+                            'user-ref': {
+                                'href': 'http://example.com/test1/',
+                                'api-ref': 'http://example.com/api/1.0/test1/'
+                            },
+                            'oauth_access_token_url': 'http://example.com/oauth/access_token/',
+                            'oauth_authorize_url': 'http://example.com/oauth/authenticate/',
+                            'oauth_request_token_url': 'http://example.com/oauth/request_token/'
+                         })
+
+
+    def testRootBadMethods(self):
+        # PUT/POST/DELETE are not allowed
+        response = self.admin_requester.put('/api/1.0/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.post('/api/1.0/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.delete('/api/1.0/')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+
+    def testNotes(self):
+        noteJson = '{"guid": "002e91a2-2e34-4e2d-bf88-21def49a7705",' + \
+                   '"title" :"New Note 6",' + \
+                   '"note-content" :"New Note 6\\nDescribe youre note <b>here</b>.",' + \
+                   '"note-content-version" : 0.2,' + \
+                   '"last-change-date" : "2009-04-19T21:29:23.2197340-07:00",' + \
+                   '"last-metadata-change-date" : "2009-04-19T21:29:23.2197340-07:00",' + \
+                   '"create-date" : "2008-03-06T13:44:46.4342680-08:00",' + \
+                   '"last-sync-revision" : 0,' + \
+                   '"open-on-startup" : false,' + \
+                   '"tags" : ["tag1","tag2"]' + \
+                   '}'
+        notesJson = '{"latest-sync-revision" : 0,' + \
+                    '"note-changes" : [' + noteJson + ']}'
+
+        full_notes = {
+                        "notes": [
+                            {
+                                "guid": "002e91a2-2e34-4e2d-bf88-21def49a7705", 
+                                "ref": {
+                                    "href": "http://example.com/admin/notes/1/";, 
+                                    "api-ref": "http://example.com/api/1.0/admin/notes/1/";
+                                }, 
+                                "title": "New Note 6"
+                            }
+                        ], 
+                        "latest-sync-revision": 0
+                    }
+        public_notes = {
+                        "notes": [], 
+                        "latest-sync-revision": 0
+                    }
+
+        response = self.admin_requester.put ('/api/1.0/admin/notes/', notesJson)
+        self.assertEqual(json.loads(response.content), full_notes)
+
+        response = self.client.get('/api/1.0/admin/notes/')
+        self.assertEqual(response.status_code, 401)
+
+        response = self.admin_requester.get('/api/1.0/admin/notes/')
+        self.assertEqual(json.loads(response.content), full_notes)
+
+        response = self.test1_requester.get('/api/1.0/admin/notes/')
+        self.assertEqual(json.loads(response.content), public_notes)
+
+        # Make a note public
+        # TODO: Test collections with a mix of public and private notes
+        admin = User.objects.get(username='admin')
+        note = Note.objects.get(author=admin,
+                                guid="002e91a2-2e34-4e2d-bf88-21def49a7705")
+        note.permissions=1
+        note.save()
+
+        response = self.test1_requester.get('/api/1.0/admin/notes/')
+        self.assertEqual(json.loads(response.content), full_notes)
+
+
+    def testNotesBadMethods(self):
+        # POST/DELETE are not allowed
+        response = self.admin_requester.post('/api/1.0/admin/notes/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET, PUT')
+        response = self.admin_requester.delete('/api/1.0/admin/notes/')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET, PUT')
+
+    def testNote(self):
+        # TODO
+        pass
+
+    def testNoteBadMethods(self):
+        # PUT/POST/DELETE are not allowed
+        response = self.admin_requester.put('/api/1.0/admin/notes/1/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.post('/api/1.0/admin/notes/1/', '')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
+        response = self.admin_requester.delete('/api/1.0/admin/notes/1/')
+        self.assertEqual(response.status_code, 405)
+        self.assertEqual(response['Allow'], 'GET')
\ No newline at end of file
diff --git a/test_settings.py b/test_settings.py
new file mode 100644
index 0000000..238c6f4
--- /dev/null
+++ b/test_settings.py
@@ -0,0 +1,7 @@
+# Test Django Settings
+
+from settings import *
+
+MIDDLEWARE_CLASSES = list(MIDDLEWARE_CLASSES)
+MIDDLEWARE_CLASSES.remove('django.contrib.csrf.middleware.CsrfViewMiddleware')
+MIDDLEWARE_CLASSES.remove('django.contrib.csrf.middleware.CsrfResponseMiddleware')
\ No newline at end of file



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]