test_server.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright © 2018 Unrud<unrud@outlook.com>
  3. #
  4. # This library is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Test the internal server.
  18. """
  19. import os
  20. import shutil
  21. import socket
  22. import ssl
  23. import tempfile
  24. import threading
  25. import time
  26. from urllib import request
  27. from urllib.error import HTTPError, URLError
  28. from radicale import config, server
  29. from .helpers import get_file_path
  30. import pytest # isort:skip
  31. class DisabledRedirectHandler(request.HTTPRedirectHandler):
  32. def http_error_302(self, req, fp, code, msg, headers):
  33. raise HTTPError(req.full_url, code, msg, headers, fp)
  34. http_error_301 = http_error_303 = http_error_307 = http_error_302
  35. class TestBaseServerRequests:
  36. """Test the internal server."""
  37. def setup(self):
  38. self.configuration = config.load()
  39. self.colpath = tempfile.mkdtemp()
  40. self.configuration["storage"]["filesystem_folder"] = self.colpath
  41. # Disable syncing to disk for better performance
  42. self.configuration["internal"]["filesystem_fsync"] = "False"
  43. self.shutdown_socket, shutdown_socket_out = socket.socketpair()
  44. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
  45. # Find available port
  46. sock.bind(("127.0.0.1", 0))
  47. self.sockname = sock.getsockname()
  48. self.configuration["server"]["hosts"] = "[%s]:%d" % self.sockname
  49. self.thread = threading.Thread(target=server.serve, args=(
  50. self.configuration, shutdown_socket_out))
  51. ssl_context = ssl.create_default_context()
  52. ssl_context.check_hostname = False
  53. ssl_context.verify_mode = ssl.CERT_NONE
  54. self.opener = request.build_opener(
  55. request.HTTPSHandler(context=ssl_context),
  56. DisabledRedirectHandler)
  57. def teardown(self):
  58. self.shutdown_socket.sendall(b" ")
  59. try:
  60. self.thread.join()
  61. except RuntimeError: # Thread never started
  62. pass
  63. shutil.rmtree(self.colpath)
  64. def request(self, method, path, data=None, **headers):
  65. """Send a request."""
  66. scheme = ("https" if self.configuration.getboolean("server", "ssl")
  67. else "http")
  68. req = request.Request(
  69. "%s://[%s]:%d%s" % (scheme, *self.sockname, path),
  70. data=data, headers=headers, method=method)
  71. while True:
  72. assert self.thread.is_alive()
  73. try:
  74. with self.opener.open(req) as f:
  75. return f.getcode(), f.info(), f.read().decode()
  76. except HTTPError as e:
  77. return e.code, e.headers, e.read().decode()
  78. except URLError as e:
  79. if not isinstance(e.reason, ConnectionRefusedError):
  80. raise
  81. time.sleep(0.1)
  82. def test_root(self):
  83. self.thread.start()
  84. status, _, _ = self.request("GET", "/")
  85. assert status == 302
  86. def test_ssl(self):
  87. self.configuration["server"]["ssl"] = "True"
  88. self.configuration["server"]["certificate"] = get_file_path("cert.pem")
  89. self.configuration["server"]["key"] = get_file_path("key.pem")
  90. self.thread.start()
  91. status, _, _ = self.request("GET", "/")
  92. assert status == 302
  93. def test_ipv6(self):
  94. if (not server.HAS_IPV6 or os.environ.get("TRAVIS_OS_NAME") == "osx" or
  95. os.environ.get("TRAVIS_SUDO") == "true"):
  96. pytest.skip("IPv6 not support")
  97. if os.name == "nt" and server.EAI_ADDRFAMILY is None:
  98. # HACK: incomplete errno conversion in WINE
  99. server.EAI_ADDRFAMILY = -9
  100. with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
  101. sock.setsockopt(server.IPPROTO_IPV6, server.IPV6_V6ONLY, 1)
  102. # Find available port
  103. sock.bind(("::1", 0))
  104. self.sockname = sock.getsockname()[:2]
  105. self.configuration["server"]["hosts"] = "[%s]:%d" % self.sockname
  106. self.thread.start()
  107. status, _, _ = self.request("GET", "/")
  108. assert status == 302