Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pytest-httpserver for
openSUSE:Factory checked in at 2023-05-24 20:21:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pytest-httpserver (Old)
and /work/SRC/openSUSE:Factory/.python-pytest-httpserver.new.1533 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-httpserver"
Wed May 24 20:21:39 2023 rev:11 rq:1088466 version:1.0.7
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-pytest-httpserver/python-pytest-httpserver.changes
2023-05-09 13:06:32.352735776 +0200
+++
/work/SRC/openSUSE:Factory/.python-pytest-httpserver.new.1533/python-pytest-httpserver.changes
2023-05-24 20:21:47.471975183 +0200
@@ -1,0 +2,25 @@
+Mon May 22 21:17:53 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 1.0.7:
+ * With werkzeug 2.3.x the headers type has been updated to not
+ allow integers as header values. This restriction followed up
+ in pytest-httpserver.
+ * Python versions earlier than 3.8 have been deprecated in
+ order to support the latest werkzeug.
+ * Type hinting for header_value_matcher has been fixed. From
+ now, specifying a callable as ``Callable[[str,
+ Optional[str], str], bool]`` will be accepted also.
+ Providing a ``HeaderValueMatcher`` object will be also
+ accepted as before, as it provides the same callable signature
+ * Fix Werkzeug deprecation warning about
+ ``parse_authorization_header`` call.
+ * Replace ``parse_authorization_header`` with
+ ``Authorization.from_header`` as suggested. This fix should
+ not introduce any functional change for the users.
+ * Fix Werkzeug deprecation warning about
+ ``werkzeug.urls.url_decode`` call. This call has been changed
+ to ``urllib.parse.parse_qsl`` in the implementation.
+ This fix should not introduce any functional change for the
+ users.
+
+-------------------------------------------------------------------
Old:
----
pytest_httpserver-1.0.6.tar.gz
New:
----
pytest_httpserver-1.0.7.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pytest-httpserver.spec ++++++
--- /var/tmp/diff_new_pack.NxByS2/_old 2023-05-24 20:21:48.435980931 +0200
+++ /var/tmp/diff_new_pack.NxByS2/_new 2023-05-24 20:21:48.443980979 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-pytest-httpserver
-Version: 1.0.6
+Version: 1.0.7
Release: 0
Summary: A HTTP server for pytest
License: MIT
++++++ pytest_httpserver-1.0.6.tar.gz -> pytest_httpserver-1.0.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/CHANGES.rst
new/pytest_httpserver-1.0.7/CHANGES.rst
--- old/pytest_httpserver-1.0.6/CHANGES.rst 2022-09-12 09:25:14.312314500
+0200
+++ new/pytest_httpserver-1.0.7/CHANGES.rst 2023-05-16 21:40:52.284019700
+0200
@@ -2,6 +2,50 @@
Release Notes
=============
+.. _Release Notes_1.0.7:
+
+1.0.7
+=====
+
+.. _Release Notes_1.0.7_Upgrade Notes:
+
+Upgrade Notes
+-------------
+
+- With werkzeug 2.3.x the headers type has been updated to not allow integers
as header values. This restriction followed up in pytest-httpserver.
+
+
+.. _Release Notes_1.0.7_Deprecation Notes:
+
+Deprecation Notes
+-----------------
+
+- Python versions earlier than 3.8 have been deprecated in order to support
+ the latest werkzeug. Users using 3.7 or earlier python may use
+ pytest-httpserver with earlier werkzeug versions but tests are no longer run
+ for these python versions.
+
+
+.. _Release Notes_1.0.7_Bug Fixes:
+
+Bug Fixes
+---------
+
+- Type hinting for header_value_matcher has been fixed. From now, specifying a
+ callable as ``Callable[[str, Optional[str], str], bool]`` will be accepted
+ also. Providing a ``HeaderValueMatcher`` object will be also accepted as
+ before, as it provides the same callable signature.
+
+- Fix Werkzeug deprecation warning about ``parse_authorization_header`` call.
+ Replace ``parse_authorization_header`` with ``Authorization.from_header`` as
+ suggested. This fix should not introduce any functional change for the
+ users.
+
+- Fix Werkzeug deprecation warning about ``werkzeug.urls.url_decode`` call.
This
+ call has been changed to ``urllib.parse.parse_qsl`` in the implementation.
+ This fix should not introduce any functional change for the users.
+
+
.. _Release Notes_1.0.6:
1.0.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/CONTRIBUTION.md
new/pytest_httpserver-1.0.7/CONTRIBUTION.md
--- old/pytest_httpserver-1.0.6/CONTRIBUTION.md 2022-09-03 10:53:55.022256900
+0200
+++ new/pytest_httpserver-1.0.7/CONTRIBUTION.md 2023-05-16 20:58:37.762110000
+0200
@@ -20,8 +20,9 @@
There are a few rules you are kindly asked to accept:
-* Coding style is checked by `pre-commit`. You can run `make precommit` before
proceeding
- with the PR.
+* Coding style is checked by `pre-commit`. You can run `make precommit` before
+ proceeding with the PR. To install the pre-commit hooks to your git (so it
+ will be run for each commit), run `pre-commit install`.
* Tests should be written for the new code. If there's a complex logic
implemented, it should be tested on different valid and invalid inputs and
@@ -41,11 +42,15 @@
* You can let your IDE of your choice to use the `.venv/bin/python`
interpreter,
so it will know all the dependencies.
-* running tests on the localhost can be done by issuing `make quick-test`. It
is
- "quick" because it tests the software with only one interpreter. Note that
the
+* running tests on the localhost can be done by issuing `make test`. Note that
the
library can be used by many supported interpreters and unless it is
absolutely
required, we don't want to drop support.
+* running tests on multiple versions of interpreter locally can be done by
+ `tox`. Keep in mind that the CI job uses github actions with caching for
+ effective use, and `tox` is provided for the developers only.
+
+
## More technical details
* Release notes must be written for significant changes. This is done by
@@ -56,3 +61,12 @@
docstrings, but if the PR changes the code and the way of working
conceptually, the main documentation (located in the doc directory) needs to
be updated and extended.
+
+* nix files are provided on a best-effort basis. `tox.nix` can be used to run
+ `tox`, `shell.nix` can be used instead of poetry for development. No tests
+ have been written for these (yet!), so they may be out of sync occasionally.
+
+* to release a new version, you can use the `scripts/release.py` script to make
+ the wheels and sdist, generate the changelog, and tag the commit. This tool
+ won't upload the artifacts as they need to be checked manually (by installing
+ the wheel to a new venv, for example).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/PKG-INFO
new/pytest_httpserver-1.0.7/PKG-INFO
--- old/pytest_httpserver-1.0.6/PKG-INFO 1970-01-01 01:00:00.000000000
+0100
+++ new/pytest_httpserver-1.0.7/PKG-INFO 1970-01-01 01:00:00.000000000
+0100
@@ -1,22 +1,22 @@
Metadata-Version: 2.1
Name: pytest-httpserver
-Version: 1.0.6
+Version: 1.0.7
Summary: pytest-httpserver is a httpserver for pytest
Home-page: https://github.com/csernazs/pytest-httpserver
License: MIT
Author: Zsolt Cserna
Author-email: [email protected]
-Requires-Python: >=3.7,<4.0
+Requires-Python: >=3.8,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: Werkzeug (>=2.0.0)
Project-URL: Bug Tracker, https://github.com/csernazs/pytest-httpserver/issues
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/doc/background.rst
new/pytest_httpserver-1.0.7/doc/background.rst
--- old/pytest_httpserver-1.0.6/doc/background.rst 2022-07-13
18:49:54.026996000 +0200
+++ new/pytest_httpserver-1.0.7/doc/background.rst 2023-05-16
21:40:52.284019700 +0200
@@ -40,24 +40,15 @@
Example:
-.. code-block:: python
-
- def test_query_params(httpserver):
- httpserver.expect_request("/foo", query_string={"user":
"user1"}).respond_with_data(
- "OK"
- )
+.. literalinclude :: ../tests/examples/test_example_query_params1.py
+ :language: python
It is simple in the most simple cases, but once the expectation is more
specific, the line can grow significantly, so here the user is expected to put
the literals into variables:
-.. code-block:: python
-
- def test_query_params(httpserver):
- httpserver.expect_request("/foo",
query_string=expected_query).respond_with_data(
- "OK"
- )
-
+.. literalinclude :: ../tests/examples/test_example_query_params2.py
+ :language: python
If the user wants something more complex, classes are available for this which
can be instantiated and then specified for the parameters normally accepting
@@ -176,12 +167,11 @@
The library should be tested periodically on the supported versions.
Dropping support for old python versions is possible if supporting would cause
-an issue or require extensive workaround. Currently, 3.4 is still supported by
-the library, however it is deprecated by PSF. As it causes no problems for
-*pytest-httpserver* (there's an additional requirement for this in the
setup.py,
-but that's all), the support for this version will be maintained as long as
-possible. Once a new change is added to the library which require great effort
-to maintain compatibility with 3.4, the support for it will be dropped.
+an issue or require extensive workaround.
+
+Python support for a given version is also dropped if it is near to the end of
+support or when a dependency deprecates it - this is needed to move forward
with
+the community in order to support the latest versions of the dependencies.
Testing and coverage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/doc/conf.py
new/pytest_httpserver-1.0.7/doc/conf.py
--- old/pytest_httpserver-1.0.6/doc/conf.py 2022-09-12 09:25:14.313314400
+0200
+++ new/pytest_httpserver-1.0.7/doc/conf.py 2023-05-16 21:40:52.284019700
+0200
@@ -66,7 +66,7 @@
# built documents.
#
# The short X.Y version.
-version = "1.0.6"
+version = "1.0.7"
# The full version, including alpha/beta/rc tags.
release = version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/doc/howto.rst
new/pytest_httpserver-1.0.7/doc/howto.rst
--- old/pytest_httpserver-1.0.6/doc/howto.rst 2022-09-12 08:46:31.629976300
+0200
+++ new/pytest_httpserver-1.0.7/doc/howto.rst 2023-05-16 20:58:37.763110000
+0200
@@ -15,17 +15,13 @@
To match query parameters, you must not included them to the URI, as this will
not work:
-.. code-block:: python
-
- def test_query_params(httpserver):
- httpserver.expect_request("/foo?user=bar") # never do this
+.. literalinclude :: ../tests/examples/test_howto_query_params_never_do_this.py
+ :language: python
There's an explicit place where the query string should go:
-.. code-block:: python
-
- def test_query_params(httpserver):
- httpserver.expect_request("/foo", query_string="user=bar")
+.. literalinclude :: ../tests/examples/test_howto_query_params_proper_use.py
+ :language: python
The ``query_string`` is the parameter which does not contain the leading
question mark ``?``.
@@ -43,15 +39,9 @@
strange but we wanted to keep API compatibility and this dict matching feature
was added later).
-.. code-block:: python
-
- def test_query_params(httpserver):
- httpserver.expect_request(
- "/foo", query_string={"user": "user1", "group": "group1"}
- ).respond_with_data("OK")
- assert requests.get("/foo?user=user1&group=group1").status_code == 200
- assert requests.get("/foo?group=group1&user=user1").status_code == 200
+.. literalinclude :: ../tests/examples/test_howto_query_params_dict.py
+ :language: python
In the example above, both requests pass the test as we specified the expected
query string as a dictionary.
@@ -70,9 +60,8 @@
If this is not desired, you can specify a regexp object (returned by the
``re.compile()`` call).
-.. code:: python
-
- httpserver.expect_request(re.compile("^/foo"), method="GET")
+.. literalinclude :: ../tests/examples/test_howto_regexp.py
+ :language: python
The above will match every URI starting with "/foo".
@@ -82,18 +71,9 @@
string and should return a boolean value.
-.. code:: python
-
- class PrefixMatch(URIPattern):
- def __init__(self, prefix: str):
- self.prefix = prefix
-
- def match(self, uri):
- return uri.startswith(self.prefix)
-
+.. literalinclude :: ../tests/examples/test_howto_url_matcher.py
+ :language: python
- def test_uripattern_object(httpserver: HTTPServer):
-
httpserver.expect_request(PrefixMatch("/foo")).respond_with_json({"foo": "bar"})
Authentication
--------------
@@ -121,50 +101,8 @@
By default, pytest-httpserver includes an Authorization header parser so the
order of the parameters in the ``Authorization`` header does not matter.
-.. code:: python
-
- def test_authorization_headers(httpserver: HTTPServer):
- headers_with_values_in_direct_order = {
- "Authorization": (
- 'Digest username="Mufasa",'
- 'realm="[email protected]",'
- 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
- 'uri="/dir/index.html",'
- "qop=auth,"
- "nc=00000001,"
- 'cnonce="0a4f113b",'
- 'response="6629fae49393a05397450978507c4ef1",'
- 'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
- )
- }
- httpserver.expect_request(
- uri="/", headers=headers_with_values_in_direct_order
- ).respond_with_data("OK")
- response = requests.get(
- httpserver.url_for("/"),
headers=headers_with_values_in_direct_order
- )
- assert response.status_code == 200
- assert response.text == "OK"
-
- headers_with_values_in_modified_order = {
- "Authorization": (
- "Digest qop=auth,"
- 'username="Mufasa",'
- 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
- 'uri="/dir/index.html",'
- "nc=00000001,"
- 'realm="[email protected]",'
- 'response="6629fae49393a05397450978507c4ef1",'
- 'cnonce="0a4f113b",'
- 'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
- )
- }
- response = requests.get(
- httpserver.url_for("/"),
headers=headers_with_values_in_modified_order
- )
- assert response.status_code == 200
- assert response.text == "OK"
-
+.. literalinclude :: ../tests/examples/test_howto_authorization_headers.py
+ :language: python
JSON matching
-------------
@@ -185,16 +123,8 @@
Example:
-.. code:: python
-
- def test_json_matcher(httpserver: HTTPServer):
- httpserver.expect_request("/foo", json={"foo":
"bar"}).respond_with_data(
- "Hello world!"
- )
- resp = requests.get(httpserver.url_for("/foo"), json={"foo": "bar"})
- assert resp.status_code == 200
- assert resp.text == "Hello world!"
-
+.. literalinclude :: ../tests/examples/test_howto_json_matcher.py
+ :language: python
.. note::
JSON requests usually come with ``Content-Type: application/json`` header.
@@ -222,29 +152,8 @@
You need to implement such a function and then use it:
-.. code:: python
-
- def case_insensitive_matcher(header_name: str, actual: str, expected: str)
-> bool:
- if header_name == "X-Foo":
- return actual.lower() == expected.lower()
- else:
- return actual == expected
-
-
- def test_case_insensitive_matching(httpserver: HTTPServer):
- httpserver.expect_request(
- "/", header_value_matcher=case_insensitive_matcher,
headers={"X-Foo": "bar"}
- ).respond_with_data("OK")
-
- assert (
- requests.get(httpserver.url_for("/"), headers={"X-Foo":
"bar"}).status_code
- == 200
- )
- assert (
- requests.get(httpserver.url_for("/"), headers={"X-Foo":
"BAR"}).status_code
- == 200
- )
-
+.. literalinclude :: ../tests/examples/test_howto_case_insensitive_matcher.py
+ :language: python
.. note::
Header value matcher is the basis of the ``Authorization`` header parsing.
@@ -285,30 +194,8 @@
In case you don't want to change the defaults, you can provide the
``HeaderValueMatcher`` object itself.
-.. code:: python
-
- from pytest_httpserver import HeaderValueMatcher
-
-
- def case_insensitive_compare(actual: str, expected: str) -> bool:
- return actual.lower() == expected.lower()
-
-
- def test_own_matcher_object(httpserver: HTTPServer):
- matcher = HeaderValueMatcher({"X-Bar": case_insensitive_compare})
-
- httpserver.expect_request(
- "/", headers={"X-Bar": "bar"}, header_value_matcher=matcher
- ).respond_with_data("OK")
-
- assert (
- requests.get(httpserver.url_for("/"), headers={"X-Bar":
"bar"}).status_code
- == 200
- )
- assert (
- requests.get(httpserver.url_for("/"), headers={"X-Bar":
"BAR"}).status_code
- == 200
- )
+.. literalinclude :: ../tests/examples/test_howto_header_value_matcher.py
+ :language: python
Using custom request handler
----------------------------
@@ -316,18 +203,9 @@
you can pass a function to the ``respond_with_handler`` function. This function
will be called with a request object and it should return a Response object.
-.. code:: python
-
- from werkzeug.wrappers import Request, Response
- from random import randint
-
-
- def test_expected_request_handler(httpserver: HTTPServer):
- def handler(request: Request):
- return Response(str(randint(1, 10)))
-
- httpserver.expect_request("/foobar").respond_with_handler(handler)
+.. literalinclude :: ../tests/examples/test_howto_custom_handler.py
+ :language: python
The above code implements a handler which returns a random number between 1 and
10. Not particularly useful but shows that the handler can return any computed
@@ -344,57 +222,15 @@
Two notable examples for this:
-.. code:: python
-
- def test_check_assertions_raises_handler_assertions(httpserver:
HTTPServer):
- def handler(_):
- assert 1 == 2
-
- httpserver.expect_request("/foobar").respond_with_handler(handler)
-
- requests.get(httpserver.url_for("/foobar"))
-
- # if you leave this "with" statement out, check_assertions() will break
- # the test by re-raising the assertion error caused by the handler
- # pytest will pick this exception as it was happened in the main thread
- with pytest.raises(AssertionError):
- httpserver.check_assertions()
-
- httpserver.check_handler_errors()
-
-
- def test_check_handler_errors_raises_handler_error(httpserver: HTTPServer):
- def handler(_):
- raise ValueError("should be propagated")
-
- httpserver.expect_request("/foobar").respond_with_handler(handler)
-
- requests.get(httpserver.url_for("/foobar"))
-
- httpserver.check_assertions()
-
- # if you leave this "with" statement out, check_handler_errors() will
- # break the test with the original exception
- with pytest.raises(ValueError):
- httpserver.check_handler_errors()
-
+.. literalinclude :: ../tests/examples/test_howto_check_handler_errors.py
+ :language: python
If you want to call both methods (``check_handler_errors()`` and
``check_assertions()``) you can call the ``check()`` method, which will call
these.
-
-.. code:: python
-
- def test_check_assertions(httpserver: HTTPServer):
- def handler(_):
- assert 1 == 2
-
- httpserver.expect_request("/foobar").respond_with_handler(handler)
-
- requests.get(httpserver.url_for("/foobar"))
-
- httpserver.check()
+.. literalinclude :: ../tests/examples/test_howto_check.py
+ :language: python
.. note::
The scope of the errors checked by the ``check()`` method may
@@ -478,24 +314,8 @@
Last, you need to assert on the ``result`` attribute of the context object.
-.. code-block:: python
-
- def test_wait_success(httpserver: HTTPServer):
- waiting_timeout = 0.1
-
- with httpserver.wait(stop_on_nohandler=False, timeout=waiting_timeout)
as waiting:
- requests.get(httpserver.url_for("/foobar"))
- httpserver.expect_oneshot_request("/foobar").respond_with_data("OK
foobar")
- requests.get(httpserver.url_for("/foobar"))
- assert waiting.result
-
- httpserver.expect_oneshot_request("/foobar").respond_with_data("OK
foobar")
- httpserver.expect_oneshot_request("/foobaz").respond_with_data("OK
foobaz")
- with httpserver.wait(timeout=waiting_timeout) as waiting:
- requests.get(httpserver.url_for("/foobar"))
- requests.get(httpserver.url_for("/foobaz"))
- assert waiting.result
-
+.. literalinclude :: ../tests/examples/test_howto_wait_success.py
+ :language: python
In the above code, all the request.get() calls could be in a different thread,
eg. running in parallel, but the exit condition of the context object is to
wait
@@ -509,19 +329,10 @@
by peer* or *Connection refused*, you can simply do it by connecting to a
random
port number where no service is listening:
-.. code-block:: python
-
- import pytest
- import requests
-
-
- def test_connection_refused():
- # assumes that there's no server listening at localhost:1234
- with pytest.raises(requests.exceptions.ConnectionError):
- requests.get("http://localhost:1234")
-
+.. literalinclude :: ../tests/examples/test_howto_timeout_requests.py
+ :language: python
-However connecting to the port where the httpserver had been started will still
+However, connecting to the port where the httpserver had been started will
still
succeed as the server is running continuously. This is working by design as
starting/stopping the server is costly.
@@ -530,6 +341,7 @@
import pytest
import requests
+
# setting a fixed port for httpserver
@pytest.fixture(scope="session")
def httpserver_listen_address():
@@ -607,20 +419,46 @@
@pytest.fixture(scope="session")
- def localhost_cert(ca):
- return ca.issue_cert("localhost")
+ def httpserver_ssl_context(ca):
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ localhost_cert = ca.issue_cert("localhost")
+ localhost_cert.configure_cert(context)
+ return context
@pytest.fixture(scope="session")
- def httpserver_ssl_context(localhost_cert):
- context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ def httpclient_ssl_context(ca):
+ with ca.cert_pem.tempfile() as ca_temp_path:
+ return ssl.create_default_context(cafile=ca_temp_path)
- crt = localhost_cert.cert_chain_pems[0]
- key = localhost_cert.private_key_pem
- with crt.tempfile() as crt_file, key.tempfile() as key_file:
- context.load_cert_chain(crt_file, key_file)
- return context
+ @pytest.mark.asyncio
+ async def test_aiohttp(httpserver, httpclient_ssl_context):
+ import aiohttp
+
+ httpserver.expect_request("/").respond_with_data("hello world!")
+ connector = aiohttp.TCPConnector(ssl=httpclient_ssl_context)
+ async with aiohttp.ClientSession(connector=connector) as session:
+ async with session.get(httpserver.url_for("/")) as result:
+ assert (await result.text()) == "hello world!"
+
+
+ def test_requests(httpserver, ca):
+ import requests
+
+ httpserver.expect_request("/").respond_with_data("hello world!")
+ with ca.cert_pem.tempfile() as ca_temp_path:
+ result = requests.get(httpserver.url_for("/"), verify=ca_temp_path)
+ assert result.text == "hello world!"
+
+
+ def test_httpx(httpserver, httpclient_ssl_context):
+ import httpx
+
+ httpserver.expect_request("/").respond_with_data("hello world!")
+ result = httpx.get(httpserver.url_for("/"),
verify=httpclient_ssl_context)
+ assert result.text == "hello world!"
+
Using httpserver on a dual-stack (IPv4 and IPv6) system
-------------------------------------------------------
@@ -672,5 +510,5 @@
Example:
-.. literalinclude :: ../tests/test_blocking_httpserver_howto.py
+.. literalinclude :: ../tests/examples/test_example_blocking_httpserver.py
:language: python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/doc/tutorial.rst
new/pytest_httpserver-1.0.7/doc/tutorial.rst
--- old/pytest_httpserver-1.0.6/doc/tutorial.rst 2022-07-13
18:49:54.027996000 +0200
+++ new/pytest_httpserver-1.0.7/doc/tutorial.rst 2023-05-16
20:58:37.763110000 +0200
@@ -69,7 +69,7 @@
It is advised to use the ``url_for()`` method to construct an URL as it will
always contain the correct port number in the URL.
-If you need the http port as an integer, you can get is by the ``port``
+If you need the http port as an integer, you can get it by the ``port``
attribute of the ``httpserver`` object.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/pyproject.toml
new/pytest_httpserver-1.0.7/pyproject.toml
--- old/pytest_httpserver-1.0.6/pyproject.toml 2022-09-12 09:25:14.313314400
+0200
+++ new/pytest_httpserver-1.0.7/pyproject.toml 2023-05-16 21:40:52.284019700
+0200
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pytest_httpserver"
-version = "1.0.6"
+version = "1.0.7"
description = "pytest-httpserver is a httpserver for pytest"
authors = ["Zsolt Cserna <[email protected]>"]
license = "MIT"
@@ -24,7 +24,7 @@
]
[tool.poetry.dependencies]
-python = ">=3.7,<4.0"
+python = ">=3.8,<4.0"
Werkzeug = ">= 2.0.0"
@@ -46,10 +46,11 @@
mypy = "^0.971"
types-requests = "^2.28.9"
pytest = "^7.1.3"
-pytest-cov = "^3.0.0"
-coverage = "^6.4.4"
-ipdb = "^0.13.9"
+pytest-cov = ">=3,<5"
+coverage = ">=6.4.4,<8.0.0"
types-toml = "^0.10.8"
+toml = "^0.10.2"
+black = "^23.1.0"
[tool.poetry.group.doc]
@@ -65,13 +66,14 @@
[tool.poetry.group.test.dependencies]
pytest = "^7.1.3"
-pytest-cov = "^3.0.0"
-coverage = "^6.4.4"
+pytest-cov = ">=3,<5"
+coverage = ">=6.4.4,<8.0.0"
requests = "^2.28.1"
mypy = "^0.971"
types-requests = "^2.28.9"
pre-commit = "^2.20.0"
types-toml = "^0.10.8"
+toml = "^0.10.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/pytest_httpserver/httpserver.py
new/pytest_httpserver-1.0.7/pytest_httpserver/httpserver.py
--- old/pytest_httpserver-1.0.6/pytest_httpserver/httpserver.py 2022-09-07
21:46:29.731832500 +0200
+++ new/pytest_httpserver-1.0.7/pytest_httpserver/httpserver.py 2023-05-16
20:58:37.764109800 +0200
@@ -1,9 +1,11 @@
import abc
+import ipaddress
import json
import queue
import re
import threading
import time
+import urllib.parse
from collections import defaultdict
from contextlib import contextmanager
from contextlib import suppress
@@ -21,9 +23,9 @@
from typing import Tuple
from typing import Union
-import werkzeug.urls
+import werkzeug.http
+from werkzeug.datastructures import Authorization
from werkzeug.datastructures import MultiDict
-from werkzeug.http import parse_authorization_header
from werkzeug.serving import make_server
from werkzeug.wrappers import Request
from werkzeug.wrappers import Response
@@ -32,10 +34,12 @@
METHOD_ALL = "__ALL"
HEADERS_T = Union[
- Mapping[str, Union[str, int, Iterable[Union[str, int]]]],
- Iterable[Tuple[str, Union[str, int]]],
+ Mapping[str, Union[str, Iterable[str]]],
+ Iterable[Tuple[str, str]],
]
+HVMATCHER_T = Callable[[str, Optional[str], str], bool]
+
class Undefined:
def __repr__(self):
@@ -130,7 +134,10 @@
@staticmethod
def authorization_header_value_matcher(actual: Optional[str], expected:
str) -> bool:
- return parse_authorization_header(actual) ==
parse_authorization_header(expected)
+ callable = getattr(Authorization, "from_header", None)
+ if callable is None: # Werkzeug < 2.3.0
+ callable = werkzeug.http.parse_authorization_header
+ return callable(actual) == callable(expected)
@staticmethod
def default_header_value_matcher(actual: Optional[str], expected: str) ->
bool:
@@ -213,7 +220,7 @@
self.query_dict = query_dict
def get_comparing_values(self, request_query_string: bytes) -> tuple:
- query = werkzeug.urls.url_decode(request_query_string)
+ query =
MultiDict(urllib.parse.parse_qsl(request_query_string.decode("utf-8")))
if isinstance(self.query_dict, MultiDict):
return (query, self.query_dict)
else:
@@ -288,6 +295,13 @@
specified in the request. If multiple values specified for a given
key, the first
value will be used. If multiple values needed to be handled, use
``MultiDict``
object from werkzeug.
+ :param header_value_matcher: :py:class:`HeaderValueMatcher` that matches
+ values of headers, or a ``Callable[[str, Optional[str], str], bool]``
+ receiving the header key (from `headers`), header value (or `None`)
and the expected
+ value (from `headers`) and should return ``True`` if the header
matches, ``False`` otherwise.
+ :param json: a python object (eg. a dict) whose value will be compared to
the request body after it
+ is loaded as json. If load fails, this matcher will be failed also.
*Content-Type* is not checked.
+ If that's desired, add it to the headers parameter.
"""
def __init__(
@@ -298,10 +312,9 @@
data_encoding: str = "utf-8",
headers: Optional[Mapping[str, str]] = None,
query_string: Union[None, QueryMatcher, str, bytes, Mapping] = None,
- header_value_matcher: Optional[HeaderValueMatcher] = None,
+ header_value_matcher: Optional[HVMATCHER_T] = None,
json: Any = UNDEFINED,
):
-
if json is not UNDEFINED and data is not None:
raise ValueError("data and json parameters are mutually exclusive")
@@ -321,7 +334,10 @@
self.data = data
self.data_encoding = data_encoding
- self.header_value_matcher = HeaderValueMatcher() if
header_value_matcher is None else header_value_matcher
+ self.header_value_matcher: HVMATCHER_T = HeaderValueMatcher()
+
+ if header_value_matcher is not None:
+ self.header_value_matcher = header_value_matcher
def __repr__(self):
"""
@@ -610,6 +626,9 @@
self.ssl_context = ssl_context
self.no_handler_status_code = 500
+ def __repr__(self):
+ return f"<{self.__class__.__name__} host={self.host} port={self.port}>"
+
def clear(self):
"""
Clears and resets the state attributes of the object.
@@ -650,6 +669,11 @@
This basically means that it prepends the string
``http://$HOST:$PORT/`` to the `suffix` parameter
(where $HOST and $PORT are the parameters given to the constructor).
+ When host is an IPv6 address, the required square brackets will be
added
+ to it, forming a valid URL.
+
+ When SSL or TLS is in use, the protocol of the returned URL will be
``https``.
+
:param suffix: the suffix which will be added to the base url. It can
start with ``/`` (slash) or
not, the url will be the same.
:return: the full url which refers to the server
@@ -663,7 +687,9 @@
else:
protocol = "https"
- return "{}://{}:{}{}".format(protocol, self.host, self.port, suffix)
+ host = self.format_host(self.host)
+
+ return "{}://{}:{}{}".format(protocol, host, self.port, suffix)
def create_matcher(self, *args, **kwargs) -> RequestMatcher:
"""
@@ -837,6 +863,24 @@
if self.is_running():
self.stop()
+ @staticmethod
+ def format_host(host):
+ """
+ Formats a hostname so it can be used in a URL.
+ Notably, this adds brackets around IPV6 addresses when
+ they are missing.
+ """
+ try:
+ ipaddress.IPv6Address(host)
+ is_ipv6 = True
+ except ValueError:
+ is_ipv6 = False
+
+ if is_ipv6 and not host.startswith("[") and not host.endswith("]"):
+ return f"[{host}]"
+
+ return host
+
class HTTPServer(HTTPServerBase): # pylint:
disable=too-many-instance-attributes
"""
@@ -910,7 +954,7 @@
data_encoding: str = "utf-8",
headers: Optional[Mapping[str, str]] = None,
query_string: Union[None, QueryMatcher, str, bytes, Mapping] = None,
- header_value_matcher: Optional[HeaderValueMatcher] = None,
+ header_value_matcher: Optional[HVMATCHER_T] = None,
handler_type: HandlerType = HandlerType.PERMANENT,
json: Any = UNDEFINED,
) -> RequestHandler:
@@ -944,7 +988,10 @@
specified in the request. If multiple values specified for a given
key, the first
value will be used. If multiple values needed to be handled, use
``MultiDict``
object from werkzeug.
- :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches values of headers.
+ :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches
+ values of headers, or a ``Callable[[str, Optional[str], str],
bool]``
+ receiving the header key (from `headers`), header value (or
`None`) and the expected
+ value (from `headers`) and should return ``True`` if the header
matches, ``False`` otherwise.
:param handler_type: type of handler
:param json: a python object (eg. a dict) whose value will be compared
to the request body after it
is loaded as json. If load fails, this matcher will be failed
also. *Content-Type* is not checked.
@@ -982,7 +1029,7 @@
data_encoding: str = "utf-8",
headers: Optional[Mapping[str, str]] = None,
query_string: Union[None, QueryMatcher, str, bytes, Mapping] = None,
- header_value_matcher: Optional[HeaderValueMatcher] = None,
+ header_value_matcher: Optional[HVMATCHER_T] = None,
json: Any = UNDEFINED,
) -> RequestHandler:
"""
@@ -1004,7 +1051,10 @@
specified in the request. If multiple values specified for a given
key, the first
value will be used. If multiple values needed to be handled, use
``MultiDict``
object from werkzeug.
- :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches values of headers.
+ :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches
+ values of headers, or a ``Callable[[str, Optional[str], str],
bool]``
+ receiving the header key (from `headers`), header value (or
`None`) and the expected
+ value (from `headers`) and should return ``True`` if the header
matches, ``False`` otherwise.
:param json: a python object (eg. a dict) whose value will be compared
to the request body after it
is loaded as json. If load fails, this matcher will be failed
also. *Content-Type* is not checked.
If that's desired, add it to the headers parameter.
@@ -1034,7 +1084,7 @@
data_encoding: str = "utf-8",
headers: Optional[Mapping[str, str]] = None,
query_string: Union[None, QueryMatcher, str, bytes, Mapping] = None,
- header_value_matcher: Optional[HeaderValueMatcher] = None,
+ header_value_matcher: Optional[HVMATCHER_T] = None,
json: Any = UNDEFINED,
) -> RequestHandler:
"""
@@ -1056,7 +1106,10 @@
specified in the request. If multiple values specified for a given
key, the first
value will be used. If multiple values needed to be handled, use
``MultiDict``
object from werkzeug.
- :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches values of headers.
+ :param header_value_matcher: :py:class:`HeaderValueMatcher` that
matches
+ values of headers, or a ``Callable[[str, Optional[str], str],
bool]``
+ receiving the header key (from `headers`), header value (or
`None`) and the expected
+ value (from `headers`) and should return ``True`` if the header
matches, ``False`` otherwise.
:param json: a python object (eg. a dict) whose value will be compared
to the request body after it
is loaded as json. If load fails, this matcher will be failed
also. *Content-Type* is not checked.
If that's desired, add it to the headers parameter.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/pytest_httpserver/pytest_plugin.py
new/pytest_httpserver-1.0.7/pytest_httpserver/pytest_plugin.py
--- old/pytest_httpserver-1.0.6/pytest_httpserver/pytest_plugin.py
2022-07-13 18:49:54.028996000 +0200
+++ new/pytest_httpserver-1.0.7/pytest_httpserver/pytest_plugin.py
2023-05-16 20:58:37.764109800 +0200
@@ -66,3 +66,37 @@
server = make_httpserver
yield server
server.clear()
+
+
[email protected](scope="session")
+def make_httpserver_ipv4(httpserver_ssl_context):
+ server = HTTPServer(host="127.0.0.1", port=0,
ssl_context=httpserver_ssl_context)
+ server.start()
+ yield server
+ server.clear()
+ if server.is_running():
+ server.stop()
+
+
[email protected]
+def httpserver_ipv4(make_httpserver_ipv4):
+ server = make_httpserver_ipv4
+ yield server
+ server.clear()
+
+
[email protected](scope="session")
+def make_httpserver_ipv6(httpserver_ssl_context):
+ server = HTTPServer(host="::1", port=0, ssl_context=httpserver_ssl_context)
+ server.start()
+ yield server
+ server.clear()
+ if server.is_running():
+ server.stop()
+
+
[email protected]
+def httpserver_ipv6(make_httpserver_ipv6):
+ server = make_httpserver_ipv6
+ yield server
+ server.clear()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/setup.py
new/pytest_httpserver-1.0.7/setup.py
--- old/pytest_httpserver-1.0.6/setup.py 1970-01-01 01:00:00.000000000
+0100
+++ new/pytest_httpserver-1.0.7/setup.py 1970-01-01 01:00:00.000000000
+0100
@@ -15,7 +15,7 @@
setup_kwargs = {
'name': 'pytest-httpserver',
- 'version': '1.0.6',
+ 'version': '1.0.7',
'description': 'pytest-httpserver is a httpserver for pytest',
'long_description': '[](https://github.com/csernazs/pytest-httpserver/actions?query=workflow%3Abuild+branch%3Amaster)\n[](https://pytest-httpserver.readthedocs.io/en/latest/?badge=latest)\n
[](https://opensource.org/licenses/MIT)\n[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=K6PU3AGBZW4QC&item_name=pytest-httpserver¤cy_code=EUR&source=url)\n[](https://codecov.io/gh/csernazs/pytest-httpserver)\n[](https://github.com/psf/black)\n\n##
pytest_httpserve
r\n\nHTTP server for pytest\n\n\n### Nutshell\n\nThis library is designed to
help to test http clients without contacting the real http server.\nIn other
words, it is a fake http server which is accessible via localhost can be
started with\nthe pre-defined expected http requests and their
responses.\n\n### Example\n\n#### Handling a simple GET request\n```python\ndef
test_my_client(\n httpserver,\n): # httpserver is a pytest fixture which
starts the server\n # set up the server to serve /foobar with the json\n
httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})\n #
check that the request is served\n assert
requests.get(httpserver.url_for("/foobar")).json() == {"foo":
"bar"}\n```\n\n#### Handing a POST request with an expected json
body\n```python\ndef test_json_request(\n httpserver,\n): # httpserver is a
pytest fixture which starts the server\n # set up the server to serve
/foobar with the json\n httpserver.expect_request(\n "/foo
bar", method="POST", json={"id": 12, "name": "foo"}\n
).respond_with_json({"foo": "bar"})\n # check that the request is served\n
assert requests.post(\n httpserver.url_for("/foobar"), json={"id": 12,
"name": "foo"}\n ).json() == {"foo": "bar"}\n```\n\n\nYou can also use the
library without pytest. There\'s a with statement to ensure that the server is
stopped.\n\n\n```python\nwith HTTPServer() as httpserver:\n # set up the
server to serve /foobar with the json\n
httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})\n #
check that the request is served\n
print(requests.get(httpserver.url_for("/foobar")).json())\n```\n\n###
Documentation\n\nPlease find the API documentation at
https://pytest-httpserver.readthedocs.io/en/latest/.\n\n### Features\n\nYou can
set up a dozen of expectations for the requests, and also what response should
be sent by the server to the client.\n\n\n#### Requests\n\nThere are three
different types:\n\n- **permane
nt**: this will be always served when there\'s match for this request, you can
make as many HTTP requests as you want\n- **oneshot**: this will be served only
once when there\'s a match for this request, you can only make 1 HTTP
request\n- **ordered**: same as oneshot but the order must be strictly matched
to the order of setting up\n\nYou can also fine-tune the expected request. The
following can be specified:\n\n- URI (this is a must)\n- HTTP method\n-
headers\n- query string\n- data (HTTP body of the request)\n- JSON (HTTP body
loaded as JSON)\n\n\n#### Responses\n\nOnce you have the expectations for the
request set up, you should also define the response you want to send back.\nThe
following is supported currently:\n\n- respond arbitrary data (string or
bytearray)\n- respond a json (a python dict converted in-place to json)\n-
respond a Response object of werkzeug\n- use your own function\n\nSimilar to
requests, you can fine-tune what response you want to send:\n\n- HTTP status\
n- headers\n- data\n\n\n#### Behave support\n\nUsing the `BlockingHTTPServer`
class, the assertion for a request and the\nresponse can be performed in real
order. For more info, see the\n[test](tests/test_blocking_httpserver.py),
the\n[howto](https://pytest-httpserver.readthedocs.io/en/latest/howto.html#running-httpserver-in-blocking-mode)\nand
the
[API\ndocumentation](https://pytest-httpserver.readthedocs.io/en/latest/api.html#blockinghttpserver).\n\n\n###
Missing features\n* HTTP/2\n* Keepalive\n* ~~TLS~~\n\n### Donation\n\nIf you
want to donate to this project, you can find the donate button at the top\nof
the README.\n\nCurrently, this project is based heavily on werkzeug. Werkzeug
does all the heavy lifting\nbehind the scenes, parsing HTTP request and
defining Request and Response objects, which\nare currently transparent in the
API.\n\nIf you wish to donate, please consider donating to them:
https://palletsprojects.com/donate\n',
'author': 'Zsolt Cserna',
@@ -27,7 +27,7 @@
'package_data': package_data,
'install_requires': install_requires,
'entry_points': entry_points,
- 'python_requires': '>=3.7,<4.0',
+ 'python_requires': '>=3.8,<4.0',
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_example_blocking_httpserver.py
new/pytest_httpserver-1.0.7/tests/examples/test_example_blocking_httpserver.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_example_blocking_httpserver.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_example_blocking_httpserver.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,59 @@
+import threading
+from queue import Queue
+
+import pytest
+import requests
+
+from pytest_httpserver import BlockingHTTPServer
+
+# override httpserver fixture
+
+
[email protected]
+def httpserver():
+ server = BlockingHTTPServer(timeout=1)
+ server.start()
+
+ yield server
+
+ server.clear()
+ if server.is_running():
+ server.stop()
+
+ # this is to check if the client has made any request where no
+ # `assert_request` was called on it from the test
+
+ server.check_assertions()
+ server.clear()
+
+
+def test_simplified(httpserver: BlockingHTTPServer):
+ def client(response_queue: Queue):
+ response = requests.get(httpserver.url_for("/foobar"), timeout=10)
+ response_queue.put(response)
+
+ # start the client, server is not yet configured
+ # it will block until we add a request handler to the server
+ # (see the timeout parameter of the http server)
+ response_queue: Queue[requests.models.Response] = Queue(maxsize=1)
+ thread = threading.Thread(target=client, args=(response_queue,))
+ thread.start()
+
+ try:
+ # check that the request is for /foobar and it is a GET method
+ # if this does not match, it will raise AssertionError and test will
fail
+ client_connection = httpserver.assert_request(uri="/foobar",
method="GET")
+
+ # with the received client_connection, we now need to send back the
response
+ # this makes the request.get() call in client() to return
+ client_connection.respond_with_json({"foo": "bar"})
+
+ finally:
+ # wait for the client thread to complete
+ thread.join(timeout=1)
+ assert not thread.is_alive() # check if join() has not timed out
+
+ # check the response the client received
+ response = response_queue.get(timeout=1)
+ assert response.status_code == 200
+ assert response.json() == {"foo": "bar"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_example_query_params1.py
new/pytest_httpserver-1.0.7/tests/examples/test_example_query_params1.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_example_query_params1.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_example_query_params1.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,2 @@
+def test_query_params(httpserver):
+ httpserver.expect_request("/foo", query_string={"user":
"user1"}).respond_with_data("OK")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_example_query_params2.py
new/pytest_httpserver-1.0.7/tests/examples/test_example_query_params2.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_example_query_params2.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_example_query_params2.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,3 @@
+def test_query_params(httpserver):
+ expected_query = {"user": "user1"}
+ httpserver.expect_request("/foo",
query_string=expected_query).respond_with_data("OK")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_authorization_headers.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_authorization_headers.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_authorization_headers.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_authorization_headers.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,40 @@
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
+def test_authorization_headers(httpserver: HTTPServer):
+ headers_with_values_in_direct_order = {
+ "Authorization": (
+ 'Digest username="Mufasa",'
+ 'realm="[email protected]",'
+ 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
+ 'uri="/dir/index.html",'
+ "qop=auth,"
+ "nc=00000001,"
+ 'cnonce="0a4f113b",'
+ 'response="6629fae49393a05397450978507c4ef1",'
+ 'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
+ )
+ }
+ httpserver.expect_request(uri="/",
headers=headers_with_values_in_direct_order).respond_with_data("OK")
+ response = requests.get(httpserver.url_for("/"),
headers=headers_with_values_in_direct_order)
+ assert response.status_code == 200
+ assert response.text == "OK"
+
+ headers_with_values_in_modified_order = {
+ "Authorization": (
+ "Digest qop=auth,"
+ 'username="Mufasa",'
+ 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
+ 'uri="/dir/index.html",'
+ "nc=00000001,"
+ 'realm="[email protected]",'
+ 'response="6629fae49393a05397450978507c4ef1",'
+ 'cnonce="0a4f113b",'
+ 'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
+ )
+ }
+ response = requests.get(httpserver.url_for("/"),
headers=headers_with_values_in_modified_order)
+ assert response.status_code == 200
+ assert response.text == "OK"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_case_insensitive_matcher.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_case_insensitive_matcher.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_case_insensitive_matcher.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_case_insensitive_matcher.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,24 @@
+from typing import Optional
+
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
+def case_insensitive_matcher(header_name: str, actual: Optional[str],
expected: str) -> bool:
+ if actual is None:
+ return False
+
+ if header_name == "X-Foo":
+ return actual.lower() == expected.lower()
+ else:
+ return actual == expected
+
+
+def test_case_insensitive_matching(httpserver: HTTPServer):
+ httpserver.expect_request(
+ "/", header_value_matcher=case_insensitive_matcher, headers={"X-Foo":
"bar"}
+ ).respond_with_data("OK")
+
+ assert requests.get(httpserver.url_for("/"), headers={"X-Foo":
"bar"}).status_code == 200
+ assert requests.get(httpserver.url_for("/"), headers={"X-Foo":
"BAR"}).status_code == 200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_check.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_check.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_check.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_check.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,17 @@
+import pytest
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
[email protected]
+def test_check_assertions(httpserver: HTTPServer):
+ def handler(_):
+ assert 1 == 2
+
+ httpserver.expect_request("/foobar").respond_with_handler(handler)
+
+ requests.get(httpserver.url_for("/foobar"))
+
+ # this will raise AssertionError:
+ httpserver.check()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_check_handler_errors.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_check_handler_errors.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_check_handler_errors.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_check_handler_errors.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,37 @@
+import pytest
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
+def test_check_assertions_raises_handler_assertions(httpserver: HTTPServer):
+ def handler(_):
+ assert 1 == 2
+
+ httpserver.expect_request("/foobar").respond_with_handler(handler)
+
+ requests.get(httpserver.url_for("/foobar"))
+
+ # if you leave this "with" statement out, check_assertions() will break
+ # the test by re-raising the assertion error caused by the handler
+ # pytest will pick this exception as it was happened in the main thread
+ with pytest.raises(AssertionError):
+ httpserver.check_assertions()
+
+ httpserver.check_handler_errors()
+
+
+def test_check_handler_errors_raises_handler_error(httpserver: HTTPServer):
+ def handler(_):
+ raise ValueError("should be propagated")
+
+ httpserver.expect_request("/foobar").respond_with_handler(handler)
+
+ requests.get(httpserver.url_for("/foobar"))
+
+ httpserver.check_assertions()
+
+ # if you leave this "with" statement out, check_handler_errors() will
+ # break the test with the original exception
+ with pytest.raises(ValueError):
+ httpserver.check_handler_errors()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_custom_handler.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_custom_handler.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_custom_handler.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_custom_handler.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,13 @@
+from random import randint
+
+from werkzeug.wrappers import Request
+from werkzeug.wrappers import Response
+
+from pytest_httpserver import HTTPServer
+
+
+def test_expected_request_handler(httpserver: HTTPServer):
+ def handler(request: Request):
+ return Response(str(randint(1, 10)))
+
+ httpserver.expect_request("/foobar").respond_with_handler(handler)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_header_value_matcher.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_header_value_matcher.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_header_value_matcher.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_header_value_matcher.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,22 @@
+from typing import Optional
+
+import requests
+
+from pytest_httpserver import HeaderValueMatcher
+from pytest_httpserver import HTTPServer
+
+
+def case_insensitive_compare(actual: Optional[str], expected: str) -> bool:
+ # actual is `None` if it is not specified
+ if actual is None:
+ return False
+ return actual.lower() == expected.lower()
+
+
+def test_own_matcher_object(httpserver: HTTPServer):
+ matcher = HeaderValueMatcher({"X-Bar": case_insensitive_compare})
+
+ httpserver.expect_request("/", headers={"X-Bar": "bar"},
header_value_matcher=matcher).respond_with_data("OK")
+
+ assert requests.get(httpserver.url_for("/"), headers={"X-Bar":
"bar"}).status_code == 200
+ assert requests.get(httpserver.url_for("/"), headers={"X-Bar":
"BAR"}).status_code == 200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_json_matcher.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_json_matcher.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_json_matcher.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_json_matcher.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,10 @@
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
+def test_json_matcher(httpserver: HTTPServer):
+ httpserver.expect_request("/foo", json={"foo":
"bar"}).respond_with_data("Hello world!")
+ resp = requests.get(httpserver.url_for("/foo"), json={"foo": "bar"})
+ assert resp.status_code == 200
+ assert resp.text == "Hello world!"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_dict.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_dict.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_dict.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_dict.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,8 @@
+import requests
+
+
+def test_query_params(httpserver):
+ httpserver.expect_request("/foo", query_string={"user": "user1", "group":
"group1"}).respond_with_data("OK")
+
+ assert
requests.get(httpserver.url_for("/foo?user=user1&group=group1")).status_code ==
200
+ assert
requests.get(httpserver.url_for("/foo?group=group1&user=user1")).status_code ==
200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_never_do_this.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_never_do_this.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_never_do_this.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_never_do_this.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,2 @@
+def test_query_params(httpserver):
+ httpserver.expect_request("/foo?user=bar") # never do this
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_proper_use.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_proper_use.py
---
old/pytest_httpserver-1.0.6/tests/examples/test_howto_query_params_proper_use.py
1970-01-01 01:00:00.000000000 +0100
+++
new/pytest_httpserver-1.0.7/tests/examples/test_howto_query_params_proper_use.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,2 @@
+def test_query_params(httpserver):
+ httpserver.expect_request("/foo", query_string="user=bar")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_regexp.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_regexp.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_regexp.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_regexp.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,8 @@
+import re
+
+import requests
+
+
+def test_httpserver_with_regexp(httpserver):
+ httpserver.expect_request(re.compile("^/foo"), method="GET")
+ requests.get(httpserver.url_for("/foobar"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_timeout_requests.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_timeout_requests.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_timeout_requests.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_timeout_requests.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,8 @@
+import pytest
+import requests
+
+
+def test_connection_refused():
+ # assumes that there's no server listening at localhost:1234
+ with pytest.raises(requests.exceptions.ConnectionError):
+ requests.get("http://localhost:1234")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_url_matcher.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_url_matcher.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_url_matcher.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_url_matcher.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,14 @@
+from pytest_httpserver import HTTPServer
+from pytest_httpserver import URIPattern
+
+
+class PrefixMatch(URIPattern):
+ def __init__(self, prefix: str):
+ self.prefix = prefix
+
+ def match(self, uri):
+ return uri.startswith(self.prefix)
+
+
+def test_uripattern_object(httpserver: HTTPServer):
+ httpserver.expect_request(PrefixMatch("/foo")).respond_with_json({"foo":
"bar"})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/examples/test_howto_wait_success.py
new/pytest_httpserver-1.0.7/tests/examples/test_howto_wait_success.py
--- old/pytest_httpserver-1.0.6/tests/examples/test_howto_wait_success.py
1970-01-01 01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/examples/test_howto_wait_success.py
2023-05-16 20:58:37.764109800 +0200
@@ -0,0 +1,20 @@
+import requests
+
+from pytest_httpserver import HTTPServer
+
+
+def test_wait_success(httpserver: HTTPServer):
+ waiting_timeout = 0.1
+
+ with httpserver.wait(stop_on_nohandler=False, timeout=waiting_timeout) as
waiting:
+ requests.get(httpserver.url_for("/foobar"))
+ httpserver.expect_oneshot_request("/foobar").respond_with_data("OK
foobar")
+ requests.get(httpserver.url_for("/foobar"))
+ assert waiting.result
+
+ httpserver.expect_oneshot_request("/foobar").respond_with_data("OK foobar")
+ httpserver.expect_oneshot_request("/foobaz").respond_with_data("OK foobaz")
+ with httpserver.wait(timeout=waiting_timeout) as waiting:
+ requests.get(httpserver.url_for("/foobar"))
+ requests.get(httpserver.url_for("/foobaz"))
+ assert waiting.result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/test_blocking_httpserver.py
new/pytest_httpserver-1.0.7/tests/test_blocking_httpserver.py
--- old/pytest_httpserver-1.0.6/tests/test_blocking_httpserver.py
2022-09-09 09:40:14.155206700 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_blocking_httpserver.py
2023-05-16 20:58:37.765109800 +0200
@@ -59,7 +59,6 @@
)
with when_a_request_is_being_sent_to_the_server(request) as
server_connection:
-
client_connection = then_the_server_gets_the_request(httpserver,
request)
response = {"foo": "bar"}
@@ -76,7 +75,6 @@
)
with when_a_request_is_being_sent_to_the_server(request):
-
with pytest.raises(AssertionError) as exc:
httpserver.assert_request(uri="/not/my/path/")
@@ -99,7 +97,6 @@
)
with when_a_request_is_being_sent_to_the_server(request) as
server_connection:
-
assert server_connection.get(timeout=9).text == "No handler found for
this request"
@@ -110,7 +107,6 @@
)
with when_a_request_is_being_sent_to_the_server(request):
-
then_the_server_gets_the_request(httpserver, request)
httpserver.stop() # waiting for timeout of waiting for the response
@@ -120,3 +116,7 @@
assert "/my/path" in str(exc)
assert "no response" in str(exc).lower()
+
+
+def test_repr(httpserver: BlockingHTTPServer):
+ assert repr(httpserver) == f"<BlockingHTTPServer host=localhost
port={httpserver.port}>"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/pytest_httpserver-1.0.6/tests/test_blocking_httpserver_howto.py
new/pytest_httpserver-1.0.7/tests/test_blocking_httpserver_howto.py
--- old/pytest_httpserver-1.0.6/tests/test_blocking_httpserver_howto.py
2022-09-12 08:46:31.629976300 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_blocking_httpserver_howto.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,59 +0,0 @@
-import threading
-from queue import Queue
-
-import pytest
-import requests
-
-from pytest_httpserver import BlockingHTTPServer
-
-# override httpserver fixture
-
-
[email protected]
-def httpserver():
- server = BlockingHTTPServer(timeout=1)
- server.start()
-
- yield server
-
- server.clear()
- if server.is_running():
- server.stop()
-
- # this is to check if the client has made any request where no
- # `assert_request` was called on it from the test
-
- server.check_assertions()
- server.clear()
-
-
-def test_simplified(httpserver: BlockingHTTPServer):
- def client(response_queue: Queue):
- response = requests.get(httpserver.url_for("/foobar"), timeout=10)
- response_queue.put(response)
-
- # start the client, server is not yet configured
- # it will block until we add a request handler to the server
- # (see the timeout parameter of the http server)
- response_queue: Queue[requests.models.Response] = Queue(maxsize=1)
- thread = threading.Thread(target=client, args=(response_queue,))
- thread.start()
-
- try:
- # check that the request is for /foobar and it is a GET method
- # if this does not match, it will raise AssertionError and test will
fail
- client_connection = httpserver.assert_request(uri="/foobar",
method="GET")
-
- # with the received client_connection, we now need to send back the
response
- # this makes the request.get() call in client() to return
- client_connection.respond_with_json({"foo": "bar"})
-
- finally:
- # wait for the client thread to complete
- thread.join(timeout=1)
- assert not thread.is_alive() # check if join() has not timed out
-
- # check the response the client received
- response = response_queue.get(timeout=1)
- assert response.status_code == 200
- assert response.json() == {"foo": "bar"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_headers.py
new/pytest_httpserver-1.0.7/tests/test_headers.py
--- old/pytest_httpserver-1.0.6/tests/test_headers.py 2022-07-13
18:49:54.028996000 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_headers.py 2023-05-16
20:58:37.765109800 +0200
@@ -82,15 +82,14 @@
def test_header_one_key_multiple_values(httpserver: HTTPServer):
httpserver.expect_request(uri="/t1").respond_with_data(headers=[("X-Foo",
"123"), ("X-Foo", "456")])
httpserver.expect_request(uri="/t2").respond_with_data(headers={"X-Foo":
["123", "456"]})
- httpserver.expect_request(uri="/t3").respond_with_data(headers={"X-Foo":
[123, 456]})
headers = Headers()
headers.add("X-Foo", "123")
headers.add("X-Foo", "456")
- httpserver.expect_request(uri="/t4").respond_with_data(headers=headers)
+ httpserver.expect_request(uri="/t3").respond_with_data(headers=headers)
- for uri in ("/t1", "/t2", "/t3", "/t4"):
+ for uri in ("/t1", "/t2", "/t3"):
conn =
http.client.HTTPConnection("localhost:{}".format(httpserver.port))
conn.request("GET", uri)
response = conn.getresponse()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_ip_protocols.py
new/pytest_httpserver-1.0.7/tests/test_ip_protocols.py
--- old/pytest_httpserver-1.0.6/tests/test_ip_protocols.py 1970-01-01
01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/test_ip_protocols.py 2023-05-16
20:58:37.765109800 +0200
@@ -0,0 +1,18 @@
+import requests
+
+
+def test_ipv4(httpserver_ipv4):
+ httpserver_ipv4.expect_request("/").respond_with_data("OK")
+ assert httpserver_ipv4.host == "127.0.0.1"
+
+ response = requests.get(httpserver_ipv4.url_for("/"))
+ assert response.text == "OK"
+
+
+def test_ipv6(httpserver_ipv6):
+ httpserver_ipv6.expect_request("/").respond_with_data("OK")
+ assert httpserver_ipv6.host == "::1"
+ assert httpserver_ipv6.url_for("/") ==
f"http://[::1]:{httpserver_ipv6.port}/"
+
+ response = requests.get(httpserver_ipv6.url_for("/"))
+ assert response.text == "OK"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_mixed.py
new/pytest_httpserver-1.0.7/tests/test_mixed.py
--- old/pytest_httpserver-1.0.6/tests/test_mixed.py 2022-07-13
18:49:54.029996000 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_mixed.py 2023-05-16
20:58:37.765109800 +0200
@@ -82,3 +82,7 @@
assert len(httpserver.ordered_handlers) == 2
assert len(httpserver.oneshot_handlers) == 2
assert len(httpserver.handlers) == 1
+
+
+def test_repr(httpserver: HTTPServer):
+ assert repr(httpserver) == f"<HTTPServer host=localhost
port={httpserver.port}>"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_parse_qs.py
new/pytest_httpserver-1.0.7/tests/test_parse_qs.py
--- old/pytest_httpserver-1.0.6/tests/test_parse_qs.py 1970-01-01
01:00:00.000000000 +0100
+++ new/pytest_httpserver-1.0.7/tests/test_parse_qs.py 2023-05-16
20:58:37.765109800 +0200
@@ -0,0 +1,30 @@
+import urllib.parse
+from typing import List
+from typing import Tuple
+
+import pytest
+import werkzeug.urls
+from werkzeug.datastructures import MultiDict
+
+parse_qsl_semicolon_cases = [
+ ("&", []),
+ ("&&", []),
+ ("&a=b", [("a", "b")]),
+ ("a=a+b&b=b+c", [("a", "a b"), ("b", "b c")]),
+ ("a=1&a=2", [("a", "1"), ("a", "2")]),
+ ("a=", [("a", "")]),
+ ("a=foo bar&b=bar foo", [("a", "foo bar"), ("b", "bar foo")]),
+ ("a=foo%20bar&b=bar%20foo", [("a", "foo bar"), ("b", "bar foo")]),
+ ("a=%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D",
[("a", " !\"#$%&'()*+,/:;=?@[]")]),
+]
+
+
[email protected]("qs,expected", parse_qsl_semicolon_cases)
+def test_qsl(qs: str, expected: List[Tuple[bytes, bytes]]):
+ assert urllib.parse.parse_qsl(qs, keep_blank_values=True) == expected
+
+
[email protected](reason="skipped to avoid werkzeug warnings")
[email protected]("qs,expected", parse_qsl_semicolon_cases)
+def test_qsl_werkzeug(qs: str, expected: List[Tuple[bytes, bytes]]):
+ assert werkzeug.urls.url_decode(qs) == MultiDict(expected)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_permanent.py
new/pytest_httpserver-1.0.7/tests/test_permanent.py
--- old/pytest_httpserver-1.0.6/tests/test_permanent.py 2022-07-13
18:49:54.029996000 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_permanent.py 2023-05-16
20:58:37.765109800 +0200
@@ -93,3 +93,16 @@
assert httpserver.ordered_handlers == []
assert httpserver.oneshot_handlers == []
assert httpserver.handlers == []
+
+
+def test_response_handler_replaced(httpserver: HTTPServer):
+ # https://github.com/csernazs/pytest-httpserver/issues/229
+ handler = httpserver.expect_request("/foobar")
+ handler.respond_with_data("FOO")
+ response = requests.get(httpserver.url_for("/foobar"))
+ assert response.text == "FOO"
+ assert response.status_code == 200
+ handler.respond_with_json({"foo": "bar"})
+ response = requests.get(httpserver.url_for("/foobar"))
+ assert response.json() == {"foo": "bar"}
+ assert response.status_code == 200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pytest_httpserver-1.0.6/tests/test_release.py
new/pytest_httpserver-1.0.7/tests/test_release.py
--- old/pytest_httpserver-1.0.6/tests/test_release.py 2022-09-12
08:46:48.840120300 +0200
+++ new/pytest_httpserver-1.0.7/tests/test_release.py 2023-05-16
20:58:37.765109800 +0200
@@ -78,7 +78,7 @@
@pytest.fixture(scope="session")
-def build(request) -> Iterable[Build]:
+def build() -> Iterable[Build]:
dist_path = Path("dist").resolve()
if dist_path.is_dir():
shutil.rmtree(dist_path)
@@ -195,15 +195,17 @@
"tests": {
"assets",
"conftest.py",
+ "examples",
"test_blocking_httpserver.py",
- "test_blocking_httpserver_howto.py",
"test_handler_errors.py",
"test_headers.py",
+ "test_ip_protocols.py",
"test_json_matcher.py",
"test_mixed.py",
"test_oneshot.py",
"test_ordered.py",
"test_permanent.py",
+ "test_parse_qs.py",
"test_port_changing.py",
"test_querymatcher.py",
"test_querystring.py",