[Zope3-checkins] SVN: Zope3/trunk/src/zope/testbrowser/ Added a new more robust API for setting file-upload data.

Jim Fulton jim at zope.com
Wed May 3 19:55:17 EDT 2006


Log message for revision 67955:
  Added a new more robust API for setting file-upload data.
  

Changed:
  U   Zope3/trunk/src/zope/testbrowser/README.txt
  U   Zope3/trunk/src/zope/testbrowser/browser.py
  A   Zope3/trunk/src/zope/testbrowser/tests.py

-=-
Modified: Zope3/trunk/src/zope/testbrowser/README.txt
===================================================================
--- Zope3/trunk/src/zope/testbrowser/README.txt	2006-05-03 23:49:45 UTC (rev 67954)
+++ Zope3/trunk/src/zope/testbrowser/README.txt	2006-05-03 23:55:14 UTC (rev 67955)
@@ -618,9 +618,15 @@
 
   - File Control
 
-    The minimum setup required for file controls is to assign a file-like
-    object to the control's ``value`` attribute:
+    File controls are used when a form has a file-upload field.
+    To specify data, call the add_file method, passing:
 
+    - A file-like object
+
+    - a content type, and 
+ 
+    - a file name
+
     >>> ctrl = browser.getControl('File Control')
     >>> ctrl
     <Control name='file-value' type='file'>
@@ -629,13 +635,10 @@
     >>> ctrl.value is None
     True
     >>> import cStringIO
-    >>> ctrl.value = cStringIO.StringIO('File contents')
 
-    The file control's content type and file name can also be set:
+    >>> ctrl.add_file(cStringIO.StringIO('File contents'),
+    ...               'text/plain', 'test.txt')
 
-    >>> ctrl.filename = 'test.txt'
-    >>> ctrl.content_type = 'text/plain'
-
     The file control (like the other controls) also knows if it is disabled
     or if it can have multiple values.
 

Modified: Zope3/trunk/src/zope/testbrowser/browser.py
===================================================================
--- Zope3/trunk/src/zope/testbrowser/browser.py	2006-05-03 23:49:45 UTC (rev 67954)
+++ Zope3/trunk/src/zope/testbrowser/browser.py	2006-05-03 23:55:14 UTC (rev 67955)
@@ -19,7 +19,7 @@
 from test import pystone
 from zope.testbrowser import interfaces
 import ClientForm
-import StringIO
+from cStringIO import StringIO
 import mechanize
 import operator
 import pullparser
@@ -465,6 +465,14 @@
                 self.mech_control.value = value
         return property(fget, fset)
 
+    def add_file(self, file, content_type, filename):
+        if not self.mech_control.type == 'file':
+            raise TypeError("Can't call add_file on %s controls"
+                            % self.mech_control.type)
+        if isinstance(file, str):
+            file = StringIO(file)
+        self.mech_control.add_file(file, content_type, filename)
+
     def clear(self):
         if self._browser_counter != self.browser._counter:
             raise interfaces.ExpiredError

Added: Zope3/trunk/src/zope/testbrowser/tests.py
===================================================================
--- Zope3/trunk/src/zope/testbrowser/tests.py	2006-05-03 23:49:45 UTC (rev 67954)
+++ Zope3/trunk/src/zope/testbrowser/tests.py	2006-05-03 23:55:14 UTC (rev 67955)
@@ -0,0 +1,235 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Real test for file-upload and beginning of a better internal test framework
+
+$Id$
+"""
+
+import unittest
+import httplib
+import re
+import urllib2
+from cStringIO import StringIO
+
+import mechanize
+import ClientCookie
+
+from zope.testbrowser import browser
+from zope.testing import renormalizing, doctest
+
+
+def set_next_response(body, headers=None, status='200', reason='OK'):
+    global next_response_body
+    global next_response_headers
+    global next_response_status
+    global next_response_reason
+    if headers is None:
+        headers = (
+            'Content-Type: text/html\r\n'
+            'Content-Length: %s\r\n'
+            % len(body)
+            )
+    next_response_body = body
+    next_response_headers = headers
+    next_response_status = status
+    next_response_reason = reason
+
+
+class FauxConnection(object):
+    """A ``urllib2`` compatible connection obejct."""
+
+    def __init__(self, host):
+        pass
+
+    def set_debuglevel(self, level):
+        pass
+
+    def _quote(self, url):
+        # the publisher expects to be able to split on whitespace, so we have
+        # to make sure there is none in the URL
+        return url.replace(' ', '%20')
+
+
+    def request(self, method, url, body=None, headers=None):
+        if body is None:
+            body = ''
+
+        if url == '':
+            url = '/'
+
+        url = self._quote(url)
+
+        # Construct the headers.
+        header_chunks = []
+        if headers is not None:
+            for header in headers.items():
+                header_chunks.append('%s: %s' % header)
+            headers = '\n'.join(header_chunks) + '\n'
+        else:
+            headers = ''
+
+        # Construct the full HTTP request string, since that is what the
+        # ``HTTPCaller`` wants.
+        request_string = (method + ' ' + url + ' HTTP/1.1\n'
+                          + headers + '\n' + body)
+
+        print request_string.replace('\r', '')
+
+    def getresponse(self):
+        """Return a ``urllib2`` compatible response.
+
+        The goal of ths method is to convert the Zope Publisher's reseponse to
+        a ``urllib2`` compatible response, which is also understood by
+        mechanize.
+        """
+        return FauxResponse(next_response_body,
+                            next_response_headers,
+                            next_response_status,
+                            next_response_reason,
+                            )
+
+class FauxResponse(object):
+
+    def __init__(self, content, headers, status, reason):
+        self.content = content
+        self.status = status
+        self.reason = reason
+        self.msg = httplib.HTTPMessage(StringIO(headers), 0)
+        self.content_as_file = StringIO(self.content)
+
+    def read(self, amt=None):
+        return self.content_as_file.read(amt)
+
+
+class FauxHTTPHandler(urllib2.HTTPHandler):
+
+    http_request = urllib2.AbstractHTTPHandler.do_request_
+
+    def http_open(self, req):
+        """Open an HTTP connection having a ``urllib2`` request."""
+        # Here we connect to the publisher.
+        return self.do_open(FauxConnection, req)
+
+
+class FauxMechanizeBrowser(mechanize.Browser):
+
+    handler_classes = {
+        # scheme handlers
+        "http": FauxHTTPHandler,
+
+        "_http_error": ClientCookie.HTTPErrorProcessor,
+        "_http_request_upgrade": ClientCookie.HTTPRequestUpgradeProcessor,
+        "_http_default_error": urllib2.HTTPDefaultErrorHandler,
+
+        # feature handlers
+        "_authen": urllib2.HTTPBasicAuthHandler,
+        "_redirect": ClientCookie.HTTPRedirectHandler,
+        "_cookies": ClientCookie.HTTPCookieProcessor,
+        "_refresh": ClientCookie.HTTPRefreshProcessor,
+        "_referer": mechanize.Browser.handler_classes['_referer'],
+        "_equiv": ClientCookie.HTTPEquivProcessor,
+        "_seek": ClientCookie.SeekableProcessor,
+        }
+
+    default_schemes = ["http"]
+    default_others = ["_http_error", "_http_request_upgrade",
+                      "_http_default_error"]
+    default_features = ["_authen", "_redirect", "_cookies", "_seek"]
+
+
+class Browser(browser.Browser):
+
+    def __init__(self, url=None):
+        mech_browser = FauxMechanizeBrowser()
+        super(Browser, self).__init__(url=url, mech_browser=mech_browser)
+
+    def open(self, body, headers=None, status=200, reason='OK'):
+        set_next_response(body, headers, status, reason)
+        browser.Browser.open(self, 'http://localhost/')
+
+def test_file_upload():
+    """
+    
+    >>> browser = Browser()
+
+When given a form with a file-upload
+
+    >>> browser.open('''\
+    ... <html><body>
+    ...   <form action="." method="post" enctype="multipart/form-data">
+    ...      <input name="foo" type="file" />
+    ...      <input type="submit" value="OK" />
+    ...   </form></body></html>
+    ... ''') # doctest: +ELLIPSIS
+    GET / HTTP/1.1
+    ...
+
+Fill in the form value using add_file:
+
+    >>> browser.getControl(name='foo').add_file(
+    ...     StringIO('sample_data'), 'text/foo', 'x.foo')
+    >>> browser.getControl('OK').click()
+    POST / HTTP/1.1
+    Content-length: 173
+    Connection: close
+    Content-type: multipart/form-data; boundary=127.0.0.11000318041146699896411
+    Host: localhost
+    User-agent: Python-urllib/2.99
+    <BLANKLINE>
+    --127.0.0.11000318041146699896411
+    Content-disposition: form-data; name="foo"; filename="x.foo"
+    Content-type: text/foo
+    <BLANKLINE>
+    sample_data
+    --127.0.0.11000318041146699896411--
+    <BLANKLINE>
+
+You can pass s atring to add_file:
+
+
+    >>> browser.getControl(name='foo').add_file(
+    ...     'blah blah blah', 'text/blah', 'x.blah')
+    >>> browser.getControl('OK').click()
+    POST / HTTP/1.1
+    Content-length: 178
+    Connection: close
+    Content-type: multipart/form-data; boundary=127.0.0.11000318541146700017052
+    Host: localhost
+    User-agent: Python-urllib/2.98
+    <BLANKLINE>
+    --127.0.0.11000318541146700017052
+    Content-disposition: form-data; name="foo"; filename="x.blah"
+    Content-type: text/blah
+    <BLANKLINE>
+    blah blah blah
+    --127.0.0.11000318541146700017052--
+    <BLANKLINE>
+
+
+    """
+
+checker = renormalizing.RENormalizing([
+    (re.compile('127.0.0.\S+'), '-'*30),
+    (re.compile('User-agent:\s+\S+'), 'User-agent: XXX'),
+    ])
+
+def test_suite():
+    from zope.testing import doctest
+    return unittest.TestSuite((
+        doctest.DocTestSuite(checker=checker),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: Zope3/trunk/src/zope/testbrowser/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list