1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
from __future__ import annotations
import os
import socket
import ssl
import sys
from typing import Tuple
from unittest.mock import Mock, NonCallableMock
import pytest
from _pytest.monkeypatch import MonkeyPatch
import hypercorn.config
from hypercorn.config import Config
access_log_format = "bob"
h11_max_incomplete_size = 4
def _check_standard_config(config: Config) -> None:
assert config.access_log_format == access_log_format
assert config.h11_max_incomplete_size == h11_max_incomplete_size
assert config.bind == ["127.0.0.1:5555"]
def test_config_from_pyfile() -> None:
path = os.path.join(os.path.dirname(__file__), "assets/config.py")
config = Config.from_pyfile(path)
_check_standard_config(config)
def test_config_from_object() -> None:
sys.path.append(os.path.join(os.path.dirname(__file__)))
config = Config.from_object("assets.config")
_check_standard_config(config)
def test_ssl_config_from_pyfile() -> None:
path = os.path.join(os.path.dirname(__file__), "assets/config_ssl.py")
config = Config.from_pyfile(path)
_check_standard_config(config)
assert config.ssl_enabled
def test_config_from_toml() -> None:
path = os.path.join(os.path.dirname(__file__), "assets/config.toml")
config = Config.from_toml(path)
_check_standard_config(config)
def test_create_ssl_context() -> None:
path = os.path.join(os.path.dirname(__file__), "assets/config_ssl.py")
config = Config.from_pyfile(path)
context = config.create_ssl_context()
# NOTE: In earlier versions of python context.options is equal to <Options.OP_NO_COMPRESSION: 0>
# hence the ANDing context.options with the specified ssl options results in
# "<Options.OP_NO_COMPRESSION: 0>", which as a Boolean value, is False.
#
# To overcome this, instead of checking that the result in True, we will check that it is
# equal to "context.options".
assert (
context.options
& (
ssl.OP_NO_SSLv2
| ssl.OP_NO_SSLv3
| ssl.OP_NO_TLSv1
| ssl.OP_NO_TLSv1_1
| ssl.OP_NO_COMPRESSION
)
== context.options
)
@pytest.mark.parametrize(
"bind, expected_family, expected_binding",
[
("127.0.0.1:5000", socket.AF_INET, ("127.0.0.1", 5000)),
("127.0.0.1", socket.AF_INET, ("127.0.0.1", 8000)),
("[::]:5000", socket.AF_INET6, ("::", 5000)),
("[::]", socket.AF_INET6, ("::", 8000)),
],
)
def test_create_sockets_ip(
bind: str,
expected_family: socket.AddressFamily,
expected_binding: Tuple[str, int],
monkeypatch: MonkeyPatch,
) -> None:
mock_socket = Mock()
monkeypatch.setattr(socket, "socket", mock_socket)
config = Config()
config.bind = [bind]
sockets = config.create_sockets()
sock = sockets.insecure_sockets[0]
mock_socket.assert_called_with(expected_family, socket.SOCK_STREAM)
sock.setsockopt.assert_called_with(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # type: ignore
sock.bind.assert_called_with(expected_binding) # type: ignore
sock.setblocking.assert_called_with(False) # type: ignore
sock.set_inheritable.assert_called_with(True) # type: ignore
def test_create_sockets_unix(monkeypatch: MonkeyPatch) -> None:
mock_socket = Mock()
monkeypatch.setattr(socket, "socket", mock_socket)
monkeypatch.setattr(os, "chown", Mock())
config = Config()
config.bind = ["unix:/tmp/hypercorn.sock"]
sockets = config.create_sockets()
sock = sockets.insecure_sockets[0]
mock_socket.assert_called_with(socket.AF_UNIX, socket.SOCK_STREAM)
sock.setsockopt.assert_called_with(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # type: ignore
sock.bind.assert_called_with("/tmp/hypercorn.sock") # type: ignore
sock.setblocking.assert_called_with(False) # type: ignore
sock.set_inheritable.assert_called_with(True) # type: ignore
def test_create_sockets_fd(monkeypatch: MonkeyPatch) -> None:
mock_sock_class = Mock(
return_value=NonCallableMock(
**{"getsockopt.return_value": socket.SOCK_STREAM} # type: ignore
)
)
monkeypatch.setattr(socket, "socket", mock_sock_class)
config = Config()
config.bind = ["fd://2"]
sockets = config.create_sockets()
sock = sockets.insecure_sockets[0]
mock_sock_class.assert_called_with(fileno=2)
sock.getsockopt.assert_called_with(socket.SOL_SOCKET, socket.SO_TYPE) # type: ignore
sock.setsockopt.assert_called_with(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # type: ignore
sock.setblocking.assert_called_with(False) # type: ignore
sock.set_inheritable.assert_called_with(True) # type: ignore
def test_create_sockets_multiple(monkeypatch: MonkeyPatch) -> None:
mock_socket = Mock()
monkeypatch.setattr(socket, "socket", mock_socket)
monkeypatch.setattr(os, "chown", Mock())
config = Config()
config.bind = ["127.0.0.1", "unix:/tmp/hypercorn.sock"]
sockets = config.create_sockets()
assert len(sockets.insecure_sockets) == 2
def test_response_headers(monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr(hypercorn.config, "time", lambda: 1_512_229_395)
config = Config()
assert config.response_headers("test") == [
(b"date", b"Sat, 02 Dec 2017 15:43:15 GMT"),
(b"server", b"hypercorn-test"),
]
config.include_server_header = False
assert config.response_headers("test") == [(b"date", b"Sat, 02 Dec 2017 15:43:15 GMT")]
|