# HG changeset patch # User Filip de Waard # Date 1292706296 -3600 # Node ID 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 # Parent f9575bd432937270955299e3ad25997bd36bc25b working version of Feed controller create method diff -r f9575bd432937270955299e3ad25997bd36bc25b -r 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 development.ini --- a/development.ini Fri Dec 17 17:38:45 2010 +0100 +++ b/development.ini Sat Dec 18 22:04:56 2010 +0100 @@ -42,6 +42,8 @@ #authentication session duration in seconds auth_session_duration = 60 +tag_uri_authority = vix.io + # Logging configuration [loggers] keys = root, routes, vix diff -r f9575bd432937270955299e3ad25997bd36bc25b -r 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 test.ini --- a/test.ini Fri Dec 17 17:38:45 2010 +0100 +++ b/test.ini Sat Dec 18 22:04:56 2010 +0100 @@ -19,5 +19,6 @@ use = config:development.ini couchdb_uri = http://localhost:5984/vix_tests couchdb_database = vix_tests +tag_uri_authority = vix.io # Add additional test specific configuration options as necessary. diff -r f9575bd432937270955299e3ad25997bd36bc25b -r 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 vix/config/routing.py --- a/vix/config/routing.py Fri Dec 17 17:38:45 2010 +0100 +++ b/vix/config/routing.py Sat Dec 18 22:04:56 2010 +0100 @@ -17,9 +17,11 @@ map.connect('/error/{action}', controller='error') map.connect('/error/{action}/{id}', controller='error') - # CUSTOM ROUTES HERE + # Routes for 'feed' controller + map.connect('create_feed', '/feed', controller='feed', + action='create', conditions=dict(method='POST')) - map.connect('/{controller}/{action}') - map.connect('/{controller}/{action}/{id}') + # Routes for 'api' controller + map.connect('/api/:(action)', controller='api') return map diff -r f9575bd432937270955299e3ad25997bd36bc25b -r 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 vix/controllers/feed.py --- a/vix/controllers/feed.py Fri Dec 17 17:38:45 2010 +0100 +++ b/vix/controllers/feed.py Sat Dec 18 22:04:56 2010 +0100 @@ -19,6 +19,7 @@ """ +import simplejson as json import logging from pylons import request, response, url, config @@ -28,6 +29,8 @@ from vix.lib.base import BaseController from vix.lib.decorators import authenticate, authorize +import vix.model as model + log = logging.getLogger(__name__) class FeedController(BaseController): @@ -36,4 +39,55 @@ @authenticate() @authorize(config['couchdb_database'], '*', 'POST') def create(self): - return "hey" + """ + Creates a Feed resource. + + This controller method accepts three different input values + through a POST request: a slug suggestion through the 'Slug' + header and the title and subtitle of the feed in JSON format. + + Here is a sample request:: + + POST /feeds HTTTP/1.1 + Host vix.example.org + Content-type: application/json + Slug: blog + Authorization: Basic ZHRhZ2dhcnQ6am9objEyMw== + + {'title': 'Taggart Transcontinental company weblog', + 'subtitle': 'From ocean to ocean.'} + + This method requires the 'application/json' HTTP Media Type + to be passed through the Content-type header (AtomPub support + will be added in the future). If called correctly the JSON + representation of the newly created CouchDB document will be + returned with a 201 status code. + + Requires HTTP Base authentication with a user that has + sufficient privileges. + + """ + + #TODO: add application/atom+xml support + if 'application/json' in request.headers.get('Content-type'): + try: + feed_json = json.loads(request.body) + except: + abort(400, 'Invalid JSON input') + + slug_suggestion = request.headers.get('Slug') + + feed = model.Feed(title=feed_json['title'], + subtitle=feed_json['subtitle']) + + feed.create(model.db, + slug_suggestion=slug_suggestion, + authority=config['tag_uri_authority']) + + response.status = 201 + response.content_type = 'application/json' + + #return a JSON representation of the CouchDB document + return json.dumps(feed.unwrap()) + else: + abort(415, 'Only accepting application/json.') diff -r f9575bd432937270955299e3ad25997bd36bc25b -r 8237a0077a2dfca8d3f73a37c3106d0e56cc0a60 vix/tests/functional/test_feed.py --- a/vix/tests/functional/test_feed.py Fri Dec 17 17:38:45 2010 +0100 +++ b/vix/tests/functional/test_feed.py Sat Dec 18 22:04:56 2010 +0100 @@ -1,14 +1,21 @@ +import re import base64 import bcrypt +from datetime import datetime + +import simplejson as json from pylons import url from vix.tests import TestController +from vix.lib.util import datetime_to_rfc3339 import vix.model as model +import vix.model.views as views -#FIXME: expect 201 instead of 200 status code from create method #TODO: enforce SSL with https decorator +_couchdb_rev_re = re.compile('\A[0-9]*-[a-z0-9]{32}\Z') + class TestFeedController(TestController): def test_create_authorization(self): @@ -25,11 +32,12 @@ actions=perm) user.store(model.db) - #make an request for this user + #make an request for this user (returns 415 status due to invalid + #HTTP Content-type header) authentication = 'Basic %s' % (base64.b64encode('fmw:@!^%$&*()_+@:')) - response = self.app.post(url(controller='feed', action='create'), + response = self.app.post(url('create_feed'), headers={'Authorization': authentication}, - status=200) + status=415) #set up a user that shouldn't pass the authentication check p = bcrypt.hashpw(u'@!^%$&*()_+@:', bcrypt.gensalt()) @@ -43,7 +51,7 @@ #make the unauthorized request authentication = 'Basic %s' % (base64.b64encode('wmf:@!^%$&*()_+@:')) - response = self.app.post(url(controller='feed', action='create'), + response = self.app.post(url('create_feed'), headers={'Authorization': authentication}, status=403) @@ -58,18 +66,76 @@ user.store(model.db) authentication = 'Basic %s' % (base64.b64encode('fmw_:@!^%$&*()_+@:')) - response = self.app.post(url(controller='feed', action='create'), + response = self.app.post(url('create_feed'), headers={'Authorization': authentication}, status=403) - def test_create_enforce_post(self): - """Make sure that the Feed 'create' method only responds to 'POST'""" + def test_create_json(self): + """Test creating Feed resources with JSON input.""" - response = self.app.get(url(controller='feed', action='create'), - status=405) + #add by_slug CouchDB view + views.by_slug.sync(model.db) + + #set up a user to pass the authentication check + p = bcrypt.hashpw(u'123', bcrypt.gensalt()) + user = model.User(username=u"fmw", password=p) + + #set up basic permissions + perm = {'GET': True, 'POST': True, 'PUT': True, 'DELETE': True} + user.set_permissions(database=u'vix_tests', feed=u'*', admin=True, + actions=perm) + user.store(model.db) + + headers = {} + headers['Authorization'] = 'Basic %s' % (base64.b64encode('fmw:123')) + headers['Slug'] = 'weblog' - response = self.app.put(url(controller='feed', action='create'), - status=405) + new_feed = {'title': u'Vix weblog', 'subtitle': u'The Vix Weblog...'} - response = self.app.delete(url(controller='feed', action='create'), - status=405) + response = self.app.post(url('create_feed', format='json'), + content_type='application/json', + params=json.dumps(new_feed), + headers=headers, + status=201) + + response_json = json.loads(response.body) + + #test the values as they are returned by the controller + #(i.e. the JSON for the object as stored in CouchDB) + self.assertTrue(_couchdb_rev_re.match(response.json['_rev'])) + self.assertEquals(response.json['title'], u'Vix weblog') + self.assertEquals(response.json['subtitle'], u'The Vix Weblog...') + self.assertEquals(response.json['type'], u'feed') + self.assertEquals(response.json['slug'], u'/feeds/weblog') + + #seconds are excluded, but this could possibly backfire if there is + #a discrepancy between the minutes/dates of the two calls, but + #as this is rather unlikely and there is a simple recourse (i.e. + #repeating the tests) this approach will do just fine. + datetime_substr = datetime_to_rfc3339(datetime.utcnow()).rsplit(':')[0] + date_str = datetime_substr.split('T')[0] + + assert datetime_substr in response.json['published'] + assert datetime_substr in response.json['updated'] + + self.assertEquals(response.json['_id'], + u'tag:vix.io,%s:/feeds/weblog' % (date_str)) + + #try sending a request with invalid JSON input + response = self.app.post(url('create_feed', format='json'), + content_type='application/json', + params=u'foobar$ "', + headers=headers, + status=400) + + #test w/o slug + del headers['Slug'] + response = self.app.post(url('create_feed', format='json'), + content_type='application/json', + params=json.dumps(new_feed), + headers=headers, + status=201) + + self.assertEquals(response.json['slug'], u'/feeds/vix-weblog') + self.assertEquals(response.json['_id'], + u'tag:vix.io,%s:/feeds/vix-weblog' % (date_str))