[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