Browse Source

Pass all tests.

Stefano Cossu 7 years ago
parent
commit
ff5b9aceef

+ 39 - 24
lakesuperior/api/resource.py

@@ -1,6 +1,7 @@
 import logging
 import logging
 
 
 from functools import wraps
 from functools import wraps
+from itertools import groupby
 from multiprocessing import Process
 from multiprocessing import Process
 from threading import Lock, Thread
 from threading import Lock, Thread
 
 
@@ -12,7 +13,8 @@ from rdflib.namespace import XSD
 from lakesuperior.config_parser import config
 from lakesuperior.config_parser import config
 from lakesuperior.exceptions import InvalidResourceError
 from lakesuperior.exceptions import InvalidResourceError
 from lakesuperior.env import env
 from lakesuperior.env import env
-from lakesuperior.model.ldp_factory import LdpFactory
+from lakesuperior.globals import RES_DELETED
+from lakesuperior.model.ldp_factory import LDP_NR_TYPE, LdpFactory
 from lakesuperior.store.ldp_rs.lmdb_store import TxnManager
 from lakesuperior.store.ldp_rs.lmdb_store import TxnManager
 
 
 
 
@@ -26,6 +28,9 @@ def transaction(write=False):
     This wrapper ensures that a write operation is performed atomically. It
     This wrapper ensures that a write operation is performed atomically. It
     also takes care of sending a message for each resource changed in the
     also takes care of sending a message for each resource changed in the
     transaction.
     transaction.
+
+    ALL write operations on the LDP-RS and LDP-NR stores go through this
+    wrapper.
     '''
     '''
     def _transaction_deco(fn):
     def _transaction_deco(fn):
         @wraps(fn)
         @wraps(fn)
@@ -53,7 +58,7 @@ def process_queue():
     lock = Lock()
     lock = Lock()
     lock.acquire()
     lock.acquire()
     while len(app_globals.changelog):
     while len(app_globals.changelog):
-        send_event_msg(app_globals.changelog.popleft())
+        send_event_msg(*app_globals.changelog.popleft())
     lock.release()
     lock.release()
 
 
 
 
@@ -69,7 +74,7 @@ def send_event_msg(remove_trp, add_trp, metadata):
 
 
     subjects = set(remove_dict.keys()) | set(add_dict.keys())
     subjects = set(remove_dict.keys()) | set(add_dict.keys())
     for rsrc_uri in subjects:
     for rsrc_uri in subjects:
-        self._logger.info('subject: {}'.format(rsrc_uri))
+        logger.info('subject: {}'.format(rsrc_uri))
         app_globals.messenger.send
         app_globals.messenger.send
 
 
 
 
@@ -171,15 +176,23 @@ def create_or_replace(uid, stream=None, **kwargs):
 
 
 
 
 @transaction(True)
 @transaction(True)
-def update(uid, update_str):
+def update(uid, update_str, is_metadata=False):
     '''
     '''
     Update a resource with a SPARQL-Update string.
     Update a resource with a SPARQL-Update string.
 
 
     @param uid (string) Resource UID.
     @param uid (string) Resource UID.
     @param update_str (string) SPARQL-Update statements.
     @param update_str (string) SPARQL-Update statements.
+    @param is_metadata (bool) Whether the resource metadata is being updated.
+    If False, and the resource being updated is a LDP-NR, an error is raised.
     '''
     '''
     rsrc = LdpFactory.from_stored(uid)
     rsrc = LdpFactory.from_stored(uid)
-    rsrc.patch(update_str)
+    if LDP_NR_TYPE in rsrc.ldp_types:
+        if is_metadata:
+            rsrc.patch_metadata(update_str)
+        else:
+            raise InvalidResourceError(uid)
+    else:
+        rsrc.patch(update_str)
 
 
     return rsrc
     return rsrc
 
 
@@ -210,30 +223,28 @@ def delete(uid, leave_tstone=True):
     '''
     '''
     # If referential integrity is enforced, grab all inbound relationships
     # If referential integrity is enforced, grab all inbound relationships
     # to break them.
     # to break them.
-    refint = rdfly.config['referential_integrity']
+    refint = app_globals.rdfly.config['referential_integrity']
     inbound = True if refint else inbound
     inbound = True if refint else inbound
     repr_opts = {'incl_inbound' : True} if refint else {}
     repr_opts = {'incl_inbound' : True} if refint else {}
 
 
-    rsrc = LdpFactory.from_stored(uid, repr_opts)
-
-    children = rdfly.get_descendants(uid)
+    children = app_globals.rdfly.get_descendants(uid)
 
 
-    ret = (
-            rsrc.bury_rsrc(inbound)
-            if leave_tstone
-            else rsrc.forget_rsrc(inbound))
+    if leave_tstone:
+        rsrc = LdpFactory.from_stored(uid, repr_opts)
+        ret = rsrc.bury_rsrc(inbound)
 
 
-    for child_uri in children:
-        try:
-            child_rsrc = LdpFactory.from_stored(
-                rdfly.uri_to_uid(child_uri),
-                repr_opts={'incl_children' : False})
-        except (TombstoneError, ResourceNotExistsError):
-            continue
-        if leave_tstone:
+        for child_uri in children:
+            try:
+                child_rsrc = LdpFactory.from_stored(
+                    app_globals.rdfly.uri_to_uid(child_uri),
+                    repr_opts={'incl_children' : False})
+            except (TombstoneError, ResourceNotExistsError):
+                continue
             child_rsrc.bury_rsrc(inbound, tstone_pointer=rsrc.uri)
             child_rsrc.bury_rsrc(inbound, tstone_pointer=rsrc.uri)
-        else:
-            child_rsrc.forget_rsrc(inbound)
+    else:
+        ret = forget(uid, inbound)
+        for child_uri in children:
+            forget(app_globals.rdfly.uri_to_uid(child_uri), inbound)
 
 
     return ret
     return ret
 
 
@@ -258,5 +269,9 @@ def forget(uid, inbound=True):
     as well. If referential integrity is checked system-wide inbound references
     as well. If referential integrity is checked system-wide inbound references
     are always deleted and this option has no effect.
     are always deleted and this option has no effect.
     '''
     '''
-    return LdpFactory.from_stored(uid).forget_rsrc(inbound)
+    refint = app_globals.rdfly.config['referential_integrity']
+    inbound = True if refint else inbound
+    app_globals.rdfly.forget_rsrc(uid, inbound)
+
+    return RES_DELETED
 
 

+ 25 - 12
lakesuperior/endpoints/ldp.py

@@ -256,13 +256,19 @@ def put_resource(uid):
     rsp_headers = {'Content-Type' : 'text/plain; charset=utf-8'}
     rsp_headers = {'Content-Type' : 'text/plain; charset=utf-8'}
 
 
     handling, disposition = set_post_put_params()
     handling, disposition = set_post_put_params()
+    #import pdb; pdb.set_trace()
     stream, mimetype = _bistream_from_req()
     stream, mimetype = _bistream_from_req()
 
 
     if LdpFactory.is_rdf_parsable(mimetype):
     if LdpFactory.is_rdf_parsable(mimetype):
         # If the content is RDF, localize in-repo URIs.
         # If the content is RDF, localize in-repo URIs.
         global_rdf = stream.read()
         global_rdf = stream.read()
         local_rdf = global_rdf.replace(
         local_rdf = global_rdf.replace(
-                g.webroot.encode('utf-8'), nsc['fcres'].encode('utf-8'))
+            (g.webroot + '/').encode('utf-8'),
+            nsc['fcres'].encode('utf-8')
+        ).replace(
+            g.webroot.encode('utf-8'),
+            nsc['fcres'].encode('utf-8')
+        )
         stream = BytesIO(local_rdf)
         stream = BytesIO(local_rdf)
         is_rdf = True
         is_rdf = True
     else:
     else:
@@ -294,7 +300,7 @@ def put_resource(uid):
 
 
 
 
 @ldp.route('/<path:uid>', methods=['PATCH'], strict_slashes=False)
 @ldp.route('/<path:uid>', methods=['PATCH'], strict_slashes=False)
-def patch_resource(uid):
+def patch_resource(uid, is_metadata=False):
     '''
     '''
     https://www.w3.org/TR/ldp/#ldpr-HTTP_PATCH
     https://www.w3.org/TR/ldp/#ldpr-HTTP_PATCH
 
 
@@ -306,15 +312,17 @@ def patch_resource(uid):
                 .format(request.mimetype), 415
                 .format(request.mimetype), 415
 
 
     update_str = request.get_data().decode('utf-8')
     update_str = request.get_data().decode('utf-8')
-    local_update_str = g.tbox.localize_ext_str(update_str, nsc['fcres'][uri])
+    local_update_str = g.tbox.localize_ext_str(update_str, nsc['fcres'][uid])
     try:
     try:
-        rsrc = rsrc_api.update(uid, local_update_str)
+        rsrc = rsrc_api.update(uid, local_update_str, is_metadata)
     except ResourceNotExistsError as e:
     except ResourceNotExistsError as e:
         return str(e), 404
         return str(e), 404
     except TombstoneError as e:
     except TombstoneError as e:
         return _tombstone_response(e, uid)
         return _tombstone_response(e, uid)
     except (ServerManagedTermError, SingleSubjectError) as e:
     except (ServerManagedTermError, SingleSubjectError) as e:
         return str(e), 412
         return str(e), 412
+    except InvalidResourceError as e:
+        return str(e), 415
     else:
     else:
         rsp_headers.update(_headers_from_metadata(rsrc))
         rsp_headers.update(_headers_from_metadata(rsrc))
         return '', 204, rsp_headers
         return '', 204, rsp_headers
@@ -322,7 +330,7 @@ def patch_resource(uid):
 
 
 @ldp.route('/<path:uid>/fcr:metadata', methods=['PATCH'])
 @ldp.route('/<path:uid>/fcr:metadata', methods=['PATCH'])
 def patch_resource_metadata(uid):
 def patch_resource_metadata(uid):
-    return patch_resource(uid)
+    return patch_resource(uid, True)
 
 
 
 
 @ldp.route('/<path:uid>', methods=['DELETE'])
 @ldp.route('/<path:uid>', methods=['DELETE'])
@@ -408,7 +416,7 @@ def post_version(uid):
     except TombstoneError as e:
     except TombstoneError as e:
         return _tombstone_response(e, uid)
         return _tombstone_response(e, uid)
     else:
     else:
-        return '', 201, {'Location': g.tbox.uri_to_uri(ver_uid)}
+        return '', 201, {'Location': g.tbox.uid_to_uri(ver_uid)}
 
 
 
 
 @ldp.route('/<path:uid>/fcr:versions/<ver_uid>', methods=['PATCH'])
 @ldp.route('/<path:uid>/fcr:versions/<ver_uid>', methods=['PATCH'])
@@ -454,13 +462,11 @@ def _bistream_from_req():
     '''
     '''
     Find how a binary file and its MIMEtype were uploaded in the request.
     Find how a binary file and its MIMEtype were uploaded in the request.
     '''
     '''
-    logger.debug('Content type: {}'.format(request.mimetype))
-    logger.debug('files: {}'.format(request.files))
-    logger.debug('stream: {}'.format(request.stream))
+    #logger.debug('Content type: {}'.format(request.mimetype))
+    #logger.debug('files: {}'.format(request.files))
+    #logger.debug('stream: {}'.format(request.stream))
 
 
-    if request.mimetype == '':
-        stream = mimetype = None
-    elif request.mimetype == 'multipart/form-data':
+    if request.mimetype == 'multipart/form-data':
         # This seems the "right" way to upload a binary file, with a
         # This seems the "right" way to upload a binary file, with a
         # multipart/form-data MIME type and the file in the `file`
         # multipart/form-data MIME type and the file in the `file`
         # field. This however is not supported by FCREPO4.
         # field. This however is not supported by FCREPO4.
@@ -474,8 +480,15 @@ def _bistream_from_req():
         # the request as application/x-www-form-urlencoded.
         # the request as application/x-www-form-urlencoded.
         # This is how FCREPO4 accepts binary uploads.
         # This is how FCREPO4 accepts binary uploads.
         stream = request.stream
         stream = request.stream
+        # @FIXME Must decide what to do with this.
         mimetype = request.mimetype
         mimetype = request.mimetype
 
 
+    if mimetype == '' or mimetype == 'application/x-www-form-urlencoded':
+        if stream.limit == 0:
+            stream = mimetype = None
+        else:
+            mimetype = 'application/octet-stream'
+
     return stream, mimetype
     return stream, mimetype
 
 
 
 

+ 7 - 7
lakesuperior/model/ldp_factory.py

@@ -16,6 +16,9 @@ from lakesuperior.exceptions import (
         ResourceNotExistsError)
         ResourceNotExistsError)
 
 
 
 
+LDP_NR_TYPE = nsc['ldp'].NonRDFSource
+LDP_RS_TYPE = nsc['ldp'].RDFSource
+
 rdfly = env.app_globals.rdfly
 rdfly = env.app_globals.rdfly
 
 
 
 
@@ -24,9 +27,6 @@ class LdpFactory:
     Generate LDP instances.
     Generate LDP instances.
     The instance classes are based on provided client data or on stored data.
     The instance classes are based on provided client data or on stored data.
     '''
     '''
-    LDP_NR_TYPE = nsc['ldp'].NonRDFSource
-    LDP_RS_TYPE = nsc['ldp'].RDFSource
-
     _logger = logging.getLogger(__name__)
     _logger = logging.getLogger(__name__)
 
 
 
 
@@ -63,10 +63,10 @@ class LdpFactory:
         #        pformat(set(rsrc_meta.graph))))
         #        pformat(set(rsrc_meta.graph))))
         rdf_types = set(rsrc_meta.graph[imr_urn : RDF.type])
         rdf_types = set(rsrc_meta.graph[imr_urn : RDF.type])
 
 
-        if __class__.LDP_NR_TYPE in rdf_types:
+        if LDP_NR_TYPE in rdf_types:
             __class__._logger.info('Resource is a LDP-NR.')
             __class__._logger.info('Resource is a LDP-NR.')
             rsrc = model.ldp_nr.LdpNr(uid, repr_opts, **kwargs)
             rsrc = model.ldp_nr.LdpNr(uid, repr_opts, **kwargs)
-        elif __class__.LDP_RS_TYPE in rdf_types:
+        elif LDP_RS_TYPE in rdf_types:
             __class__._logger.info('Resource is a LDP-RS.')
             __class__._logger.info('Resource is a LDP-RS.')
             rsrc = model.ldp_rs.LdpRs(uid, repr_opts, **kwargs)
             rsrc = model.ldp_rs.LdpRs(uid, repr_opts, **kwargs)
         else:
         else:
@@ -122,7 +122,7 @@ class LdpFactory:
             inst = cls(uid, provided_imr=provided_imr, **kwargs)
             inst = cls(uid, provided_imr=provided_imr, **kwargs)
 
 
             # Make sure we are not updating an LDP-RS with an LDP-NR.
             # Make sure we are not updating an LDP-RS with an LDP-NR.
-            if inst.is_stored and __class__.LDP_NR_TYPE in inst.ldp_types:
+            if inst.is_stored and LDP_NR_TYPE in inst.ldp_types:
                 raise IncompatibleLdpTypeError(uid, mimetype)
                 raise IncompatibleLdpTypeError(uid, mimetype)
 
 
             if kwargs.get('handling', 'strict') != 'none':
             if kwargs.get('handling', 'strict') != 'none':
@@ -135,7 +135,7 @@ class LdpFactory:
                     provided_imr=provided_imr, **kwargs)
                     provided_imr=provided_imr, **kwargs)
 
 
             # Make sure we are not updating an LDP-NR with an LDP-RS.
             # Make sure we are not updating an LDP-NR with an LDP-RS.
-            if inst.is_stored and __class__.LDP_RS_TYPE in inst.ldp_types:
+            if inst.is_stored and LDP_RS_TYPE in inst.ldp_types:
                 raise IncompatibleLdpTypeError(uid, mimetype)
                 raise IncompatibleLdpTypeError(uid, mimetype)
 
 
         logger.info('Creating resource of type: {}'.format(
         logger.info('Creating resource of type: {}'.format(

+ 11 - 0
lakesuperior/model/ldp_nr.py

@@ -85,6 +85,17 @@ class LdpNr(Ldpr):
             return ev_type
             return ev_type
 
 
 
 
+    def patch_metadata(self, update_str):
+        '''
+        Update resource metadata by applying a SPARQL-UPDATE query.
+
+        @param update_str (string) SPARQL-Update staements.
+        '''
+        self.handling = 'lenient' # FCREPO does that and Hyrax requires it.
+
+        return self._sparql_update(update_str)
+
+
     ## PROTECTED METHODS ##
     ## PROTECTED METHODS ##
 
 
     def _add_srv_mgd_triples(self, create=False):
     def _add_srv_mgd_triples(self, create=False):

+ 3 - 45
lakesuperior/model/ldp_rs.py

@@ -1,8 +1,7 @@
-from flask import current_app, g
 from rdflib import Graph
 from rdflib import Graph
-from rdflib.plugins.sparql.algebra import translateUpdate
-from rdflib.plugins.sparql.parser import parseUpdate
 
 
+from lakesuperior.env import env
+from lakesuperior.globals import RES_UPDATED
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.model.ldpr import Ldpr
 from lakesuperior.model.ldpr import Ldpr
 
 
@@ -45,49 +44,8 @@ class LdpRs(Ldpr):
         @param update_str (string) SPARQL-Update staements.
         @param update_str (string) SPARQL-Update staements.
         '''
         '''
         self.handling = 'lenient' # FCREPO does that and Hyrax requires it.
         self.handling = 'lenient' # FCREPO does that and Hyrax requires it.
-        self._logger.debug('Local update string: {}'.format(local_update_str))
 
 
-        return self._sparql_update(local_update_str)
-
-
-    def _sparql_update(self, update_str, notify=True):
-        '''
-        Apply a SPARQL update to a resource.
-
-        The SPARQL string is validated beforehand to make sure that it does
-        not contain server-managed terms.
-
-        In theory, server-managed terms in DELETE statements are harmless
-        because the patch is only applied over the user-provided triples, but
-        at the moment those are also checked.
-        '''
-        # Parse the SPARQL update string and validate contents.
-        qry_struct = translateUpdate(parseUpdate(update_str))
-        check_ins_gr = Graph()
-        check_del_gr = Graph()
-        for stmt in qry_struct:
-            try:
-                check_ins_gr += set(stmt.insert.triples)
-            except AttributeError:
-                pass
-            try:
-                check_del_gr += set(stmt.delete.triples)
-            except AttributeError:
-                pass
-
-        self._check_mgd_terms(check_ins_gr)
-        self._check_mgd_terms(check_del_gr)
-
-        self.rdfly.patch_rsrc(self.uid, update_str)
-
-        if notify and current_app.config.get('messaging'):
-            self._enqueue_msg(self.RES_UPDATED, check_del_gr, check_ins_gr)
-
-        # @FIXME Ugly workaround until we find how to recompose a SPARQL query
-        # string from a parsed query object.
-        self.rdfly.clear_smt(self.uid)
-
-        return self.RES_UPDATED
+        return self._sparql_update(update_str)
 
 
 
 
     #def _sparql_delta(self, q):
     #def _sparql_delta(self, q):

+ 59 - 14
lakesuperior/model/ldpr.py

@@ -1,4 +1,5 @@
 import logging
 import logging
+import pdb
 
 
 from abc import ABCMeta
 from abc import ABCMeta
 from collections import defaultdict
 from collections import defaultdict
@@ -6,10 +7,12 @@ from uuid import uuid4
 
 
 import arrow
 import arrow
 
 
-from rdflib import Graph
+from flask import current_app
+from rdflib import Graph, URIRef, Literal
 from rdflib.resource import Resource
 from rdflib.resource import Resource
 from rdflib.namespace import RDF
 from rdflib.namespace import RDF
-from rdflib.term import URIRef, Literal
+from rdflib.plugins.sparql.algebra import translateUpdate
+from rdflib.plugins.sparql.parser import parseUpdate
 
 
 from lakesuperior.env import env
 from lakesuperior.env import env
 from lakesuperior.globals import (
 from lakesuperior.globals import (
@@ -271,7 +274,7 @@ class Ldpr(metaclass=ABCMeta):
                 #@ TODO get_version_info should return a graph.
                 #@ TODO get_version_info should return a graph.
                 self._version_info = rdfly.get_version_info(self.uid).graph
                 self._version_info = rdfly.get_version_info(self.uid).graph
             except ResourceNotExistsError as e:
             except ResourceNotExistsError as e:
-                self._version_info = Graph(identifer=self.uri)
+                self._version_info = Graph(identifier=self.uri)
 
 
         return self._version_info
         return self._version_info
 
 
@@ -282,7 +285,8 @@ class Ldpr(metaclass=ABCMeta):
         Return a generator of version UIDs (relative to their parent resource).
         Return a generator of version UIDs (relative to their parent resource).
         '''
         '''
         gen = self.version_info[
         gen = self.version_info[
-            nsc['fcrepo'].hasVersion / nsc['fcrepo'].hasVersionLabel]
+                self.uri :
+                nsc['fcrepo'].hasVersion / nsc['fcrepo'].hasVersionLabel :]
 
 
         return {str(uid) for uid in gen}
         return {str(uid) for uid in gen}
 
 
@@ -625,7 +629,7 @@ class Ldpr(metaclass=ABCMeta):
                 elif actor is None and t[1] == nsc['fcrepo'].createdBy:
                 elif actor is None and t[1] == nsc['fcrepo'].createdBy:
                     actor = t[2]
                     actor = t[2]
 
 
-        env.changelog.append((set(remove_trp), set(add_trp), {
+        env.app_globals.changelog.append((set(remove_trp), set(add_trp), {
             'ev_type': ev_type,
             'ev_type': ev_type,
             'time': env.timestamp,
             'time': env.timestamp,
             'type': type,
             'type': type,
@@ -637,15 +641,16 @@ class Ldpr(metaclass=ABCMeta):
         gr = self.provided_imr.graph
         gr = self.provided_imr.graph
 
 
         for o in gr.objects():
         for o in gr.objects():
-            if isinstance(o, URIRef) and str(o).startswith(nsc['fcres'])\
-                    and not rdfly.ask_rsrc_exists(o):
-                if config == 'strict':
-                    raise RefIntViolationError(o)
-                else:
-                    self._logger.info(
-                        'Removing link to non-existent repo resource: {}'
-                        .format(o))
-                    gr.remove((None, None, o))
+            if isinstance(o, URIRef) and str(o).startswith(nsc['fcres']):
+                obj_uid = rdfly.uri_to_uid(o)
+                if not rdfly.ask_rsrc_exists(obj_uid):
+                    if config == 'strict':
+                        raise RefIntViolationError(obj_uid)
+                    else:
+                        self._logger.info(
+                            'Removing link to non-existent repo resource: {}'
+                            .format(obj_uid))
+                        gr.remove((None, None, o))
 
 
 
 
     def _check_mgd_terms(self, gr):
     def _check_mgd_terms(self, gr):
@@ -816,3 +821,43 @@ class Ldpr(metaclass=ABCMeta):
             target_rsrc._modify_rsrc(RES_UPDATED, add_trp={(s, p, o)})
             target_rsrc._modify_rsrc(RES_UPDATED, add_trp={(s, p, o)})
 
 
         self._modify_rsrc(RES_UPDATED, add_trp=add_trp)
         self._modify_rsrc(RES_UPDATED, add_trp=add_trp)
+
+
+    def _sparql_update(self, update_str, notify=True):
+        '''
+        Apply a SPARQL update to a resource.
+
+        The SPARQL string is validated beforehand to make sure that it does
+        not contain server-managed terms.
+
+        In theory, server-managed terms in DELETE statements are harmless
+        because the patch is only applied over the user-provided triples, but
+        at the moment those are also checked.
+        '''
+        # Parse the SPARQL update string and validate contents.
+        qry_struct = translateUpdate(parseUpdate(update_str))
+        check_ins_gr = Graph()
+        check_del_gr = Graph()
+        for stmt in qry_struct:
+            try:
+                check_ins_gr += set(stmt.insert.triples)
+            except AttributeError:
+                pass
+            try:
+                check_del_gr += set(stmt.delete.triples)
+            except AttributeError:
+                pass
+
+        self._check_mgd_terms(check_ins_gr)
+        self._check_mgd_terms(check_del_gr)
+
+        env.app_globals.rdfly.patch_rsrc(self.uid, update_str)
+
+        if notify and current_app.config.get('messaging'):
+            self._enqueue_msg(RES_UPDATED, check_del_gr, check_ins_gr)
+
+        # @FIXME Ugly workaround until we find how to recompose a SPARQL query
+        # string from a parsed query object.
+        env.app_globals.rdfly.clear_smt(self.uid)
+
+        return RES_UPDATED

+ 3 - 2
lakesuperior/store/ldp_nr/default_layout.py

@@ -8,8 +8,7 @@ from lakesuperior.store.ldp_nr.base_non_rdf_layout import BaseNonRdfLayout
 
 
 class DefaultLayout(BaseNonRdfLayout):
 class DefaultLayout(BaseNonRdfLayout):
     '''
     '''
-    This is momentarily a stub until more non-RDF layouts use cases are
-    gathered.
+    Default file layout.
     '''
     '''
 
 
     ## INTERFACE METHODS ##
     ## INTERFACE METHODS ##
@@ -55,6 +54,8 @@ class DefaultLayout(BaseNonRdfLayout):
             self._logger.exception('File write failed on {}.'.format(tmp_file))
             self._logger.exception('File write failed on {}.'.format(tmp_file))
             os.unlink(tmp_file)
             os.unlink(tmp_file)
             raise
             raise
+        if size == 0:
+            self._logger.warn('Zero-file size received.')
 
 
         # Move temp file to final destination.
         # Move temp file to final destination.
         uuid = hash.hexdigest()
         uuid = hash.hexdigest()

+ 31 - 14
tests/endpoints/test_ldp.py

@@ -231,7 +231,7 @@ class TestLdp:
         PREFIX ns: <http://example.org#>
         PREFIX ns: <http://example.org#>
         PREFIX res: <http://example-source.org/res/>
         PREFIX res: <http://example-source.org/res/>
         <> ns:p1 res:bogus ;
         <> ns:p1 res:bogus ;
-          ns:p2 <{0}/> ;
+          ns:p2 <{0}> ;
           ns:p3 <{0}/nonexistent> .
           ns:p3 <{0}/nonexistent> .
         '''.format(g.webroot)
         '''.format(g.webroot)
         put_rsp = self.client.put('/ldp/test_missing_ref', data=data, headers={
         put_rsp = self.client.put('/ldp/test_missing_ref', data=data, headers={
@@ -245,9 +245,10 @@ class TestLdp:
         gr = Graph().parse(data=resp.data, format='text/turtle')
         gr = Graph().parse(data=resp.data, format='text/turtle')
         assert URIRef('http://example-source.org/res/bogus') in \
         assert URIRef('http://example-source.org/res/bogus') in \
                 gr.objects(None, URIRef('http://example.org#p1'))
                 gr.objects(None, URIRef('http://example.org#p1'))
+        #pdb.set_trace()
         assert URIRef(g.webroot + '/') in \
         assert URIRef(g.webroot + '/') in \
                 gr.objects(None, URIRef('http://example.org#p2'))
                 gr.objects(None, URIRef('http://example.org#p2'))
-        assert URIRef(g.webroot + '/nonexistent') in \
+        assert URIRef(g.webroot + '/nonexistent') not in \
                 gr.objects(None, URIRef('http://example.org#p3'))
                 gr.objects(None, URIRef('http://example.org#p3'))
 
 
 
 
@@ -333,16 +334,16 @@ class TestLdp:
 
 
         uri = g.webroot + '/test_patch_ssr'
         uri = g.webroot + '/test_patch_ssr'
 
 
-        #nossr_qry = 'INSERT { <http://bogus.org> a <urn:ns:A> . } WHERE {}'
+        nossr_qry = 'INSERT { <http://bogus.org> a <urn:ns:A> . } WHERE {}'
         abs_qry = 'INSERT {{ <{}> a <urn:ns:A> . }} WHERE {{}}'.format(uri)
         abs_qry = 'INSERT {{ <{}> a <urn:ns:A> . }} WHERE {{}}'.format(uri)
         frag_qry = 'INSERT {{ <{}#frag> a <urn:ns:A> . }} WHERE {{}}'\
         frag_qry = 'INSERT {{ <{}#frag> a <urn:ns:A> . }} WHERE {{}}'\
                 .format(uri)
                 .format(uri)
 
 
         # @TODO Leave commented until a decision is made about SSR.
         # @TODO Leave commented until a decision is made about SSR.
-        #assert self.client.patch(
-        #    path, data=nossr_qry,
-        #    headers={'content-type': 'application/sparql-update'}
-        #).status_code == 412
+        assert self.client.patch(
+            path, data=nossr_qry,
+            headers={'content-type': 'application/sparql-update'}
+        ).status_code == 204
 
 
         assert self.client.patch(
         assert self.client.patch(
             path, data=abs_qry,
             path, data=abs_qry,
@@ -357,8 +358,7 @@ class TestLdp:
 
 
     def test_patch_ldp_nr_metadata(self):
     def test_patch_ldp_nr_metadata(self):
         '''
         '''
-        Test patching a LDP-NR metadata resource, both from the fcr:metadata
-        and the resource URIs.
+        Test patching a LDP-NR metadata resource from the fcr:metadata URI.
         '''
         '''
         path = '/ldp/ldpnr01'
         path = '/ldp/ldpnr01'
 
 
@@ -372,11 +372,11 @@ class TestLdp:
 
 
         uri = g.webroot + '/ldpnr01'
         uri = g.webroot + '/ldpnr01'
         gr = Graph().parse(data=resp.data, format='text/turtle')
         gr = Graph().parse(data=resp.data, format='text/turtle')
-        assert gr[ URIRef(uri) : nsc['dc'].title : Literal('Hello') ]
+        assert gr[URIRef(uri) : nsc['dc'].title : Literal('Hello')]
 
 
         with open(
         with open(
                 'tests/data/sparql_update/delete+insert+where.sparql') as data:
                 'tests/data/sparql_update/delete+insert+where.sparql') as data:
-            patch_resp = self.client.patch(path,
+            patch_resp = self.client.patch(path + '/fcr:metadata',
                     data=data,
                     data=data,
                     headers={'content-type' : 'application/sparql-update'})
                     headers={'content-type' : 'application/sparql-update'})
         assert patch_resp.status_code == 204
         assert patch_resp.status_code == 204
@@ -388,17 +388,34 @@ class TestLdp:
         assert gr[ URIRef(uri) : nsc['dc'].title : Literal('Ciao') ]
         assert gr[ URIRef(uri) : nsc['dc'].title : Literal('Ciao') ]
 
 
 
 
-    def test_patch_ldp_nr(self, rnd_img):
+    def test_patch_ldpnr(self):
+        '''
+        Verify that a direct PATCH to a LDP-NR results in a 415.
+        '''
+        with open(
+                'tests/data/sparql_update/delete+insert+where.sparql') as data:
+            patch_resp = self.client.patch('/ldp/ldpnr01',
+                    data=data,
+                    headers={'content-type': 'application/sparql-update'})
+        assert patch_resp.status_code == 415
+
+
+    def test_patch_invalid_mimetype(self, rnd_img):
         '''
         '''
         Verify that a PATCH using anything other than an
         Verify that a PATCH using anything other than an
         `application/sparql-update` MIME type results in an error.
         `application/sparql-update` MIME type results in an error.
         '''
         '''
+        self.client.put('/ldp/test_patch_invalid_mimetype')
         rnd_img['content'].seek(0)
         rnd_img['content'].seek(0)
-        resp = self.client.patch('/ldp/ldpnr01/fcr:metadata',
+        ldpnr_resp = self.client.patch('/ldp/ldpnr01/fcr:metadata',
                 data=rnd_img,
                 data=rnd_img,
                 headers={'content-type' : 'image/jpeg'})
                 headers={'content-type' : 'image/jpeg'})
 
 
-        assert resp.status_code == 415
+        ldprs_resp = self.client.patch('/ldp/test_patch_invalid_mimetype',
+                data=b'Hello, I\'m not a SPARQL update.',
+                headers={'content-type' : 'text/plain'})
+
+        assert ldprs_resp.status_code == ldpnr_resp.status_code == 415
 
 
 
 
     def test_delete(self):
     def test_delete(self):