Skip to content

Commit d3a02cc

Browse files
rominfcsernazs
authored andcommitted
Unify expect_request functions
This change is not backward compatible! 1. Define enum `HandlerType`. 2. Make `expect_request` universal so that it could register permanent, oneshot, and ordered handlers by adding enum `HandlerType` argument with default value `HandlerType.PERMANENT`. 3. Remove ordered argument from expect_oneshot_request. 4. Add convenience method expect_ordered_request. Fixes #9.
1 parent d93a168 commit d3a02cc

4 files changed

Lines changed: 118 additions & 71 deletions

File tree

pytest_httpserver/httpserver.py

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import threading
33
import json
44
from collections import defaultdict
5+
from enum import Enum
56
from typing import Callable, Mapping, Optional, Union
67
from ssl import SSLContext
78

@@ -296,6 +297,12 @@ def match(self, request: Request) -> RequestHandler:
296297
return None
297298

298299

300+
class HandlerType(Enum):
301+
PERMANENT = 'permanent'
302+
ONESHOT = 'oneshot'
303+
ORDERED = 'ordered'
304+
305+
299306
class HTTPServer: # pylint: disable=too-many-instance-attributes
300307
"""
301308
Server instance which manages handlers to serve pre-defined requests.
@@ -398,29 +405,30 @@ def create_matcher(self, *args, **kwargs) -> RequestMatcher:
398405

399406
return RequestMatcher(*args, **kwargs)
400407

401-
def expect_oneshot_request(
408+
def expect_request(
402409
self,
403410
uri: str,
404411
method: str = METHOD_ALL,
405412
data: Union[str, bytes, None] = None,
406413
data_encoding: str = "utf-8",
407414
headers: Optional[Mapping[str, str]] = None,
408415
query_string: Optional[str] = None,
409-
*,
410-
ordered=False,
411-
header_value_matcher: Optional[HeaderValueMatcher] = None) -> RequestHandler:
416+
header_value_matcher: Optional[HeaderValueMatcher] = None,
417+
handler_type: HandlerType = HandlerType.PERMANENT) -> RequestHandler:
412418
"""
413-
Create and register a oneshot request handler.
419+
Create and register a request handler.
414420
415-
This handler can be only used once. Once the server serves a response for this handler,
416-
the handler will be dropped.
421+
If `handler_type` is `HandlerType.PERMANENT` a permanent request handler is created. This handler can be used as
422+
many times as the request matches it, but ordered handlers have higher priority so if there's one or more
423+
ordered handler registered, those must be used first.
417424
418-
Ordered handler (when `ordered` parameter is `True`) also determines the
419-
order of the requests to be served. For example if there are two ordered handlers
420-
registered, the first request must hit the first handler, and the second request must hit the
421-
second one, and not vica versa.
425+
If `handler_type` is `HandlerType.ONESHOT` a oneshot request handler is created. This handler can be only used
426+
once. Once the server serves a response for this handler, the handler will be dropped.
422427
423-
If one or more ordered handler defined, those must be exhausted first.
428+
If `handler_type` is `HandlerType.ORDERED` an ordered request handler is created. Comparing to oneshot handler,
429+
ordered handler also determines the order of the requests to be served. For example if there are two ordered
430+
handlers registered, the first request must hit the first handler, and the second request must hit the second
431+
one, and not vice versa. If one or more ordered handler defined, those must be exhausted first.
424432
425433
:param uri: URI of the request. This must be an absolute path starting with ``/``.
426434
:param method: HTTP method of the request. If not specified (or `METHOD_ALL`
@@ -430,8 +438,8 @@ def expect_oneshot_request(
430438
:param data_encoding: the encoding used for data parameter if data is a string.
431439
:param headers: dictionary of the headers of the request to be matched
432440
:param query_string: the http query string starting with ``?``, such as ``?username=user``
433-
:param ordered: specifies whether to create an ordered handler or not. See above for details.
434441
:param header_value_matcher: :py:class:`HeaderValueMatcher` that matches values of headers.
442+
:param handler_type: type of handler
435443
436444
:return: Created and register :py:class:`RequestHandler`.
437445
"""
@@ -446,14 +454,15 @@ def expect_oneshot_request(
446454
header_value_matcher=header_value_matcher,
447455
)
448456
request_handler = RequestHandler(matcher)
449-
if ordered:
450-
self.ordered_handlers.append(request_handler)
451-
else:
457+
if handler_type == HandlerType.PERMANENT:
458+
self.handlers.append(request_handler)
459+
elif handler_type == HandlerType.ONESHOT:
452460
self.oneshot_handlers.append(request_handler)
453-
461+
elif handler_type == HandlerType.ORDERED:
462+
self.ordered_handlers.append(request_handler)
454463
return request_handler
455464

456-
def expect_request(
465+
def expect_oneshot_request(
457466
self,
458467
uri: str,
459468
method: str = METHOD_ALL,
@@ -463,10 +472,9 @@ def expect_request(
463472
query_string: Optional[str] = None,
464473
header_value_matcher: Optional[HeaderValueMatcher] = None) -> RequestHandler:
465474
"""
466-
Create and register a permanent request handler.
475+
Create and register a oneshot request handler.
467476
468-
This handler can be used as many times as the request matches it, but ordered handlers
469-
have higher priority so if there's one or more ordered handler registered, those must be used first.
477+
This is a method for convenience. See :py:meth:`expect_request` for documentation.
470478
471479
:param uri: URI of the request. This must be an absolute path starting with ``/``.
472480
:param method: HTTP method of the request. If not specified (or `METHOD_ALL`
@@ -475,24 +483,60 @@ def expect_request(
475483
by default, see `data_encoding`) or a bytes object.
476484
:param data_encoding: the encoding used for data parameter if data is a string.
477485
:param headers: dictionary of the headers of the request to be matched
478-
:param ordered: specifies whether to create an ordered handler or not. See above for details.
486+
:param query_string: the http query string starting with ``?``, such as ``?username=user``
479487
:param header_value_matcher: :py:class:`HeaderValueMatcher` that matches values of headers.
480488
481489
:return: Created and register :py:class:`RequestHandler`.
482490
"""
483491

484-
matcher = self.create_matcher(
485-
uri,
492+
return self.expect_request(
493+
uri=uri,
486494
method=method,
487495
data=data,
488496
data_encoding=data_encoding,
489497
headers=headers,
490498
query_string=query_string,
491499
header_value_matcher=header_value_matcher,
500+
handler_type=HandlerType.ONESHOT,
501+
)
502+
503+
def expect_ordered_request(
504+
self,
505+
uri: str,
506+
method: str = METHOD_ALL,
507+
data: Union[str, bytes, None] = None,
508+
data_encoding: str = "utf-8",
509+
headers: Optional[Mapping[str, str]] = None,
510+
query_string: Optional[str] = None,
511+
header_value_matcher: Optional[HeaderValueMatcher] = None) -> RequestHandler:
512+
"""
513+
Create and register a ordered request handler.
514+
515+
This is a method for convenience. See :py:meth:`expect_request` for documentation.
516+
517+
:param uri: URI of the request. This must be an absolute path starting with ``/``.
518+
:param method: HTTP method of the request. If not specified (or `METHOD_ALL`
519+
specified), all HTTP requests will match.
520+
:param data: payload of the HTTP request. This could be a string (utf-8 encoded
521+
by default, see `data_encoding`) or a bytes object.
522+
:param data_encoding: the encoding used for data parameter if data is a string.
523+
:param headers: dictionary of the headers of the request to be matched
524+
:param query_string: the http query string starting with ``?``, such as ``?username=user``
525+
:param header_value_matcher: :py:class:`HeaderValueMatcher` that matches values of headers.
526+
527+
:return: Created and register :py:class:`RequestHandler`.
528+
"""
529+
530+
return self.expect_request(
531+
uri=uri,
532+
method=method,
533+
data=data,
534+
data_encoding=data_encoding,
535+
headers=headers,
536+
query_string=query_string,
537+
header_value_matcher=header_value_matcher,
538+
handler_type=HandlerType.ORDERED,
492539
)
493-
request_handler = RequestHandler(matcher)
494-
self.handlers.append(request_handler)
495-
return request_handler
496540

497541
def thread_target(self):
498542
"""

tests/test_mixed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ def _setup_oneshot(server: HTTPServer):
1010

1111

1212
def _setup_ordered(server: HTTPServer):
13-
server.expect_oneshot_request("/ordered1", ordered=True).respond_with_data("OK ordered1")
14-
server.expect_oneshot_request("/ordered2", ordered=True).respond_with_data("OK ordered2")
13+
server.expect_ordered_request("/ordered1").respond_with_data("OK ordered1")
14+
server.expect_ordered_request("/ordered2").respond_with_data("OK ordered2")
1515

1616

1717
def _setup_all(server: HTTPServer):

tests/test_oneshot.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,6 @@ def test_oneshot(httpserver: HTTPServer):
2727
assert requests.get(httpserver.url_for("/foobaz")).status_code == 500
2828

2929

30-
def test_ordered_ok(httpserver: HTTPServer):
31-
httpserver.expect_oneshot_request("/foobar", ordered=True).respond_with_data("OK foobar")
32-
httpserver.expect_oneshot_request("/foobaz", ordered=True).respond_with_data("OK foobaz")
33-
34-
assert len(httpserver.ordered_handlers) == 2
35-
36-
# first requests should pass
37-
response = requests.get(httpserver.url_for("/foobar"))
38-
httpserver.check_assertions()
39-
assert response.status_code == 200
40-
assert response.text == "OK foobar"
41-
42-
response = requests.get(httpserver.url_for("/foobaz"))
43-
httpserver.check_assertions()
44-
assert response.status_code == 200
45-
assert response.text == "OK foobaz"
46-
47-
assert len(httpserver.ordered_handlers) == 0
48-
49-
# second requests should fail due to 'oneshot' type
50-
assert requests.get(httpserver.url_for("/foobar")).status_code == 500
51-
assert requests.get(httpserver.url_for("/foobaz")).status_code == 500
52-
53-
54-
def test_ordered_invalid_order(httpserver: HTTPServer):
55-
httpserver.expect_oneshot_request("/foobar", ordered=True).respond_with_data("OK foobar")
56-
httpserver.expect_oneshot_request("/foobaz", ordered=True).respond_with_data("OK foobaz")
57-
58-
assert len(httpserver.ordered_handlers) == 2
59-
60-
# these would not pass as the order is different
61-
# this mark the whole thing 'permanently failed' so no further requests must pass
62-
response = requests.get(httpserver.url_for("/foobaz"))
63-
assert response.status_code == 500
64-
65-
response = requests.get(httpserver.url_for("/foobar"))
66-
assert response.status_code == 500
67-
68-
# as no ordered handlers are triggered yet, these must be intact..
69-
assert len(httpserver.ordered_handlers) == 2
70-
71-
7230
def test_oneshot_any_method(httpserver: HTTPServer):
7331
for _ in range(5):
7432
httpserver.expect_oneshot_request("/foobar").respond_with_data("OK")

tests/test_ordered.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
import requests
3+
from pytest_httpserver import HTTPServer
4+
5+
6+
def test_ordered_ok(httpserver: HTTPServer):
7+
httpserver.expect_ordered_request("/foobar").respond_with_data("OK foobar")
8+
httpserver.expect_ordered_request("/foobaz").respond_with_data("OK foobaz")
9+
10+
assert len(httpserver.ordered_handlers) == 2
11+
12+
# first requests should pass
13+
response = requests.get(httpserver.url_for("/foobar"))
14+
httpserver.check_assertions()
15+
assert response.status_code == 200
16+
assert response.text == "OK foobar"
17+
18+
response = requests.get(httpserver.url_for("/foobaz"))
19+
httpserver.check_assertions()
20+
assert response.status_code == 200
21+
assert response.text == "OK foobaz"
22+
23+
assert len(httpserver.ordered_handlers) == 0
24+
25+
# second requests should fail due to 'oneshot' type
26+
assert requests.get(httpserver.url_for("/foobar")).status_code == 500
27+
assert requests.get(httpserver.url_for("/foobaz")).status_code == 500
28+
29+
30+
def test_ordered_invalid_order(httpserver: HTTPServer):
31+
httpserver.expect_ordered_request("/foobar").respond_with_data("OK foobar")
32+
httpserver.expect_ordered_request("/foobaz").respond_with_data("OK foobaz")
33+
34+
assert len(httpserver.ordered_handlers) == 2
35+
36+
# these would not pass as the order is different
37+
# this mark the whole thing 'permanently failed' so no further requests must pass
38+
response = requests.get(httpserver.url_for("/foobaz"))
39+
assert response.status_code == 500
40+
41+
response = requests.get(httpserver.url_for("/foobar"))
42+
assert response.status_code == 500
43+
44+
# as no ordered handlers are triggered yet, these must be intact..
45+
assert len(httpserver.ordered_handlers) == 2

0 commit comments

Comments
 (0)