[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