pytest_httpserver 1.1.0__tar.gz → 1.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/CHANGES.rst +28 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/PKG-INFO +4 -4
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/README.md +1 -1
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/conf.py +1 -1
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/howto.rst +11 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pyproject.toml +7 -9
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/blocking_httpserver.py +2 -2
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/httpserver.py +73 -40
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/pytest_plugin.py +3 -3
- pytest_httpserver-1.1.2/tests/examples/test_howto_custom_request_matcher.py +37 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_blocking_httpserver.py +2 -2
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_log_leak.py +1 -1
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_log_querying.py +1 -1
- pytest_httpserver-1.1.2/tests/test_matcher.py +11 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_mixed.py +1 -1
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_port_changing.py +1 -1
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_release.py +15 -5
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_ssl.py +11 -4
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_threaded.py +2 -10
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/CONTRIBUTION.md +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/LICENSE +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/Makefile +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/_static/.placeholder +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/api.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/background.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/changes.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/fixtures.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/guide.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/index.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/patch.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/tutorial.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/doc/upgrade.rst +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/example.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/example_pytest.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/__init__.py +7 -7
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/hooks.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/py.typed +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/Makefile +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/README +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/rootCA.cnf +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/rootCA.crt +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/rootCA.key +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/rootCA.srl +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/server.cnf +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/server.crt +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/server.csr +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/server.key +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/assets/v3.ext +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/conftest.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_example_blocking_httpserver.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_example_query_params1.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_example_query_params2.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_authorization_headers.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_case_insensitive_matcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_check.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_check_handler_errors.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_custom_handler.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_custom_hooks.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_header_value_matcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_hooks.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_json_matcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_log_querying.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_query_params_dict.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_query_params_never_do_this.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_query_params_proper_use.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_regexp.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_timeout_requests.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_url_matcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_wait_success.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_handler_errors.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_headers.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_hooks.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_ip_protocols.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_json_matcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_oneshot.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_ordered.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_parse_qs.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_permanent.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_querymatcher.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_querystring.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_urimatch.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_wait.py +0 -0
- {pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/test_with_statement.py +0 -0
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
Release Notes
|
|
3
3
|
=============
|
|
4
4
|
|
|
5
|
+
.. _Release Notes_1.1.2:
|
|
6
|
+
|
|
7
|
+
1.1.2
|
|
8
|
+
=====
|
|
9
|
+
|
|
10
|
+
.. _Release Notes_1.1.2_Deprecation Notes:
|
|
11
|
+
|
|
12
|
+
Deprecation Notes
|
|
13
|
+
-----------------
|
|
14
|
+
|
|
15
|
+
- Python versions earlier than 3.9 have been deprecated in order to make the
|
|
16
|
+
code more type safe. Python 3.8 has reached EOL on 2024-10-07.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
.. _Release Notes_1.1.1:
|
|
20
|
+
|
|
21
|
+
1.1.1
|
|
22
|
+
=====
|
|
23
|
+
|
|
24
|
+
.. _Release Notes_1.1.1_New Features:
|
|
25
|
+
|
|
26
|
+
New Features
|
|
27
|
+
------------
|
|
28
|
+
|
|
29
|
+
- Add a new ``expect`` method to the ``HTTPServer`` object which allows
|
|
30
|
+
developers to provide their own request matcher object.
|
|
31
|
+
|
|
32
|
+
|
|
5
33
|
.. _Release Notes_1.1.0:
|
|
6
34
|
|
|
7
35
|
1.1.0
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytest_httpserver
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: pytest-httpserver is a httpserver for pytest
|
|
5
5
|
Home-page: https://github.com/csernazs/pytest-httpserver
|
|
6
6
|
License: MIT
|
|
7
7
|
Author: Zsolt Cserna
|
|
8
8
|
Author-email: cserna.zsolt@gmail.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Framework :: Pytest
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Requires-Dist: Werkzeug (>=2.0.0)
|
|
23
23
|
Project-URL: Bug Tracker, https://github.com/csernazs/pytest-httpserver/issues
|
|
@@ -25,7 +25,7 @@ Project-URL: Documentation, https://pytest-httpserver.readthedocs.io/en/latest/
|
|
|
25
25
|
Project-URL: Repository, https://github.com/csernazs/pytest-httpserver
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
|
-
[](https://github.com/csernazs/pytest-httpserver/actions?query=workflow%3Abuild+branch%3Amaster)
|
|
29
29
|
[](https://pytest-httpserver.readthedocs.io/en/latest/?badge=latest)
|
|
30
30
|
[](https://opensource.org/licenses/MIT)
|
|
31
31
|
[](https://codecov.io/gh/csernazs/pytest-httpserver)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/csernazs/pytest-httpserver/actions?query=workflow%3Abuild+branch%3Amaster)
|
|
2
2
|
[](https://pytest-httpserver.readthedocs.io/en/latest/?badge=latest)
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://codecov.io/gh/csernazs/pytest-httpserver)
|
|
@@ -238,6 +238,17 @@ these.
|
|
|
238
238
|
the server.
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
Using custom request matcher
|
|
242
|
+
----------------------------
|
|
243
|
+
In the case when you want to extend or modify the request matcher in
|
|
244
|
+
*pytest-httpserrver*, then you can use your own request matcher.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
|
|
248
|
+
.. literalinclude :: ../tests/examples/test_howto_custom_request_matcher.py
|
|
249
|
+
:language: python
|
|
250
|
+
|
|
251
|
+
|
|
241
252
|
Customizing host and port
|
|
242
253
|
-------------------------
|
|
243
254
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pytest_httpserver"
|
|
3
|
-
version = "1.1.
|
|
3
|
+
version = "1.1.2"
|
|
4
4
|
description = "pytest-httpserver is a httpserver for pytest"
|
|
5
5
|
authors = ["Zsolt Cserna <cserna.zsolt@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -24,7 +24,7 @@ include = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[tool.poetry.dependencies]
|
|
27
|
-
python = ">=3.
|
|
27
|
+
python = ">=3.9"
|
|
28
28
|
Werkzeug = ">= 2.0.0"
|
|
29
29
|
|
|
30
30
|
|
|
@@ -41,14 +41,13 @@ optional = true
|
|
|
41
41
|
pre-commit = ">=2.20,<4.0"
|
|
42
42
|
requests = "*"
|
|
43
43
|
Sphinx = ">=5.1.1,<8.0.0"
|
|
44
|
-
sphinx-rtd-theme = ">=1,<
|
|
44
|
+
sphinx-rtd-theme = ">=1,<4"
|
|
45
45
|
reno = "*"
|
|
46
46
|
types-requests = "*"
|
|
47
47
|
pytest = ">=7.1.3,<9.0.0"
|
|
48
48
|
pytest-cov = ">=3,<6"
|
|
49
49
|
coverage = ">=6.4.4,<8.0.0"
|
|
50
|
-
|
|
51
|
-
toml = "^0.10.2"
|
|
50
|
+
tomli = { version = "*", markers = "python_version < '3.11'"}
|
|
52
51
|
black = "*"
|
|
53
52
|
ruff = "*"
|
|
54
53
|
mypy = "*"
|
|
@@ -59,7 +58,7 @@ optional = true
|
|
|
59
58
|
|
|
60
59
|
[tool.poetry.group.doc.dependencies]
|
|
61
60
|
Sphinx = ">=5.1.1,<8.0.0"
|
|
62
|
-
sphinx-rtd-theme = ">=1,<
|
|
61
|
+
sphinx-rtd-theme = ">=1,<4"
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
[tool.poetry.group.test]
|
|
@@ -72,8 +71,7 @@ coverage = "*"
|
|
|
72
71
|
requests = "*"
|
|
73
72
|
types-requests = "*"
|
|
74
73
|
pre-commit = "*"
|
|
75
|
-
|
|
76
|
-
toml = "*"
|
|
74
|
+
tomli = { version = "*", markers = "python_version < '3.11'"}
|
|
77
75
|
mypy = "*"
|
|
78
76
|
|
|
79
77
|
[build-system]
|
|
@@ -139,5 +137,5 @@ lint.ignore = [
|
|
|
139
137
|
"UP032",
|
|
140
138
|
]
|
|
141
139
|
line-length = 120
|
|
142
|
-
target-version = "
|
|
140
|
+
target-version = "py39"
|
|
143
141
|
exclude = ["doc", "example*.py", "tests/examples/*.py"]
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/pytest_httpserver/blocking_httpserver.py
RENAMED
|
@@ -4,8 +4,6 @@ from queue import Empty
|
|
|
4
4
|
from queue import Queue
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
from typing import Any
|
|
7
|
-
from typing import Mapping
|
|
8
|
-
from typing import Pattern
|
|
9
7
|
|
|
10
8
|
from pytest_httpserver.httpserver import METHOD_ALL
|
|
11
9
|
from pytest_httpserver.httpserver import UNDEFINED
|
|
@@ -16,6 +14,8 @@ from pytest_httpserver.httpserver import RequestHandlerBase
|
|
|
16
14
|
from pytest_httpserver.httpserver import URIPattern
|
|
17
15
|
|
|
18
16
|
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from re import Pattern
|
|
19
19
|
from ssl import SSLContext
|
|
20
20
|
|
|
21
21
|
from werkzeug import Request
|
|
@@ -9,20 +9,19 @@ import threading
|
|
|
9
9
|
import time
|
|
10
10
|
import urllib.parse
|
|
11
11
|
from collections import defaultdict
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from collections.abc import MutableMapping
|
|
12
15
|
from contextlib import contextmanager
|
|
13
16
|
from contextlib import suppress
|
|
14
17
|
from copy import copy
|
|
15
18
|
from enum import Enum
|
|
19
|
+
from re import Pattern
|
|
16
20
|
from typing import TYPE_CHECKING
|
|
17
21
|
from typing import Any
|
|
18
22
|
from typing import Callable
|
|
19
23
|
from typing import ClassVar
|
|
20
|
-
from typing import Iterable
|
|
21
|
-
from typing import Mapping
|
|
22
|
-
from typing import MutableMapping
|
|
23
24
|
from typing import Optional
|
|
24
|
-
from typing import Pattern
|
|
25
|
-
from typing import Tuple
|
|
26
25
|
from typing import Union
|
|
27
26
|
|
|
28
27
|
import werkzeug.http
|
|
@@ -34,13 +33,16 @@ from werkzeug.serving import make_server
|
|
|
34
33
|
|
|
35
34
|
if TYPE_CHECKING:
|
|
36
35
|
from ssl import SSLContext
|
|
36
|
+
from types import TracebackType
|
|
37
|
+
|
|
38
|
+
from werkzeug.serving import BaseWSGIServer
|
|
37
39
|
|
|
38
40
|
URI_DEFAULT = ""
|
|
39
41
|
METHOD_ALL = "__ALL"
|
|
40
42
|
|
|
41
43
|
HEADERS_T = Union[
|
|
42
44
|
Mapping[str, Union[str, Iterable[str]]],
|
|
43
|
-
Iterable[
|
|
45
|
+
Iterable[tuple[str, str]],
|
|
44
46
|
]
|
|
45
47
|
|
|
46
48
|
HVMATCHER_T = Callable[[str, Optional[str], str], bool]
|
|
@@ -113,11 +115,13 @@ class Waiting:
|
|
|
113
115
|
|
|
114
116
|
@property
|
|
115
117
|
def result(self) -> bool:
|
|
116
|
-
return self._result
|
|
118
|
+
return bool(self._result)
|
|
117
119
|
|
|
118
120
|
@property
|
|
119
121
|
def elapsed_time(self) -> float:
|
|
120
122
|
"""Elapsed time in seconds"""
|
|
123
|
+
if self._stop is None:
|
|
124
|
+
raise TypeError("unsupported operand type(s) for -: 'NoneType' and 'float'")
|
|
121
125
|
return self._stop - self._start
|
|
122
126
|
|
|
123
127
|
|
|
@@ -139,7 +143,7 @@ class HeaderValueMatcher:
|
|
|
139
143
|
func = getattr(Authorization, "from_header", None)
|
|
140
144
|
if func is None: # Werkzeug < 2.3.0
|
|
141
145
|
func = werkzeug.http.parse_authorization_header # type: ignore[attr-defined]
|
|
142
|
-
return func(actual) == func(expected)
|
|
146
|
+
return func(actual) == func(expected) # type: ignore
|
|
143
147
|
|
|
144
148
|
@staticmethod
|
|
145
149
|
def default_header_value_matcher(actual: str | None, expected: str) -> bool:
|
|
@@ -174,7 +178,7 @@ class QueryMatcher(abc.ABC):
|
|
|
174
178
|
return values[0] == values[1]
|
|
175
179
|
|
|
176
180
|
@abc.abstractmethod
|
|
177
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
181
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[Any, Any]:
|
|
178
182
|
pass
|
|
179
183
|
|
|
180
184
|
|
|
@@ -195,10 +199,10 @@ class StringQueryMatcher(QueryMatcher):
|
|
|
195
199
|
|
|
196
200
|
self.query_string = query_string
|
|
197
201
|
|
|
198
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
202
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[bytes, bytes]:
|
|
199
203
|
if isinstance(self.query_string, str):
|
|
200
204
|
query_string = self.query_string.encode()
|
|
201
|
-
elif isinstance(self.query_string, bytes):
|
|
205
|
+
elif isinstance(self.query_string, bytes): # type: ignore
|
|
202
206
|
query_string = self.query_string
|
|
203
207
|
else:
|
|
204
208
|
raise TypeError("query_string must be a string, or a bytes-like object")
|
|
@@ -211,7 +215,7 @@ class MappingQueryMatcher(QueryMatcher):
|
|
|
211
215
|
Matches a query string to a dictionary or MultiDict specified
|
|
212
216
|
"""
|
|
213
217
|
|
|
214
|
-
def __init__(self, query_dict: Mapping | MultiDict):
|
|
218
|
+
def __init__(self, query_dict: Mapping[str, str] | MultiDict[str, str]):
|
|
215
219
|
"""
|
|
216
220
|
:param query_dict: if dictionary (Mapping) is specified, it will be used as a
|
|
217
221
|
key-value mapping where both key and value should be string. If there are multiple
|
|
@@ -221,7 +225,7 @@ class MappingQueryMatcher(QueryMatcher):
|
|
|
221
225
|
"""
|
|
222
226
|
self.query_dict = query_dict
|
|
223
227
|
|
|
224
|
-
def get_comparing_values(self, request_query_string: bytes) -> tuple:
|
|
228
|
+
def get_comparing_values(self, request_query_string: bytes) -> tuple[Mapping[str, str], Mapping[str, str]]:
|
|
225
229
|
query = MultiDict(urllib.parse.parse_qsl(request_query_string.decode("utf-8")))
|
|
226
230
|
if isinstance(self.query_dict, MultiDict):
|
|
227
231
|
return (query, self.query_dict)
|
|
@@ -241,14 +245,14 @@ class BooleanQueryMatcher(QueryMatcher):
|
|
|
241
245
|
"""
|
|
242
246
|
self.result = result
|
|
243
247
|
|
|
244
|
-
def get_comparing_values(self, request_query_string): # noqa: ARG002
|
|
248
|
+
def get_comparing_values(self, request_query_string: bytes): # noqa: ARG002
|
|
245
249
|
if self.result:
|
|
246
250
|
return (True, True)
|
|
247
251
|
else:
|
|
248
252
|
return (True, False)
|
|
249
253
|
|
|
250
254
|
|
|
251
|
-
def _create_query_matcher(query_string: None | QueryMatcher | str | bytes | Mapping) -> QueryMatcher:
|
|
255
|
+
def _create_query_matcher(query_string: None | QueryMatcher | str | bytes | Mapping[str, str]) -> QueryMatcher:
|
|
252
256
|
if isinstance(query_string, QueryMatcher):
|
|
253
257
|
return query_string
|
|
254
258
|
|
|
@@ -312,7 +316,7 @@ class RequestMatcher:
|
|
|
312
316
|
data: str | bytes | None = None,
|
|
313
317
|
data_encoding: str = "utf-8",
|
|
314
318
|
headers: Mapping[str, str] | None = None,
|
|
315
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
319
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
316
320
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
317
321
|
json: Any = UNDEFINED,
|
|
318
322
|
):
|
|
@@ -410,7 +414,7 @@ class RequestMatcher:
|
|
|
410
414
|
|
|
411
415
|
return json_received == self.json
|
|
412
416
|
|
|
413
|
-
def difference(self, request: Request) -> list[tuple]:
|
|
417
|
+
def difference(self, request: Request) -> list[tuple[str, str, str | URIPattern]]:
|
|
414
418
|
"""
|
|
415
419
|
Calculates the difference between the matcher and the request.
|
|
416
420
|
|
|
@@ -422,7 +426,7 @@ class RequestMatcher:
|
|
|
422
426
|
matches the fields set in the matcher object.
|
|
423
427
|
"""
|
|
424
428
|
|
|
425
|
-
retval: list[tuple] = []
|
|
429
|
+
retval: list[tuple[str, Any, Any]] = []
|
|
426
430
|
|
|
427
431
|
if not self.match_uri(request):
|
|
428
432
|
retval.append(("uri", request.path, self.uri))
|
|
@@ -433,8 +437,8 @@ class RequestMatcher:
|
|
|
433
437
|
if not self.query_matcher.match(request.query_string):
|
|
434
438
|
retval.append(("query_string", request.query_string, self.query_string))
|
|
435
439
|
|
|
436
|
-
request_headers = {}
|
|
437
|
-
expected_headers = {}
|
|
440
|
+
request_headers: dict[str, str | None] = {}
|
|
441
|
+
expected_headers: dict[str, str] = {}
|
|
438
442
|
for key, value in self.headers.items():
|
|
439
443
|
if not self.header_value_matcher(key, request.headers.get(key), value):
|
|
440
444
|
request_headers[key] = request.headers.get(key)
|
|
@@ -467,7 +471,7 @@ class RequestHandlerBase(abc.ABC):
|
|
|
467
471
|
|
|
468
472
|
def respond_with_json(
|
|
469
473
|
self,
|
|
470
|
-
response_json,
|
|
474
|
+
response_json: Any,
|
|
471
475
|
status: int = 200,
|
|
472
476
|
headers: Mapping[str, str] | None = None,
|
|
473
477
|
content_type: str = "application/json",
|
|
@@ -578,7 +582,7 @@ class RequestHandler(RequestHandlerBase):
|
|
|
578
582
|
return retval
|
|
579
583
|
|
|
580
584
|
|
|
581
|
-
class RequestHandlerList(list):
|
|
585
|
+
class RequestHandlerList(list[RequestHandler]):
|
|
582
586
|
"""
|
|
583
587
|
Represents a list of :py:class:`RequestHandler` objects.
|
|
584
588
|
|
|
@@ -638,9 +642,9 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
638
642
|
"""
|
|
639
643
|
self.host = host
|
|
640
644
|
self.port = port
|
|
641
|
-
self.server = None
|
|
642
|
-
self.server_thread = None
|
|
643
|
-
self.assertions: list[str] = []
|
|
645
|
+
self.server: BaseWSGIServer | None = None
|
|
646
|
+
self.server_thread: threading.Thread | None = None
|
|
647
|
+
self.assertions: list[str | AssertionError] = []
|
|
644
648
|
self.handler_errors: list[Exception] = []
|
|
645
649
|
self.log: list[tuple[Request, Response]] = []
|
|
646
650
|
self.ssl_context = ssl_context
|
|
@@ -727,7 +731,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
727
731
|
|
|
728
732
|
This should not be called directly, but can be overridden to tailor it to your needs.
|
|
729
733
|
"""
|
|
730
|
-
|
|
734
|
+
assert self.server is not None
|
|
731
735
|
self.server.serve_forever()
|
|
732
736
|
|
|
733
737
|
def is_running(self) -> bool:
|
|
@@ -736,7 +740,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
736
740
|
"""
|
|
737
741
|
return bool(self.server)
|
|
738
742
|
|
|
739
|
-
def start(self):
|
|
743
|
+
def start(self) -> None:
|
|
740
744
|
"""
|
|
741
745
|
Start the server in a thread.
|
|
742
746
|
|
|
@@ -755,9 +759,16 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
755
759
|
if self.is_running():
|
|
756
760
|
raise HTTPServerError("Server is already running")
|
|
757
761
|
|
|
762
|
+
app = Request.application(self.application)
|
|
763
|
+
|
|
758
764
|
self.server = make_server(
|
|
759
|
-
self.host,
|
|
765
|
+
self.host,
|
|
766
|
+
self.port,
|
|
767
|
+
app,
|
|
768
|
+
ssl_context=self.ssl_context,
|
|
769
|
+
threaded=self.threaded,
|
|
760
770
|
)
|
|
771
|
+
|
|
761
772
|
self.port = self.server.port # Update port (needed if `port` was set to 0)
|
|
762
773
|
self.server_thread = threading.Thread(target=self.thread_target)
|
|
763
774
|
self.server_thread.start()
|
|
@@ -772,6 +783,8 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
772
783
|
Only a running server can be stopped. If the sever is not running, :py:class`HTTPServerError`
|
|
773
784
|
will be raised.
|
|
774
785
|
"""
|
|
786
|
+
assert self.server is not None
|
|
787
|
+
assert self.server_thread is not None
|
|
775
788
|
if not self.is_running():
|
|
776
789
|
raise HTTPServerError("Server is not running")
|
|
777
790
|
self.server.shutdown()
|
|
@@ -779,7 +792,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
779
792
|
self.server = None
|
|
780
793
|
self.server_thread = None
|
|
781
794
|
|
|
782
|
-
def add_assertion(self, obj):
|
|
795
|
+
def add_assertion(self, obj: str | AssertionError):
|
|
783
796
|
"""
|
|
784
797
|
Add a new assertion
|
|
785
798
|
|
|
@@ -848,8 +861,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
848
861
|
:return: the response object what the handler responded, or a response which contains the error
|
|
849
862
|
"""
|
|
850
863
|
|
|
851
|
-
|
|
852
|
-
def application(self, request: Request):
|
|
864
|
+
def application(self, request: Request) -> Response:
|
|
853
865
|
"""
|
|
854
866
|
Entry point of werkzeug.
|
|
855
867
|
|
|
@@ -875,7 +887,12 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
875
887
|
self.start()
|
|
876
888
|
return self
|
|
877
889
|
|
|
878
|
-
def __exit__(
|
|
890
|
+
def __exit__(
|
|
891
|
+
self,
|
|
892
|
+
exc_type: type[BaseException] | None,
|
|
893
|
+
exc_value: BaseException | None,
|
|
894
|
+
traceback: TracebackType | None,
|
|
895
|
+
):
|
|
879
896
|
"""
|
|
880
897
|
Provide the context API
|
|
881
898
|
|
|
@@ -886,7 +903,7 @@ class HTTPServerBase(abc.ABC): # pylint: disable=too-many-instance-attributes
|
|
|
886
903
|
self.stop()
|
|
887
904
|
|
|
888
905
|
@staticmethod
|
|
889
|
-
def format_host(host):
|
|
906
|
+
def format_host(host: str):
|
|
890
907
|
"""
|
|
891
908
|
Formats a hostname so it can be used in a URL.
|
|
892
909
|
Notably, this adds brackets around IPV6 addresses when
|
|
@@ -929,8 +946,8 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
929
946
|
|
|
930
947
|
def __init__(
|
|
931
948
|
self,
|
|
932
|
-
host=DEFAULT_LISTEN_HOST,
|
|
933
|
-
port=DEFAULT_LISTEN_PORT,
|
|
949
|
+
host: str = DEFAULT_LISTEN_HOST,
|
|
950
|
+
port: int = DEFAULT_LISTEN_PORT,
|
|
934
951
|
ssl_context: SSLContext | None = None,
|
|
935
952
|
default_waiting_settings: WaitingSettings | None = None,
|
|
936
953
|
*,
|
|
@@ -972,6 +989,22 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
972
989
|
self.oneshot_handlers = RequestHandlerList()
|
|
973
990
|
self.handlers = RequestHandlerList()
|
|
974
991
|
|
|
992
|
+
def expect(self, matcher: RequestMatcher, handler_type: HandlerType = HandlerType.PERMANENT) -> RequestHandler:
|
|
993
|
+
"""
|
|
994
|
+
Create and register a request handler.
|
|
995
|
+
|
|
996
|
+
:param matcher: :py:class:`RequestMatcher` used to match requests.
|
|
997
|
+
:param handler_type: type of handler
|
|
998
|
+
"""
|
|
999
|
+
request_handler = RequestHandler(matcher)
|
|
1000
|
+
if handler_type == HandlerType.PERMANENT:
|
|
1001
|
+
self.handlers.append(request_handler)
|
|
1002
|
+
elif handler_type == HandlerType.ONESHOT:
|
|
1003
|
+
self.oneshot_handlers.append(request_handler)
|
|
1004
|
+
elif handler_type == HandlerType.ORDERED:
|
|
1005
|
+
self.ordered_handlers.append(request_handler)
|
|
1006
|
+
return request_handler
|
|
1007
|
+
|
|
975
1008
|
def expect_request(
|
|
976
1009
|
self,
|
|
977
1010
|
uri: str | URIPattern | Pattern[str],
|
|
@@ -979,7 +1012,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
979
1012
|
data: str | bytes | None = None,
|
|
980
1013
|
data_encoding: str = "utf-8",
|
|
981
1014
|
headers: Mapping[str, str] | None = None,
|
|
982
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1015
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
983
1016
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
984
1017
|
handler_type: HandlerType = HandlerType.PERMANENT,
|
|
985
1018
|
json: Any = UNDEFINED,
|
|
@@ -1062,7 +1095,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1062
1095
|
data: str | bytes | None = None,
|
|
1063
1096
|
data_encoding: str = "utf-8",
|
|
1064
1097
|
headers: Mapping[str, str] | None = None,
|
|
1065
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1098
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
1066
1099
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
1067
1100
|
json: Any = UNDEFINED,
|
|
1068
1101
|
) -> RequestHandler:
|
|
@@ -1117,7 +1150,7 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1117
1150
|
data: str | bytes | None = None,
|
|
1118
1151
|
data_encoding: str = "utf-8",
|
|
1119
1152
|
headers: Mapping[str, str] | None = None,
|
|
1120
|
-
query_string: None | QueryMatcher | str | bytes | Mapping = None,
|
|
1153
|
+
query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None,
|
|
1121
1154
|
header_value_matcher: HVMATCHER_T | None = None,
|
|
1122
1155
|
json: Any = UNDEFINED,
|
|
1123
1156
|
) -> RequestHandler:
|
|
@@ -1175,13 +1208,13 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
|
|
|
1175
1208
|
This method is primarily used when reporting errors.
|
|
1176
1209
|
"""
|
|
1177
1210
|
|
|
1178
|
-
def format_handlers(handlers):
|
|
1211
|
+
def format_handlers(handlers: list[RequestHandler]):
|
|
1179
1212
|
if handlers:
|
|
1180
1213
|
return [" {!r}".format(handler.matcher) for handler in handlers]
|
|
1181
1214
|
else:
|
|
1182
1215
|
return [" none"]
|
|
1183
1216
|
|
|
1184
|
-
lines = []
|
|
1217
|
+
lines: list[str] = []
|
|
1185
1218
|
lines.append("Ordered matchers:")
|
|
1186
1219
|
lines.extend(format_handlers(self.ordered_handlers))
|
|
1187
1220
|
lines.append("")
|
|
@@ -61,7 +61,7 @@ def pytest_sessionfinish(session, exitstatus): # noqa: ARG001
|
|
|
61
61
|
Plugin.SERVER.stop()
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
@pytest.fixture
|
|
64
|
+
@pytest.fixture
|
|
65
65
|
def httpserver(make_httpserver):
|
|
66
66
|
server = make_httpserver
|
|
67
67
|
server.clear()
|
|
@@ -78,7 +78,7 @@ def make_httpserver_ipv4(httpserver_ssl_context):
|
|
|
78
78
|
server.stop()
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
@pytest.fixture
|
|
81
|
+
@pytest.fixture
|
|
82
82
|
def httpserver_ipv4(make_httpserver_ipv4):
|
|
83
83
|
server = make_httpserver_ipv4
|
|
84
84
|
server.clear()
|
|
@@ -95,7 +95,7 @@ def make_httpserver_ipv6(httpserver_ssl_context):
|
|
|
95
95
|
server.stop()
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
@pytest.fixture
|
|
98
|
+
@pytest.fixture
|
|
99
99
|
def httpserver_ipv6(make_httpserver_ipv6):
|
|
100
100
|
server = make_httpserver_ipv6
|
|
101
101
|
server.clear()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from werkzeug import Request
|
|
3
|
+
|
|
4
|
+
from pytest_httpserver import HTTPServer
|
|
5
|
+
from pytest_httpserver import RequestMatcher
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MyMatcher(RequestMatcher):
|
|
9
|
+
def match(self, request: Request) -> bool:
|
|
10
|
+
match = super().match(request)
|
|
11
|
+
if not match: # existing parameters didn't match -> return with False
|
|
12
|
+
return match
|
|
13
|
+
|
|
14
|
+
# match the json's "value" key: if it is an integer and it is an even
|
|
15
|
+
# number, it returns True
|
|
16
|
+
json = request.json
|
|
17
|
+
if isinstance(json, dict) and isinstance(json.get("value"), int):
|
|
18
|
+
return json["value"] % 2 == 0
|
|
19
|
+
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_custom_request_matcher(httpserver: HTTPServer):
|
|
24
|
+
httpserver.expect(MyMatcher("/foo")).respond_with_data("OK")
|
|
25
|
+
|
|
26
|
+
# with even number it matches the request
|
|
27
|
+
resp = requests.post(httpserver.url_for("/foo"), json={"value": 42})
|
|
28
|
+
resp.raise_for_status()
|
|
29
|
+
assert resp.text == "OK"
|
|
30
|
+
|
|
31
|
+
resp = requests.post(httpserver.url_for("/foo"), json={"value": 198})
|
|
32
|
+
resp.raise_for_status()
|
|
33
|
+
assert resp.text == "OK"
|
|
34
|
+
|
|
35
|
+
# with an odd number, it does not match the request
|
|
36
|
+
resp = requests.post(httpserver.url_for("/foo"), json={"value": 43})
|
|
37
|
+
assert resp.status_code == 500
|
|
@@ -40,7 +40,7 @@ def then_the_response_is_got_from(server_connection, response):
|
|
|
40
40
|
assert server_connection.get(timeout=9).json() == response
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
@pytest.fixture
|
|
43
|
+
@pytest.fixture
|
|
44
44
|
def httpserver():
|
|
45
45
|
server = BlockingHTTPServer(timeout=1)
|
|
46
46
|
server.start()
|
|
@@ -122,4 +122,4 @@ def test_raises_assertion_error_when_request_was_not_responded(httpserver: Block
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
def test_repr(httpserver: BlockingHTTPServer):
|
|
125
|
-
assert repr(httpserver) == f"<BlockingHTTPServer host=
|
|
125
|
+
assert repr(httpserver) == f"<BlockingHTTPServer host={httpserver.host} port={httpserver.port}>"
|
|
@@ -47,7 +47,7 @@ def test_verify_assert_msg(httpserver: HTTPServer):
|
|
|
47
47
|
"Path: /foo",
|
|
48
48
|
"Method: GET",
|
|
49
49
|
"Body: b''",
|
|
50
|
-
f"Headers: Host:
|
|
50
|
+
f"Headers: Host: {httpserver.host}:{httpserver.port}",
|
|
51
51
|
"User-Agent: requests",
|
|
52
52
|
"Accept-Encoding: gzip, deflate",
|
|
53
53
|
"Accept: */*",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from pytest_httpserver import HTTPServer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_expect_method(httpserver: HTTPServer):
|
|
7
|
+
expected_response = "OK"
|
|
8
|
+
matcher = httpserver.create_matcher(uri="/test", method="POST")
|
|
9
|
+
httpserver.expect(matcher).respond_with_data(expected_response)
|
|
10
|
+
resp = requests.post(httpserver.url_for("/test"), json={"list": [1, 2, 3, 4]})
|
|
11
|
+
assert resp.text == expected_response
|
|
@@ -85,4 +85,4 @@ def test_all_ordered_missing(httpserver: HTTPServer):
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def test_repr(httpserver: HTTPServer):
|
|
88
|
-
assert repr(httpserver) == f"<HTTPServer host=
|
|
88
|
+
assert repr(httpserver) == f"<HTTPServer host={httpserver.host} port={httpserver.port}>"
|
|
@@ -8,10 +8,19 @@ import tarfile
|
|
|
8
8
|
import zipfile
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
import pytest
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Iterable
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import tomllib
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Unfortunately mypy cannot handle this try/expect pattern, and "type: ignore"
|
|
22
|
+
# is the simplest work-around. See: https://github.com/python/mypy/issues/1153
|
|
23
|
+
import tomli as tomllib # type: ignore
|
|
15
24
|
|
|
16
25
|
# TODO: skip if poetry is not available or add mark to test it explicitly
|
|
17
26
|
|
|
@@ -20,7 +29,7 @@ pytestmark = pytest.mark.release
|
|
|
20
29
|
|
|
21
30
|
NAME = "pytest-httpserver"
|
|
22
31
|
NAME_UNDERSCORE = NAME.replace("-", "_")
|
|
23
|
-
PY_MAX_VERSION = (3,
|
|
32
|
+
PY_MAX_VERSION = (3, 13)
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
@pytest.fixture(scope="session")
|
|
@@ -31,8 +40,8 @@ def pyproject_path() -> Path:
|
|
|
31
40
|
@pytest.fixture(scope="session")
|
|
32
41
|
def pyproject(pyproject_path: Path):
|
|
33
42
|
assert pyproject_path.is_file()
|
|
34
|
-
with pyproject_path.open() as infile:
|
|
35
|
-
pyproject =
|
|
43
|
+
with pyproject_path.open("rb") as infile:
|
|
44
|
+
pyproject = tomllib.load(infile)
|
|
36
45
|
return pyproject
|
|
37
46
|
|
|
38
47
|
|
|
@@ -228,6 +237,7 @@ def test_sdist_contents(build: Build, version: str):
|
|
|
228
237
|
"test_urimatch.py",
|
|
229
238
|
"test_wait.py",
|
|
230
239
|
"test_with_statement.py",
|
|
240
|
+
"test_matcher.py",
|
|
231
241
|
},
|
|
232
242
|
}
|
|
233
243
|
|
|
@@ -5,6 +5,8 @@ from os.path import join as pjoin
|
|
|
5
5
|
import pytest
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
+
from pytest_httpserver import HTTPServer
|
|
9
|
+
|
|
8
10
|
pytestmark = pytest.mark.ssl
|
|
9
11
|
|
|
10
12
|
test_dir = os.path.dirname(os.path.realpath(__file__))
|
|
@@ -24,18 +26,23 @@ def httpserver_ssl_context():
|
|
|
24
26
|
return ssl.SSLContext(protocol)
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
def test_ssl(httpserver):
|
|
29
|
+
def test_ssl(httpserver: HTTPServer):
|
|
28
30
|
server_crt = pjoin(assets_dir, "server.crt")
|
|
29
31
|
server_key = pjoin(assets_dir, "server.key")
|
|
30
32
|
root_ca = pjoin(assets_dir, "rootCA.crt")
|
|
31
|
-
context = httpserver.ssl_context
|
|
32
33
|
|
|
33
34
|
assert (
|
|
34
|
-
|
|
35
|
+
httpserver.ssl_context is not None
|
|
35
36
|
), "SSLContext not set. The session was probably started with a test that did not define an SSLContext."
|
|
36
37
|
|
|
37
38
|
httpserver.ssl_context.load_cert_chain(server_crt, server_key)
|
|
38
39
|
httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})
|
|
39
40
|
|
|
40
41
|
assert httpserver.is_running()
|
|
41
|
-
|
|
42
|
+
|
|
43
|
+
assert httpserver.url_for("/").startswith("https://")
|
|
44
|
+
|
|
45
|
+
# ensure we are using "localhost" and not "127.0.0.1" to pass cert verification
|
|
46
|
+
url = f"https://localhost:{httpserver.port}/foobar"
|
|
47
|
+
|
|
48
|
+
assert requests.get(url, verify=root_ca).json() == {"foo": "bar"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http.client
|
|
2
2
|
import threading
|
|
3
3
|
import time
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Iterable
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
from werkzeug import Request
|
|
@@ -10,7 +10,7 @@ from werkzeug import Response
|
|
|
10
10
|
from pytest_httpserver import HTTPServer
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@pytest.fixture
|
|
13
|
+
@pytest.fixture
|
|
14
14
|
def threaded() -> Iterable[HTTPServer]:
|
|
15
15
|
server = HTTPServer(threaded=True)
|
|
16
16
|
server.start()
|
|
@@ -33,8 +33,6 @@ def test_threaded(threaded: HTTPServer):
|
|
|
33
33
|
|
|
34
34
|
threaded.expect_request("/foo").respond_with_handler(handler)
|
|
35
35
|
|
|
36
|
-
t_start = time.perf_counter()
|
|
37
|
-
|
|
38
36
|
number_of_connections = 5
|
|
39
37
|
conns = [http.client.HTTPConnection(threaded.host, threaded.port) for _ in range(number_of_connections)]
|
|
40
38
|
|
|
@@ -51,10 +49,4 @@ def test_threaded(threaded: HTTPServer):
|
|
|
51
49
|
for conn in conns:
|
|
52
50
|
conn.close()
|
|
53
51
|
|
|
54
|
-
t_elapsed = time.perf_counter() - t_start
|
|
55
|
-
|
|
56
52
|
assert len(thread_ids) == len(set(thread_ids)), "thread ids returned should be unique"
|
|
57
|
-
|
|
58
|
-
assert (
|
|
59
|
-
t_elapsed < number_of_connections * sleep_time * 0.9
|
|
60
|
-
), "elapsed time should be less than processing sequential requests"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -4,19 +4,19 @@ This is package provides the main API for the pytest_httpserver package.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
|
+
"METHOD_ALL",
|
|
8
|
+
"URI_DEFAULT",
|
|
9
|
+
"BlockingHTTPServer",
|
|
10
|
+
"BlockingRequestHandler",
|
|
11
|
+
"Error",
|
|
7
12
|
"HTTPServer",
|
|
8
13
|
"HTTPServerError",
|
|
9
|
-
"Error",
|
|
10
|
-
"NoHandlerError",
|
|
11
|
-
"WaitingSettings",
|
|
12
14
|
"HeaderValueMatcher",
|
|
15
|
+
"NoHandlerError",
|
|
13
16
|
"RequestHandler",
|
|
14
17
|
"RequestMatcher",
|
|
15
18
|
"URIPattern",
|
|
16
|
-
"
|
|
17
|
-
"METHOD_ALL",
|
|
18
|
-
"BlockingHTTPServer",
|
|
19
|
-
"BlockingRequestHandler",
|
|
19
|
+
"WaitingSettings",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
from .blocking_httpserver import BlockingHTTPServer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_example_query_params1.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_example_query_params2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_custom_handler.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_custom_hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_json_matcher.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_log_querying.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_query_params_dict.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_timeout_requests.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_url_matcher.py
RENAMED
|
File without changes
|
{pytest_httpserver-1.1.0 → pytest_httpserver-1.1.2}/tests/examples/test_howto_wait_success.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|