[Checkins] SVN: Products.ExternalEditor/trunk/ - Add functional tests

Sidnei da Silva sidnei at enfoldsystems.com
Tue Jul 11 15:51:53 EDT 2006


Log message for revision 69097:
  - Add functional tests
  - Add a new 'skip_data' option
  

Changed:
  U   Products.ExternalEditor/trunk/CHANGES.txt
  U   Products.ExternalEditor/trunk/ExternalEditor.py
  A   Products.ExternalEditor/trunk/tests/
  A   Products.ExternalEditor/trunk/tests/__init__.py
  A   Products.ExternalEditor/trunk/tests/edit.txt
  A   Products.ExternalEditor/trunk/tests/link.txt
  A   Products.ExternalEditor/trunk/tests/test_functional.py

-=-
Modified: Products.ExternalEditor/trunk/CHANGES.txt
===================================================================
--- Products.ExternalEditor/trunk/CHANGES.txt	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/CHANGES.txt	2006-07-11 19:51:48 UTC (rev 69097)
@@ -2,6 +2,9 @@
 
   mm/dd/yyyy
 
+    - Added 'skip_data' option to make External Editor send out only
+      the metadata part and skip appending data to the 'body'.
+
     - Add a simple callback registry that can be used to add extra
       metadata headers or set special response headers when a file is
       edited through External Editor.

Modified: Products.ExternalEditor/trunk/ExternalEditor.py
===================================================================
--- Products.ExternalEditor/trunk/ExternalEditor.py	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/ExternalEditor.py	2006-07-11 19:51:48 UTC (rev 69097)
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
 # All Rights Reserved.
-# 
+#
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
-# 
+#
 ##############################################################################
 """$Id$
 """
@@ -37,11 +37,28 @@
 except ImportError:
     # pre-2.7.1 Zope without stream iterators
     IStreamIterator = None
-    
+
 ExternalEditorPermission = 'Use external editor'
 
 _callbacks = []
 
+class PDataStreamIterator:
+
+    __implements__ = (IStreamIterator,)
+
+    def __init__(self, data):
+        self.data = data
+
+    def __iter__(self):
+        return iter(self)
+
+    def next(self):
+        if self.data is None:
+            raise StopIteration
+        data = self.data.data
+        self.data = self.data.next
+        return data
+
 def registerCallback(cb):
     """Register a callback to be called by the External Editor when
     it's about to be finished with collecting metadata for the
@@ -64,10 +81,10 @@
     """Create a response that encapsulates the data needed by the
        ZopeEdit helper application
     """
-    
+
     security = ClassSecurityInfo()
     security.declareObjectProtected(ExternalEditorPermission)
-    
+
     def __before_publishing_traverse__(self, self2, request):
         path = request['TraversalRequestNameStack']
         if path:
@@ -80,10 +97,10 @@
             path[:] = []
         else:
             request.set('target', None)
-    
+
     def index_html(self, REQUEST, RESPONSE, path=None):
         """Publish the object to the external editor helper app"""
-        
+
         security = getSecurityManager()
         if path is None:
             parent = self.aq_parent
@@ -96,11 +113,11 @@
                 ob = parent.propertysheets.methods[REQUEST['target']]
         else:
             ob = self.restrictedTraverse( path )
-        
+
         r = []
         r.append('url:%s' % ob.absolute_url())
         r.append('meta_type:%s' % ob.meta_type)
-        
+
         title = getattr(Acquisition.aq_base(ob), 'title', None)
         if title is not None:
             if callable(title):
@@ -108,7 +125,7 @@
             if isinstance(title, types.UnicodeType):
                 title = unicode.encode(title, 'utf-8')
             r.append('title:%s' % title)
-                
+
         if hasattr(Acquisition.aq_base(ob), 'content_type'):
             if callable(ob.content_type):
                 r.append('content_type:%s' % ob.content_type())
@@ -120,13 +137,13 @@
                 auth = REQUEST._auth[:-1]
             else:
                 auth = REQUEST._auth
-                
+
             r.append('auth:%s' % auth)
-            
+
         r.append('cookie:%s' % REQUEST.environ.get('HTTP_COOKIE',''))
-        
+
         if wl_isLocked(ob):
-            # Object is locked, send down the lock token 
+            # Object is locked, send down the lock token
             # owned by this user (if any)
             user_id = security.getUser().getId()
             for lock in ob.wl_lockValues():
@@ -138,35 +155,35 @@
                     r.append('lock-token:%s' % lock.getLockToken())
                     if REQUEST.get('borrow_lock'):
                         r.append('borrow_lock:1')
-                    break       
-              
+                    break
+
         # Apply any extra callbacks that might have been registered.
         applyCallbacks(ob, r, REQUEST, RESPONSE)
 
+        # Finish metadata with an empty line.
         r.append('')
-        streamiterator = None
-        
+        metadata = join(r, '\n')
+        metadata_len = len(metadata)
+
         # Using RESPONSE.setHeader('Pragma', 'no-cache') would be better, but
         # this chokes crappy most MSIE versions when downloads happen on SSL.
         # cf. http://support.microsoft.com/support/kb/articles/q316/4/31.asp
         RESPONSE.setHeader('Last-Modified', rfc1123_date())
-        
-        if hasattr(Acquisition.aq_base(ob), 'data') \
-           and hasattr(ob.data, '__class__') \
-           and ob.data.__class__ is Image.Pdata:
+        RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
+
+        # Check if we should send the file's data down the response.
+        if REQUEST.get('skip_data'):
+            # We've been requested to send only the metadata. The
+            # client will presumably fetch the data itself.
+            self._write_metadata(RESPONSE, metadata, metadata_len)
+            return ''
+
+        ob_data = getattr(Acquisition.aq_base(ob), 'data', None)
+        if (ob_data is not None and isinstance(ob_data, Image.Pdata)):
             # We have a File instance with chunked data, lets stream it
-            metadata = join(r, '\n')
-            RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
-            RESPONSE.setHeader('Content-Length', 
-                               len(metadata) + ob.get_size() + 1)
-            RESPONSE.write(metadata)
-            RESPONSE.write('\n')
-            data = ob.data
-            while data is not None:
-                RESPONSE.write(data.data)
-                data = data.next         
-            return ''
-        if hasattr(ob, 'manage_FTPget'):
+            RESPONSE.setHeader('Content-Length', ob.get_size())
+            body = PDataStreamIterator(ob.data)
+        elif hasattr(ob, 'manage_FTPget'):
             try:
                 body = ob.manage_FTPget()
             except TypeError: # some need the R/R pair!
@@ -180,8 +197,6 @@
         else:
             # can't read it!
             raise 'BadRequest', 'Object does not support external editing'
-        
-        RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
 
         if (IStreamIterator is not None and
             IStreamIterator.isImplementedBy(body)):
@@ -191,21 +206,24 @@
             # here because we insert metadata before the body.
             clen = RESPONSE.headers.get('content-length', None)
             assert clen is not None
-            metadata = join(r, '\n')
-            RESPONSE.setHeader('Content-Length', len(metadata) + int(clen) + 1)
-            RESPONSE.write(metadata)
-            RESPONSE.write('\n')
+            self._write_metadata(RESPONSE, metadata, metadata_len + int(clen))
             for data in body:
                 RESPONSE.write(data)
-        else:
-            r.append(body)
-            return join(r, '\n')
+            return ''
 
+        # If we reached this point, body *must* be a string.
+        return join((metadata, body), '\n')
+
+    def _write_metadata(self, RESPONSE, metadata, length):
+        RESPONSE.setHeader('Content-Length', length + 1)
+        RESPONSE.write(metadata)
+        RESPONSE.write('\n')
+
 InitializeClass(ExternalEditor)
 
 is_mac_user_agent = re.compile('.*Mac OS X.*|.*Mac_PowerPC.*').match
 
-def EditLink(self, object, borrow_lock=0):
+def EditLink(self, object, borrow_lock=0, skip_data=0):
     """Insert the external editor link to an object if appropriate"""
     base = Acquisition.aq_base(object)
     user = getSecurityManager().getUser()
@@ -226,8 +244,10 @@
             ext = ''
         if borrow_lock:
             query['borrow_lock'] = 1
-        url = "%s/externalEdit_/%s%s%s" % (object.aq_parent.absolute_url(), 
-                                           urllib.quote(object.getId()), 
+        if skip_data:
+            query['skip_data'] = 1
+        url = "%s/externalEdit_/%s%s%s" % (object.aq_parent.absolute_url(),
+                                           urllib.quote(object.getId()),
                                            ext, querystr(query))
         return ('<a href="%s" '
                 'title="Edit using external editor">'

Added: Products.ExternalEditor/trunk/tests/__init__.py
===================================================================
--- Products.ExternalEditor/trunk/tests/__init__.py	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/tests/__init__.py	2006-07-11 19:51:48 UTC (rev 69097)
@@ -0,0 +1,13 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################


Property changes on: Products.ExternalEditor/trunk/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision HeadURL
Name: svn:eol-style
   + native

Added: Products.ExternalEditor/trunk/tests/edit.txt
===================================================================
--- Products.ExternalEditor/trunk/tests/edit.txt	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/tests/edit.txt	2006-07-11 19:51:48 UTC (rev 69097)
@@ -0,0 +1,228 @@
+Tests for the External Editor
+=============================
+
+  >>> from Acquisition import Implicit
+  >>> from AccessControl.SecurityManagement import getSecurityManager
+  >>> from Testing.ZopeTestCase import user_name, user_password
+  >>> from Products.ExternalEditor.ExternalEditor import registerCallback
+  >>> from Products.ExternalEditor.ExternalEditor import _callbacks
+
+  >>> self.login()
+  >>> self.setRoles(('Manager',))
+
+Create a OFS.File instance, see if it gets properly sent out by
+External Editor:
+
+  >>> self.folder.manage_addFile('some-file', file='some content')
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: 167
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  <BLANKLINE>
+  some content
+
+Lock the file, should now send out the lock-token in the metadata:
+
+  >>> self.folder['some-file'].wl_clearLocks()
+  >>> print http(r"""
+  ... LOCK /test_folder_1_/some-file HTTP/1.1
+  ... Content-Type: text/xml; charset="utf-8"
+  ... Depth: 0
+  ... Authorization: Basic %s:%s
+  ...
+  ... <?xml version="1.0" encoding="utf-8"?>
+  ... <DAV:lockinfo xmlns:DAV="DAV:">
+  ... <DAV:lockscope><DAV:exclusive/></DAV:lockscope>
+  ... <DAV:locktype><DAV:write/></DAV:locktype>
+  ... </DAV:lockinfo>""" % (user_name, user_password))
+  HTTP/1.1 200 OK
+  ...
+  Lock-Token: ...
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: ...
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  lock-token:...
+  <BLANKLINE>
+  some content
+
+If 'borrow_lock' is found in the request, then a 'borrow_lock:1' is
+appended to the metadata along with the lock-token:
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file?borrow_lock=1 HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: ...
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  lock-token:...
+  borrow_lock:1
+  <BLANKLINE>
+  some content
+
+If 'skip_data' is found in the request, then the file data is **not**
+appended after the metadata:
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file?skip_data=1 HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: ...
+  Content-Type: application/x-zope-edit
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  lock-token:...
+  <BLANKLINE>
+
+A user that is not the lock owner will not get the 'lock-token' or
+'borrow_lock':
+
+  >>> user_name_2 = 'test_user_2_'
+  >>> user_password_2 = 'frob'
+
+  >>> uf = self.folder.acl_users
+  >>> uf.userFolderAddUser(user_name_2, user_password_2, ['Manager'], [])
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file?borrow_lock=1 HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name_2, user_password_2))
+  HTTP/1.1 200 OK
+  Content-Length: 163
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  <BLANKLINE>
+  some content
+
+Clear the locks:
+
+  >>> self.folder['some-file'].wl_clearLocks()
+
+Callback Registry
+=================
+
+There is a callback registry that can be used to modify the metadata
+that is sent out:
+
+  >>> def md_callback(ob, md, req, resp):
+  ...     md.append('x-my-custom-metadata:42')
+
+  >>> old_cb = _callbacks[:]
+  >>> registerCallback(md_callback)
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file?borrow_lock=1 HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: 191
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  x-my-custom-metadata:42
+  <BLANKLINE>
+  some content
+
+  >>> _callbacks[:] = old_cb
+
+The same callback registry can also be used to set a value in the
+REQUEST, for example the 'skip_data' parameter:
+
+  >>> def req_callback(ob, md, req, resp):
+  ...     req.other['skip_data'] = '1'
+
+  >>> old_cb = _callbacks[:]
+  >>> registerCallback(req_callback)
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: 155
+  Content-Type: application/x-zope-edit
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/some-file
+  meta_type:File
+  title:
+  content_type:application/octet-stream
+  auth:...
+  cookie:
+  <BLANKLINE>
+
+  >>> _callbacks[:] = old_cb
+
+Or, if the client supports gzip compression, enabling compression in
+the RESPONSE:
+
+  >>> def resp_callback(ob, md, req, resp):
+  ...     resp.enableHTTPCompression(force=1)
+
+  >>> old_cb = _callbacks[:]
+  >>> registerCallback(resp_callback)
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/some-file HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Encoding: gzip
+  Content-Length: 159
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+
+  >>> _callbacks[:] = old_cb


Property changes on: Products.ExternalEditor/trunk/tests/edit.txt
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision HeadURL
Name: svn:eol-style
   + native

Added: Products.ExternalEditor/trunk/tests/link.txt
===================================================================
--- Products.ExternalEditor/trunk/tests/link.txt	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/tests/link.txt	2006-07-11 19:51:48 UTC (rev 69097)
@@ -0,0 +1,46 @@
+Tests for the External Editor link
+==================================
+
+  >>> from Acquisition import Implicit
+  >>> from AccessControl.SecurityManagement import getSecurityManager
+  >>> from Products.ExternalEditor.ExternalEditor import EditLink
+
+  >>> class File(Implicit):
+  ...    def __init__(self, id):
+  ...       self.id = id
+  ...    def getId(self):
+  ...       return self.id
+  ...    def manage_FTPget(self):
+  ...       return '%s content' % self.id
+
+
+  >>> ob = File('some-file').__of__(self.folder)
+
+A user that has no permission cannot see the link:
+
+  >>> self.logout()
+  >>> getSecurityManager().getUser().getUserName()
+  'Anonymous User'
+
+  >>> EditLink(self.folder, ob)
+  ''
+
+A user that has the permission can see the link:
+
+  >>> self.login()
+  >>> self.setRoles(('Manager',))
+  >>> getSecurityManager().getUser().getUserName()
+  'test_user_1_'
+
+  >>> EditLink(self.folder, ob)
+  '<a href="http://nohost/test_folder_1_/externalEdit_/some-file" title="Edit using external editor"><img src="/misc_/ExternalEditor/edit_icon" align="middle" hspace="2" border="0" alt="External Editor" /></a>'
+
+Borrow Lock feature:
+
+  >>> EditLink(self.folder, ob, borrow_lock=1)
+  '<a href="http://nohost/test_folder_1_/externalEdit_/some-file?borrow_lock=1" title="Edit using external editor"><img src="/misc_/ExternalEditor/edit_icon" align="middle" hspace="2" border="0" alt="External Editor" /></a>'
+
+Skip Data feature:
+
+  >>> EditLink(self.folder, ob, skip_data=1)
+  '<a href="http://nohost/test_folder_1_/externalEdit_/some-file?skip_data=1" title="Edit using external editor"><img src="/misc_/ExternalEditor/edit_icon" align="middle" hspace="2" border="0" alt="External Editor" /></a>'


Property changes on: Products.ExternalEditor/trunk/tests/link.txt
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision HeadURL
Name: svn:eol-style
   + native

Added: Products.ExternalEditor/trunk/tests/test_functional.py
===================================================================
--- Products.ExternalEditor/trunk/tests/test_functional.py	2006-07-11 14:58:04 UTC (rev 69096)
+++ Products.ExternalEditor/trunk/tests/test_functional.py	2006-07-11 19:51:48 UTC (rev 69097)
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import os, sys
+
+# Load fixture
+from Testing import ZopeTestCase
+
+# Install our product
+ZopeTestCase.installProduct('ExternalEditor')
+
+def test_suite():
+    import unittest
+    suite = unittest.TestSuite()
+    from Testing.ZopeTestCase import doctest
+    FileSuite = doctest.FunctionalDocFileSuite
+    files = [
+        'link.txt',
+        'edit.txt',
+        ]
+    for f in files:
+        suite.addTest(
+            FileSuite(f, package='Products.ExternalEditor.tests'))
+    return suite


Property changes on: Products.ExternalEditor/trunk/tests/test_functional.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision HeadURL
Name: svn:eol-style
   + native



More information about the Checkins mailing list