1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-10-09 21:40:50 +00:00

Synced with origin

This commit is contained in:
Tuna Celik 2023-02-10 22:03:33 +01:00
parent 9b3bb2de2b
commit cf81d1f9a7
94 changed files with 5096 additions and 3560 deletions

View file

@ -1,4 +1,4 @@
# This file is part of Radicale Server - Calendar Server
# This file is part of Radicale - CalDAV and CardDAV server
# Copyright © 2018-2019 Unrud <unrud@outlook.com>
#
# This library is free software: you can redistribute it and/or modify
@ -21,15 +21,15 @@ Test the internal server.
import errno
import os
import shutil
import socket
import ssl
import subprocess
import sys
import tempfile
import threading
import time
from configparser import RawConfigParser
from http.client import HTTPMessage
from typing import IO, Callable, Dict, Optional, Tuple, cast
from urllib import request
from urllib.error import HTTPError, URLError
@ -41,31 +41,29 @@ from radicale.tests.helpers import configuration_to_dict, get_file_path
class DisabledRedirectHandler(request.HTTPRedirectHandler):
def http_error_302(self, req, fp, code, msg, headers):
raise HTTPError(req.full_url, code, msg, headers, fp)
http_error_301 = http_error_303 = http_error_307 = http_error_302
def redirect_request(
self, req: request.Request, fp: IO[bytes], code: int, msg: str,
headers: HTTPMessage, newurl: str) -> None:
return None
class TestBaseServerRequests(BaseTest):
"""Test the internal server."""
def setup(self):
self.configuration = config.load()
self.colpath = tempfile.mkdtemp()
shutdown_socket: socket.socket
thread: threading.Thread
opener: request.OpenerDirector
def setup(self) -> None:
super().setup()
self.shutdown_socket, shutdown_socket_out = socket.socketpair()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Find available port
sock.bind(("127.0.0.1", 0))
self.sockname = sock.getsockname()
self.configuration.update({
"storage": {"filesystem_folder": self.colpath,
# Disable syncing to disk for better performance
"_filesystem_fsync": "False"},
"server": {"hosts": "[%s]:%d" % self.sockname},
# Enable debugging for new processes
"logging": {"level": "debug"}},
"test", privileged=True)
self.configure({"server": {"hosts": "[%s]:%d" % self.sockname},
# Enable debugging for new processes
"logging": {"level": "debug"}})
self.thread = threading.Thread(target=server.serve, args=(
self.configuration, shutdown_socket_out))
ssl_context = ssl.create_default_context()
@ -75,48 +73,67 @@ class TestBaseServerRequests(BaseTest):
request.HTTPSHandler(context=ssl_context),
DisabledRedirectHandler)
def teardown(self):
def teardown(self) -> None:
self.shutdown_socket.close()
try:
self.thread.join()
except RuntimeError: # Thread never started
pass
shutil.rmtree(self.colpath)
super().teardown()
def request(self, method, path, data=None, is_alive_fn=None, **headers):
def request(self, method: str, path: str, data: Optional[str] = None,
check: Optional[int] = None, **kwargs
) -> Tuple[int, Dict[str, str], str]:
"""Send a request."""
login = kwargs.pop("login", None)
if login is not None and not isinstance(login, str):
raise TypeError("login argument must be %r, not %r" %
(str, type(login)))
if login:
raise NotImplementedError
is_alive_fn: Optional[Callable[[], bool]] = kwargs.pop(
"is_alive_fn", None)
headers: Dict[str, str] = kwargs
for k, v in headers.items():
if not isinstance(v, str):
raise TypeError("type of %r is %r, expected %r" %
(k, type(v), str))
if is_alive_fn is None:
is_alive_fn = self.thread.is_alive
scheme = ("https" if self.configuration.get("server", "ssl") else
"http")
encoding: str = self.configuration.get("encoding", "request")
scheme = "https" if self.configuration.get("server", "ssl") else "http"
data_bytes = None
if data:
data_bytes = data.encode(encoding)
req = request.Request(
"%s://[%s]:%d%s" % (scheme, *self.sockname, path),
data=data, headers=headers, method=method)
data=data_bytes, headers=headers, method=method)
while True:
assert is_alive_fn()
try:
with self.opener.open(req) as f:
return f.getcode(), f.info(), f.read().decode()
return f.getcode(), dict(f.info()), f.read().decode()
except HTTPError as e:
return e.code, e.headers, e.read().decode()
assert check is None or e.code == check, "%d != %d" % (e.code,
check)
return e.code, dict(e.headers), e.read().decode()
except URLError as e:
if not isinstance(e.reason, ConnectionRefusedError):
raise
time.sleep(0.1)
def test_root(self):
def test_root(self) -> None:
self.thread.start()
self.get("/", check=302)
def test_ssl(self):
self.configuration.update({
"server": {"ssl": "True",
"certificate": get_file_path("cert.pem"),
"key": get_file_path("key.pem")}}, "test")
def test_ssl(self) -> None:
self.configure({"server": {"ssl": "True",
"certificate": get_file_path("cert.pem"),
"key": get_file_path("key.pem")}})
self.thread.start()
self.get("/", check=302)
def test_bind_fail(self):
def test_bind_fail(self) -> None:
for address_family, address in [(socket.AF_INET, "::1"),
(socket.AF_INET6, "127.0.0.1")]:
with socket.socket(address_family, socket.SOCK_STREAM) as sock:
@ -132,10 +149,11 @@ class TestBaseServerRequests(BaseTest):
socket.EAI_NONAME, server.COMPAT_EAI_ADDRFAMILY,
server.COMPAT_EAI_NODATA) or
str(exc_info.value) == "address family mismatched" or
exc_info.value.errno == errno.EADDRNOTAVAIL or
exc_info.value.errno == errno.EAFNOSUPPORT)
exc_info.value.errno in (
errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT,
errno.EPROTONOSUPPORT))
def test_ipv6(self):
def test_ipv6(self) -> None:
try:
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
# Only allow IPv6 connections to the IPv6 socket
@ -145,44 +163,54 @@ class TestBaseServerRequests(BaseTest):
sock.bind(("::1", 0))
self.sockname = sock.getsockname()[:2]
except OSError as e:
if e.errno in (errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT):
if e.errno in (errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT,
errno.EPROTONOSUPPORT):
pytest.skip("IPv6 not supported")
raise
self.configuration.update({
"server": {"hosts": "[%s]:%d" % self.sockname}}, "test")
self.configure({"server": {"hosts": "[%s]:%d" % self.sockname}})
self.thread.start()
self.get("/", check=302)
def test_command_line_interface(self):
def test_command_line_interface(self, with_bool_options=False) -> None:
self.configure({"headers": {"Test-Server": "test"}})
config_args = []
for section, values in config.DEFAULT_CONFIG_SCHEMA.items():
for section in self.configuration.sections():
if section.startswith("_"):
continue
for option, data in values.items():
for option in self.configuration.options(section):
if option.startswith("_"):
continue
long_name = "--%s-%s" % (section, option.replace("_", "-"))
if data["type"] == bool:
if not self.configuration.get(section, option):
if with_bool_options and config.DEFAULT_CONFIG_SCHEMA.get(
section, {}).get(option, {}).get("type") == bool:
if not cast(bool, self.configuration.get(section, option)):
long_name = "--no%s" % long_name[1:]
config_args.append(long_name)
else:
config_args.append(long_name)
config_args.append(
self.configuration.get_raw(section, option))
env = os.environ.copy()
env["PYTHONPATH"] = os.pathsep.join(sys.path)
raw_value = self.configuration.get_raw(section, option)
assert isinstance(raw_value, str)
config_args.append(raw_value)
config_args.append("--headers-Test-Header=test")
p = subprocess.Popen(
[sys.executable, "-m", "radicale"] + config_args, env=env)
[sys.executable, "-m", "radicale"] + config_args,
env={**os.environ, "PYTHONPATH": os.pathsep.join(sys.path)})
try:
self.get("/", is_alive_fn=lambda: p.poll() is None, check=302)
status, headers, _ = self.request(
"GET", "/", check=302, is_alive_fn=lambda: p.poll() is None)
for key in self.configuration.options("headers"):
assert headers.get(key) == self.configuration.get(
"headers", key)
finally:
p.terminate()
p.wait()
if os.name == "posix":
if sys.platform != "win32":
assert p.returncode == 0
def test_wsgi_server(self):
def test_command_line_interface_with_bool_options(self) -> None:
self.test_command_line_interface(with_bool_options=True)
def test_wsgi_server(self) -> None:
config_path = os.path.join(self.colpath, "config")
parser = RawConfigParser()
parser.read_dict(configuration_to_dict(self.configuration))
@ -191,9 +219,10 @@ class TestBaseServerRequests(BaseTest):
env = os.environ.copy()
env["PYTHONPATH"] = os.pathsep.join(sys.path)
env["RADICALE_CONFIG"] = config_path
raw_server_hosts = self.configuration.get_raw("server", "hosts")
assert isinstance(raw_server_hosts, str)
p = subprocess.Popen([
sys.executable, "-m", "waitress",
"--listen", self.configuration.get_raw("server", "hosts"),
sys.executable, "-m", "waitress", "--listen", raw_server_hosts,
"radicale:application"], env=env)
try:
self.get("/", is_alive_fn=lambda: p.poll() is None, check=302)