diff -Nru webtest-2.0.23/CHANGELOG.rst webtest-2.0.28/CHANGELOG.rst --- webtest-2.0.23/CHANGELOG.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/CHANGELOG.rst 2017-08-01 15:29:14.000000000 +0000 @@ -1,6 +1,56 @@ News ==== +2.0.28 (2017-08-01) +------------------- + +- Fixed #185: Fix strict cookie policy + +- Fixed #146: Improve fields value checking when enctype is multipart + +- Fixed #119: Assertion error should be raised when you have non-string + response header + +- Bugfix: Allow to set an int value to form fields when enctype is multipart + +- Added py36 to tox.ini / .travis.yaml + + +2.0.27 (2017-03-15) +------------------- + +- Bugfix: Allow to use set_cookie when HTTP_HOST is set + +- Fix #177: resp.json now always decode body as utf8 + + +2.0.26 (2017-03-05) +------------------- + +- Added JWT auth support + +- Always show response body when response status is invalid + + +2.0.25 (2017-02-05) +------------------- + +- Fix #173: Do not omit file uploads without a file from post. + [Michael Howitz] + + +2.0.24 (2016-12-16) +------------------- + +- Drop python 2.6 support. Newer versions may still work if you use waitress < 1.0 + +- Remove bs4 warnings + +- Docs improvments + +- Tets are WebOb 1.7.x compatible + + 2.0.23 (2016-07-21) ------------------- diff -Nru webtest-2.0.23/debian/changelog webtest-2.0.28/debian/changelog --- webtest-2.0.23/debian/changelog 2016-07-31 04:42:59.000000000 +0000 +++ webtest-2.0.28/debian/changelog 2017-11-14 18:41:51.000000000 +0000 @@ -1,3 +1,31 @@ +webtest (2.0.28-1ubuntu1) bionic; urgency=medium + + * Merge from Debian unstable. Remaining changes: + - d/control: Move python-pyquery to Suggests. + + -- Corey Bryant Tue, 14 Nov 2017 13:41:51 -0500 + +webtest (2.0.28-1) unstable; urgency=medium + + * New upstream release + * Standards-Version bumped to 4.1.1 (no changes needed) + * Generate documentation using Python 3's version of sphinx + + -- Piotr Ożarowski Fri, 29 Sep 2017 12:52:31 +0200 + +webtest (2.0.27-1) unstable; urgency=medium + + * New upstream release + * Standards-Version bumped to 4.0.0 (no changes needed) + + -- Piotr Ożarowski Tue, 20 Jun 2017 20:01:46 +0200 + +webtest (2.0.24-1) unstable; urgency=medium + + * New upstream release + + -- Piotr Ożarowski Wed, 21 Dec 2016 12:45:16 +0100 + webtest (2.0.23-1ubuntu1) zesty; urgency=low * Merge from Debian unstable. Remaining changes: diff -Nru webtest-2.0.23/debian/control webtest-2.0.28/debian/control --- webtest-2.0.23/debian/control 2016-07-31 04:42:59.000000000 +0000 +++ webtest-2.0.28/debian/control 2017-11-14 18:41:51.000000000 +0000 @@ -9,10 +9,10 @@ python-setuptools, python-webob, python3-setuptools, python3-webob, # docs & tests - python-sphinx (>= 1.0.7+dfsg-1~), python-six, python-bs4, python-waitress, + python3-sphinx, python3-six, python3-bs4, python3-waitress, # tests # python-unittest2, python-pyquery -Standards-Version: 3.9.8 +Standards-Version: 4.1.1 X-Python-Version: >=2.6 Homepage: http://pythonpaste.org/webtest/ Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/webtest.git @@ -23,7 +23,8 @@ Depends: ${python:Depends}, ${misc:Depends}, python-webob (>= 1.2), python-six, python-waitress (>= 0.8.5), python-bs4, python-paste (>= 1.7.1), python-pastedeploy -Suggests: python-webtest-doc, python-pyquery, python-lxml +Recommends: python-lxml +Suggests: python-webtest-doc, python-pyquery Description: wraps any WSGI application and makes it easy to test WebTest helps you test your WSGI-based web applications. This can be any application that has a WSGI (Web Server Gateway Interface) interface, @@ -36,7 +37,9 @@ Depends: ${python3:Depends}, ${misc:Depends}, python3-webob, python3-six, python3-waitress (>= 0.8.5), python3-bs4, python3-paste, python3-pastedeploy -Suggests: python-webtest-doc, python3-lxml +Recommends: python3-lxml +#, python3-pyquery +Suggests: python-webtest-doc Description: wraps any WSGI application and makes it easy to test WebTest helps you test your WSGI-based web applications. This can be any application that has a WSGI (Web Server Gateway Interface) interface, diff -Nru webtest-2.0.23/debian/.git-dpm webtest-2.0.28/debian/.git-dpm --- webtest-2.0.23/debian/.git-dpm 2016-07-30 21:49:06.000000000 +0000 +++ webtest-2.0.28/debian/.git-dpm 2017-09-29 10:52:16.000000000 +0000 @@ -1,11 +1,11 @@ # see git-dpm(1) from git-dpm package -ad92116946d037b6907d2c9a5ec99be509f18a81 -ad92116946d037b6907d2c9a5ec99be509f18a81 -ad92116946d037b6907d2c9a5ec99be509f18a81 -ad92116946d037b6907d2c9a5ec99be509f18a81 -webtest_2.0.23.orig.tar.gz -4da68f548062e6621a70284cff01d3fb3892ef5e -67003 +17e96f699f343b182d9cc6870fdc3552e167ea25 +17e96f699f343b182d9cc6870fdc3552e167ea25 +17e96f699f343b182d9cc6870fdc3552e167ea25 +17e96f699f343b182d9cc6870fdc3552e167ea25 +webtest_2.0.28.orig.tar.gz +cb7753ba27ccd61f78206f65f417e6e800edb273 +74081 debianTag="debian/%e%v" patchedTag="patched/%e%v" upstreamTag="upstream/%e%u" diff -Nru webtest-2.0.23/debian/rules webtest-2.0.28/debian/rules --- webtest-2.0.23/debian/rules 2016-07-31 04:42:59.000000000 +0000 +++ webtest-2.0.28/debian/rules 2017-09-29 10:52:31.000000000 +0000 @@ -11,7 +11,7 @@ dh_auto_install --buildsystem=pybuild mkdir -p docs/_static # Sphinx needs it dh_installdirs - http_proxy='http://127.0.0.1:9/' PYTHONPATH=. sphinx-build -N -q -E -b html docs/ debian/python-webtest-doc/usr/share/doc/python-webtest-doc/ + http_proxy='http://127.0.0.1:9/' PYTHONPATH=. python3 -m sphinx -N -q -E -b html docs/ debian/python-webtest-doc/usr/share/doc/python-webtest-doc/ # it's not an extra license file echo "python-webtest-doc: extra-license-file usr/share/doc/python-webtest-doc/_sources/license.txt" \ >> debian/python-webtest-doc/usr/share/lintian/overrides/python-webtest-doc diff -Nru webtest-2.0.23/docs/changelog.rst webtest-2.0.28/docs/changelog.rst --- webtest-2.0.23/docs/changelog.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/changelog.rst 2017-08-01 15:29:14.000000000 +0000 @@ -1,6 +1,56 @@ News ==== +2.0.28 (2017-08-01) +------------------- + +- Fixed #185: Fix strict cookie policy + +- Fixed #146: Improve fields value checking when enctype is multipart + +- Fixed #119: Assertion error should be raised when you have non-string + response header + +- Bugfix: Allow to set an int value to form fields when enctype is multipart + +- Added py36 to tox.ini / .travis.yaml + + +2.0.27 (2017-03-15) +------------------- + +- Bugfix: Allow to use set_cookie when HTTP_HOST is set + +- Fix #177: resp.json now always decode body as utf8 + + +2.0.26 (2017-03-05) +------------------- + +- Added JWT auth support + +- Always show response body when response status is invalid + + +2.0.25 (2017-02-05) +------------------- + +- Fix #173: Do not omit file uploads without a file from post. + [Michael Howitz] + + +2.0.24 (2016-12-16) +------------------- + +- Drop python 2.6 support. Newer versions may still work if you use waitress < 1.0 + +- Remove bs4 warnings + +- Docs improvments + +- Tets are WebOb 1.7.x compatible + + 2.0.23 (2016-07-21) ------------------- diff -Nru webtest-2.0.23/docs/conf.py webtest-2.0.28/docs/conf.py --- webtest-2.0.23/docs/conf.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/conf.py 2017-08-01 15:29:14.000000000 +0000 @@ -124,7 +124,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. diff -Nru webtest-2.0.23/docs/forms.rst webtest-2.0.28/docs/forms.rst --- webtest-2.0.23/docs/forms.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/forms.rst 2017-08-01 15:29:14.000000000 +0000 @@ -43,7 +43,7 @@ .. code-block:: python >>> # dict of fields - >>> form.fields.values() #doctest: +SKIP + >>> form.fields.items() #doctest: +SKIP [(u'text', []), ..., (u'submit', [])] You can check the current value: diff -Nru webtest-2.0.23/docs/index.rst webtest-2.0.28/docs/index.rst --- webtest-2.0.23/docs/index.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/index.rst 2017-08-01 15:29:14.000000000 +0000 @@ -66,7 +66,7 @@ Quick start =========== -The most important object in WebTest is :class:`~webtest.TestApp`, the wrapper +The most important object in WebTest is :class:`~webtest.app.TestApp`, the wrapper for WSGI applications. It also allows you to perform HTTP requests on it. To use it, you simply instantiate it with your WSGI application. @@ -83,7 +83,7 @@ ... start_response('200 OK', headers) ... return [body] -Wrap it into a :class:`~webtest.TestApp`:: +Wrap it into a :class:`~webtest.app.TestApp`:: >>> from webtest import TestApp >>> app = TestApp(application) @@ -107,7 +107,7 @@ >>> resp.mustcontain('') >>> assert 'form' in resp -WebTest can do much more. In particular, it can handle :doc:`forms` and :doc:`json`. +WebTest can do much more. In particular, it can handle :doc:`forms`. Contents diff -Nru webtest-2.0.23/docs/testapp_fixt.py webtest-2.0.28/docs/testapp_fixt.py --- webtest-2.0.23/docs/testapp_fixt.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/testapp_fixt.py 2017-08-01 15:29:14.000000000 +0000 @@ -20,10 +20,10 @@ body = six.b('hey!') elif req.path_info.endswith('.json'): content_type = 'application/json' - body = json.dumps({"a": 1, "b": 2}) + body = six.b(json.dumps({"a": 1, "b": 2})) elif '/resource/' in req.path_info: content_type = 'application/json' - body = json.dumps(dict(id=1, value='value')) + body = six.b(json.dumps(dict(id=1, value='value'))) resp = Response(body, content_type=content_type) return resp(environ, start_response) diff -Nru webtest-2.0.23/docs/testapp.rst webtest-2.0.28/docs/testapp.rst --- webtest-2.0.23/docs/testapp.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/testapp.rst 2017-08-01 15:29:14.000000000 +0000 @@ -10,10 +10,10 @@ app.get('/path', [params], [headers], [extra_environ], ...) -This call to :meth:`~webtest.TestApp.get` does a request for +This call to :meth:`~webtest.app.TestApp.get` does a request for ``/path``, with any params, extra headers or WSGI environment keys that you indicate. This returns a -:class:`~webtest.TestResponse` object, +:class:`~webtest.response.TestResponse` object, based on :class:`webob.response.Response`. It has some additional methods to make it easier to test. @@ -24,7 +24,7 @@ app.post('/path', {'vars': 'values'}, [headers], [extra_environ], [upload_files], ...) -Specifically the second argument of :meth:`~webtest.TestApp.post` +Specifically the second argument of :meth:`~webtest.app.TestApp.post` is the *body* of the request. You can pass in a dictionary (or dictionary-like object), or a string body (dictionary objects are turned into HTML form submissions). @@ -33,8 +33,8 @@ list of ``[(fieldname, filename, field_content)]``. File uploads use a different form submission data type to pass the structured data. -You can use :meth:`~webtest.TestApp.put` and -:meth:`~webtest.TestApp.delete` for PUT and DELETE requests. +You can use :meth:`~webtest.app.TestApp.put` and +:meth:`~webtest.app.TestApp.delete` for PUT and DELETE requests. Making JSON Requests @@ -45,10 +45,10 @@ The ``*_json`` methods will transform data to json before ``POST``/``PUT`` and add the correct ``Content-Type`` for you. -Also Response have an attribute ``.json`` to allow you to retrieve json +Also Response have an attribute :attr:`~webtest.response.TestResponse.json` to allow you to retrieve json contents as a python dict. -Doing *POST* request with :meth:`webtest.TestApp.post_json`: +Doing *POST* request with :meth:`webtest.app.TestApp.post_json`: .. code-block:: python @@ -63,7 +63,7 @@ True -Doing *GET* request with :meth:`webtest.TestApp.get` and using :attr:`webtest.response.json`: +Doing *GET* request with :meth:`webtest.app.TestApp.get` and using :attr:`~webtest.response.TestResponse.json`: To just parse body of the response, use Response.json: diff -Nru webtest-2.0.23/docs/testresponse_fixt.py webtest-2.0.28/docs/testresponse_fixt.py --- webtest-2.0.23/docs/testresponse_fixt.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/testresponse_fixt.py 2017-08-01 15:29:14.000000000 +0000 @@ -20,7 +20,7 @@ body = six.b('hey!') elif req.path_info.endswith('.json'): content_type = 'application/json' - body = json.dumps({"a": 1, "b": 2}) + body = six.b(json.dumps({"a": 1, "b": 2})) resp = Response(body, content_type=content_type) return resp(environ, start_response) diff -Nru webtest-2.0.23/docs/testresponse.rst webtest-2.0.28/docs/testresponse.rst --- webtest-2.0.23/docs/testresponse.rst 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/docs/testresponse.rst 2017-08-01 15:29:14.000000000 +0000 @@ -33,13 +33,13 @@ ``response.follow(**kw)``: Follows the redirect, returning the new response. It is an error if this response wasn't a redirect. All keyword arguments are - passed to :class:`webtest.TestApp` (e.g., ``status``). Returns + passed to :class:`webtest.app.TestApp` (e.g., ``status``). Returns another response object. ``response.maybe_follow(**kw)``: Follows all redirects; does nothing if this response is not a redirect. All keyword arguments are passed - to :class:`webtest.TestApp` (e.g., ``status``). Returns another + to :class:`webtest.app.TestApp` (e.g., ``status``). Returns another response object. ``x in response``: @@ -62,7 +62,7 @@ `doctest `_ ``response.click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False)``: - Clicks the described link (see :class:`~webtest.TestResponse.click`) + Clicks the described link (see :meth:`~webtest.response.TestResponse.click`) ``response.forms``: Return a dictionary of forms; you can use both indexes (refer to diff -Nru webtest-2.0.23/PKG-INFO webtest-2.0.28/PKG-INFO --- webtest-2.0.23/PKG-INFO 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/PKG-INFO 2017-08-01 15:29:15.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: WebTest -Version: 2.0.23 +Version: 2.0.28 Summary: Helper to test WSGI applications Home-page: http://webtest.pythonpaste.org/ Author: Gael Pasgrimaud @@ -27,9 +27,9 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 diff -Nru webtest-2.0.23/RELEASING.rst webtest-2.0.28/RELEASING.rst --- webtest-2.0.23/RELEASING.rst 1970-01-01 00:00:00.000000000 +0000 +++ webtest-2.0.28/RELEASING.rst 2017-08-01 15:29:14.000000000 +0000 @@ -0,0 +1,70 @@ +Releasing WebTest +================= + +- For clarity, we define releases as follows. + + - Alpha, beta, dev and similar statuses do not qualify whether a release is + major or minor. The term "pre-release" means alpha, beta, or dev. + + - A release is final when it is no longer pre-release. + + - A *major* release is where the first number either before or after the + first dot increases. Examples: 1.6 to 1.7a1, or 1.8 to 2.0. + + - A *minor* or *bug fix* release is where the number after the second dot + increases. Example: 1.6 to 1.6.1. + + +Releasing with zest.releaser +---------------------------- + +- First install `zest.releaser `_:: + + $ pip install zest.releaser[recommanded] + +- Add this to your ``~/.pypirc``:: + + [zest.releaser] + no-input = true + create-wheel = yes + +- Edit ``CHANGELOG.rst`` + +- Run the fullrelease script:: + + $ fullrelease + +Marketing and communications +---------------------------- + +- Announce to Twitter:: + + WebTest 1.x released. + + PyPI + https://pypi.python.org/pypi/webtest/2.x + + Changes + http://docs.pylonsproject.org/projects/webtest + + Issues + https://github.com/Pylons/webtest/issues + +- Announce to maillist:: + + WebTest 2.X.X has been released. + + Here are the changes: + + <> + + You can install it via PyPI: + + pip install WebTest==2.X + + Enjoy, and please report any issues you find to the issue tracker at + https://github.com/Pylons/webtest/issues + + Thanks! + + - WebTest developers diff -Nru webtest-2.0.23/setup.cfg webtest-2.0.28/setup.cfg --- webtest-2.0.23/setup.cfg 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/setup.cfg 2017-08-01 15:29:15.000000000 +0000 @@ -11,7 +11,7 @@ doctest-extension = rst doctest-fixtures = _fixt include = docs -exclude = (CHANGELOG|contributing).rst +exclude = (CHANGELOG|changelog|contributing).rst cover-package = webtest doctest-options = +ELLIPSIS,+NORMALIZE_WHITESPACE diff -Nru webtest-2.0.23/setup.py webtest-2.0.28/setup.py --- webtest-2.0.23/setup.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/setup.py 2017-08-01 15:29:14.000000000 +0000 @@ -5,7 +5,7 @@ from setuptools import setup from setuptools import find_packages -version = '2.0.23' +version = '2.0.28' install_requires = [ 'six', @@ -19,14 +19,6 @@ 'PasteDeploy', 'WSGIProxy2', 'pyquery' ] -if sys.version_info[0:2] < (2, 7): - install_requires.append('ordereddict') - -if sys.version_info[0:2] <= (2, 7): - tests_require.append('unittest2') -else: - tests_require.append('unittest2py3k') - setup(name='WebTest', version=version, @@ -40,12 +32,12 @@ "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", ], keywords='wsgi test unit tests web', author='Ian Bicking', @@ -67,9 +59,7 @@ test_suite='nose.collector', tests_require=tests_require, extras_require={ - ':python_version=="2.6"': ['ordereddict'], 'tests': tests_require, - 'tests:python_version=="2.6"': ['unittest2'], }, entry_points=""" [paste.app_factory] diff -Nru webtest-2.0.23/tests/compat.py webtest-2.0.28/tests/compat.py --- webtest-2.0.23/tests/compat.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tests/compat.py 2017-08-01 15:29:14.000000000 +0000 @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- -try: - # py < 2.7 - import unittest2 as unittest -except ImportError: - import unittest # noqa +import unittest # noqa try: unicode() diff -Nru webtest-2.0.23/tests/test_app.py webtest-2.0.28/tests/test_app.py --- webtest-2.0.23/tests/test_app.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tests/test_app.py 2017-08-01 15:29:14.000000000 +0000 @@ -4,7 +4,7 @@ from webob import Response from webtest.compat import to_bytes from webtest.compat import PY3 -from webtest.compat import OrderedDict +from collections import OrderedDict from webtest.debugapp import debug_app from webtest import http from tests.compat import unittest @@ -199,6 +199,13 @@ app.get('/') app.reset() + app = webtest.TestApp(cookie_app, + extra_environ={'HTTP_HOST': 'testserver'}) + app.set_cookie('foo', 'bar') + app.set_cookie('fizz', ';bar=baz') # Make sure we're escaping. + app.get('/') + app.reset() + def test_preserves_cookies(self): def cookie_app(environ, start_response): req = Request(environ) @@ -350,6 +357,39 @@ self.assertEqual(res.request.environ['HTTP_COOKIE'], 'spam=eggs') self.assertEqual(dict(res.request.cookies), {'spam': 'eggs'}) + def test_cookie_policy(self): + from six.moves import http_cookiejar + + def cookie_app(environ, start_response): + status = to_bytes("200 OK") + body = 'Cookie.' + headers = [ + ('Content-Type', 'text/plain'), + ('Content-Length', str(len(body))), + ('Set-Cookie', + 'spam=eggs; secure; Domain=.example.org;'), + ] + start_response(status, headers) + return [to_bytes(body)] + + policy = webtest.app.CookiePolicy() + flags = ( + policy.DomainStrictNoDots | + policy.DomainRFC2965Match | + policy.DomainStrictNonDomain) + policy.strict_ns_domain |= flags + cookiejar = http_cookiejar.CookieJar(policy=policy) + app = webtest.TestApp( + cookie_app, + cookiejar=cookiejar, + extra_environ={'HTTP_HOST': 'example.org'}) + res = app.get('/') + res = app.get('/') + self.assertFalse(app.cookies, + 'Response should not have set cookies') + self.assertNotIn('HTTP_COOKIE', res.request.environ) + self.assertEqual(dict(res.request.cookies), {}) + class TestEnviron(unittest.TestCase): diff -Nru webtest-2.0.23/tests/test_forms.py webtest-2.0.28/tests/test_forms.py --- webtest-2.0.23/tests/test_forms.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tests/test_forms.py 2017-08-01 15:29:14.000000000 +0000 @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import cgi import os.path import struct import sys @@ -320,6 +321,8 @@ headers = [ ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(status, headers) return [body] @@ -372,6 +375,8 @@ headers = [ ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(status, headers) return [body] @@ -424,6 +429,8 @@ headers = [ ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(status, headers) return [body] @@ -475,6 +482,8 @@ headers = [ ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(status, headers) if not isinstance(body, binary_type): raise AssertionError('Body is not %s' % binary_type) @@ -735,6 +744,7 @@ display = multiple_form.submit("button") self.assertIn("

You selected

", display, display) + class SingleUploadFileApp(object): body = b""" @@ -744,6 +754,7 @@
+
@@ -767,6 +778,8 @@ headers = [ ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(status, headers) assert(isinstance(body, binary_type)) return [body] @@ -776,9 +789,12 @@ uploaded_files = [(k, v) for k, v in req.POST.items() if 'file' in k] uploaded_files = sorted(uploaded_files) for name, uploaded_file in uploaded_files: - filename = to_bytes(uploaded_file.filename) - value = to_bytes(uploaded_file.value, 'ascii') - content_type = to_bytes(uploaded_file.type, 'ascii') + if isinstance(uploaded_file, cgi.FieldStorage): + filename = to_bytes(uploaded_file.filename) + value = to_bytes(uploaded_file.value, 'ascii') + content_type = to_bytes(uploaded_file.type, 'ascii') + else: + filename = value = content_type = b'' file_parts.append(b"""

You selected '""" + filename + b"""'

with contents: '""" + value + b"""'

@@ -936,6 +952,25 @@ self.assertFile(uploaded_file1_name, uploaded_file1_contents, display) self.assertFile(uploaded_file1_name, uploaded_file1_contents, display) + def test_post_int(self): + binary_data = struct.pack(str('255h'), *range(0, 255)) + app = webtest.TestApp(SingleUploadFileApp()) + res = app.get('/') + single_form = res.forms["file_upload_form"] + single_form.set("file-field", ('my_file.dat', binary_data)) + single_form.set("int-field", 100) + # just check it does not raise + single_form.submit("button") + + def test_invalid_types(self): + binary_data = struct.pack(str('255h'), *range(0, 255)) + app = webtest.TestApp(SingleUploadFileApp()) + res = app.get('/') + single_form = res.forms["file_upload_form"] + single_form.set("file-field", ('my_file.dat', binary_data)) + single_form.set("int-field", SingleUploadFileApp()) + self.assertRaises(ValueError, single_form.submit, "button") + def test_upload_invalid_content(self): app = webtest.TestApp(SingleUploadFileApp()) res = app.get('/') diff -Nru webtest-2.0.23/tests/test_lint.py webtest-2.0.28/tests/test_lint.py --- webtest-2.0.23/tests/test_lint.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tests/test_lint.py 2017-08-01 15:29:14.000000000 +0000 @@ -114,6 +114,8 @@ headers = [ ('Content-Type', 'text/plain; charset=utf-8'), ('Content-Length', str(len(body)))] + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(to_bytes('200 OK'), headers, ('stuff',)) return [body] @@ -141,13 +143,33 @@ class TestCheckHeaders(unittest.TestCase): - @unittest.skipIf(not PY3, 'Useless in Python2') + @unittest.skipIf(PY3, 'unicode is str in Python3') + def test_header_unicode_name(self): + headers = [(u'X-Price', str('100'))] + self.assertRaises(AssertionError, check_headers, headers) + + @unittest.skipIf(PY3, 'unicode is str in Python3') def test_header_unicode_value(self): - self.assertRaises(AssertionError, check_headers, [('X-Price', '100€')]) + headers = [(str('X-Price'), u'100')] + self.assertRaises(AssertionError, check_headers, headers) - @unittest.skipIf(not PY3, 'Useless in Python2') - def test_header_unicode_name(self): - self.assertRaises(AssertionError, check_headers, [('X-€', 'foo')]) + @unittest.skipIf(not PY3, 'bytes is str in Python2') + def test_header_bytes_name(self): + headers = [(b'X-Price', '100')] + self.assertRaises(AssertionError, check_headers, headers) + + @unittest.skipIf(not PY3, 'bytes is str in Python2') + def test_header_bytes_value(self): + headers = [('X-Price', b'100')] + self.assertRaises(AssertionError, check_headers, headers) + + def test_header_non_latin1_value(self): + headers = [(str('X-Price'), '100€')] + self.assertRaises(AssertionError, check_headers, headers) + + def test_header_non_latin1_name(self): + headers = [('X-€', str('foo'))] + self.assertRaises(AssertionError, check_headers, headers) class TestCheckEnviron(unittest.TestCase): diff -Nru webtest-2.0.23/tests/test_response.py webtest-2.0.28/tests/test_response.py --- webtest-2.0.23/tests/test_response.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tests/test_response.py 2017-08-01 15:29:14.000000000 +0000 @@ -115,7 +115,8 @@ ] if req.path_info in utf8_paths: headers[0] = ('Content-Type', str('text/html; charset=utf-8')) - + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(str(status), headers) return [body] @@ -127,7 +128,8 @@ ('Content-Type', str('text/html')), ('Content-Encoding', str('gzip')), ] - + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(str(status), headers) return encoded_body @@ -423,6 +425,8 @@ remaining_redirects[0] -= 1 headers.append(('Content-Length', str(len(body)))) + # PEP 3333 requires native strings: + headers = [(str(k), str(v)) for k, v in headers] start_response(str(status), headers) return [body] diff -Nru webtest-2.0.23/tox.ini webtest-2.0.28/tox.ini --- webtest-2.0.23/tox.ini 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/tox.ini 2017-08-01 15:29:14.000000000 +0000 @@ -1,6 +1,6 @@ [tox] skip_missing_interpreters=true -envlist=py26,py27,py33,py34,py35,coverage +envlist=py27,py33,py34,py35,py36,coverage [testenv] setenv = @@ -9,7 +9,7 @@ COVERAGE_FILE=.coverage.{envname} deps = nose<1.3.0 - webtest[tests] + .[tests] lxml pyquery mock @@ -19,9 +19,9 @@ commands = {envbindir}/python --version {envbindir}/pip freeze - py{26,27,33}: {envbindir}/nosetests --with-coverage --with-xunit --xunit-file=nosetests-{envname}.xml [] - py{26,27,33}: {envbindir}/coverage xml -o coverage-{envname}.xml - py{34,35}: {envbindir}/nosetests + py{27,33}: {envbindir}/nosetests --with-coverage --with-xunit --xunit-file=nosetests-{envname}.xml [] + py{27,33}: {envbindir}/coverage xml -o coverage-{envname}.xml + py{34,35,36}: {envbindir}/nosetests [testenv:coverage] deps = diff -Nru webtest-2.0.23/webtest/app.py webtest-2.0.28/webtest/app.py --- webtest-2.0.23/webtest/app.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/webtest/app.py 2017-08-01 15:29:14.000000000 +0000 @@ -11,7 +11,6 @@ import os import re -import sys import json import random import fnmatch @@ -196,7 +195,8 @@ self.authorization_value = value if value is not None: invalid_value = ( - "You should use a value like ('Basic', ('user', 'password')) OR ('Bearer', 'token')" + "You should use a value like ('Basic', ('user', 'password'))" + " OR ('Bearer', 'token') OR ('JWT', 'token')" ) if isinstance(value, (list, tuple)) and len(value) == 2: authtype, val = value @@ -205,7 +205,7 @@ val = ':'.join(list(val)) val = b64encode(to_bytes(val)).strip() val = val.decode('latin1') - elif authtype == 'Bearer' and val and \ + elif authtype in ('Bearer', 'JWT') and val and \ isinstance(val, (str, text_type)): val = val.strip() else: @@ -231,6 +231,10 @@ Sets a cookie to be passed through with requests. """ + cookie_domain = self.extra_environ.get('HTTP_HOST', '.localhost') + cookie_domain = cookie_domain.split(':', 1)[0] + if '.' not in cookie_domain: + cookie_domain = "%s.local" % cookie_domain value = escape_cookie_value(value) cookie = http_cookiejar.Cookie( version=0, @@ -238,7 +242,7 @@ value=value, port=None, port_specified=False, - domain='.localhost', + domain=cookie_domain, domain_specified=True, domain_initial_dot=False, path='/', @@ -503,6 +507,10 @@ if isinstance(value, forms.File): if value.value: _append_file([key] + list(value.value)) + else: + # If no file was uploaded simulate an empty file with no + # name like real browsers do: + _append_file([key, b'', b'']) elif isinstance(value, forms.Upload): file_info = [key, value.filename] if value.content is not None: @@ -511,8 +519,15 @@ file_info.append(value.content_type) _append_file(file_info) else: - if isinstance(value, text_type): + if isinstance(value, int): + value = str(value).encode('utf8') + elif isinstance(value, text_type): value = value.encode('utf8') + elif not isinstance(value, (bytes, str)): + raise ValueError(( + 'Value for field {0} is a {1} ({2}). ' + 'It must be str, bytes or an int' + ).format(key, type(value), value)) lines.extend([ b'--' + boundary, b'Content-Disposition: form-data; name="' + key + b'"', @@ -606,7 +621,7 @@ # verify wsgi compatibility app = lint.middleware(self.app) if self.lint else self.app - ## FIXME: should it be an option to not catch exc_info? + # FIXME: should it be an option to not catch exc_info? res = req.get_response(app, catch_exc_info=True) # be sure to decode the content @@ -618,7 +633,7 @@ res.app = app res.test_app = self - # We do this to make sure the app_iter is exausted: + # We do this to make sure the app_iter is exhausted: try: res.body except TypeError: # pragma: no cover @@ -668,7 +683,7 @@ res) if status != res.status_int: raise AppError( - "Bad response: %s (not %s)", res_status, status) + "Bad response: %s (not %s)\n%s", res_status, status, res) def _check_errors(self, res): errors = res.errors diff -Nru webtest-2.0.23/webtest/compat.py webtest-2.0.28/webtest/compat.py --- webtest-2.0.23/webtest/compat.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/webtest/compat.py 2017-08-01 15:29:14.000000000 +0000 @@ -35,11 +35,6 @@ value = value.encode('utf8') six.print_(value, file=sys.stderr) -try: - from collections import OrderedDict -except ImportError: # pragma: no cover - from ordereddict import OrderedDict # noqa - def escape_cookie_value(value): """ diff -Nru webtest-2.0.23/webtest/forms.py webtest-2.0.28/webtest/forms.py --- webtest-2.0.23/webtest/forms.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/webtest/forms.py 2017-08-01 15:29:14.000000000 +0000 @@ -5,7 +5,7 @@ import sys from bs4 import BeautifulSoup -from webtest.compat import OrderedDict +from collections import OrderedDict from webtest import utils @@ -400,11 +400,7 @@ def __init__(self, response, text, parser_features='html.parser'): self.response = response self.text = text - if response and response.charset: - self.html = BeautifulSoup(self.text, parser_features, - from_encoding=response.charset) - else: - self.html = BeautifulSoup(self.text, parser_features) + self.html = BeautifulSoup(self.text, parser_features) attrs = self.html('form')[0].attrs self.action = attrs.get('action', '') diff -Nru webtest-2.0.23/webtest/lint.py webtest-2.0.28/webtest/lint.py --- webtest-2.0.23/webtest/lint.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/webtest/lint.py 2017-08-01 15:29:14.000000000 +0000 @@ -59,7 +59,8 @@ * That the headers is a list (not a subclass, not another kind of sequence). -* That the items of the headers are tuples of strings. +* That the items of the headers are tuples of 'native' strings (i.e. + bytestrings in Python2, and unicode strings in Python3). * That there is no 'status' header (that is used in CGI, but not in WSGI). @@ -412,12 +413,12 @@ "digits and a space (4th characters is not a space here)") % status -def _assert_latin1_py3(string, message): - if PY3 and type(string) is str: - try: - string.encode('latin1') - except UnicodeEncodeError: - raise AssertionError(message) +def _assert_latin1_str(string, message): + assert type(string) is str, message + try: + string.encode('latin1') + except UnicodeEncodeError: + raise AssertionError(message) def check_headers(headers): @@ -430,30 +431,30 @@ % (item, type(item))) assert len(item) == 2 name, value = item - _assert_latin1_py3( + _assert_latin1_str( name, - "Headers values must be latin1 string or bytes." - "%r is not a valid latin1 string" % (value,) + "Header names must be latin1 string " + "(not Py2 unicode or Py3 bytes type). " + "%r is not a valid latin1 string" % (name,) ) - str_name = to_string(name) - assert str_name.lower() != 'status', ( + assert name.lower() != 'status', ( "The Status header cannot be used; it conflicts with CGI " "script, and HTTP status is not given through headers " "(value: %r)." % value) - assert '\n' not in str_name and ':' not in str_name, ( + assert '\n' not in name and ':' not in name, ( "Header names may not contain ':' or '\\n': %r" % name) - assert header_re.search(str_name), "Bad header name: %r" % name - assert not str_name.endswith('-') and not str_name.endswith('_'), ( + assert header_re.search(name), "Bad header name: %r" % name + assert not name.endswith('-') and not name.endswith('_'), ( "Names may not end in '-' or '_': %r" % name) - _assert_latin1_py3( + _assert_latin1_str( value, - "Headers values must be latin1 string or bytes." + "Header values must be latin1 string " + "(not Py2 unicode or Py3 bytes type)." "%r is not a valid latin1 string" % (value,) ) - str_value = to_string(value) - assert not bad_header_value_re.search(str_value), ( + assert not bad_header_value_re.search(value), ( "Bad header value: %r (bad char: %r)" - % (str_value, bad_header_value_re.search(str_value).group(0))) + % (str_value, bad_header_value_re.search(value).group(0))) def check_content_type(status, headers): @@ -464,13 +465,11 @@ NO_MESSAGE_TYPE = (204, 304) length = None for name, value in headers: - str_name = to_string(name) - if str_name.lower() == 'content-length' and value.isdigit(): + if name.lower() == 'content-length' and value.isdigit(): length = int(value) break for name, value in headers: - str_name = to_string(name) - if str_name.lower() == 'content-type': + if name.lower() == 'content-type': if code not in NO_MESSAGE_TYPE: return elif length == 0: diff -Nru webtest-2.0.23/webtest/response.py webtest-2.0.28/webtest/response.py --- webtest-2.0.23/webtest/response.py 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/webtest/response.py 2017-08-01 15:29:14.000000000 +0000 @@ -74,7 +74,7 @@ def _parse_forms(self): forms_ = self._forms_indexed = {} - form_texts = [str(f) for f in self.html('form')] + form_texts = [text_type(f) for f in self.html('form')] for i, text in enumerate(form_texts): form = forms.Form(self, text, self.parser_features) forms_[i] = form @@ -426,11 +426,7 @@ raise AttributeError( "Not an HTML response body (content-type: %s)" % self.content_type) - if self.charset: - soup = BeautifulSoup(self.testbody, self.parser_features, - from_encoding=self.charset) - else: - soup = BeautifulSoup(self.testbody, self.parser_features) + soup = BeautifulSoup(self.testbody, self.parser_features) return soup @property @@ -497,17 +493,14 @@ @property def json(self): """ - Return the response as a JSON response. You must have `simplejson - `_ installed to use this, or be using a Python - version with the json module. - + Return the response as a JSON response. The content type must be one of json type to use this. """ if not self.content_type.endswith(('+json', '/json')): raise AttributeError( "Not a JSON response body (content-type: %s)" % self.content_type) - return loads(self.testbody) + return self.json_body @property def pyquery(self): diff -Nru webtest-2.0.23/WebTest.egg-info/PKG-INFO webtest-2.0.28/WebTest.egg-info/PKG-INFO --- webtest-2.0.23/WebTest.egg-info/PKG-INFO 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/WebTest.egg-info/PKG-INFO 2017-08-01 15:29:15.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: WebTest -Version: 2.0.23 +Version: 2.0.28 Summary: Helper to test WSGI applications Home-page: http://webtest.pythonpaste.org/ Author: Gael Pasgrimaud @@ -27,9 +27,9 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 diff -Nru webtest-2.0.23/WebTest.egg-info/requires.txt webtest-2.0.28/WebTest.egg-info/requires.txt --- webtest-2.0.23/WebTest.egg-info/requires.txt 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/WebTest.egg-info/requires.txt 2017-08-01 15:29:15.000000000 +0000 @@ -3,9 +3,6 @@ waitress>=0.8.5 beautifulsoup4 -[:python_version=="2.6"] -ordereddict - [tests] nose<1.3.0 coverage @@ -13,7 +10,3 @@ PasteDeploy WSGIProxy2 pyquery -unittest2py3k - -[tests:python_version=="2.6"] -unittest2 diff -Nru webtest-2.0.23/WebTest.egg-info/SOURCES.txt webtest-2.0.28/WebTest.egg-info/SOURCES.txt --- webtest-2.0.23/WebTest.egg-info/SOURCES.txt 2016-07-21 09:04:01.000000000 +0000 +++ webtest-2.0.28/WebTest.egg-info/SOURCES.txt 2017-08-01 15:29:15.000000000 +0000 @@ -2,6 +2,7 @@ CHANGELOG.rst MANIFEST.in README.rst +RELEASING.rst setup.cfg setup.py tox.ini