1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-08-01 18:18:31 +00:00

Type hints for tests

This commit is contained in:
Unrud 2021-07-26 20:56:47 +02:00 committed by Unrud
parent 698ae875ce
commit 60f25bf19a
12 changed files with 501 additions and 379 deletions

View file

@ -22,13 +22,19 @@ Tests for Radicale.
import base64
import logging
import shutil
import sys
import tempfile
import xml.etree.ElementTree as ET
from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple, Union
import defusedxml.ElementTree as DefusedET
import radicale
from radicale import xmlutils
from radicale import app, config, xmlutils
RESPONSES = Dict[str, Union[int, Dict[str, Tuple[int, ET.Element]]]]
# Enable debug output
radicale.log.logger.setLevel(logging.DEBUG)
@ -37,40 +43,70 @@ radicale.log.logger.setLevel(logging.DEBUG)
class BaseTest:
"""Base class for tests."""
def request(self, method, path, data=None, login=None, **args):
colpath: str
configuration: config.Configuration
application: app.Application
def setup(self) -> None:
self.configuration = config.load()
self.colpath = tempfile.mkdtemp()
self.configuration.update({
"storage": {"filesystem_folder": self.colpath,
# Disable syncing to disk for better performance
"_filesystem_fsync": "False"},
# Set incorrect authentication delay to a short duration
"auth": {"delay": "0.001"}}, "test", privileged=True)
self.application = app.Application(self.configuration)
def teardown(self) -> None:
shutil.rmtree(self.colpath)
def request(self, method: str, path: str, data: Optional[str] = None,
**kwargs) -> Tuple[int, Dict[str, str], str]:
"""Send a request."""
for key in args:
args[key.upper()] = args[key]
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)))
environ: Dict[str, Any] = {k.upper(): v for k, v in kwargs.items()}
for k, v in environ.items():
if not isinstance(v, str):
raise TypeError("type of %r is %r, expected %r" %
(k, type(v), str))
encoding: str = self.configuration.get("encoding", "request")
if login:
args["HTTP_AUTHORIZATION"] = "Basic " + base64.b64encode(
login.encode()).decode()
args["REQUEST_METHOD"] = method.upper()
args["PATH_INFO"] = path
environ["HTTP_AUTHORIZATION"] = "Basic " + base64.b64encode(
login.encode(encoding)).decode()
environ["REQUEST_METHOD"] = method.upper()
environ["PATH_INFO"] = path
if data:
data = data.encode()
args["wsgi.input"] = BytesIO(data)
args["CONTENT_LENGTH"] = str(len(data))
args["wsgi.errors"] = sys.stderr
data_bytes = data.encode(encoding)
environ["wsgi.input"] = BytesIO(data_bytes)
environ["CONTENT_LENGTH"] = str(len(data_bytes))
environ["wsgi.errors"] = sys.stderr
status = headers = None
def start_response(status_, headers_):
def start_response(status_: str, headers_: List[Tuple[str, str]]
) -> None:
nonlocal status, headers
status = status_
headers = headers_
answer = self.application(args, start_response)
answers = list(self.application(environ, start_response))
assert status is not None and headers is not None
return (int(status.split()[0]), dict(headers),
answer[0].decode() if answer else None)
answers[0].decode() if answers else "")
@staticmethod
def parse_responses(text):
def parse_responses(text: str) -> RESPONSES:
xml = DefusedET.fromstring(text)
assert xml.tag == xmlutils.make_clark("D:multistatus")
path_responses = {}
path_responses: Dict[str, Union[
int, Dict[str, Tuple[int, ET.Element]]]] = {}
for response in xml.findall(xmlutils.make_clark("D:response")):
href = response.find(xmlutils.make_clark("D:href"))
assert href.text not in path_responses
prop_respones = {}
prop_respones: Dict[str, Tuple[int, ET.Element]] = {}
for propstat in response.findall(
xmlutils.make_clark("D:propstat")):
status = propstat.find(xmlutils.make_clark("D:status"))
@ -92,70 +128,90 @@ class BaseTest:
return path_responses
@staticmethod
def _check_status(status, good_status, check=True):
if check is True:
assert status == good_status
elif check is not False:
assert status == check
def _check_status(status: int, good_status: int,
check: Union[bool, int] = True) -> bool:
if check is not False:
expected = good_status if check is True else check
assert status == expected, "%d != %d" % (status, expected)
return status == good_status
def get(self, path, check=True, **args):
status, _, answer = self.request("GET", path, **args)
def get(self, path: str, check: Union[bool, int] = True, **kwargs
) -> Tuple[int, str]:
assert "data" not in kwargs
status, _, answer = self.request("GET", path, **kwargs)
self._check_status(status, 200, check)
return status, answer
def post(self, path, data=None, check=True, **args):
status, _, answer = self.request("POST", path, data, **args)
def post(self, path: str, data: str = None, check: Union[bool, int] = True,
**kwargs) -> Tuple[int, str]:
status, _, answer = self.request("POST", path, data, **kwargs)
self._check_status(status, 200, check)
return status, answer
def put(self, path, data, check=True, **args):
status, _, answer = self.request("PUT", path, data, **args)
def put(self, path: str, data: str, check: Union[bool, int] = True,
**kwargs) -> Tuple[int, str]:
status, _, answer = self.request("PUT", path, data, **kwargs)
self._check_status(status, 201, check)
return status, answer
def propfind(self, path, data=None, check=True, **args):
status, _, answer = self.request("PROPFIND", path, data, **args)
def propfind(self, path: str, data: Optional[str] = None,
check: Union[bool, int] = True, **kwargs
) -> Tuple[int, RESPONSES]:
status, _, answer = self.request("PROPFIND", path, data, **kwargs)
if not self._check_status(status, 207, check):
return status, None
return status, {}
assert answer is not None
responses = self.parse_responses(answer)
if args.get("HTTP_DEPTH", "0") == "0":
if kwargs.get("HTTP_DEPTH", "0") == "0":
assert len(responses) == 1 and path in responses
return status, responses
def proppatch(self, path, data=None, check=True, **args):
status, _, answer = self.request("PROPPATCH", path, data, **args)
def proppatch(self, path: str, data: Optional[str] = None,
check: Union[bool, int] = True, **kwargs
) -> Tuple[int, RESPONSES]:
status, _, answer = self.request("PROPPATCH", path, data, **kwargs)
if not self._check_status(status, 207, check):
return status, None
return status, {}
assert answer is not None
responses = self.parse_responses(answer)
assert len(responses) == 1 and path in responses
return status, responses
def report(self, path, data, check=True, **args):
status, _, answer = self.request("REPORT", path, data, **args)
def report(self, path: str, data: str, check: Union[bool, int] = True,
**kwargs) -> Tuple[int, RESPONSES]:
status, _, answer = self.request("REPORT", path, data, **kwargs)
if not self._check_status(status, 207, check):
return status, None
return status, {}
assert answer is not None
return status, self.parse_responses(answer)
def delete(self, path, check=True, **args):
status, _, answer = self.request("DELETE", path, **args)
def delete(self, path: str, check: Union[bool, int] = True, **kwargs
) -> Tuple[int, RESPONSES]:
assert "data" not in kwargs
status, _, answer = self.request("DELETE", path, **kwargs)
if not self._check_status(status, 200, check):
return status, None
return status, {}
assert answer is not None
responses = self.parse_responses(answer)
assert len(responses) == 1 and path in responses
return status, responses
def mkcalendar(self, path, data=None, check=True, **args):
status, _, answer = self.request("MKCALENDAR", path, data, **args)
def mkcalendar(self, path: str, data: Optional[str] = None,
check: Union[bool, int] = True, **kwargs
) -> Tuple[int, str]:
status, _, answer = self.request("MKCALENDAR", path, data, **kwargs)
self._check_status(status, 201, check)
return status, answer
def mkcol(self, path, data=None, check=True, **args):
status, _, _ = self.request("MKCOL", path, data, **args)
def mkcol(self, path: str, data: Optional[str] = None,
check: Union[bool, int] = True, **kwargs) -> int:
status, _, _ = self.request("MKCOL", path, data, **kwargs)
self._check_status(status, 201, check)
return status
def create_addressbook(self, path, check=True, **args):
def create_addressbook(self, path: str, check: Union[bool, int] = True,
**kwargs) -> int:
assert "data" not in kwargs
return self.mkcol(path, """\
<?xml version="1.0" encoding="UTF-8" ?>
<create xmlns="DAV:" xmlns:CR="urn:ietf:params:xml:ns:carddav">
@ -167,4 +223,4 @@ class BaseTest:
</resourcetype>
</prop>
</set>
</create>""", check=check, **args)
</create>""", check=check, **kwargs)