[ckan-changes] commit/ckan: 5 new changesets

Bitbucket commits-noreply at bitbucket.org
Thu Jul 7 21:01:53 UTC 2011


5 new changesets in ckan:

http://bitbucket.org/okfn/ckan/changeset/34cab6558001/
changeset:   34cab6558001
branch:      defect-1214-api-improvements
user:        dread
date:        2011-07-07 21:33:11
summary:     [model,logic][xs]: Little tidy-ups to do with relationships.
affected #:  3 files (1.7 KB)

--- a/ckan/logic/action/create.py	Thu Jul 07 20:03:18 2011 +0100
+++ b/ckan/logic/action/create.py	Thu Jul 07 20:33:11 2011 +0100
@@ -91,7 +91,7 @@
     user = context['user']
     id = context["id"]
     id2 = context["id2"]
-    rel = context["rel"]
+    rel_type = context["rel"]
     api = context.get('api_version') or '1'
     ref_package_by = 'id' if api == '2' else 'name'
 
@@ -112,14 +112,14 @@
     ##FIXME should have schema
     comment = data_dict.get('comment', u'')
 
-    existing_rels = pkg1.get_relationships_with(pkg2, rel)
+    existing_rels = pkg1.get_relationships_with(pkg2, rel_type)
     if existing_rels:
         return _update_package_relationship(existing_rels[0],
                                             comment, context)
     rev = model.repo.new_revision()
     rev.author = user
-    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel, pkg2)
-    rel = pkg1.add_relationship(rel, pkg2, comment=comment)
+    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel_type, pkg2)
+    rel = pkg1.add_relationship(rel_type, pkg2, comment=comment)
     model.repo.commit_and_remove()
     relationship_dicts = rel.as_dict(ref_package_by=ref_package_by)
     return relationship_dicts


--- a/ckan/model/package.py	Thu Jul 07 20:03:18 2011 +0100
+++ b/ckan/model/package.py	Thu Jul 07 20:33:11 2011 +0100
@@ -225,7 +225,7 @@
             subject = related_package
             object_ = self
         else:
-            raise NotImplementedError, 'Package relationship type: %r' % type_
+            raise KeyError, 'Package relationship type: %r' % type_
 
         rels = self.get_relationships(with_package=related_package,
                                       type=type_, active=False, direction="forward")


--- a/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 20:03:18 2011 +0100
+++ b/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 20:33:11 2011 +0100
@@ -1,4 +1,5 @@
 from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
 
 from ckan import model
 
@@ -116,6 +117,21 @@
         expected = []
         self.check_relationships_rest('warandpeace', 'annakarenina', expected)
 
+    def test_create_relationship_unknown(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'unheard_of_type', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def test_create_relationship_incorrectly(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'relationships', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'type':'parent_of'})
+        # type should do in the URL offset, not the params.
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
     def create_annakarenina_parent_of_war_and_peace(self):
         # Create package relationship.
         # Todo: Redesign this in a RESTful style, so that a relationship is 
@@ -124,6 +140,12 @@
         postparams = '%s=1' % self.dumps({'comment':self.comment})
         res = self.app.post(offset, params=postparams, status=[201],
                             extra_environ=self.extra_environ)
+        # Check the response
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+        
         # Check the model, directly.
         rels = self.anna.get_relationships()
         assert len(rels) == 1, rels
@@ -135,12 +157,25 @@
         offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
         postparams = '%s=1' % self.dumps({'comment':comment})
         res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
+        # Check the response (normalised to 'child_of')
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        import pdb; pdb.set_trace()
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+
+        # Check the model, directly (normalised to 'child_of')
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
         return res
 
     def delete_annakarenina_parent_of_war_and_peace(self):
         offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
         res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
-        return res
+        assert not res.body
 
     def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
         offset = self.relationship_offset(package1_name, type, package2_name)


http://bitbucket.org/okfn/ckan/changeset/b6c7760aea49/
changeset:   b6c7760aea49
branch:      defect-1214-api-improvements
user:        dread
date:        2011-07-07 21:35:25
summary:     [tests][xs]: Remove stray pdb.
affected #:  1 file (36 bytes)

--- a/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 20:33:11 2011 +0100
+++ b/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 20:35:25 2011 +0100
@@ -160,7 +160,6 @@
         # Check the response (normalised to 'child_of')
         rel = self.loads(res.body)
         assert_equal(rel['type'], 'child_of')
-        import pdb; pdb.set_trace()
         assert_equal(rel['subject'], self.ref_package(self.war))
         assert_equal(rel['object'], self.ref_package(self.anna))
 


http://bitbucket.org/okfn/ckan/changeset/fc09267587fc/
changeset:   fc09267587fc
branch:      defect-1214-api-improvements
user:        dread
date:        2011-07-07 21:46:21
summary:     [branch] close.
affected #:  0 files (0 bytes)

http://bitbucket.org/okfn/ckan/changeset/be2be28660b1/
changeset:   be2be28660b1
user:        dread
date:        2011-07-07 21:46:38
summary:     [merge] from defect-1214-api-improvements.
affected #:  22 files (37.4 KB)

--- a/ckan/controllers/api.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/controllers/api.py	Thu Jul 07 20:46:38 2011 +0100
@@ -30,10 +30,6 @@
     }
 class ApiController(BaseController):
 
-    content_type_text = 'text/;charset=utf-8'
-    content_type_html = 'text/html;charset=utf-8'
-    content_type_json = 'application/json;charset=utf-8'
-
     def __call__(self, environ, start_response):
         self._identify_user()
         if not self.authorizer.am_authorized(c, model.Action.SITE_READ, model.System):
@@ -45,6 +41,8 @@
             start_response(body, response.headers.items())
             return [response_msg]
         else:
+            # avoid status_code_redirect intercepting error responses
+            environ['pylons.status_code_redirect'] = True
             return BaseController.__call__(self, environ, start_response)
 
     def _finish(self, status_int, response_data=None,
@@ -106,6 +104,14 @@
                             response_data=response_data,
                             content_type='json')
 
+    def _finish_bad_request(self, extra_msg=None):
+        response_data = _('Bad request')
+        if extra_msg:
+            response_data = '%s - %s' % (response_data, extra_msg)
+        return self._finish(status_int=400,
+                            response_data=response_data,
+                            content_type='json')
+
     def _wrap_jsonp(self, callback, response_msg):
         return '%s(%s);' % (callback, response_msg)
 
@@ -140,8 +146,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot list entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot list entity of this type: %s') % register)
         try:
             return self._finish_ok(action(context))
         except NotFound, e:
@@ -151,7 +157,6 @@
             return self._finish_not_authz()
 
     def show(self, ver=None, register=None, subregister=None, id=None, id2=None):
-
         action_map = {
             'revision': get.revision_show,
             'group': get.group_show_rest,
@@ -171,8 +176,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot read entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot read entity of this type: %s') % register)
         try:
             
             return self._finish_ok(action(context))
@@ -205,15 +210,16 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
 
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot create new entity of this type: %s %s') % (register, subregister)
+            return self._finish_bad_request(
+                gettext('Cannot create new entity of this type: %s %s') % \
+                (register, subregister))
         try:
             response_data = action(request_data, context)
             location = None
@@ -251,15 +257,15 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot update entity of this type: %s') % \
-                    register.encode('utf-8')
+            return self._finish_bad_request(
+                gettext('Cannot update entity of this type: %s') % \
+                    register.encode('utf-8'))
         try:
             response_data = action(request_data, context)
             return self._finish_ok(response_data)
@@ -294,8 +300,9 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot delete entity of this type: %s %s') % (register, subregister or '')
+            return self._finish_bad_request(
+                gettext('Cannot delete entity of this type: %s %s') %\
+                (register, subregister or ''))
         try:
             response_data = action(context)
             return self._finish_ok(response_data)
@@ -315,29 +322,31 @@
             since_time = None
             if request.params.has_key('since_id'):
                 id = request.params['since_id']
+                if not id:
+                    return self._finish_bad_request(
+                        gettext(u'No revision specified'))
                 rev = model.Session.query(model.Revision).get(id)
                 if rev is None:
-                    response.status_int = 400
-                    return gettext(u'There is no revision with id: %s') % id
+                    return self._finish_not_found(
+                        gettext(u'There is no revision with id: %s') % id)
                 since_time = rev.timestamp
             elif request.params.has_key('since_time'):
                 since_time_str = request.params['since_time']
                 try:
                     since_time = model.strptimestamp(since_time_str)
                 except ValueError, inst:
-                    response.status_int = 400
-                    return 'ValueError: %s' % inst
+                    return self._finish_bad_request('ValueError: %s' % inst)
             else:
-                response.status_int = 400
-                return gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')")
+                return self._finish_bad_request(
+                    gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')"))
             revs = model.Session.query(model.Revision).filter(model.Revision.timestamp>since_time)
             return self._finish_ok([rev.id for rev in revs])
         elif register == 'package' or register == 'resource':
             try:
                 params = self._get_search_params(request.params)
             except ValueError, e:
-                response.status_int = 400
-                return gettext('Could not read parameters: %r' % e)
+                return self._finish_bad_request(
+                    gettext('Could not read parameters: %r' % e))
             options = QueryOptions()
             for k, v in params.items():
                 if (k in DEFAULT_OPTIONS.keys()):
@@ -373,11 +382,11 @@
                 return self._finish_ok(results)
             except SearchError, e:
                 log.exception(e)
-                response.status_int = 400
-                return gettext('Bad search option: %s') % e
+                return self._finish_bad_request(
+                    gettext('Bad search option: %s') % e)
         else:
-            response.status_int = 404
-            return gettext('Unknown register: %s') % register
+            return self._finish_not_found(
+                gettext('Unknown register: %s') % register)
 
     @classmethod
     def _get_search_params(cls, request_params):


--- a/ckan/lib/base.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/lib/base.py	Thu Jul 07 20:46:38 2011 +0100
@@ -146,22 +146,25 @@
         '''
         cls.log.debug('Retrieving request params: %r' % request.params)
         cls.log.debug('Retrieving request POST: %r' % request.POST)
-
+        request_data = None
         if request.POST:
             try:
-                request_data = request.POST.keys() or request.body
+                request_data = request.POST.keys()
             except Exception, inst:
                 msg = _("Could not find the POST data: %r : %s") % \
                       (request.POST, inst)
                 raise ValueError, msg
             request_data = request_data[0]
-        elif request.body:
-            cls.log.debug('Retrieving request POST body: %r' % request.body)
+        else:
             try:
                 request_data = request.body
             except Exception, inst:
-                msg = _("Could not find the POST data: %r : %s") % \
-                      (request.POST, inst)
+                msg = _("Could not extract request body data: %s") % \
+                      (inst)
+                raise ValueError, msg
+            cls.log.debug('Retrieved request body: %r' % request.body)
+            if not request_data:
+                msg = _("No request body data")
                 raise ValueError, msg
         if request_data:
             request_data = json.loads(request_data, encoding='utf8')


--- a/ckan/lib/dictization/model_save.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Thu Jul 07 20:46:38 2011 +0100
@@ -67,9 +67,6 @@
 
 def package_extras_save(extra_dicts, obj, context):
 
-    allow_partial_update = context.get("allow_partial_update", False)
-    if not extra_dicts and allow_partial_update:
-        return
     model = context["model"]
     session = context["session"]
 
@@ -81,7 +78,10 @@
     for extra_dict in extra_dicts:
         if extra_dict.get("deleted"):
             continue
-        if extras_as_string:
+        
+        if extra_dict['value'] is None:
+            pass
+        elif extras_as_string:
             new_extras[extra_dict["key"]] = extra_dict["value"]
         else:
             new_extras[extra_dict["key"]] = json.loads(extra_dict["value"])
@@ -318,10 +318,15 @@
             updated_extras.update(value)
 
             new_value = []
+            
             for extras_key, extras_value in updated_extras.iteritems():
                 if extras_value is not None:
                     new_value.append({"key": extras_key,
                                       "value": json.dumps(extras_value)})
+                else:
+                    new_value.append({"key": extras_key,
+                                      "value": None})
+
         dictized[key] = new_value
 
     groups = dictized.pop('groups', None)


--- a/ckan/logic/action/create.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/logic/action/create.py	Thu Jul 07 20:46:38 2011 +0100
@@ -91,7 +91,7 @@
     user = context['user']
     id = context["id"]
     id2 = context["id2"]
-    rel = context["rel"]
+    rel_type = context["rel"]
     api = context.get('api_version') or '1'
     ref_package_by = 'id' if api == '2' else 'name'
 
@@ -112,14 +112,14 @@
     ##FIXME should have schema
     comment = data_dict.get('comment', u'')
 
-    existing_rels = pkg1.get_relationships_with(pkg2, rel)
+    existing_rels = pkg1.get_relationships_with(pkg2, rel_type)
     if existing_rels:
         return _update_package_relationship(existing_rels[0],
                                             comment, context)
     rev = model.repo.new_revision()
     rev.author = user
-    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel, pkg2)
-    rel = pkg1.add_relationship(rel, pkg2, comment=comment)
+    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel_type, pkg2)
+    rel = pkg1.add_relationship(rel_type, pkg2, comment=comment)
     model.repo.commit_and_remove()
     relationship_dicts = rel.as_dict(ref_package_by=ref_package_by)
     return relationship_dicts


--- a/ckan/logic/action/get.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/logic/action/get.py	Thu Jul 07 20:46:38 2011 +0100
@@ -212,7 +212,7 @@
 
 def tag_show(context):
     model = context['model']
-    api = context.get('api') or '1'
+    api = context.get('api_version') or '1'
     id = context['id']
     ref_package_by = 'id' if api == '2' else 'name'
     obj = model.Tag.get(id) #TODO tags


--- a/ckan/logic/action/update.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/logic/action/update.py	Thu Jul 07 20:46:38 2011 +0100
@@ -6,7 +6,12 @@
 from ckan.plugins import PluginImplementations, IGroupController, IPackageController
 from ckan.logic import NotFound, check_access, NotAuthorized, ValidationError
 from ckan.lib.base import _
-from ckan.lib.dictization.model_dictize import group_dictize, package_dictize
+from ckan.lib.dictization.model_dictize import (package_dictize,
+                                                package_to_api1,
+                                                package_to_api2,
+                                                group_dictize,
+                                                group_to_api1,
+                                                group_to_api2)
 from ckan.lib.dictization.model_save import (group_api_to_dict,
                                              package_api_to_dict,
                                              group_dict_save,
@@ -171,7 +176,7 @@
         if 'message' in context:
             rev.message = context['message']
         else:
-            rev.message = _(u'REST API: Create object %s') % data.get("name")
+            rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     pkg = package_dict_save(data, context)
 
@@ -255,7 +260,7 @@
     if 'message' in context:
         rev.message = context['message']
     else:
-        rev.message = _(u'REST API: Create object %s') % data.get("name")
+        rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     group = group_dict_save(data, context)
 
@@ -274,19 +279,38 @@
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     pkg = model.Package.get(id)
     context["package"] = pkg
     context["allow_partial_update"] = True
     dictized_package = package_api_to_dict(data_dict, context)
-    return package_update(dictized_package, context)
+    dictized_after = package_update(dictized_package, context)
+
+    pkg = context['package']
+
+    if api == '1':
+        package_dict = package_to_api1(pkg, context)
+    else:
+        package_dict = package_to_api2(pkg, context)
+
+    return package_dict
 
 def group_update_rest(data_dict, context):
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     group = model.Group.get(id)
     context["group"] = group
     context["allow_partial_update"] = True
     dictized_package = group_api_to_dict(data_dict, context)
-    return group_update(dictized_package, context)
+    dictized_after = group_update(dictized_package, context)
 
+    group = context['group']
+
+    if api == '1':
+        group_dict = group_to_api1(group, context)
+    else:
+        group_dict = group_to_api2(group, context)
+
+    return group_dict


--- a/ckan/logic/schema.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/logic/schema.py	Thu Jul 07 20:46:38 2011 +0100
@@ -167,7 +167,7 @@
     schema = {
         'id': [ignore],
         'key': [not_empty, unicode],
-        'value': [not_missing, unicode],
+        'value': [not_missing],
         'state': [ignore],
         'deleted': [ignore_missing],
         'revision_timestamp': [ignore],


--- a/ckan/model/package.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/model/package.py	Thu Jul 07 20:46:38 2011 +0100
@@ -225,7 +225,7 @@
             subject = related_package
             object_ = self
         else:
-            raise NotImplementedError, 'Package relationship type: %r' % type_
+            raise KeyError, 'Package relationship type: %r' % type_
 
         rels = self.get_relationships(with_package=related_package,
                                       type=type_, active=False, direction="forward")


--- a/ckan/tests/functional/api/base.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/base.py	Thu Jul 07 20:46:38 2011 +0100
@@ -69,7 +69,7 @@
         return '%s%s' % (base, path)
 
     def package_offset(self, package_name=None):
-        if package_name == None:
+        if package_name is None:
             # Package Register
             return self.offset('/rest/package')
         else:
@@ -79,14 +79,14 @@
 
     def package_ref_from_name(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
 
     def package_id_from_ref(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
@@ -96,7 +96,7 @@
         return getattr(package, self.ref_package_by)
 
     def group_offset(self, group_name=None):
-        if group_name == None:
+        if group_name is None:
             # Group Register
             return self.offset('/rest/group')
         else:
@@ -106,7 +106,7 @@
 
     def group_ref_from_name(self, group_name):
         group = self.get_group_by_name(unicode(group_name))
-        if group == None:
+        if group is None:
             return group_name
         else:
             return self.ref_group(group)
@@ -115,11 +115,52 @@
         assert self.ref_group_by in ['id', 'name']
         return getattr(group, self.ref_group_by)
 
+    def revision_offset(self, revision_id=None):
+        if revision_id is None:
+            # Revision Register
+            return self.offset('/rest/revision')
+        else:
+            # Revision Entity
+            return self.offset('/rest/revision/%s' % revision_id)
+
+    def rating_offset(self, package_name=None):
+        if package_name is None:
+            # Revision Register
+            return self.offset('/rest/rating')
+        else:
+            # Revision Entity
+            package_ref = self.package_ref_from_name(package_name)
+            return self.offset('/rest/rating/%s' % package_ref)
+
+    def relationship_offset(self, package_1_name=None,
+                            relationship_type=None,
+                            package_2_name=None,
+                            ):
+        assert package_1_name
+        package_1_ref = self.package_ref_from_name(package_1_name)
+        if package_2_name is None:
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships' % \
+                                   package_1_ref)
+            else:
+                return self.offset('/rest/package/%s/%s' %
+                                   (package_1_ref, relationship_type))
+        else:
+            package_2_ref = self.package_ref_from_name(package_2_name)
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships/%s' % \
+                                   (package_1_ref, package_2_ref))
+            else:
+                return self.offset('/rest/package/%s/%s/%s' % \
+                                   (package_1_ref,
+                                    relationship_type,
+                                    package_2_ref))
+
     def anna_offset(self, postfix=''):
         return self.package_offset('annakarenina') + postfix
 
     def tag_offset(self, tag_name=None):
-        if tag_name == None:
+        if tag_name is None:
             # Tag Register
             return self.offset('/rest/tag')
         else:
@@ -129,7 +170,7 @@
 
     def tag_ref_from_name(self, tag_name):
         tag = self.get_tag_by_name(unicode(tag_name))
-        if tag == None:
+        if tag is None:
             return tag_name
         else:
             return self.ref_tag(tag)
@@ -199,9 +240,10 @@
     def assert_msg_represents_russian(self, msg):
         data = self.loads(msg)
         pkgs = set(data)
-        expected_pkgs = set(['annakarenina', 'warandpeace'])
-        missing_pkgs = expected_pkgs - pkgs
-        assert not missing_pkgs, missing_pkgs
+        expected_pkgs = set([self.package_ref_from_name('annakarenina'),
+                             self.package_ref_from_name('warandpeace')])
+        differences = expected_pkgs ^ pkgs
+        assert not differences, '%r != %r' % (pkgs, expected_pkgs)
 
     def data_from_res(self, res):
         return self.loads(res.body)
@@ -218,6 +260,16 @@
         except ValueError, inst:
             raise Exception, "Couldn't loads string '%s': %s" % (chars, inst)
 
+    def assert_json_response(self, res, expected_in_body=None):
+        content_type = res.header_dict['Content-Type']
+        assert 'application/json' in content_type, content_type
+        res_json = self.loads(res.body)
+        if expected_in_body:
+            assert expected_in_body in res_json or \
+                   expected_in_body in str(res_json), \
+                   'Expected to find %r in JSON response %r' % \
+                   (expected_in_body, res_json)
+
 # Todo: Rename to Version1TestCase.
 class Api1TestCase(ApiTestCase):
 
@@ -297,8 +349,6 @@
     def teardown(self):
         model.Session.remove()
         model.repo.rebuild_db()
-        #self.delete_common_fixtures()
-        #self.commit_remove()
         super(BaseModelApiTestCase, self).teardown()
 
     def init_extra_environ(self):
@@ -312,10 +362,38 @@
         application/x-www-form-urlencoded)
 
         '''
+        return self.http_request(offset, data, content_type='application/json',
+                                 request_method='POST',
+                                 content_length=len(data),
+                                 status=status, extra_environ=extra_environ)
+
+    def delete_request(self, offset, status=None, extra_environ=None):
+        ''' Sends a delete request. Similar to the paste.delete but it
+        does not send the content type or content length.
+        '''
+        return self.http_request(offset, data='', content_type=None,
+                                 request_method='DELETE',
+                                 content_length=None,
+                                 status=status,
+                                 extra_environ=extra_environ)
+
+    def http_request(self, offset, data,
+                     content_type='application/json',
+                     request_method='POST',
+                     content_length=None,
+                     status=None,
+                     extra_environ=None):
+        ''' Posts data in the body in a user-specified format.
+        (rather than Paste Fixture\'s default Content-Type of
+        application/x-www-form-urlencoded)
+
+        '''
         environ = self.app._make_environ()
-        environ['CONTENT_TYPE'] = 'application/json'
-        environ['CONTENT_LENGTH'] = str(len(data))
-        environ['REQUEST_METHOD'] = 'POST'
+        if content_type:
+            environ['CONTENT_TYPE'] = content_type
+        if content_length is not None:
+            environ['CONTENT_LENGTH'] = str(content_length)
+        environ['REQUEST_METHOD'] = request_method
         environ['wsgi.input'] = StringIO(data)
         if extra_environ:
             environ.update(extra_environ)


--- a/ckan/tests/functional/api/model/test_group.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/model/test_group.py	Thu Jul 07 20:46:38 2011 +0100
@@ -1,5 +1,7 @@
 import copy
 
+from ckan import model
+
 from nose.tools import assert_equal 
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -29,6 +31,10 @@
         # check group object
         group = self.get_group_by_name(self.testgroupvalues['name'])
         assert group
+        assert group.title == self.testgroupvalues['title'], group
+        assert group.description == self.testgroupvalues['description'], group
+        pkg_names = [pkg.name for pkg in group.packages]
+        assert set(pkg_names) == set(('annakarenina', 'warandpeace')), pkg_names
 
         # check register updated
         res = self.app.get(offset, status=self.STATUS_200_OK)
@@ -53,11 +59,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_409_CONFLICT,
                             extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
     
     def test_entity_get_ok(self):
         offset = self.group_offset(self.roger.name)
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_roger(msg=res.body)
+        assert self.package_ref_from_name('annakarenina') in res, res
+        assert self.group_ref_from_name('roger') in res, res
+        assert not self.package_ref_from_name('warandpeace') in res, res
 
     def test_entity_get_then_post(self):
         # (ticket 662) Ensure an entity you 'get' from a register can be
@@ -70,6 +80,123 @@
                             status=self.STATUS_200_OK,
                             extra_environ=self.extra_environ)
 
+    def test_05_get_group_entity_not_found(self):
+        offset = self.offset('/rest/group/22222')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_10_edit_group(self):
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            offset = self.offset('/rest/group')
+            postparams = '%s=1' % self.dumps(self.testgroupvalues)
+            res = self.app.post(offset, params=postparams, status=[201],
+                    extra_environ=self.extra_environ)
+            model.Session.remove()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert len(group.packages) == 2, group.packages
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # edit it
+        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
+                      'packages':[u'annakarenina']}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
+        assert group.name == group_vals['name']
+        assert group.title == group_vals['title']
+        assert len(group.packages) == 1, group.packages
+        assert group.packages[0].name == group_vals['packages'][0]
+
+    def test_10_edit_group_name_duplicate(self):
+        # create a group with testgroupvalues
+        if not model.Group.by_name(self.testgroupvalues['name']):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.Session.commit()
+
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            rev = model.repo.new_revision()
+            model.repo.commit_and_remove()
+        assert model.Group.by_name(self.testgroupvalues['name'])
+        
+        # create a group with name 'dupname'
+        dupname = u'dupname'
+        if not model.Group.by_name(dupname):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = dupname
+            model.Session.commit()
+        assert model.Group.by_name(dupname)
+
+        # edit first group to have dupname
+        group_vals = {'name':dupname}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
+        
+    def test_11_delete_group(self):
+        # Test Groups Entity Delete 200.
+
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.repo.commit_and_remove()
+
+            rev = model.repo.new_revision()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            model.repo.commit_and_remove()
+        assert group
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # delete it
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[200],
+                extra_environ=self.extra_environ)
+
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert group.state == 'deleted', group.state
+
+        res = self.app.get(offset, status=[403])
+        self.assert_json_response(res, 'Access denied')
+        res = self.app.get(offset, status=[200],
+                           extra_environ=self.extra_environ)
+
+    def test_12_get_group_404(self):
+        # Test Package Entity Get 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_13_delete_group_404(self):
+        # Test Packages Entity Delete 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[404],
+                              extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'not found')
+
+
 class TestGroupsVersion1(Version1TestCase, GroupsTestCase): pass
 class TestGroupsVersion2(Version2TestCase, GroupsTestCase): pass
 class TestGroupsUnversioned(UnversionedTestCase, GroupsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_licenses.py	Thu Jul 07 20:46:38 2011 +0100
@@ -0,0 +1,32 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class LicensesTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = False
+
+    def test_register_get_ok(self):
+        from ckan.model.license import LicenseRegister
+        register = LicenseRegister()
+        assert len(register), "No changesets found in model."
+        offset = self.offset('/rest/licenses')
+        res = self.app.get(offset, status=[200])
+        licenses_data = self.data_from_res(res)
+        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
+        for license_data in licenses_data:
+            id = license_data['id']
+            license = register[id]
+            assert license['title'] == license.title
+            assert license['url'] == license.url
+
+
+class TestLicensesVersion1(Version1TestCase, LicensesTestCase): pass
+class TestLicensesVersion2(Version2TestCase, LicensesTestCase): pass
+class TestLicensesUnversioned(UnversionedTestCase, LicensesTestCase): pass


--- a/ckan/tests/functional/api/model/test_package.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/model/test_package.py	Thu Jul 07 20:46:38 2011 +0100
@@ -1,3 +1,5 @@
+import copy
+
 from nose.tools import assert_equal
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -35,6 +37,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_201_CREATED,
                             extra_environ=self.extra_environ)
+        
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], self.package_fixture_data['name'])
+        assert_equal(pkg['title'], self.package_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(self.package_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(self.package_fixture_data['resources']))
+        assert_equal(pkg['extras'], self.package_fixture_data['extras'])
+
         # Check the value of the Location header.
         location = res.header('Location')
         assert offset in location
@@ -102,8 +113,19 @@
         package = self.get_package_by_name(self.package_fixture_data['name'])
         assert package
         self.assert_equal(package.title, self.package_fixture_data['title'])
+
+    def test_register_post_json(self):
+        assert not self.get_package_by_name(self.package_fixture_data['name'])
+        offset = self.package_offset()
+        data = self.dumps(self.package_fixture_data)
+        res = self.post_json(offset, data, status=self.STATUS_201_CREATED,
+                             extra_environ=self.extra_environ)
+        # Check the database record.
+        self.remove()
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        assert package
+        self.assert_equal(package.title, self.package_fixture_data['title'])
         
-
     def test_register_post_bad_request(self):
         test_params = {
             'name':u'testpackage06_400',
@@ -200,7 +222,8 @@
     def create_package_roles_revision(self, package_data):
         self.create_package(admins=[self.user], data=package_data)
 
-    def assert_package_update_ok(self, package_ref_attribute):
+    def assert_package_update_ok(self, package_ref_attribute,
+                                 method_str):
         old_fixture_data = {
             'name': self.package_fixture_data['name'],
             'url': self.package_fixture_data['url'],
@@ -237,10 +260,28 @@
             'tags': [u'tag1', u'tag2', u'tag4', u'tag5'],
         }
         self.create_package_roles_revision(old_fixture_data)
-        offset = self.package_offset(old_fixture_data['name'])
+        pkg = self.get_package_by_name(old_fixture_data['name'])
+        # This is the one occasion where we reference package explicitly
+        # by name or ID, rather than use the value from self.ref_package_by
+        # because you should be able to specify the package both ways round
+        # for both versions of the API.
+        package_ref = getattr(pkg, package_ref_attribute)
+        offset = self.offset('/rest/package/%s' % package_ref)
         params = '%s=1' % self.dumps(new_fixture_data)
-        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
-                            extra_environ=self.extra_environ)
+        method_func = getattr(self.app, method_str)
+        res = method_func(offset, params=params, status=self.STATUS_200_OK,
+                          extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        assert_equal(pkg['title'], new_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(new_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(new_fixture_data['resources']))
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key2']
+        expected_extras['key1'] = old_fixture_data['extras']['key1']
+        assert_equal(pkg['extras'], expected_extras)
 
         # Check submitted field have changed.
         self.remove()
@@ -285,10 +326,78 @@
         assert not package.extras.has_key('key2')
 
     def test_package_update_ok_by_id(self):
-        self.assert_package_update_ok('id')
+        self.assert_package_update_ok('id', 'post')
 
     def test_entity_update_ok_by_name(self):
-        self.assert_package_update_ok('name')
+        self.assert_package_update_ok('name', 'post')
+
+    def test_package_update_ok_by_id_by_put(self):
+        self.assert_package_update_ok('id', 'put')
+
+    def test_entity_update_ok_by_name_by_put(self):
+        self.assert_package_update_ok('name', 'put')
+
+    def test_package_update_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {
+                u'key1': None, 
+                },
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key1']
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        self.assert_equal(package.extras, {})
+
+    def test_package_update_do_not_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {}, # no extras specified, but existing
+                          # ones should be left alone
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = {u'key1': u'val1'} # should not be deleted
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was not deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        assert len(package.extras) == 1, package.extras
 
     def test_entity_update_conflict(self):
         package1_name = self.package_fixture_data['name']
@@ -300,18 +409,19 @@
         package1_offset = self.package_offset(package1_name)
         self.post(package1_offset, package2_data, self.STATUS_409_CONFLICT)
 
+    def test_entity_update_empty(self):
+        package1_name = self.package_fixture_data['name']
+        package1_data = {'name': package1_name}
+        package1 = self.create_package_roles_revision(package1_data)
+        package2_data = '' # this is the error
+        package1_offset = self.package_offset(package1_name)
+        self.app.put(package1_offset, package2_data,
+                     status=self.STATUS_400_BAD_REQUEST)
+
     def test_entity_delete_ok(self):
         # create a package with package_fixture_data
         if not self.get_package_by_name(self.package_fixture_data['name']):
-            rev = model.repo.new_revision()
-            package = model.Package()
-            model.Session.add(package)
-            package.name = self.package_fixture_data['name']
-            model.repo.commit_and_remove()
-            rev = model.repo.new_revision()
-            package = self.get_package_by_name(self.package_fixture_data['name'])
-            model.setup_default_user_roles(package, [self.user])
-            model.repo.commit_and_remove()
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
         assert self.get_package_by_name(self.package_fixture_data['name'])
         # delete it
         offset = self.package_offset(self.package_fixture_data['name'])
@@ -321,6 +431,19 @@
         self.assert_equal(package.state, 'deleted')
         model.Session.remove()
 
+    def test_entity_delete_ok_without_request_headers(self):
+        # create a package with package_fixture_data
+        if not self.get_package_by_name(self.package_fixture_data['name']):
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
+        assert self.get_package_by_name(self.package_fixture_data['name'])
+        # delete it
+        offset = self.package_offset(self.package_fixture_data['name'])
+        res = self.delete_request(offset, status=self.STATUS_200_OK,
+                                  extra_environ=self.extra_environ)
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        self.assert_equal(package.state, 'deleted')
+        model.Session.remove()
+
     def test_entity_delete_not_found(self):
         package_name = u'random_one'
         assert not model.Session.query(model.Package).filter_by(name=package_name).count()
@@ -363,7 +486,52 @@
         assert len(revisions) == 3, len(revisions)
 
 
-class TestPackagesVersion1(Version1TestCase, PackagesTestCase): pass
+class TestPackagesVersion1(Version1TestCase, PackagesTestCase):
+    def test_06_create_pkg_using_download_url(self):
+        test_params = {
+            'name':u'testpkg06',
+            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
+            }
+        offset = self.package_offset()
+        postparams = '%s=1' % self.dumps(test_params)
+        res = self.app.post(offset, params=postparams, 
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(test_params['name'])
+        assert pkg
+        assert pkg.name == test_params['name'], pkg
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
+
+    def test_10_edit_pkg_with_download_url(self):
+        test_params = {
+            'name':u'testpkg10',
+            'download_url':u'testurl',
+            }
+        rev = model.repo.new_revision()
+        pkg = model.Package()
+        model.Session.add(pkg)
+        pkg.name = test_params['name']
+        pkg.download_url = test_params['download_url']
+        model.Session.commit()
+
+        pkg = self.get_package_by_name(test_params['name'])
+        model.setup_default_user_roles(pkg, [self.user])
+        rev = model.repo.new_revision()
+        model.repo.commit_and_remove()
+        assert self.get_package_by_name(test_params['name'])
+
+        # edit it
+        pkg_vals = {'download_url':u'newurl'}
+        offset = self.package_offset(test_params['name'])
+        postparams = '%s=1' % self.dumps(pkg_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == pkg_vals['download_url']
+
 class TestPackagesVersion2(Version2TestCase, PackagesTestCase): pass
 class TestPackagesUnversioned(UnversionedTestCase, PackagesTestCase): pass
 


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_ratings.py	Thu Jul 07 20:46:38 2011 +0100
@@ -0,0 +1,94 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RatingsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    def test_register_get(self):
+        raise SkipTest('"Rating register get" functionality is not implemented')
+        rating1 = model.Rating(user_ip_address='1.2.3.4',
+                               package=self.anna,
+                               rating=4.0)
+        rating2 = model.Rating(user=model.User.by_name(u'annafan'),
+                               package=self.anna,
+                               rating=2.0)
+        model.Session.add_all((rating1, rating2))
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset()
+        res = self.app.get(offset, status=[200])
+
+    def test_entity_get(self):
+        raise SkipTest('"Rating entity get" functionality is not implemented')
+        rating = model.Rating(user_ip_address='1.2.3.4',
+                              package=self.anna,
+                              rating=4.0)
+        model.Session.add(rating)
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset(self.anna.name)
+        res = self.app.get(offset, status=[200])
+        assert_equal(res, rating_opts['rating'])
+
+    def test_register_post(self):
+        # Test Rating Register Post 200.
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':5}
+        pkg_name = rating_opts['package']
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+        # Get package to see rating
+        offset = self.package_offset(pkg_name)
+        res = self.app.get(offset, status=[200])
+        assert pkg_name in res, res
+        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
+        assert '"ratings_count": 1' in res, res
+        
+        model.Session.remove()
+        
+        # Rerate package
+        offset = self.rating_offset()
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+    def test_entity_post_invalid(self):
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':0}
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'rating')
+        model.Session.remove()
+        pkg = self.get_package_by_name(rating_opts['package'])
+        assert pkg
+        assert len(pkg.ratings) == 0
+
+class TestRatingsVersion1(Version1TestCase, RatingsTestCase): pass
+class TestRatingsVersion2(Version2TestCase, RatingsTestCase): pass
+class TestRatingsUnversioned(UnversionedTestCase, RatingsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 20:46:38 2011 +0100
@@ -0,0 +1,242 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RelationshipsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    @classmethod
+    def setup_class(cls):
+#        super(RelationshipsTestCase, cls).setup_class()
+        cls.testsysadmin = model.User.by_name(u'testsysadmin')
+        cls.comment = u'Comment umlaut: \xfc.'
+
+    def test_01_create_and_read_relationship(self):
+        # check anna has no existing relationships
+        assert not self.anna.get_relationships()
+        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
+        assert self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace') == []
+        assert self.get_relationships(package1_name='annakarenina',
+                                       type='child_of',
+                                       package2_name='warandpeace') == 404
+        assert self.get_relationships_via_package('annakarenina') == []
+
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check package relationship register.
+        rels = self.get_relationships(package1_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       type='parent_of',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # same checks in reverse direction
+        rels = self.get_relationships(package1_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       type='child_of',
+                                      package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        # Check package entity relationships.
+        rels = self.get_relationships_via_package('annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+        
+    def test_03_update_relationship(self):
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check the relationship before update.
+        self.check_relationships_rest('warandpeace', 'annakarenina',
+                                      [{'type': 'child_of',
+                                        'comment': self.comment}])
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+        # Repeat update with same values, to check it remains the same?
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+    def test_05_delete_relationship(self):
+        self.create_annakarenina_parent_of_war_and_peace()
+        self.update_annakarenina_parent_of_war_and_peace()
+        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+        self.delete_annakarenina_parent_of_war_and_peace()
+
+        expected = []
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+    def test_create_relationship_unknown(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'unheard_of_type', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def test_create_relationship_incorrectly(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'relationships', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'type':'parent_of'})
+        # type should do in the URL offset, not the params.
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def create_annakarenina_parent_of_war_and_peace(self):
+        # Create package relationship.
+        # Todo: Redesign this in a RESTful style, so that a relationship is 
+        # created by posting a relationship to a relationship **register**.
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[201],
+                            extra_environ=self.extra_environ)
+        # Check the response
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+        
+        # Check the model, directly.
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+
+    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':comment})
+        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
+        # Check the response (normalised to 'child_of')
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+
+        # Check the model, directly (normalised to 'child_of')
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+        return res
+
+    def delete_annakarenina_parent_of_war_and_peace(self):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
+        assert not res.body
+
+    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
+        offset = self.relationship_offset(package1_name, type, package2_name)
+        allowable_statuses = [200]
+        if type:
+            allowable_statuses.append(404)
+        res = self.app.get(offset, status=allowable_statuses)
+        if res.status == 200:
+            res_dict = self.data_from_res(res) if res.body else []
+            return res_dict
+        else:
+            return 404
+
+    def get_relationships_via_package(self, package1_name):
+        offset = self.package_offset(package1_name)
+        res = self.app.get(offset, status=200)
+        res_dict = self.data_from_res(res)
+        return res_dict['relationships']
+
+    def assert_len_relationships(self, relationships, expected_relationships):
+        len_relationships = len(relationships)
+        len_expected_relationships = len(expected_relationships)
+        if len_relationships != len_expected_relationships:
+            msg = 'Found %i relationships, ' % len_relationships
+            msg += 'but expected %i.' % len_expected_relationships
+            if len_relationships:
+                msg += ' Found: '
+                for r in relationships:
+                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
+                msg += '.'
+            raise Exception, msg
+
+    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
+                                 expected_relationships=[]):
+        rels = self.get_relationships(package1_name=pkg1_name,
+                                      package2_name=pkg2_name)
+        self.assert_len_relationships(rels, expected_relationships) 
+        for rel in rels:
+            the_expected_rel = None
+            for expected_rel in expected_relationships:
+                if expected_rel['type'] == rel['type'] and \
+                   (pkg2_name or expected_rel['object'] == pkg2_name):
+                    the_expected_rel = expected_rel
+                    break
+            if not the_expected_rel:
+                raise Exception('Unexpected relationship: %s %s %s' %
+                                (rel['subject'], rel['type'], rel['object']))
+            for field in ('subject', 'object', 'type', 'comment'):
+                if the_expected_rel.has_key(field):
+                    value = rel[field]
+                    expected = the_expected_rel[field]
+                    assert value == expected, (value, expected, field, rel)
+
+    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
+        subject_ref = self.package_ref_from_name(subject_name)
+        object_ref = self.package_ref_from_name(object_name)
+
+        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
+        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
+        assert rel_dict['type'] == type, (rel_dict, type)
+        assert rel_dict['comment'] == comment, (rel_dict, comment)
+
+class TestRelationshipsVersion1(Version1TestCase, RelationshipsTestCase): pass
+class TestRelationshipsVersion2(Version2TestCase, RelationshipsTestCase): pass
+class TestRelationshipsUnversioned(UnversionedTestCase, RelationshipsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_revisions.py	Thu Jul 07 20:46:38 2011 +0100
@@ -0,0 +1,49 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RevisionsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+    
+    def test_register_get_ok(self):
+        # Check mock register behaviour.
+        offset = self.revision_offset()
+        res = self.app.get(offset, status=200)
+        revs = model.Session.query(model.Revision).all()
+        assert revs, 'There are no revisions in the model.'
+        res_dict = self.data_from_res(res)
+        for rev in revs:
+            assert rev.id in res_dict, (rev.id, res_dict)
+
+    def test_entity_get_ok(self):
+        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
+        assert rev.id
+        assert rev.timestamp.isoformat()
+        offset = self.revision_offset(rev.id)
+        response = self.app.get(offset, status=[200])
+        response_data = self.data_from_res(response)
+        assert_equal(rev.id, response_data['id'])
+        assert_equal(rev.timestamp.isoformat(), response_data['timestamp'])
+        assert 'packages' in response_data
+        packages = response_data['packages']
+        assert isinstance(packages, list)
+        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
+        assert self.ref_package(self.anna) in packages, packages
+        assert self.ref_package(self.war) in packages, packages
+
+    def test_entity_get_404(self):
+        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
+        offset = self.revision_offset(revision_id)
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+class TestRevisionsVersion1(Version1TestCase, RevisionsTestCase): pass
+class TestRevisionsVersion2(Version2TestCase, RevisionsTestCase): pass
+class TestRevisionsUnversioned(UnversionedTestCase, RevisionsTestCase): pass


--- a/ckan/tests/functional/api/model/test_tag.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/model/test_tag.py	Thu Jul 07 20:46:38 2011 +0100
@@ -23,6 +23,10 @@
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_russian(msg=res.body)
 
+    def test_entity_get_not_found(self):
+        offset = self.tag_offset('doesntexist')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
 
 class TestTagsVersion1(Version1TestCase, TagsTestCase): pass
 class TestTagsVersion2(Version2TestCase, TagsTestCase): pass


--- a/ckan/tests/functional/api/test_model.py	Tue Jul 05 17:16:08 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,578 +0,0 @@
-from ckan.tests.functional.api.base import *
-from ckan.lib.create_test_data import CreateTestData
-from ckan.tests import TestController as ControllerTestCase
-
-class ModelApiTestCase(BaseModelApiTestCase):
-
-    def setup(self):
-        self.create_common_fixtures()
-        self.init_extra_environ()
-        self.source = None
-        self.source1 = None
-        self.source2 = None
-        self.source3 = None
-        self.source4 = None
-        self.source5 = None
-        self.job = None
-        self.job1 = None
-        self.job2 = None
-        self.job3 = None
-
-    def teardown(self):
-        model.repo.rebuild_db()
-
-    def test_02_get_tag_register_ok(self):
-        # Test Packages Register Get 200.
-        offset = self.offset('/rest/tag')
-        res = self.app.get(offset, status=[200])
-        assert 'russian' in res, res
-        assert 'tolstoy' in res, res
-
-    def test_02_get_group_register_ok(self):
-        offset = self.offset('/rest/group')
-        res = self.app.get(offset, status=[200])
-        assert self.group_ref_from_name('david') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-
-    def test_04_get_tag(self):
-        offset = self.offset('/rest/tag/tolstoy')
-        res = self.app.get(offset, status=[200])
-        assert 'annakarenina' in res, res
-        assert not 'warandpeace' in res, res
-
-    def test_04_get_group(self):
-        offset = self.offset('/rest/group/roger')
-        res = self.app.get(offset, status=[200])
-        assert self.package_ref_from_name('annakarenina') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-        assert not self.package_ref_from_name('warandpeace') in res, res
-        
-    def test_05_get_group_entity_not_found(self):
-        offset = self.offset('/rest/group/22222')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_05_get_tag_entity_not_found(self):
-        offset = self.offset('/rest/tag/doesntexist')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_06_create_group_entity_ok(self):
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=201,
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        rev = model.repo.new_revision()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        model.setup_default_user_roles(group, [self.user])
-        model.repo.commit_and_remove()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.title == self.testgroupvalues['title'], group
-        assert group.description == self.testgroupvalues['description'], group
-        assert len(group.packages) == 2, len(group.packages)
-        anna = self.anna
-        warandpeace = self.war
-        assert anna in group.packages
-        assert warandpeace in group.packages
-
-        # Check group packages.
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=[200])
-        assert self.testgroupvalues['name'] in res, res
-        assert self.package_ref_from_name(self.testgroupvalues['packages'][0]) in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][0])
-        assert ref in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][1])
-        assert ref in res, res
-        model.Session.remove()
-        
-        # Check create group entity conflict.
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-
-    def test_06_rate_package(self):
-        # Test Rating Register Post 200.
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':5}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-        # Get package to see rating
-        offset = self.offset('/rest/package/%s' % rating_opts['package'])
-        res = self.app.get(offset, status=[200])
-        assert rating_opts['package'] in res, res
-        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
-        assert '"ratings_count": 1' in res, res
-        
-        model.Session.remove()
-        
-        # Rerate package
-        offset = self.offset('/rest/rating')
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-    def test_06_rate_package_out_of_range(self):
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':0}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 0
-
-    def test_10_edit_group(self):
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            offset = self.offset('/rest/group')
-            postparams = '%s=1' % self.dumps(self.testgroupvalues)
-            res = self.app.post(offset, params=postparams, status=[201],
-                    extra_environ=self.extra_environ)
-            model.Session.remove()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert len(group.packages) == 2, group.packages
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # edit it
-        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
-                      'packages':[u'annakarenina']}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
-        assert group.name == group_vals['name']
-        assert group.title == group_vals['title']
-        assert len(group.packages) == 1, group.packages
-        assert group.packages[0].name == group_vals['packages'][0]
-
-    def test_10_edit_group_name_duplicate(self):
-        # create a group with testgroupvalues
-        if not model.Group.by_name(self.testgroupvalues['name']):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.Session.commit()
-
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            rev = model.repo.new_revision()
-            model.repo.commit_and_remove()
-        assert model.Group.by_name(self.testgroupvalues['name'])
-        
-        # create a group with name 'dupname'
-        dupname = u'dupname'
-        if not model.Group.by_name(dupname):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = dupname
-            model.Session.commit()
-        assert model.Group.by_name(dupname)
-
-        # edit first group to have dupname
-        group_vals = {'name':dupname}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[409],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        
-    def test_11_delete_group(self):
-        # Test Groups Entity Delete 200.
-
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.repo.commit_and_remove()
-
-            rev = model.repo.new_revision()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            model.repo.commit_and_remove()
-        assert group
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # delete it
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[200],
-                extra_environ=self.extra_environ)
-
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.state == 'deleted', group.state
-
-        res = self.app.get(offset, status=[403])
-        res = self.app.get(offset, status=[200],
-                           extra_environ=self.extra_environ)
-
-        model.Session.remove()
-
-    def test_12_get_group_404(self):
-        # Test Package Entity Get 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_13_delete_group_404(self):
-        # Test Packages Entity Delete 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[404],
-                              extra_environ=self.extra_environ)
-
-    def test_14_list_revisions(self):
-        # Check mock register behaviour.
-        offset = self.offset('/rest/revision')
-        res = self.app.get(offset, status=200)
-        revs = model.Session.query(model.Revision).all()
-        assert revs, "There are no revisions in the model."
-        res_dict = self.data_from_res(res)
-        for rev in revs:
-            assert rev.id in res_dict, (rev.id, res_dict)
-
-    def test_14_get_revision(self):
-        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
-        assert rev.id
-        assert rev.timestamp.isoformat()
-        offset = self.offset('/rest/revision/%s' % rev.id)
-        response = self.app.get(offset, status=[200])
-        response_data = self.data_from_res(response)
-        assert rev.id == response_data['id']
-        assert rev.timestamp.isoformat() == response_data['timestamp'], (rev.timestamp.isoformat(), response_data['timestamp'])
-        assert 'packages' in response_data
-        packages = response_data['packages']
-        assert isinstance(packages, list)
-        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
-        assert self.ref_package(self.anna) in packages, packages
-        assert self.ref_package(self.war) in packages, packages
-
-    def test_14_get_revision_404(self):
-        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
-        offset = self.offset('/rest/revision/%s' % revision_id)
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_16_list_licenses(self):
-        from ckan.model.license import LicenseRegister
-        register = LicenseRegister()
-        assert len(register), "No changesets found in model."
-        offset = self.offset('/rest/licenses')
-        res = self.app.get(offset, status=[200])
-        licenses_data = self.data_from_res(res)
-        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
-        for license_data in licenses_data:
-            id = license_data['id']
-            license = register[id]
-            assert license['title'] == license.title
-            assert license['url'] == license.url
-
-
-class RelationshipsApiTestCase(ApiTestCase, ControllerTestCase):
-
-    @classmethod
-    def setup_class(self):
-        CreateTestData.create()
-        self.user = self.create_user(name=u'barry')
-        self.testsysadmin = model.User.by_name(u'testsysadmin')
-        self.extra_environ={ 'Authorization' : str(self.user.apikey) }
-        self.comment = u'Comment umlaut: \xfc.'
-
-    @classmethod
-    def teardown_class(self):
-        model.Session.remove()
-        model.repo.rebuild_db()
-        model.Session.remove()
-
-    def teardown(self):
-        for relationship in self.anna.get_relationships():
-            relationship.purge()
-        model.Session.commit()
-        relationships = self.anna.get_relationships()
-        assert relationships == [], "There are still some relationships: %s" % relationships
-
-    def test_01_create_and_read_relationship(self):
-        # check anna has no existing relationships
-        assert not self.anna.get_relationships()
-        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
-        assert self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace') == []
-        assert self.get_relationships(package1_name='annakarenina',
-                                       type='child_of',
-                                       package2_name='warandpeace') == 404
-        assert self.get_relationships_via_package('annakarenina') == []
-
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check package relationship register.
-        rels = self.get_relationships(package1_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       type='parent_of',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # same checks in reverse direction
-        rels = self.get_relationships(package1_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       type='child_of',
-                                      package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        # Check package entity relationships.
-        rels = self.get_relationships_via_package('annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-        
-    def test_03_update_relationship(self):
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check the relationship before update.
-        self.check_relationships_rest('warandpeace', 'annakarenina',
-                                      [{'type': 'child_of',
-                                        'comment': self.comment}])
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-        # Repeat update with same values, to check it remains the same?
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-    def test_05_delete_relationship(self):
-        self.create_annakarenina_parent_of_war_and_peace()
-        self.update_annakarenina_parent_of_war_and_peace()
-        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-        self.delete_annakarenina_parent_of_war_and_peace()
-
-        expected = []
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-    def create_annakarenina_parent_of_war_and_peace(self):
-        # Create package relationship.
-        # Todo: Redesign this in a RESTful style, so that a relationship is 
-        # created by posting a relationship to a relationship **register**.
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':self.comment})
-        res = self.app.post(offset, params=postparams, status=[201],
-                            extra_environ=self.extra_environ)
-        # Check the model, directly.
-        rels = self.anna.get_relationships()
-        assert len(rels) == 1, rels
-        assert rels[0].type == 'child_of'
-        assert rels[0].subject.name == 'warandpeace'
-        assert rels[0].object.name == 'annakarenina'
-
-    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':comment})
-        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
-        return res
-
-    def delete_annakarenina_parent_of_war_and_peace(self):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
-        return res
-
-    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
-        package1_ref = self.package_ref_from_name(package1_name)
-        if not package2_name:
-            offset = self.offset('/rest/package/%s/%s' % (package1_ref, type))
-        else:
-            package2_ref = self.package_ref_from_name(package2_name)
-            offset = self.offset('/rest/package/%s/%s/%s' % (
-                str(package1_ref), type, str(package2_ref)))
-        allowable_statuses = [200]
-        if type:
-            allowable_statuses.append(404)
-        res = self.app.get(offset, status=allowable_statuses)
-        if res.status == 200:
-            res_dict = self.data_from_res(res) if res.body else []
-            return res_dict
-        else:
-            return 404
-
-    def get_relationships_via_package(self, package1_name):
-        offset = self.offset('/rest/package/%s' % (str(package1_name)))
-        res = self.app.get(offset, status=200)
-        res_dict = self.data_from_res(res)
-        return res_dict['relationships']
-
-    def assert_len_relationships(self, relationships, expected_relationships):
-        len_relationships = len(relationships)
-        len_expected_relationships = len(expected_relationships)
-        if len_relationships != len_expected_relationships:
-            msg = 'Found %i relationships, ' % len_relationships
-            msg += 'but expected %i.' % len_expected_relationships
-            if len_relationships:
-                msg += ' Found: '
-                for r in relationships:
-                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
-                msg += '.'
-            raise Exception, msg
-
-    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
-                                 expected_relationships=[]):
-        rels = self.get_relationships(package1_name=pkg1_name,
-                                      package2_name=pkg2_name)
-        self.assert_len_relationships(rels, expected_relationships) 
-        for rel in rels:
-            the_expected_rel = None
-            for expected_rel in expected_relationships:
-                if expected_rel['type'] == rel['type'] and \
-                   (pkg2_name or expected_rel['object'] == pkg2_name):
-                    the_expected_rel = expected_rel
-                    break
-            if not the_expected_rel:
-                raise Exception('Unexpected relationship: %s %s %s' %
-                                (rel['subject'], rel['type'], rel['object']))
-            for field in ('subject', 'object', 'type', 'comment'):
-                if the_expected_rel.has_key(field):
-                    value = rel[field]
-                    expected = the_expected_rel[field]
-                    assert value == expected, (value, expected, field, rel)
-
-    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
-        subject_ref = self.package_ref_from_name(subject_name)
-        object_ref = self.package_ref_from_name(object_name)
-
-        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
-        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
-        assert rel_dict['type'] == type, (rel_dict, type)
-        assert rel_dict['comment'] == comment, (rel_dict, comment)
- 
-
-# Tests for Version 1 of the API.
-class TestModelApi1(Api1TestCase, ModelApiTestCase):
-
-    def test_06_create_pkg_using_download_url(self):
-        test_params = {
-            'name':u'testpkg06',
-            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
-            }
-        offset = self.package_offset()
-        postparams = '%s=1' % self.dumps(test_params)
-        res = self.app.post(offset, params=postparams, 
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(test_params['name'])
-        assert pkg
-        assert pkg.name == test_params['name'], pkg
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
-
-    def test_10_edit_pkg_with_download_url(self):
-        test_params = {
-            'name':u'testpkg10',
-            'download_url':u'testurl',
-            }
-        rev = model.repo.new_revision()
-        pkg = model.Package()
-        model.Session.add(pkg)
-        pkg.name = test_params['name']
-        pkg.download_url = test_params['download_url']
-        model.Session.commit()
-
-        pkg = self.get_package_by_name(test_params['name'])
-        model.setup_default_user_roles(pkg, [self.user])
-        rev = model.repo.new_revision()
-        model.repo.commit_and_remove()
-        assert self.get_package_by_name(test_params['name'])
-
-        # edit it
-        pkg_vals = {'download_url':u'newurl'}
-        offset = self.package_offset(test_params['name'])
-        postparams = '%s=1' % self.dumps(pkg_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == pkg_vals['download_url']
-
-
-class TestRelationshipsApi1(Api1TestCase, RelationshipsApiTestCase): pass
-
-# Tests for Version 2 of the API.
-class TestModelApi2(Api2TestCase, ModelApiTestCase): pass
-class TestRelationshipsApi2(Api2TestCase, RelationshipsApiTestCase): pass
-
-# Tests for unversioned API.
-class TestModelApiUnversioned(ApiUnversionedTestCase, ModelApiTestCase): pass
-class TestRelationshipsApiUnversioned(RelationshipsApiTestCase, ApiUnversionedTestCase): pass
-


--- a/ckan/tests/functional/api/test_package_search.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/test_package_search.py	Thu Jul 07 20:46:38 2011 +0100
@@ -179,6 +179,7 @@
     def test_08_uri_qjson_malformed(self):
         offset = self.base_url + '?qjson="q":""' # user forgot the curly braces
         res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Could not read parameters')
         
     def test_08_all_fields(self):
         rating = model.Rating(user_ip_address=u'123.1.2.3',
@@ -211,6 +212,7 @@
         res = self.app.get(offset, status=400)
         assert('boolean' in res.body)
         assert('all_fields' in res.body)
+        self.assert_json_response(res, 'boolean')
 
     def test_09_just_tags(self):
         offset = self.base_url + '?tags=russian&all_fields=1'
@@ -257,6 +259,7 @@
         res = self.app.get(offset, status=400)
         assert('integer' in res.body)
         assert('offset' in res.body)
+        self.assert_json_response(res, 'integer')
 
     def test_12_all_packages_qjson(self):
         query = {'q': ''}


--- a/ckan/tests/functional/api/test_resource_search.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/test_resource_search.py	Thu Jul 07 20:46:38 2011 +0100
@@ -70,6 +70,7 @@
     def test_04_bad_option(self):
         offset = self.base_url + '?random=option'
         result = self.app.get(offset, status=400)
+        self.assert_json_response(result, 'Bad request - Bad search option')
 
     def test_05_options(self):
         offset = self.base_url + '?url=site&all_fields=1&callback=mycallback'


--- a/ckan/tests/functional/api/test_revision_search.py	Tue Jul 05 17:16:08 2011 +0100
+++ b/ckan/tests/functional/api/test_revision_search.py	Thu Jul 07 20:46:38 2011 +0100
@@ -11,13 +11,19 @@
     def teardown_class(self):
         model.repo.rebuild_db()
 
-    def test_12_search_revision_basic(self):
+    def test_12_search_revision_bad_requests(self):
         offset = self.offset('/search/revision')
         # Check bad request.
-        self.app.get(offset, status=400)
-        self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_id=', status=400)
+        res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_id=', status=400)
+        self.assert_json_response(res, 'Bad request - No revision specified')
+        res = self.app.get(offset+'?since_id=1234', status=404)
+        self.assert_json_response(res, 'Not found - There is no revision')
 
     def test_12_search_revision_since_rev(self):
         offset = self.offset('/search/revision')
@@ -54,8 +60,9 @@
         assert res_list == [], res_list
         # Check bad format.
         params = "?since_time=2010-04-31T23:45"
-        self.app.get(offset+params, status=400)
+        res = self.app.get(offset+params, status=400)
+        self.assert_json_response(res, 'Bad request - ValueError: day is out of range for month')
 
 
-class TestPackageSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
-class TestPackageSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass


--- a/doc/api/model_formats.rst.inc	Tue Jul 05 17:16:08 2011 +0100
+++ b/doc/api/model_formats.rst.inc	Thu Jul 07 20:46:38 2011 +0100
@@ -26,6 +26,6 @@
 
  * When you update an object, fields that you don't supply will remain as they were before.
 
- * To delete an 'extra' key-value pair, supply the key with a None value.
+ * To delete an 'extra' key-value pair, supply the key with JSON value: ``null``
 
  * When you read a package then some additional information is supplied that cannot current be adjusted throught the CKAN API. This includes info on Package Relationship ('relationships'), Group membership ('groups'), ratings ('ratings_average' and 'ratings_count'), full URL of the package in CKAN ('ckan_url') and Package ID ('id'). This is purely a convenience for clients, and only forms part of the Package on GET.


--- a/doc/api/model_resources_table.rst.inc	Tue Jul 05 17:16:08 2011 +0100
+++ b/doc/api/model_resources_table.rst.inc	Thu Jul 07 20:46:38 2011 +0100
@@ -8,7 +8,7 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Group Register                 | ``/rest/group``                                                   |
 +--------------------------------+-------------------------------------------------------------------+
-| Group Entity                   | ``/rest/group/GROUP-NAME``                                        |
+| Group Entity                   | ``/rest/group/GROUP-REF``                                         |
 +--------------------------------+-------------------------------------------------------------------+
 | Tag Register                   | ``/rest/tag``                                                     |
 +--------------------------------+-------------------------------------------------------------------+
@@ -16,9 +16,9 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Rating Register                | ``/rest/rating``                                                  |
 +--------------------------------+-------------------------------------------------------------------+
-| Rating Entity                  | ``/rest/rating/PACKAGE-REF``                                      |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/RELATIONSHIP-TYPE``                   |
 +--------------------------------+-------------------------------------------------------------------+
 | Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships/PACKAGE-REF``           |
 +--------------------------------+-------------------------------------------------------------------+


http://bitbucket.org/okfn/ckan/changeset/27a8c0e546da/
changeset:   27a8c0e546da
branch:      feature-1141-moderated-edits-ajax
user:        dread
date:        2011-07-07 23:01:30
summary:     [lib,logic][l]: Transplant of defect-1214-api-improvements branch (cset:be2be28660b1).
affected #:  21 files (36.7 KB)

--- a/ckan/controllers/api.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/controllers/api.py	Thu Jul 07 22:01:30 2011 +0100
@@ -30,10 +30,6 @@
     }
 class ApiController(BaseController):
 
-    content_type_text = 'text/;charset=utf-8'
-    content_type_html = 'text/html;charset=utf-8'
-    content_type_json = 'application/json;charset=utf-8'
-
     def __call__(self, environ, start_response):
         self._identify_user()
         if not self.authorizer.am_authorized(c, model.Action.SITE_READ, model.System):
@@ -45,6 +41,8 @@
             start_response(body, response.headers.items())
             return [response_msg]
         else:
+            # avoid status_code_redirect intercepting error responses
+            environ['pylons.status_code_redirect'] = True
             return BaseController.__call__(self, environ, start_response)
 
     def _finish(self, status_int, response_data=None,
@@ -106,6 +104,14 @@
                             response_data=response_data,
                             content_type='json')
 
+    def _finish_bad_request(self, extra_msg=None):
+        response_data = _('Bad request')
+        if extra_msg:
+            response_data = '%s - %s' % (response_data, extra_msg)
+        return self._finish(status_int=400,
+                            response_data=response_data,
+                            content_type='json')
+
     def _wrap_jsonp(self, callback, response_msg):
         return '%s(%s);' % (callback, response_msg)
 
@@ -140,8 +146,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot list entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot list entity of this type: %s') % register)
         try:
             return self._finish_ok(action(context))
         except NotFound, e:
@@ -151,7 +157,6 @@
             return self._finish_not_authz()
 
     def show(self, ver=None, register=None, subregister=None, id=None, id2=None):
-
         action_map = {
             'revision': get.revision_show,
             'group': get.group_show_rest,
@@ -171,8 +176,8 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot read entity of this type: %s') % register
+            return self._finish_bad_request(
+                gettext('Cannot read entity of this type: %s') % register)
         try:
             
             return self._finish_ok(action(context))
@@ -205,15 +210,16 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
 
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot create new entity of this type: %s %s') % (register, subregister)
+            return self._finish_bad_request(
+                gettext('Cannot create new entity of this type: %s %s') % \
+                (register, subregister))
         try:
             response_data = action(request_data, context)
             location = None
@@ -251,15 +257,15 @@
         try:
             request_data = self._get_request_data()
         except ValueError, inst:
-            response.status_int = 400
-            return gettext('JSON Error: %s') % str(inst)
+            return self._finish_bad_request(
+                gettext('JSON Error: %s') % str(inst))
         action = action_map.get((register, subregister)) 
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot update entity of this type: %s') % \
-                    register.encode('utf-8')
+            return self._finish_bad_request(
+                gettext('Cannot update entity of this type: %s') % \
+                    register.encode('utf-8'))
         try:
             response_data = action(request_data, context)
             return self._finish_ok(response_data)
@@ -294,8 +300,9 @@
         if not action:
             action = action_map.get(register)
         if not action:
-            response.status_int = 400
-            return gettext('Cannot delete entity of this type: %s %s') % (register, subregister or '')
+            return self._finish_bad_request(
+                gettext('Cannot delete entity of this type: %s %s') %\
+                (register, subregister or ''))
         try:
             response_data = action(context)
             return self._finish_ok(response_data)
@@ -315,29 +322,31 @@
             since_time = None
             if request.params.has_key('since_id'):
                 id = request.params['since_id']
+                if not id:
+                    return self._finish_bad_request(
+                        gettext(u'No revision specified'))
                 rev = model.Session.query(model.Revision).get(id)
                 if rev is None:
-                    response.status_int = 400
-                    return gettext(u'There is no revision with id: %s') % id
+                    return self._finish_not_found(
+                        gettext(u'There is no revision with id: %s') % id)
                 since_time = rev.timestamp
             elif request.params.has_key('since_time'):
                 since_time_str = request.params['since_time']
                 try:
                     since_time = model.strptimestamp(since_time_str)
                 except ValueError, inst:
-                    response.status_int = 400
-                    return 'ValueError: %s' % inst
+                    return self._finish_bad_request('ValueError: %s' % inst)
             else:
-                response.status_int = 400
-                return gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')")
+                return self._finish_bad_request(
+                    gettext("Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')"))
             revs = model.Session.query(model.Revision).filter(model.Revision.timestamp>since_time)
             return self._finish_ok([rev.id for rev in revs])
         elif register == 'package' or register == 'resource':
             try:
                 params = self._get_search_params(request.params)
             except ValueError, e:
-                response.status_int = 400
-                return gettext('Could not read parameters: %r' % e)
+                return self._finish_bad_request(
+                    gettext('Could not read parameters: %r' % e))
             options = QueryOptions()
             for k, v in params.items():
                 if (k in DEFAULT_OPTIONS.keys()):
@@ -373,11 +382,11 @@
                 return self._finish_ok(results)
             except SearchError, e:
                 log.exception(e)
-                response.status_int = 400
-                return gettext('Bad search option: %s') % e
+                return self._finish_bad_request(
+                    gettext('Bad search option: %s') % e)
         else:
-            response.status_int = 404
-            return gettext('Unknown register: %s') % register
+            return self._finish_not_found(
+                gettext('Unknown register: %s') % register)
 
     @classmethod
     def _get_search_params(cls, request_params):


--- a/ckan/lib/dictization/model_save.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/lib/dictization/model_save.py	Thu Jul 07 22:01:30 2011 +0100
@@ -67,9 +67,6 @@
 
 def package_extras_save(extra_dicts, obj, context):
 
-    allow_partial_update = context.get("allow_partial_update", False)
-    if not extra_dicts and allow_partial_update:
-        return
     model = context["model"]
     session = context["session"]
 
@@ -81,7 +78,10 @@
     for extra_dict in extra_dicts:
         if extra_dict.get("deleted"):
             continue
-        if extras_as_string:
+        
+        if extra_dict['value'] is None:
+            pass
+        elif extras_as_string:
             new_extras[extra_dict["key"]] = extra_dict["value"]
         else:
             new_extras[extra_dict["key"]] = json.loads(extra_dict["value"])
@@ -318,10 +318,15 @@
             updated_extras.update(value)
 
             new_value = []
+            
             for extras_key, extras_value in updated_extras.iteritems():
                 if extras_value is not None:
                     new_value.append({"key": extras_key,
                                       "value": json.dumps(extras_value)})
+                else:
+                    new_value.append({"key": extras_key,
+                                      "value": None})
+
         dictized[key] = new_value
 
     groups = dictized.pop('groups', None)


--- a/ckan/logic/action/create.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/logic/action/create.py	Thu Jul 07 22:01:30 2011 +0100
@@ -91,7 +91,7 @@
     user = context['user']
     id = context["id"]
     id2 = context["id2"]
-    rel = context["rel"]
+    rel_type = context["rel"]
     api = context.get('api_version') or '1'
     ref_package_by = 'id' if api == '2' else 'name'
 
@@ -112,14 +112,14 @@
     ##FIXME should have schema
     comment = data_dict.get('comment', u'')
 
-    existing_rels = pkg1.get_relationships_with(pkg2, rel)
+    existing_rels = pkg1.get_relationships_with(pkg2, rel_type)
     if existing_rels:
         return _update_package_relationship(existing_rels[0],
                                             comment, context)
     rev = model.repo.new_revision()
     rev.author = user
-    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel, pkg2)
-    rel = pkg1.add_relationship(rel, pkg2, comment=comment)
+    rev.message = _(u'REST API: Create package relationship: %s %s %s') % (pkg1, rel_type, pkg2)
+    rel = pkg1.add_relationship(rel_type, pkg2, comment=comment)
     model.repo.commit_and_remove()
     relationship_dicts = rel.as_dict(ref_package_by=ref_package_by)
     return relationship_dicts


--- a/ckan/logic/action/get.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/logic/action/get.py	Thu Jul 07 22:01:30 2011 +0100
@@ -215,7 +215,7 @@
 
 def tag_show(context):
     model = context['model']
-    api = context.get('api') or '1'
+    api = context.get('api_version') or '1'
     id = context['id']
     ref_package_by = 'id' if api == '2' else 'name'
     obj = model.Tag.get(id) #TODO tags


--- a/ckan/logic/action/update.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/logic/action/update.py	Thu Jul 07 22:01:30 2011 +0100
@@ -6,7 +6,12 @@
 from ckan.plugins import PluginImplementations, IGroupController, IPackageController
 from ckan.logic import NotFound, check_access, NotAuthorized, ValidationError
 from ckan.lib.base import _
-from ckan.lib.dictization.model_dictize import group_dictize, package_dictize
+from ckan.lib.dictization.model_dictize import (package_dictize,
+                                                package_to_api1,
+                                                package_to_api2,
+                                                group_dictize,
+                                                group_to_api1,
+                                                group_to_api2)
 from ckan.lib.dictization.model_save import (group_api_to_dict,
                                              package_api_to_dict,
                                              group_dict_save,
@@ -172,7 +177,7 @@
         if 'message' in context:
             rev.message = context['message']
         else:
-            rev.message = _(u'REST API: Create object %s') % data.get("name")
+            rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     pkg = package_dict_save(data, context)
 
@@ -256,7 +261,7 @@
     if 'message' in context:
         rev.message = context['message']
     else:
-        rev.message = _(u'REST API: Create object %s') % data.get("name")
+        rev.message = _(u'REST API: Update object %s') % data.get("name")
 
     group = group_dict_save(data, context)
 
@@ -275,19 +280,38 @@
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     pkg = model.Package.get(id)
     context["package"] = pkg
     context["allow_partial_update"] = True
     dictized_package = package_api_to_dict(data_dict, context)
-    return package_update(dictized_package, context)
+    dictized_after = package_update(dictized_package, context)
+
+    pkg = context['package']
+
+    if api == '1':
+        package_dict = package_to_api1(pkg, context)
+    else:
+        package_dict = package_to_api2(pkg, context)
+
+    return package_dict
 
 def group_update_rest(data_dict, context):
 
     model = context['model']
     id = context["id"]
+    api = context.get('api_version') or '1'
     group = model.Group.get(id)
     context["group"] = group
     context["allow_partial_update"] = True
     dictized_package = group_api_to_dict(data_dict, context)
-    return group_update(dictized_package, context)
+    dictized_after = group_update(dictized_package, context)
 
+    group = context['group']
+
+    if api == '1':
+        group_dict = group_to_api1(group, context)
+    else:
+        group_dict = group_to_api2(group, context)
+
+    return group_dict


--- a/ckan/logic/schema.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/logic/schema.py	Thu Jul 07 22:01:30 2011 +0100
@@ -167,7 +167,7 @@
     schema = {
         'id': [ignore],
         'key': [not_empty, unicode],
-        'value': [not_missing, unicode],
+        'value': [not_missing],
         'state': [ignore],
         'deleted': [ignore_missing],
         'revision_timestamp': [ignore],


--- a/ckan/model/package.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/model/package.py	Thu Jul 07 22:01:30 2011 +0100
@@ -225,7 +225,7 @@
             subject = related_package
             object_ = self
         else:
-            raise NotImplementedError, 'Package relationship type: %r' % type_
+            raise KeyError, 'Package relationship type: %r' % type_
 
         rels = self.get_relationships(with_package=related_package,
                                       type=type_, active=False, direction="forward")


--- a/ckan/tests/functional/api/base.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/base.py	Thu Jul 07 22:01:30 2011 +0100
@@ -69,7 +69,7 @@
         return '%s%s' % (base, path)
 
     def package_offset(self, package_name=None):
-        if package_name == None:
+        if package_name is None:
             # Package Register
             return self.offset('/rest/package')
         else:
@@ -79,14 +79,14 @@
 
     def package_ref_from_name(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
 
     def package_id_from_ref(self, package_name):
         package = self.get_package_by_name(unicode(package_name))
-        if package == None:
+        if package is None:
             return package_name
         else:
             return self.ref_package(package)
@@ -96,7 +96,7 @@
         return getattr(package, self.ref_package_by)
 
     def group_offset(self, group_name=None):
-        if group_name == None:
+        if group_name is None:
             # Group Register
             return self.offset('/rest/group')
         else:
@@ -106,7 +106,7 @@
 
     def group_ref_from_name(self, group_name):
         group = self.get_group_by_name(unicode(group_name))
-        if group == None:
+        if group is None:
             return group_name
         else:
             return self.ref_group(group)
@@ -115,11 +115,52 @@
         assert self.ref_group_by in ['id', 'name']
         return getattr(group, self.ref_group_by)
 
+    def revision_offset(self, revision_id=None):
+        if revision_id is None:
+            # Revision Register
+            return self.offset('/rest/revision')
+        else:
+            # Revision Entity
+            return self.offset('/rest/revision/%s' % revision_id)
+
+    def rating_offset(self, package_name=None):
+        if package_name is None:
+            # Revision Register
+            return self.offset('/rest/rating')
+        else:
+            # Revision Entity
+            package_ref = self.package_ref_from_name(package_name)
+            return self.offset('/rest/rating/%s' % package_ref)
+
+    def relationship_offset(self, package_1_name=None,
+                            relationship_type=None,
+                            package_2_name=None,
+                            ):
+        assert package_1_name
+        package_1_ref = self.package_ref_from_name(package_1_name)
+        if package_2_name is None:
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships' % \
+                                   package_1_ref)
+            else:
+                return self.offset('/rest/package/%s/%s' %
+                                   (package_1_ref, relationship_type))
+        else:
+            package_2_ref = self.package_ref_from_name(package_2_name)
+            if not relationship_type:
+                return self.offset('/rest/package/%s/relationships/%s' % \
+                                   (package_1_ref, package_2_ref))
+            else:
+                return self.offset('/rest/package/%s/%s/%s' % \
+                                   (package_1_ref,
+                                    relationship_type,
+                                    package_2_ref))
+
     def anna_offset(self, postfix=''):
         return self.package_offset('annakarenina') + postfix
 
     def tag_offset(self, tag_name=None):
-        if tag_name == None:
+        if tag_name is None:
             # Tag Register
             return self.offset('/rest/tag')
         else:
@@ -129,7 +170,7 @@
 
     def tag_ref_from_name(self, tag_name):
         tag = self.get_tag_by_name(unicode(tag_name))
-        if tag == None:
+        if tag is None:
             return tag_name
         else:
             return self.ref_tag(tag)
@@ -199,9 +240,10 @@
     def assert_msg_represents_russian(self, msg):
         data = self.loads(msg)
         pkgs = set(data)
-        expected_pkgs = set(['annakarenina', 'warandpeace'])
-        missing_pkgs = expected_pkgs - pkgs
-        assert not missing_pkgs, missing_pkgs
+        expected_pkgs = set([self.package_ref_from_name('annakarenina'),
+                             self.package_ref_from_name('warandpeace')])
+        differences = expected_pkgs ^ pkgs
+        assert not differences, '%r != %r' % (pkgs, expected_pkgs)
 
     def data_from_res(self, res):
         return self.loads(res.body)
@@ -218,6 +260,16 @@
         except ValueError, inst:
             raise Exception, "Couldn't loads string '%s': %s" % (chars, inst)
 
+    def assert_json_response(self, res, expected_in_body=None):
+        content_type = res.header_dict['Content-Type']
+        assert 'application/json' in content_type, content_type
+        res_json = self.loads(res.body)
+        if expected_in_body:
+            assert expected_in_body in res_json or \
+                   expected_in_body in str(res_json), \
+                   'Expected to find %r in JSON response %r' % \
+                   (expected_in_body, res_json)
+
 # Todo: Rename to Version1TestCase.
 class Api1TestCase(ApiTestCase):
 
@@ -297,8 +349,6 @@
     def teardown(self):
         model.Session.remove()
         model.repo.rebuild_db()
-        #self.delete_common_fixtures()
-        #self.commit_remove()
         super(BaseModelApiTestCase, self).teardown()
 
     def init_extra_environ(self):
@@ -312,22 +362,38 @@
         application/x-www-form-urlencoded)
 
         '''
-        return self.post_body(offset, data, content_type='application/json',
-                              content_length=len(data),
-                              status=status, extra_environ=extra_environ)
+        return self.http_request(offset, data, content_type='application/json',
+                                 request_method='POST',
+                                 content_length=len(data),
+                                 status=status, extra_environ=extra_environ)
 
-    def post_body(self, offset, data, content_type, content_length=None,
-                  status=None, extra_environ=None):
+    def delete_request(self, offset, status=None, extra_environ=None):
+        ''' Sends a delete request. Similar to the paste.delete but it
+        does not send the content type or content length.
+        '''
+        return self.http_request(offset, data='', content_type=None,
+                                 request_method='DELETE',
+                                 content_length=None,
+                                 status=status,
+                                 extra_environ=extra_environ)
+
+    def http_request(self, offset, data,
+                     content_type='application/json',
+                     request_method='POST',
+                     content_length=None,
+                     status=None,
+                     extra_environ=None):
         ''' Posts data in the body in a user-specified format.
         (rather than Paste Fixture\'s default Content-Type of
         application/x-www-form-urlencoded)
 
         '''
         environ = self.app._make_environ()
-        environ['CONTENT_TYPE'] = content_type
+        if content_type:
+            environ['CONTENT_TYPE'] = content_type
         if content_length is not None:
             environ['CONTENT_LENGTH'] = str(content_length)
-        environ['REQUEST_METHOD'] = 'POST'
+        environ['REQUEST_METHOD'] = request_method
         environ['wsgi.input'] = StringIO(data)
         if extra_environ:
             environ.update(extra_environ)


--- a/ckan/tests/functional/api/model/test_group.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/model/test_group.py	Thu Jul 07 22:01:30 2011 +0100
@@ -1,5 +1,7 @@
 import copy
 
+from ckan import model
+
 from nose.tools import assert_equal 
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -29,6 +31,10 @@
         # check group object
         group = self.get_group_by_name(self.testgroupvalues['name'])
         assert group
+        assert group.title == self.testgroupvalues['title'], group
+        assert group.description == self.testgroupvalues['description'], group
+        pkg_names = [pkg.name for pkg in group.packages]
+        assert set(pkg_names) == set(('annakarenina', 'warandpeace')), pkg_names
 
         # check register updated
         res = self.app.get(offset, status=self.STATUS_200_OK)
@@ -53,11 +59,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_409_CONFLICT,
                             extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
     
     def test_entity_get_ok(self):
         offset = self.group_offset(self.roger.name)
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_roger(msg=res.body)
+        assert self.package_ref_from_name('annakarenina') in res, res
+        assert self.group_ref_from_name('roger') in res, res
+        assert not self.package_ref_from_name('warandpeace') in res, res
 
     def test_entity_get_then_post(self):
         # (ticket 662) Ensure an entity you 'get' from a register can be
@@ -70,6 +80,123 @@
                             status=self.STATUS_200_OK,
                             extra_environ=self.extra_environ)
 
+    def test_05_get_group_entity_not_found(self):
+        offset = self.offset('/rest/group/22222')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_10_edit_group(self):
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            offset = self.offset('/rest/group')
+            postparams = '%s=1' % self.dumps(self.testgroupvalues)
+            res = self.app.post(offset, params=postparams, status=[201],
+                    extra_environ=self.extra_environ)
+            model.Session.remove()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert len(group.packages) == 2, group.packages
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # edit it
+        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
+                      'packages':[u'annakarenina']}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
+        assert group.name == group_vals['name']
+        assert group.title == group_vals['title']
+        assert len(group.packages) == 1, group.packages
+        assert group.packages[0].name == group_vals['packages'][0]
+
+    def test_10_edit_group_name_duplicate(self):
+        # create a group with testgroupvalues
+        if not model.Group.by_name(self.testgroupvalues['name']):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.Session.commit()
+
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            rev = model.repo.new_revision()
+            model.repo.commit_and_remove()
+        assert model.Group.by_name(self.testgroupvalues['name'])
+        
+        # create a group with name 'dupname'
+        dupname = u'dupname'
+        if not model.Group.by_name(dupname):
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = dupname
+            model.Session.commit()
+        assert model.Group.by_name(dupname)
+
+        # edit first group to have dupname
+        group_vals = {'name':dupname}
+        offset = self.group_offset(self.testgroupvalues['name'])
+        postparams = '%s=1' % self.dumps(group_vals)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'Group name already exists')
+        
+    def test_11_delete_group(self):
+        # Test Groups Entity Delete 200.
+
+        # create a group with testgroupvalues
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        if not group:
+            rev = model.repo.new_revision()
+            group = model.Group()
+            model.Session.add(group)
+            group.name = self.testgroupvalues['name']
+            model.repo.commit_and_remove()
+
+            rev = model.repo.new_revision()
+            group = model.Group.by_name(self.testgroupvalues['name'])
+            model.setup_default_user_roles(group, [self.user])
+            model.repo.commit_and_remove()
+        assert group
+        user = model.User.by_name(self.user_name)
+        model.setup_default_user_roles(group, [user])
+
+        # delete it
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[200],
+                extra_environ=self.extra_environ)
+
+        group = model.Group.by_name(self.testgroupvalues['name'])
+        assert group
+        assert group.state == 'deleted', group.state
+
+        res = self.app.get(offset, status=[403])
+        self.assert_json_response(res, 'Access denied')
+        res = self.app.get(offset, status=[200],
+                           extra_environ=self.extra_environ)
+
+    def test_12_get_group_404(self):
+        # Test Package Entity Get 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+    def test_13_delete_group_404(self):
+        # Test Packages Entity Delete 404.
+        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
+        offset = self.group_offset(self.testgroupvalues['name'])
+        res = self.app.delete(offset, status=[404],
+                              extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'not found')
+
+
 class TestGroupsVersion1(Version1TestCase, GroupsTestCase): pass
 class TestGroupsVersion2(Version2TestCase, GroupsTestCase): pass
 class TestGroupsUnversioned(UnversionedTestCase, GroupsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_licenses.py	Thu Jul 07 22:01:30 2011 +0100
@@ -0,0 +1,32 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class LicensesTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = False
+
+    def test_register_get_ok(self):
+        from ckan.model.license import LicenseRegister
+        register = LicenseRegister()
+        assert len(register), "No changesets found in model."
+        offset = self.offset('/rest/licenses')
+        res = self.app.get(offset, status=[200])
+        licenses_data = self.data_from_res(res)
+        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
+        for license_data in licenses_data:
+            id = license_data['id']
+            license = register[id]
+            assert license['title'] == license.title
+            assert license['url'] == license.url
+
+
+class TestLicensesVersion1(Version1TestCase, LicensesTestCase): pass
+class TestLicensesVersion2(Version2TestCase, LicensesTestCase): pass
+class TestLicensesUnversioned(UnversionedTestCase, LicensesTestCase): pass


--- a/ckan/tests/functional/api/model/test_package.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/model/test_package.py	Thu Jul 07 22:01:30 2011 +0100
@@ -1,3 +1,5 @@
+import copy
+
 from nose.tools import assert_equal
 
 from ckan.tests.functional.api.base import BaseModelApiTestCase
@@ -35,6 +37,15 @@
         res = self.app.post(offset, params=postparams,
                             status=self.STATUS_201_CREATED,
                             extra_environ=self.extra_environ)
+        
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], self.package_fixture_data['name'])
+        assert_equal(pkg['title'], self.package_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(self.package_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(self.package_fixture_data['resources']))
+        assert_equal(pkg['extras'], self.package_fixture_data['extras'])
+
         # Check the value of the Location header.
         location = res.header('Location')
         assert offset in location
@@ -107,14 +118,26 @@
         assert not self.get_package_by_name(self.package_fixture_data['name'])
         offset = self.package_offset()
         data = self.dumps(self.package_fixture_data)
-        res = self.post_body(offset, data, content_type='something/unheard_of',
-                             status=self.STATUS_400_BAD_REQUEST,
-                             extra_environ=self.extra_environ)
+        res = self.http_request(offset, data, content_type='something/unheard_of',
+                                status=self.STATUS_400_BAD_REQUEST,
+                                extra_environ=self.extra_environ)
         # Check there is no database record.
         self.remove()
         package = self.get_package_by_name(self.package_fixture_data['name'])
         assert not package
 
+    def test_register_post_json(self):
+        assert not self.get_package_by_name(self.package_fixture_data['name'])
+        offset = self.package_offset()
+        data = self.dumps(self.package_fixture_data)
+        res = self.post_json(offset, data, status=self.STATUS_201_CREATED,
+                             extra_environ=self.extra_environ)
+        # Check the database record.
+        self.remove()
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        assert package
+        self.assert_equal(package.title, self.package_fixture_data['title'])
+        
     def test_register_post_bad_request(self):
         test_params = {
             'name':u'testpackage06_400',
@@ -211,7 +234,8 @@
     def create_package_roles_revision(self, package_data):
         self.create_package(admins=[self.user], data=package_data)
 
-    def assert_package_update_ok(self, package_ref_attribute):
+    def assert_package_update_ok(self, package_ref_attribute,
+                                 method_str):
         old_fixture_data = {
             'name': self.package_fixture_data['name'],
             'url': self.package_fixture_data['url'],
@@ -248,10 +272,28 @@
             'tags': [u'tag1', u'tag2', u'tag4', u'tag5'],
         }
         self.create_package_roles_revision(old_fixture_data)
-        offset = self.package_offset(old_fixture_data['name'])
+        pkg = self.get_package_by_name(old_fixture_data['name'])
+        # This is the one occasion where we reference package explicitly
+        # by name or ID, rather than use the value from self.ref_package_by
+        # because you should be able to specify the package both ways round
+        # for both versions of the API.
+        package_ref = getattr(pkg, package_ref_attribute)
+        offset = self.offset('/rest/package/%s' % package_ref)
         params = '%s=1' % self.dumps(new_fixture_data)
-        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
-                            extra_environ=self.extra_environ)
+        method_func = getattr(self.app, method_str)
+        res = method_func(offset, params=params, status=self.STATUS_200_OK,
+                          extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        assert_equal(pkg['title'], new_fixture_data['title'])
+        assert_equal(set(pkg['tags']), set(new_fixture_data['tags']))
+        assert_equal(len(pkg['resources']), len(new_fixture_data['resources']))
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key2']
+        expected_extras['key1'] = old_fixture_data['extras']['key1']
+        assert_equal(pkg['extras'], expected_extras)
 
         # Check submitted field have changed.
         self.remove()
@@ -296,10 +338,78 @@
         assert not package.extras.has_key('key2')
 
     def test_package_update_ok_by_id(self):
-        self.assert_package_update_ok('id')
+        self.assert_package_update_ok('id', 'post')
 
     def test_entity_update_ok_by_name(self):
-        self.assert_package_update_ok('name')
+        self.assert_package_update_ok('name', 'post')
+
+    def test_package_update_ok_by_id_by_put(self):
+        self.assert_package_update_ok('id', 'put')
+
+    def test_entity_update_ok_by_name_by_put(self):
+        self.assert_package_update_ok('name', 'put')
+
+    def test_package_update_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {
+                u'key1': None, 
+                },
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = copy.deepcopy(new_fixture_data['extras'])
+        del expected_extras['key1']
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        self.assert_equal(package.extras, {})
+
+    def test_package_update_do_not_delete_last_extra(self):
+        old_fixture_data = {
+            'name': self.package_fixture_data['name'],
+            'extras': {
+                u'key1': u'val1',
+            },
+        }
+        new_fixture_data = {
+            'name':u'somethingnew',
+            'extras': {}, # no extras specified, but existing
+                          # ones should be left alone
+        }
+        self.create_package_roles_revision(old_fixture_data)
+        offset = self.package_offset(old_fixture_data['name'])
+        params = '%s=1' % self.dumps(new_fixture_data)
+        res = self.app.post(offset, params=params, status=self.STATUS_200_OK,
+                            extra_environ=self.extra_environ)
+
+        # Check the returned package is as expected
+        pkg = self.loads(res.body)
+        assert_equal(pkg['name'], new_fixture_data['name'])
+        expected_extras = {u'key1': u'val1'} # should not be deleted
+        assert_equal(pkg['extras'], expected_extras)
+
+        # Check extra was not deleted
+        self.remove()
+        package = self.get_package_by_name(new_fixture_data['name'])
+        # - title
+        assert len(package.extras) == 1, package.extras
 
     def test_entity_update_conflict(self):
         package1_name = self.package_fixture_data['name']
@@ -311,18 +421,19 @@
         package1_offset = self.package_offset(package1_name)
         self.post(package1_offset, package2_data, self.STATUS_409_CONFLICT)
 
+    def test_entity_update_empty(self):
+        package1_name = self.package_fixture_data['name']
+        package1_data = {'name': package1_name}
+        package1 = self.create_package_roles_revision(package1_data)
+        package2_data = '' # this is the error
+        package1_offset = self.package_offset(package1_name)
+        self.app.put(package1_offset, package2_data,
+                     status=self.STATUS_400_BAD_REQUEST)
+
     def test_entity_delete_ok(self):
         # create a package with package_fixture_data
         if not self.get_package_by_name(self.package_fixture_data['name']):
-            rev = model.repo.new_revision()
-            package = model.Package()
-            model.Session.add(package)
-            package.name = self.package_fixture_data['name']
-            model.repo.commit_and_remove()
-            rev = model.repo.new_revision()
-            package = self.get_package_by_name(self.package_fixture_data['name'])
-            model.setup_default_user_roles(package, [self.user])
-            model.repo.commit_and_remove()
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
         assert self.get_package_by_name(self.package_fixture_data['name'])
         # delete it
         offset = self.package_offset(self.package_fixture_data['name'])
@@ -332,6 +443,19 @@
         self.assert_equal(package.state, 'deleted')
         model.Session.remove()
 
+    def test_entity_delete_ok_without_request_headers(self):
+        # create a package with package_fixture_data
+        if not self.get_package_by_name(self.package_fixture_data['name']):
+            self.create_package(admins=[self.user], name=self.package_fixture_data['name'])
+        assert self.get_package_by_name(self.package_fixture_data['name'])
+        # delete it
+        offset = self.package_offset(self.package_fixture_data['name'])
+        res = self.delete_request(offset, status=self.STATUS_200_OK,
+                                  extra_environ=self.extra_environ)
+        package = self.get_package_by_name(self.package_fixture_data['name'])
+        self.assert_equal(package.state, 'deleted')
+        model.Session.remove()
+
     def test_entity_delete_not_found(self):
         package_name = u'random_one'
         assert not model.Session.query(model.Package).filter_by(name=package_name).count()
@@ -374,7 +498,52 @@
         assert len(revisions) == 3, len(revisions)
 
 
-class TestPackagesVersion1(Version1TestCase, PackagesTestCase): pass
+class TestPackagesVersion1(Version1TestCase, PackagesTestCase):
+    def test_06_create_pkg_using_download_url(self):
+        test_params = {
+            'name':u'testpkg06',
+            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
+            }
+        offset = self.package_offset()
+        postparams = '%s=1' % self.dumps(test_params)
+        res = self.app.post(offset, params=postparams, 
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(test_params['name'])
+        assert pkg
+        assert pkg.name == test_params['name'], pkg
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
+
+    def test_10_edit_pkg_with_download_url(self):
+        test_params = {
+            'name':u'testpkg10',
+            'download_url':u'testurl',
+            }
+        rev = model.repo.new_revision()
+        pkg = model.Package()
+        model.Session.add(pkg)
+        pkg.name = test_params['name']
+        pkg.download_url = test_params['download_url']
+        model.Session.commit()
+
+        pkg = self.get_package_by_name(test_params['name'])
+        model.setup_default_user_roles(pkg, [self.user])
+        rev = model.repo.new_revision()
+        model.repo.commit_and_remove()
+        assert self.get_package_by_name(test_params['name'])
+
+        # edit it
+        pkg_vals = {'download_url':u'newurl'}
+        offset = self.package_offset(test_params['name'])
+        postparams = '%s=1' % self.dumps(pkg_vals)
+        res = self.app.post(offset, params=postparams, status=[200],
+                            extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
+        assert len(pkg.resources) == 1, pkg.resources
+        assert pkg.resources[0].url == pkg_vals['download_url']
+
 class TestPackagesVersion2(Version2TestCase, PackagesTestCase): pass
 class TestPackagesUnversioned(UnversionedTestCase, PackagesTestCase): pass
 


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_ratings.py	Thu Jul 07 22:01:30 2011 +0100
@@ -0,0 +1,94 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RatingsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    def test_register_get(self):
+        raise SkipTest('"Rating register get" functionality is not implemented')
+        rating1 = model.Rating(user_ip_address='1.2.3.4',
+                               package=self.anna,
+                               rating=4.0)
+        rating2 = model.Rating(user=model.User.by_name(u'annafan'),
+                               package=self.anna,
+                               rating=2.0)
+        model.Session.add_all((rating1, rating2))
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset()
+        res = self.app.get(offset, status=[200])
+
+    def test_entity_get(self):
+        raise SkipTest('"Rating entity get" functionality is not implemented')
+        rating = model.Rating(user_ip_address='1.2.3.4',
+                              package=self.anna,
+                              rating=4.0)
+        model.Session.add(rating)
+        model.repo.commit_and_remove()
+
+        offset = self.rating_offset(self.anna.name)
+        res = self.app.get(offset, status=[200])
+        assert_equal(res, rating_opts['rating'])
+
+    def test_register_post(self):
+        # Test Rating Register Post 200.
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':5}
+        pkg_name = rating_opts['package']
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+        # Get package to see rating
+        offset = self.package_offset(pkg_name)
+        res = self.app.get(offset, status=[200])
+        assert pkg_name in res, res
+        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
+        assert '"ratings_count": 1' in res, res
+        
+        model.Session.remove()
+        
+        # Rerate package
+        offset = self.rating_offset()
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[201],
+                extra_environ=self.extra_environ)
+        model.Session.remove()
+        pkg = self.get_package_by_name(pkg_name)
+        assert pkg
+        assert len(pkg.ratings) == 1
+        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
+
+    def test_entity_post_invalid(self):
+        self.clear_all_tst_ratings()
+        offset = self.rating_offset()
+        rating_opts = {'package':u'warandpeace',
+                       'rating':0}
+        postparams = '%s=1' % self.dumps(rating_opts)
+        res = self.app.post(offset, params=postparams, status=[409],
+                            extra_environ=self.extra_environ)
+        self.assert_json_response(res, 'rating')
+        model.Session.remove()
+        pkg = self.get_package_by_name(rating_opts['package'])
+        assert pkg
+        assert len(pkg.ratings) == 0
+
+class TestRatingsVersion1(Version1TestCase, RatingsTestCase): pass
+class TestRatingsVersion2(Version2TestCase, RatingsTestCase): pass
+class TestRatingsUnversioned(UnversionedTestCase, RatingsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_relationships.py	Thu Jul 07 22:01:30 2011 +0100
@@ -0,0 +1,242 @@
+from nose.tools import assert_equal 
+from nose.plugins.skip import SkipTest
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RelationshipsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+
+    @classmethod
+    def setup_class(cls):
+#        super(RelationshipsTestCase, cls).setup_class()
+        cls.testsysadmin = model.User.by_name(u'testsysadmin')
+        cls.comment = u'Comment umlaut: \xfc.'
+
+    def test_01_create_and_read_relationship(self):
+        # check anna has no existing relationships
+        assert not self.anna.get_relationships()
+        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
+        assert self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace') == []
+        assert self.get_relationships(package1_name='annakarenina',
+                                       type='child_of',
+                                       package2_name='warandpeace') == 404
+        assert self.get_relationships_via_package('annakarenina') == []
+
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check package relationship register.
+        rels = self.get_relationships(package1_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # Todo: Name this?
+        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
+        rels = self.get_relationships(package1_name='annakarenina',
+                                       type='parent_of',
+                                       package2_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+
+        # same checks in reverse direction
+        rels = self.get_relationships(package1_name='warandpeace')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        rels = self.get_relationships(package1_name='warandpeace',
+                                       type='child_of',
+                                      package2_name='annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'warandpeace', 'child_of', 'annakarenina', self.comment)
+
+        # Check package entity relationships.
+        rels = self.get_relationships_via_package('annakarenina')
+        assert len(rels) == 1
+        self.check_relationship_dict(rels[0],
+               'annakarenina', 'parent_of', 'warandpeace', self.comment)
+        
+    def test_03_update_relationship(self):
+        # Create a relationship.
+        self.create_annakarenina_parent_of_war_and_peace()
+
+        # Check the relationship before update.
+        self.check_relationships_rest('warandpeace', 'annakarenina',
+                                      [{'type': 'child_of',
+                                        'comment': self.comment}])
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+        # Repeat update with same values, to check it remains the same?
+
+        # Update the relationship.
+        new_comment = u'New comment.'
+        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
+
+        # Check the relationship after update.
+        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
+
+    def test_05_delete_relationship(self):
+        self.create_annakarenina_parent_of_war_and_peace()
+        self.update_annakarenina_parent_of_war_and_peace()
+        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+        self.delete_annakarenina_parent_of_war_and_peace()
+
+        expected = []
+        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
+
+    def test_create_relationship_unknown(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'unheard_of_type', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def test_create_relationship_incorrectly(self):
+        raise SkipTest() # leaving broken for now
+        offset = self.relationship_offset('annakarenina', 'relationships', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'type':'parent_of'})
+        # type should do in the URL offset, not the params.
+        res = self.app.post(offset, params=postparams, status=[400],
+                            extra_environ=self.extra_environ)
+
+    def create_annakarenina_parent_of_war_and_peace(self):
+        # Create package relationship.
+        # Todo: Redesign this in a RESTful style, so that a relationship is 
+        # created by posting a relationship to a relationship **register**.
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':self.comment})
+        res = self.app.post(offset, params=postparams, status=[201],
+                            extra_environ=self.extra_environ)
+        # Check the response
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+        
+        # Check the model, directly.
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+
+    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        postparams = '%s=1' % self.dumps({'comment':comment})
+        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
+        # Check the response (normalised to 'child_of')
+        rel = self.loads(res.body)
+        assert_equal(rel['type'], 'child_of')
+        assert_equal(rel['subject'], self.ref_package(self.war))
+        assert_equal(rel['object'], self.ref_package(self.anna))
+
+        # Check the model, directly (normalised to 'child_of')
+        rels = self.anna.get_relationships()
+        assert len(rels) == 1, rels
+        assert rels[0].type == 'child_of'
+        assert rels[0].subject.name == 'warandpeace'
+        assert rels[0].object.name == 'annakarenina'
+        return res
+
+    def delete_annakarenina_parent_of_war_and_peace(self):
+        offset = self.relationship_offset('annakarenina', 'parent_of', 'warandpeace')
+        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
+        assert not res.body
+
+    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
+        offset = self.relationship_offset(package1_name, type, package2_name)
+        allowable_statuses = [200]
+        if type:
+            allowable_statuses.append(404)
+        res = self.app.get(offset, status=allowable_statuses)
+        if res.status == 200:
+            res_dict = self.data_from_res(res) if res.body else []
+            return res_dict
+        else:
+            return 404
+
+    def get_relationships_via_package(self, package1_name):
+        offset = self.package_offset(package1_name)
+        res = self.app.get(offset, status=200)
+        res_dict = self.data_from_res(res)
+        return res_dict['relationships']
+
+    def assert_len_relationships(self, relationships, expected_relationships):
+        len_relationships = len(relationships)
+        len_expected_relationships = len(expected_relationships)
+        if len_relationships != len_expected_relationships:
+            msg = 'Found %i relationships, ' % len_relationships
+            msg += 'but expected %i.' % len_expected_relationships
+            if len_relationships:
+                msg += ' Found: '
+                for r in relationships:
+                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
+                msg += '.'
+            raise Exception, msg
+
+    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
+                                 expected_relationships=[]):
+        rels = self.get_relationships(package1_name=pkg1_name,
+                                      package2_name=pkg2_name)
+        self.assert_len_relationships(rels, expected_relationships) 
+        for rel in rels:
+            the_expected_rel = None
+            for expected_rel in expected_relationships:
+                if expected_rel['type'] == rel['type'] and \
+                   (pkg2_name or expected_rel['object'] == pkg2_name):
+                    the_expected_rel = expected_rel
+                    break
+            if not the_expected_rel:
+                raise Exception('Unexpected relationship: %s %s %s' %
+                                (rel['subject'], rel['type'], rel['object']))
+            for field in ('subject', 'object', 'type', 'comment'):
+                if the_expected_rel.has_key(field):
+                    value = rel[field]
+                    expected = the_expected_rel[field]
+                    assert value == expected, (value, expected, field, rel)
+
+    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
+        subject_ref = self.package_ref_from_name(subject_name)
+        object_ref = self.package_ref_from_name(object_name)
+
+        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
+        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
+        assert rel_dict['type'] == type, (rel_dict, type)
+        assert rel_dict['comment'] == comment, (rel_dict, comment)
+
+class TestRelationshipsVersion1(Version1TestCase, RelationshipsTestCase): pass
+class TestRelationshipsVersion2(Version2TestCase, RelationshipsTestCase): pass
+class TestRelationshipsUnversioned(UnversionedTestCase, RelationshipsTestCase): pass


--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ckan/tests/functional/api/model/test_revisions.py	Thu Jul 07 22:01:30 2011 +0100
@@ -0,0 +1,49 @@
+from nose.tools import assert_equal 
+
+from ckan import model
+
+from ckan.tests.functional.api.base import BaseModelApiTestCase
+from ckan.tests.functional.api.base import Api1TestCase as Version1TestCase 
+from ckan.tests.functional.api.base import Api2TestCase as Version2TestCase 
+from ckan.tests.functional.api.base import ApiUnversionedTestCase as UnversionedTestCase 
+
+class RevisionsTestCase(BaseModelApiTestCase):
+
+    commit_changesets = False
+    reuse_common_fixtures = True
+    
+    def test_register_get_ok(self):
+        # Check mock register behaviour.
+        offset = self.revision_offset()
+        res = self.app.get(offset, status=200)
+        revs = model.Session.query(model.Revision).all()
+        assert revs, 'There are no revisions in the model.'
+        res_dict = self.data_from_res(res)
+        for rev in revs:
+            assert rev.id in res_dict, (rev.id, res_dict)
+
+    def test_entity_get_ok(self):
+        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
+        assert rev.id
+        assert rev.timestamp.isoformat()
+        offset = self.revision_offset(rev.id)
+        response = self.app.get(offset, status=[200])
+        response_data = self.data_from_res(response)
+        assert_equal(rev.id, response_data['id'])
+        assert_equal(rev.timestamp.isoformat(), response_data['timestamp'])
+        assert 'packages' in response_data
+        packages = response_data['packages']
+        assert isinstance(packages, list)
+        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
+        assert self.ref_package(self.anna) in packages, packages
+        assert self.ref_package(self.war) in packages, packages
+
+    def test_entity_get_404(self):
+        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
+        offset = self.revision_offset(revision_id)
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
+
+class TestRevisionsVersion1(Version1TestCase, RevisionsTestCase): pass
+class TestRevisionsVersion2(Version2TestCase, RevisionsTestCase): pass
+class TestRevisionsUnversioned(UnversionedTestCase, RevisionsTestCase): pass


--- a/ckan/tests/functional/api/model/test_tag.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/model/test_tag.py	Thu Jul 07 22:01:30 2011 +0100
@@ -23,6 +23,10 @@
         res = self.app.get(offset, status=self.STATUS_200_OK)
         self.assert_msg_represents_russian(msg=res.body)
 
+    def test_entity_get_not_found(self):
+        offset = self.tag_offset('doesntexist')
+        res = self.app.get(offset, status=404)
+        self.assert_json_response(res, 'Not found')
 
 class TestTagsVersion1(Version1TestCase, TagsTestCase): pass
 class TestTagsVersion2(Version2TestCase, TagsTestCase): pass


--- a/ckan/tests/functional/api/test_model.py	Wed Jul 06 18:48:53 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,578 +0,0 @@
-from ckan.tests.functional.api.base import *
-from ckan.lib.create_test_data import CreateTestData
-from ckan.tests import TestController as ControllerTestCase
-
-class ModelApiTestCase(BaseModelApiTestCase):
-
-    def setup(self):
-        self.create_common_fixtures()
-        self.init_extra_environ()
-        self.source = None
-        self.source1 = None
-        self.source2 = None
-        self.source3 = None
-        self.source4 = None
-        self.source5 = None
-        self.job = None
-        self.job1 = None
-        self.job2 = None
-        self.job3 = None
-
-    def teardown(self):
-        model.repo.rebuild_db()
-
-    def test_02_get_tag_register_ok(self):
-        # Test Packages Register Get 200.
-        offset = self.offset('/rest/tag')
-        res = self.app.get(offset, status=[200])
-        assert 'russian' in res, res
-        assert 'tolstoy' in res, res
-
-    def test_02_get_group_register_ok(self):
-        offset = self.offset('/rest/group')
-        res = self.app.get(offset, status=[200])
-        assert self.group_ref_from_name('david') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-
-    def test_04_get_tag(self):
-        offset = self.offset('/rest/tag/tolstoy')
-        res = self.app.get(offset, status=[200])
-        assert 'annakarenina' in res, res
-        assert not 'warandpeace' in res, res
-
-    def test_04_get_group(self):
-        offset = self.offset('/rest/group/roger')
-        res = self.app.get(offset, status=[200])
-        assert self.package_ref_from_name('annakarenina') in res, res
-        assert self.group_ref_from_name('roger') in res, res
-        assert not self.package_ref_from_name('warandpeace') in res, res
-        
-    def test_05_get_group_entity_not_found(self):
-        offset = self.offset('/rest/group/22222')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_05_get_tag_entity_not_found(self):
-        offset = self.offset('/rest/tag/doesntexist')
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_06_create_group_entity_ok(self):
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=201,
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        rev = model.repo.new_revision()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        model.setup_default_user_roles(group, [self.user])
-        model.repo.commit_and_remove()
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.title == self.testgroupvalues['title'], group
-        assert group.description == self.testgroupvalues['description'], group
-        assert len(group.packages) == 2, len(group.packages)
-        anna = self.anna
-        warandpeace = self.war
-        assert anna in group.packages
-        assert warandpeace in group.packages
-
-        # Check group packages.
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=[200])
-        assert self.testgroupvalues['name'] in res, res
-        assert self.package_ref_from_name(self.testgroupvalues['packages'][0]) in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][0])
-        assert ref in res, res
-        ref = self.package_ref_from_name(self.testgroupvalues['packages'][1])
-        assert ref in res, res
-        model.Session.remove()
-        
-        # Check create group entity conflict.
-        offset = self.offset('/rest/group')
-        postparams = '%s=1' % self.dumps(self.testgroupvalues)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-
-    def test_06_rate_package(self):
-        # Test Rating Register Post 200.
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':5}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-        # Get package to see rating
-        offset = self.offset('/rest/package/%s' % rating_opts['package'])
-        res = self.app.get(offset, status=[200])
-        assert rating_opts['package'] in res, res
-        assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res
-        assert '"ratings_count": 1' in res, res
-        
-        model.Session.remove()
-        
-        # Rerate package
-        offset = self.offset('/rest/rating')
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[201],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 1
-        assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings
-
-    def test_06_rate_package_out_of_range(self):
-        self.clear_all_tst_ratings()
-        offset = self.offset('/rest/rating')
-        rating_opts = {'package':u'warandpeace',
-                       'rating':0}
-        postparams = '%s=1' % self.dumps(rating_opts)
-        res = self.app.post(offset, params=postparams, status=[409],
-                extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(rating_opts['package'])
-        assert pkg
-        assert len(pkg.ratings) == 0
-
-    def test_10_edit_group(self):
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            offset = self.offset('/rest/group')
-            postparams = '%s=1' % self.dumps(self.testgroupvalues)
-            res = self.app.post(offset, params=postparams, status=[201],
-                    extra_environ=self.extra_environ)
-            model.Session.remove()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert len(group.packages) == 2, group.packages
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # edit it
-        group_vals = {'name':u'somethingnew', 'title':u'newtesttitle',
-                      'packages':[u'annakarenina']}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        group = model.Session.query(model.Group).filter_by(name=group_vals['name']).one()
-        assert group.name == group_vals['name']
-        assert group.title == group_vals['title']
-        assert len(group.packages) == 1, group.packages
-        assert group.packages[0].name == group_vals['packages'][0]
-
-    def test_10_edit_group_name_duplicate(self):
-        # create a group with testgroupvalues
-        if not model.Group.by_name(self.testgroupvalues['name']):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.Session.commit()
-
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            rev = model.repo.new_revision()
-            model.repo.commit_and_remove()
-        assert model.Group.by_name(self.testgroupvalues['name'])
-        
-        # create a group with name 'dupname'
-        dupname = u'dupname'
-        if not model.Group.by_name(dupname):
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = dupname
-            model.Session.commit()
-        assert model.Group.by_name(dupname)
-
-        # edit first group to have dupname
-        group_vals = {'name':dupname}
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        postparams = '%s=1' % self.dumps(group_vals)
-        res = self.app.post(offset, params=postparams, status=[409],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        
-    def test_11_delete_group(self):
-        # Test Groups Entity Delete 200.
-
-        # create a group with testgroupvalues
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        if not group:
-            rev = model.repo.new_revision()
-            group = model.Group()
-            model.Session.add(group)
-            group.name = self.testgroupvalues['name']
-            model.repo.commit_and_remove()
-
-            rev = model.repo.new_revision()
-            group = model.Group.by_name(self.testgroupvalues['name'])
-            model.setup_default_user_roles(group, [self.user])
-            model.repo.commit_and_remove()
-        assert group
-        user = model.User.by_name(self.user_name)
-        model.setup_default_user_roles(group, [user])
-
-        # delete it
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[200],
-                extra_environ=self.extra_environ)
-
-        group = model.Group.by_name(self.testgroupvalues['name'])
-        assert group
-        assert group.state == 'deleted', group.state
-
-        res = self.app.get(offset, status=[403])
-        res = self.app.get(offset, status=[200],
-                           extra_environ=self.extra_environ)
-
-        model.Session.remove()
-
-    def test_12_get_group_404(self):
-        # Test Package Entity Get 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_13_delete_group_404(self):
-        # Test Packages Entity Delete 404.
-        assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count()
-        offset = self.offset('/rest/group/%s' % self.testgroupvalues['name'])
-        res = self.app.delete(offset, status=[404],
-                              extra_environ=self.extra_environ)
-
-    def test_14_list_revisions(self):
-        # Check mock register behaviour.
-        offset = self.offset('/rest/revision')
-        res = self.app.get(offset, status=200)
-        revs = model.Session.query(model.Revision).all()
-        assert revs, "There are no revisions in the model."
-        res_dict = self.data_from_res(res)
-        for rev in revs:
-            assert rev.id in res_dict, (rev.id, res_dict)
-
-    def test_14_get_revision(self):
-        rev = model.repo.history().all()[-2] # 2nd revision is the creation of pkgs
-        assert rev.id
-        assert rev.timestamp.isoformat()
-        offset = self.offset('/rest/revision/%s' % rev.id)
-        response = self.app.get(offset, status=[200])
-        response_data = self.data_from_res(response)
-        assert rev.id == response_data['id']
-        assert rev.timestamp.isoformat() == response_data['timestamp'], (rev.timestamp.isoformat(), response_data['timestamp'])
-        assert 'packages' in response_data
-        packages = response_data['packages']
-        assert isinstance(packages, list)
-        #assert len(packages) != 0, "Revision packages is empty: %s" % packages
-        assert self.ref_package(self.anna) in packages, packages
-        assert self.ref_package(self.war) in packages, packages
-
-    def test_14_get_revision_404(self):
-        revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
-        offset = self.offset('/rest/revision/%s' % revision_id)
-        res = self.app.get(offset, status=404)
-        model.Session.remove()
-
-    def test_16_list_licenses(self):
-        from ckan.model.license import LicenseRegister
-        register = LicenseRegister()
-        assert len(register), "No changesets found in model."
-        offset = self.offset('/rest/licenses')
-        res = self.app.get(offset, status=[200])
-        licenses_data = self.data_from_res(res)
-        assert len(licenses_data) == len(register), (len(licenses_data), len(register))
-        for license_data in licenses_data:
-            id = license_data['id']
-            license = register[id]
-            assert license['title'] == license.title
-            assert license['url'] == license.url
-
-
-class RelationshipsApiTestCase(ApiTestCase, ControllerTestCase):
-
-    @classmethod
-    def setup_class(self):
-        CreateTestData.create()
-        self.user = self.create_user(name=u'barry')
-        self.testsysadmin = model.User.by_name(u'testsysadmin')
-        self.extra_environ={ 'Authorization' : str(self.user.apikey) }
-        self.comment = u'Comment umlaut: \xfc.'
-
-    @classmethod
-    def teardown_class(self):
-        model.Session.remove()
-        model.repo.rebuild_db()
-        model.Session.remove()
-
-    def teardown(self):
-        for relationship in self.anna.get_relationships():
-            relationship.purge()
-        model.Session.commit()
-        relationships = self.anna.get_relationships()
-        assert relationships == [], "There are still some relationships: %s" % relationships
-
-    def test_01_create_and_read_relationship(self):
-        # check anna has no existing relationships
-        assert not self.anna.get_relationships()
-        assert self.get_relationships(package1_name='annakarenina') == [], self.get_relationships(package1_name='annakarenina')
-        assert self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace') == []
-        assert self.get_relationships(package1_name='annakarenina',
-                                       type='child_of',
-                                       package2_name='warandpeace') == 404
-        assert self.get_relationships_via_package('annakarenina') == []
-
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check package relationship register.
-        rels = self.get_relationships(package1_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # Check '/api/VER/rest/package/annakarenina/relationships/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # Todo: Name this?
-        # check '/api/VER/rest/package/annakarenina/parent_of/warandpeace'
-        rels = self.get_relationships(package1_name='annakarenina',
-                                       type='parent_of',
-                                       package2_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-
-        # same checks in reverse direction
-        rels = self.get_relationships(package1_name='warandpeace')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        rels = self.get_relationships(package1_name='warandpeace',
-                                       type='child_of',
-                                      package2_name='annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'warandpeace', 'child_of', 'annakarenina', self.comment)
-
-        # Check package entity relationships.
-        rels = self.get_relationships_via_package('annakarenina')
-        assert len(rels) == 1
-        self.check_relationship_dict(rels[0],
-               'annakarenina', 'parent_of', 'warandpeace', self.comment)
-        
-    def test_03_update_relationship(self):
-        # Create a relationship.
-        self.create_annakarenina_parent_of_war_and_peace()
-
-        # Check the relationship before update.
-        self.check_relationships_rest('warandpeace', 'annakarenina',
-                                      [{'type': 'child_of',
-                                        'comment': self.comment}])
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-        # Repeat update with same values, to check it remains the same?
-
-        # Update the relationship.
-        new_comment = u'New comment.'
-        self.update_annakarenina_parent_of_war_and_peace(comment=new_comment)
-
-        # Check the relationship after update.
-        self.check_relationships_rest('warandpeace', 'annakarenina', [{'type': 'child_of', 'comment': new_comment}])
-
-    def test_05_delete_relationship(self):
-        self.create_annakarenina_parent_of_war_and_peace()
-        self.update_annakarenina_parent_of_war_and_peace()
-        expected = [ {'type': 'child_of', 'comment': u'New comment.'} ]
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-        self.delete_annakarenina_parent_of_war_and_peace()
-
-        expected = []
-        self.check_relationships_rest('warandpeace', 'annakarenina', expected)
-
-    def create_annakarenina_parent_of_war_and_peace(self):
-        # Create package relationship.
-        # Todo: Redesign this in a RESTful style, so that a relationship is 
-        # created by posting a relationship to a relationship **register**.
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':self.comment})
-        res = self.app.post(offset, params=postparams, status=[201],
-                            extra_environ=self.extra_environ)
-        # Check the model, directly.
-        rels = self.anna.get_relationships()
-        assert len(rels) == 1, rels
-        assert rels[0].type == 'child_of'
-        assert rels[0].subject.name == 'warandpeace'
-        assert rels[0].object.name == 'annakarenina'
-
-    def update_annakarenina_parent_of_war_and_peace(self, comment=u'New comment.'):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        postparams = '%s=1' % self.dumps({'comment':comment})
-        res = self.app.post(offset, params=postparams, status=[201], extra_environ=self.extra_environ)
-        return res
-
-    def delete_annakarenina_parent_of_war_and_peace(self):
-        offset = self.offset('/rest/package/annakarenina/parent_of/warandpeace')
-        res = self.app.delete(offset, status=[200], extra_environ=self.extra_environ)
-        return res
-
-    def get_relationships(self, package1_name=u'annakarenina', type='relationships', package2_name=None):
-        package1_ref = self.package_ref_from_name(package1_name)
-        if not package2_name:
-            offset = self.offset('/rest/package/%s/%s' % (package1_ref, type))
-        else:
-            package2_ref = self.package_ref_from_name(package2_name)
-            offset = self.offset('/rest/package/%s/%s/%s' % (
-                str(package1_ref), type, str(package2_ref)))
-        allowable_statuses = [200]
-        if type:
-            allowable_statuses.append(404)
-        res = self.app.get(offset, status=allowable_statuses)
-        if res.status == 200:
-            res_dict = self.data_from_res(res) if res.body else []
-            return res_dict
-        else:
-            return 404
-
-    def get_relationships_via_package(self, package1_name):
-        offset = self.offset('/rest/package/%s' % (str(package1_name)))
-        res = self.app.get(offset, status=200)
-        res_dict = self.data_from_res(res)
-        return res_dict['relationships']
-
-    def assert_len_relationships(self, relationships, expected_relationships):
-        len_relationships = len(relationships)
-        len_expected_relationships = len(expected_relationships)
-        if len_relationships != len_expected_relationships:
-            msg = 'Found %i relationships, ' % len_relationships
-            msg += 'but expected %i.' % len_expected_relationships
-            if len_relationships:
-                msg += ' Found: '
-                for r in relationships:
-                    msg += '%s %s %s; ' % (r['subject'], r['type'], r['object'])
-                msg += '.'
-            raise Exception, msg
-
-    def check_relationships_rest(self, pkg1_name, pkg2_name=None,
-                                 expected_relationships=[]):
-        rels = self.get_relationships(package1_name=pkg1_name,
-                                      package2_name=pkg2_name)
-        self.assert_len_relationships(rels, expected_relationships) 
-        for rel in rels:
-            the_expected_rel = None
-            for expected_rel in expected_relationships:
-                if expected_rel['type'] == rel['type'] and \
-                   (pkg2_name or expected_rel['object'] == pkg2_name):
-                    the_expected_rel = expected_rel
-                    break
-            if not the_expected_rel:
-                raise Exception('Unexpected relationship: %s %s %s' %
-                                (rel['subject'], rel['type'], rel['object']))
-            for field in ('subject', 'object', 'type', 'comment'):
-                if the_expected_rel.has_key(field):
-                    value = rel[field]
-                    expected = the_expected_rel[field]
-                    assert value == expected, (value, expected, field, rel)
-
-    def check_relationship_dict(self, rel_dict, subject_name, type, object_name, comment):
-        subject_ref = self.package_ref_from_name(subject_name)
-        object_ref = self.package_ref_from_name(object_name)
-
-        assert rel_dict['subject'] == subject_ref, (rel_dict, subject_ref)
-        assert rel_dict['object'] == object_ref, (rel_dict, object_ref)
-        assert rel_dict['type'] == type, (rel_dict, type)
-        assert rel_dict['comment'] == comment, (rel_dict, comment)
- 
-
-# Tests for Version 1 of the API.
-class TestModelApi1(Api1TestCase, ModelApiTestCase):
-
-    def test_06_create_pkg_using_download_url(self):
-        test_params = {
-            'name':u'testpkg06',
-            'download_url':u'ftp://ftp.monash.edu.au/pub/nihongo/JMdict.gz',
-            }
-        offset = self.package_offset()
-        postparams = '%s=1' % self.dumps(test_params)
-        res = self.app.post(offset, params=postparams, 
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = self.get_package_by_name(test_params['name'])
-        assert pkg
-        assert pkg.name == test_params['name'], pkg
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == test_params['download_url'], pkg.resources[0]
-
-    def test_10_edit_pkg_with_download_url(self):
-        test_params = {
-            'name':u'testpkg10',
-            'download_url':u'testurl',
-            }
-        rev = model.repo.new_revision()
-        pkg = model.Package()
-        model.Session.add(pkg)
-        pkg.name = test_params['name']
-        pkg.download_url = test_params['download_url']
-        model.Session.commit()
-
-        pkg = self.get_package_by_name(test_params['name'])
-        model.setup_default_user_roles(pkg, [self.user])
-        rev = model.repo.new_revision()
-        model.repo.commit_and_remove()
-        assert self.get_package_by_name(test_params['name'])
-
-        # edit it
-        pkg_vals = {'download_url':u'newurl'}
-        offset = self.package_offset(test_params['name'])
-        postparams = '%s=1' % self.dumps(pkg_vals)
-        res = self.app.post(offset, params=postparams, status=[200],
-                            extra_environ=self.extra_environ)
-        model.Session.remove()
-        pkg = model.Session.query(model.Package).filter_by(name=test_params['name']).one()
-        assert len(pkg.resources) == 1, pkg.resources
-        assert pkg.resources[0].url == pkg_vals['download_url']
-
-
-class TestRelationshipsApi1(Api1TestCase, RelationshipsApiTestCase): pass
-
-# Tests for Version 2 of the API.
-class TestModelApi2(Api2TestCase, ModelApiTestCase): pass
-class TestRelationshipsApi2(Api2TestCase, RelationshipsApiTestCase): pass
-
-# Tests for unversioned API.
-class TestModelApiUnversioned(ApiUnversionedTestCase, ModelApiTestCase): pass
-class TestRelationshipsApiUnversioned(RelationshipsApiTestCase, ApiUnversionedTestCase): pass
-


--- a/ckan/tests/functional/api/test_package_search.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/test_package_search.py	Thu Jul 07 22:01:30 2011 +0100
@@ -179,6 +179,7 @@
     def test_08_uri_qjson_malformed(self):
         offset = self.base_url + '?qjson="q":""' # user forgot the curly braces
         res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Could not read parameters')
         
     def test_08_all_fields(self):
         rating = model.Rating(user_ip_address=u'123.1.2.3',
@@ -251,6 +252,7 @@
         res = self.app.get(offset, status=400)
         assert('integer' in res.body)
         assert('offset' in res.body)
+        self.assert_json_response(res, 'integer')
 
     def test_12_all_packages_qjson(self):
         query = {'q': ''}


--- a/ckan/tests/functional/api/test_resource_search.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/test_resource_search.py	Thu Jul 07 22:01:30 2011 +0100
@@ -70,6 +70,7 @@
     def test_04_bad_option(self):
         offset = self.base_url + '?random=option'
         result = self.app.get(offset, status=400)
+        self.assert_json_response(result, 'Bad request - Bad search option')
 
     def test_05_options(self):
         offset = self.base_url + '?url=site&all_fields=1&callback=mycallback'


--- a/ckan/tests/functional/api/test_revision_search.py	Wed Jul 06 18:48:53 2011 +0100
+++ b/ckan/tests/functional/api/test_revision_search.py	Thu Jul 07 22:01:30 2011 +0100
@@ -11,13 +11,19 @@
     def teardown_class(self):
         model.repo.rebuild_db()
 
-    def test_12_search_revision_basic(self):
+    def test_12_search_revision_bad_requests(self):
         offset = self.offset('/search/revision')
         # Check bad request.
-        self.app.get(offset, status=400)
-        self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
-        self.app.get(offset+'?since_id=', status=400)
+        res = self.app.get(offset, status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_rev=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_revision=2010-01-01T00:00:00', status=400)
+        self.assert_json_response(res, 'Bad request - Missing search term')
+        res = self.app.get(offset+'?since_id=', status=400)
+        self.assert_json_response(res, 'Bad request - No revision specified')
+        res = self.app.get(offset+'?since_id=1234', status=404)
+        self.assert_json_response(res, 'Not found - There is no revision')
 
     def test_12_search_revision_since_rev(self):
         offset = self.offset('/search/revision')
@@ -54,8 +60,9 @@
         assert res_list == [], res_list
         # Check bad format.
         params = "?since_time=2010-04-31T23:45"
-        self.app.get(offset+params, status=400)
+        res = self.app.get(offset+params, status=400)
+        self.assert_json_response(res, 'Bad request - ValueError: day is out of range for month')
 
 
-class TestPackageSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
-class TestPackageSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi1(Api1TestCase, RevisionSearchApiTestCase): pass
+class TestRevisionSearchApi2(Api2TestCase, RevisionSearchApiTestCase): pass


--- a/doc/api/model_formats.rst.inc	Wed Jul 06 18:48:53 2011 +0100
+++ b/doc/api/model_formats.rst.inc	Thu Jul 07 22:01:30 2011 +0100
@@ -26,6 +26,6 @@
 
  * When you update an object, fields that you don't supply will remain as they were before.
 
- * To delete an 'extra' key-value pair, supply the key with a None value.
+ * To delete an 'extra' key-value pair, supply the key with JSON value: ``null``
 
  * When you read a package then some additional information is supplied that cannot current be adjusted throught the CKAN API. This includes info on Package Relationship ('relationships'), Group membership ('groups'), ratings ('ratings_average' and 'ratings_count'), full URL of the package in CKAN ('ckan_url') and Package ID ('id'). This is purely a convenience for clients, and only forms part of the Package on GET.


--- a/doc/api/model_resources_table.rst.inc	Wed Jul 06 18:48:53 2011 +0100
+++ b/doc/api/model_resources_table.rst.inc	Thu Jul 07 22:01:30 2011 +0100
@@ -8,7 +8,7 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Group Register                 | ``/rest/group``                                                   |
 +--------------------------------+-------------------------------------------------------------------+
-| Group Entity                   | ``/rest/group/GROUP-NAME``                                        |
+| Group Entity                   | ``/rest/group/GROUP-REF``                                         |
 +--------------------------------+-------------------------------------------------------------------+
 | Tag Register                   | ``/rest/tag``                                                     |
 +--------------------------------+-------------------------------------------------------------------+
@@ -16,9 +16,9 @@
 +--------------------------------+-------------------------------------------------------------------+
 | Rating Register                | ``/rest/rating``                                                  |
 +--------------------------------+-------------------------------------------------------------------+
-| Rating Entity                  | ``/rest/rating/PACKAGE-REF``                                      |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
 +--------------------------------+-------------------------------------------------------------------+
-| Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships``                       |
+| Package Relationships Register | ``/rest/package/PACKAGE-REF/RELATIONSHIP-TYPE``                   |
 +--------------------------------+-------------------------------------------------------------------+
 | Package Relationships Register | ``/rest/package/PACKAGE-REF/relationships/PACKAGE-REF``           |
 +--------------------------------+-------------------------------------------------------------------+

Repository URL: https://bitbucket.org/okfn/ckan/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.




More information about the ckan-changes mailing list