[snowy] [Test] Add initial REST API unit tests and fix allowed methods in handlers
- From: Sanford Armstrong <sharm src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [snowy] [Test] Add initial REST API unit tests and fix allowed methods in handlers
- Date: Mon, 7 Sep 2009 00:39:56 +0000 (UTC)
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]