5 Commits

Author SHA1 Message Date
klemek 0b39313f7e chore: release 1.2.2
Python CI / ruff (push) Failing after 23s
Python CI / ruff-format-check (push) Failing after 25s
Docker CI / build (push) Failing after 40s
Python CI / ty (push) Failing after 22s
Python CI / coverage (push) Failing after 23s
2026-04-27 15:24:59 +02:00
klemek a2e0f9afb9 fix: handle all errors 2026-04-27 15:24:13 +02:00
klemek 95514f16cb fix: no message on bare curl 2026-04-27 15:15:34 +02:00
klemek 6db1b561f0 chore: release 1.2.1
Python CI / ruff (push) Failing after 23s
Docker CI / build (push) Failing after 32s
Python CI / ruff-format-check (push) Failing after 34s
Python CI / ty (push) Failing after 30s
Python CI / coverage (push) Failing after 35s
2026-04-26 12:35:02 +02:00
klemek 4256398cca fix: spa index.html 2026-04-26 12:34:33 +02:00
6 changed files with 168 additions and 121 deletions
+14 -10
View File
@@ -112,33 +112,33 @@ PUT /{page}/
```bash ```bash
# create archive from 'dist' dir and upload it to /my-project/ # create archive from 'dist' dir and upload it to /my-project/
tar -czC dist -f dist.tar.gz . tar -czC dist -f dist.tar.gz .
curl -v -X PUT \ curl -X PUT \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
--data-binary "@dist.tar.gz" \ --data-binary "@dist.tar.gz" \
https://stapler-host/my-project/ https://stapler-host/my-project/
# same thing but one-liner # same thing but one-liner
tar -czC dist . | curl -v -X PUT \ tar -czC dist . | curl -X PUT \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
--data-binary @- \ --data-binary @- \
https://stapler-host/my-project/ https://stapler-host/my-project/
# make stapler server identify myproject.example.com and /my-project/ # make stapler server identify myproject.example.com and /my-project/
tar -czC dist . | curl -v -X PUT \ tar -czC dist . | curl -X PUT \
--data-binary @- \ --data-binary @- \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-Host: myproject.example.com' \ -H 'X-Host: myproject.example.com' \
https://stapler-host/my-project/ https://stapler-host/my-project/
# make stapler server identifiers myproject.example.com only # make stapler server identifiers myproject.example.com only
tar -czC dist . | curl -v -X PUT \ tar -czC dist . | curl -X PUT \
--data-binary @- \ --data-binary @- \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-Host-Only: myproject.example.com' \ -H 'X-Host-Only: myproject.example.com' \
https://stapler-host/my-project/ https://stapler-host/my-project/
# make a SPA site at /my-project/index.html # make a SPA site at /my-project/index.html
tar -czC dist . | curl -v -X PUT \ tar -czC dist . | curl -X PUT \
--data-binary @- \ --data-binary @- \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-SPA: index.html' \ -H 'X-SPA: index.html' \
@@ -160,13 +160,13 @@ PUT /{page}/
```bash ```bash
# create /my-project/ that redirects to https://github.com/my-project # create /my-project/ that redirects to https://github.com/my-project
curl -v -X PUT \ curl -X PUT \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-Redirect: https://github.com/my-project' \ -H 'X-Redirect: https://github.com/my-project' \
https://stapler-host/my-project/ https://stapler-host/my-project/
# simple redirect from root host to www # simple redirect from root host to www
curl -v -X PUT \ curl -X PUT \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-Proxy: https://www.my-website.com' \ -H 'X-Proxy: https://www.my-website.com' \
-H 'X-Host: my-website.com' \ -H 'X-Host: my-website.com' \
@@ -185,7 +185,7 @@ PUT /{page}/
```bash ```bash
# create /my-website/ that proxies to http://host.containers.internal:8000 # create /my-website/ that proxies to http://host.containers.internal:8000
curl -v -X PUT \ curl -X PUT \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
-H 'X-Proxy: http://host.containers.internal:8000' \ -H 'X-Proxy: http://host.containers.internal:8000' \
https://stapler-host/my-project/ https://stapler-host/my-project/
@@ -200,7 +200,7 @@ DELETE /{page}/
```bash ```bash
# delete /my-project/ # delete /my-project/
curl -v -X DELETE \ curl -X DELETE \
-H 'X-Token: <TOKEN>' \ -H 'X-Token: <TOKEN>' \
https://stapler-host/my-project/ https://stapler-host/my-project/
``` ```
@@ -226,7 +226,11 @@ curl -v -X DELETE \
- name: Create archive - name: Create archive
run: tar -czC dist -f dist.tar.gz . run: tar -czC dist -f dist.tar.gz .
- name: Deploy to Stapler server - name: Deploy to Stapler server
run: curl -v -X PUT -H 'X-Token: ${{ secrets.STAPLER_TOKEN }}' -H 'X-Host: ${{ vars.TARGET_HOST }}' --data-binary "@dist.tar.gz" https://stapler-host/my-project/ run: |
curl -X PUT --data-binary "@dist.tar.gz" \
-H 'X-Token: ${{ secrets.STAPLER_TOKEN }}' \
-H 'X-Host: ${{ vars.TARGET_HOST }}' \
${{ vars.STAPLER_URL }}
``` ```
### Redirecting hosts with DNS ### Redirecting hosts with DNS
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "stapler" name = "stapler"
version = "1.2.0" version = "1.2.2"
description = "Static pages as simple as a gzip file" description = "Static pages as simple as a gzip file"
requires-python = ">=3.14" requires-python = ">=3.14"
dependencies = [ dependencies = [
+4 -2
View File
@@ -122,7 +122,9 @@ class CertManager:
) )
self.logger.info("Created self-signed certificate for %s", host) self.logger.info("Created self-signed certificate for %s", host)
except CertManagerError: except CertManagerError:
self.logger.exception("Could not create certbot certificate for %s\n%s") self.logger.exception(
"Could not create self-signed certificate for %s", host
)
return False return False
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.logger.exception( self.logger.exception(
@@ -172,7 +174,7 @@ class CertManager:
) )
self.logger.info("Created certbot certificate for %s", host) self.logger.info("Created certbot certificate for %s", host)
except CertManagerError: except CertManagerError:
self.logger.exception("Could not create certbot certificate for %s\n%s") self.logger.exception("Could not create certbot certificate for %s", host)
return False return False
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.logger.exception( self.logger.exception(
+123 -96
View File
@@ -1,4 +1,5 @@
import abc import abc
import contextlib
import http import http
import http.cookiejar import http.cookiejar
import http.server import http.server
@@ -45,13 +46,25 @@ class BaseHandler(abc.ABC, http.server.BaseHTTPRequestHandler):
code: int, code: int,
message: str | None = None, message: str | None = None,
explain: str | None = None, explain: str | None = None,
) -> None:
self.send_status(code, message, explain)
def send_status(
self,
code: int,
message: str | None = None,
explain: str | None = None,
) -> None: ) -> None:
shortmsg, longmsg = self.responses[code] shortmsg, longmsg = self.responses[code]
if message is None: if message is None:
message = shortmsg message = shortmsg
if explain is None: if explain is None:
explain = longmsg explain = longmsg
if "text/" in self._get_header("Accept"): if (
not self._has_header("Accept")
or self._get_header("Accept").startswith("*/")
or self._get_header("Accept").startswith("text/")
):
self.send_basic_body( self.send_basic_body(
f"{code} {message}\n{explain}\n\n{self.server_signature()}", f"{code} {message}\n{explain}\n\n{self.server_signature()}",
code=code, code=code,
@@ -211,6 +224,14 @@ class BaseHandler(abc.ABC, http.server.BaseHTTPRequestHandler):
def server_signature(self) -> str: def server_signature(self) -> str:
return self.server_version + "\n\n" + STAPLER_ASCII + "\n" return self.server_version + "\n\n" + STAPLER_ASCII + "\n"
@contextlib.contextmanager
def handle_errors(self) -> typing.Iterator[None]:
try:
yield
except Exception as e:
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
self.logger.exception("Internal Server Error")
class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler): class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
protocol_version = "HTTP/1.1" protocol_version = "HTTP/1.1"
@@ -323,44 +344,45 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
@typing.override @typing.override
def do_HEAD(self) -> None: def do_HEAD(self) -> None:
self._pre_log_request() with self.handle_errors():
if not self._proxy_or_redirect(): self._pre_log_request()
super().do_HEAD() if not self._proxy_or_redirect():
super().do_HEAD()
@typing.override @typing.override
def do_GET(self) -> None: def do_GET(self) -> None:
self._pre_log_request() with self.handle_errors():
if self._proxy_or_redirect(): self._pre_log_request()
return None if self._proxy_or_redirect():
if self.path == "/" and self.host == self.default_host: return None
return self.send_basic_body(self.server_signature()) if self.path == "/" and self.host == self.default_host:
return super().do_GET() return self.send_basic_body(self.server_signature())
return super().do_GET()
def do_PUT(self) -> None: def do_PUT(self) -> None:
self._pre_log_request() with self.handle_errors():
if self._proxy_or_redirect(): self._pre_log_request()
return None if self._proxy_or_redirect():
if (path := self.__check_update_request()) is None: return
return None if (path := self.__check_put_request()) is None:
if not self.__check_put_headers(): return
return None if self.has_target_redirect:
if ( if not self._update_redirect(path):
self.has_target_host return
and (page := self.registry.get_from_host(self.target_host)) is not None elif self.has_target_proxy:
and page.path != path if not self._update_proxy(path):
): return
return self.send_error(http.HTTPStatus.FORBIDDEN, "Host already taken") elif not self._update_extract(path):
if self.has_target_redirect: return
self._update_redirect(path) if self.has_request_host:
elif self.has_target_proxy: self.registry.set_host(path, self.target_host)
self._update_proxy(path) if self.has_request_host_only:
else: self.registry.set_host_only(path, self.target_host)
self._update_extract(path) self.send_status(
if self.has_request_host: http.HTTPStatus.CREATED,
self.registry.set_host(path, self.target_host) "Resource updated",
if self.has_request_host_only: str(self.registry.get_from_path(path)),
self.registry.set_host_only(path, self.target_host) )
return None
def do_POST(self) -> None: def do_POST(self) -> None:
self.do_PUT() # be gentle on them self.do_PUT() # be gentle on them
@@ -369,95 +391,88 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
self.do_PUT() # be gentle on them self.do_PUT() # be gentle on them
def do_DELETE(self) -> None: def do_DELETE(self) -> None:
self._pre_log_request() with self.handle_errors():
if self._proxy_or_redirect(): self._pre_log_request()
return None if self._proxy_or_redirect():
if (path := self.__check_update_request()) is None: return
return None if (path := self.__check_update_request()) is None:
return self._update_remove(path) return
if self._update_remove(path):
self.send_status(
http.HTTPStatus.OK,
f"Resource /{path}/ removed",
)
return
def do_CONNECT(self) -> None: def do_CONNECT(self) -> None:
self._pre_log_request() with self.handle_errors():
if not self._proxy_or_redirect(): self._pre_log_request()
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED) if not self._proxy_or_redirect():
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED)
def do_OPTIONS(self) -> None: def do_OPTIONS(self) -> None:
self._pre_log_request() with self.handle_errors():
if not self._proxy_or_redirect(): self._pre_log_request()
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED) if not self._proxy_or_redirect():
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED)
def do_TRACE(self) -> None: def do_TRACE(self) -> None:
self._pre_log_request() with self.handle_errors():
if not self._proxy_or_redirect(): self._pre_log_request()
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED) if not self._proxy_or_redirect():
self.send_error(http.HTTPStatus.METHOD_NOT_ALLOWED)
def _update_extract(self, path: str) -> None: def _update_extract(self, path: str) -> bool:
if self.in_size == 0: if self.in_size == 0:
return self.send_error(http.HTTPStatus.LENGTH_REQUIRED, "No body found") self.send_error(http.HTTPStatus.LENGTH_REQUIRED, "No body found")
return False
if self.in_size > self.max_size_bytes: if self.in_size > self.max_size_bytes:
return self.send_error( self.send_error(
http.HTTPStatus.CONTENT_TOO_LARGE, http.HTTPStatus.CONTENT_TOO_LARGE,
"Archive too large", "Archive too large",
) )
return False
try: try:
file_bytes = io.BytesIO(self.rfile.read(self.in_size)) file_bytes = io.BytesIO(self.rfile.read(self.in_size))
self.data_dir.extract_tar_bytes(path, file_bytes) self.data_dir.extract_tar_bytes(path, file_bytes)
except tarfile.TarError: except tarfile.TarError:
return self.send_error(http.HTTPStatus.BAD_REQUEST, "Invalid tar archive") self.send_error(http.HTTPStatus.BAD_REQUEST, "Invalid tar archive")
except Exception as e: return False
return self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
self.registry.add(path) self.registry.add(path)
self.token_manager.set_token(path, self.token) self.token_manager.set_token(path, self.token)
if self.has_target_spa: if self.has_target_spa:
self.registry.set_spa(path, self.target_spa) self.registry.set_spa(path, self.target_spa)
self.send_status_only( return True
http.HTTPStatus.CREATED,
f"Resource /{path}/ updated",
)
return None
def _update_redirect(self, path: str) -> None: def _update_redirect(self, path: str) -> bool:
if self.in_size > 0: if self.in_size > 0:
return self.send_error( self.send_error(
http.HTTPStatus.BAD_REQUEST, http.HTTPStatus.BAD_REQUEST,
f"No content must be sent with {self.REDIRECT_HEADER}", f"No content must be sent with {self.REDIRECT_HEADER}",
) )
return False
self.registry.set_redirect(path, self.target_redirect) self.registry.set_redirect(path, self.target_redirect)
self.token_manager.set_token(path, self.token) self.token_manager.set_token(path, self.token)
self.send_status_only( return True
http.HTTPStatus.CREATED,
f"Resource /{path}/ updated",
)
return None
def _update_proxy(self, path: str) -> None: def _update_proxy(self, path: str) -> bool:
if self.in_size > 0: if self.in_size > 0:
return self.send_error( self.send_error(
http.HTTPStatus.BAD_REQUEST, http.HTTPStatus.BAD_REQUEST,
f"No content must be sent with {self.PROXY_HEADER}", f"No content must be sent with {self.PROXY_HEADER}",
) )
return False
self.registry.set_proxy(path, self.target_proxy) self.registry.set_proxy(path, self.target_proxy)
self.token_manager.set_token(path, self.token) self.token_manager.set_token(path, self.token)
self.send_status_only( return True
http.HTTPStatus.CREATED,
f"Resource /{path}/ updated",
)
return None
def _update_remove(self, path: str) -> None: def _update_remove(self, path: str) -> bool:
if not self.data_dir.exists(path): if not self.data_dir.exists(path):
self.send_error(http.HTTPStatus.NOT_FOUND, "Not found") self.send_error(http.HTTPStatus.NOT_FOUND, "Not found")
return None return False
try: self.data_dir.remove(path)
self.data_dir.remove(path)
except Exception as e:
return self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
self.send_status_only(
http.HTTPStatus.NO_CONTENT,
f"Resource /{path}/ removed",
)
self.registry.remove(path) self.registry.remove(path)
return None return True
def _proxy_or_redirect(self) -> bool: def _proxy_or_redirect(self) -> bool:
if self.has_token or self.path.startswith(self.CERTBOT_CHALLENGE_PATH): if self.has_token or self.path.startswith(self.CERTBOT_CHALLENGE_PATH):
@@ -496,6 +511,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
if ( if (
page.spa is not None page.spa is not None
and not (self.root_path / pathlib.Path(path[1:])).is_file() and not (self.root_path / pathlib.Path(path[1:])).is_file()
and not (self.root_path / pathlib.Path(path[1:]) / "index.html").is_file()
): ):
path = f"/{page.path}/{page.spa}" path = f"/{page.path}/{page.spa}"
return super().translate_path(path) return super().translate_path(path)
@@ -517,23 +533,32 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
return None return None
return sub_path return sub_path
def __check_put_headers(self) -> bool: def __check_put_request(self) -> str | None:
if (path := self.__check_update_request()) is None:
return None
if self.has_request_host and self.has_request_host_only: if self.has_request_host and self.has_request_host_only:
self.send_error( self.send_error(
http.HTTPStatus.BAD_REQUEST, http.HTTPStatus.BAD_REQUEST,
f"Cannot use {self.HOST_ONLY_HEADER} with {self.HOST_HEADER}", f"Cannot use {self.HOST_ONLY_HEADER} with {self.HOST_HEADER}",
) )
return False return None
if self.has_target_host and not self.__valid_host(self.target_host): if self.has_target_host and not self.__valid_host(self.target_host):
self.send_error(http.HTTPStatus.BAD_REQUEST, "Invalid requested host") self.send_error(http.HTTPStatus.BAD_REQUEST, "Invalid requested host")
return False return None
if self.has_target_proxy and self.has_target_redirect: if self.has_target_proxy and self.has_target_redirect:
self.send_error( self.send_error(
http.HTTPStatus.BAD_REQUEST, http.HTTPStatus.BAD_REQUEST,
f"Cannot use {self.PROXY_HEADER} with {self.REDIRECT_HEADER}", f"Cannot use {self.PROXY_HEADER} with {self.REDIRECT_HEADER}",
) )
return False return None
return True if (
self.has_target_host
and (page := self.registry.get_from_host(self.target_host)) is not None
and page.path != path
):
self.send_error(http.HTTPStatus.FORBIDDEN, "Host already taken")
return None
return path
def __get_path(self, path: str, regex: re.Pattern) -> str | None: def __get_path(self, path: str, regex: re.Pattern) -> str | None:
if (match := regex.match(path.lower())) is not None: if (match := regex.match(path.lower())) is not None:
@@ -562,11 +587,13 @@ class UpgradeHandler(RequestHandler):
server_version = "StaplerUpgradeServer/" + PKG_VERSION server_version = "StaplerUpgradeServer/" + PKG_VERSION
def do_HEAD(self) -> None: def do_HEAD(self) -> None:
self._pre_log_request() with self.handle_errors():
self.send_redirect(f"https://{self.host}{self.path}") self._pre_log_request()
self.send_redirect(f"https://{self.host}{self.path}")
def do_GET(self) -> None: def do_GET(self) -> None:
if self.path.startswith(self.CERTBOT_CHALLENGE_PATH): with self.handle_errors():
super().do_GET() if self.path.startswith(self.CERTBOT_CHALLENGE_PATH):
else: super().do_GET()
self.do_HEAD() else:
self.do_HEAD()
+25 -11
View File
@@ -136,6 +136,8 @@ class TestRequestHandler(BaseHandlerTestCase):
) -> RequestHandler: ) -> RequestHandler:
if headers is None: if headers is None:
headers = {} headers = {}
if "Accept" not in headers:
headers["Accept"] = "nothing"
with self.patch("http.server.BaseHTTPRequestHandler.__init__"): with self.patch("http.server.BaseHTTPRequestHandler.__init__"):
handler = RequestHandler( handler = RequestHandler(
unittest.mock.MagicMock(), unittest.mock.MagicMock(),
@@ -433,8 +435,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call_unchecked(self.data_dir.extract_tar_bytes), self.mock_call_unchecked(self.data_dir.extract_tar_bytes),
self.mock_call(self.registry.add, ["path"]), self.mock_call(self.registry.add, ["path"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -458,8 +461,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.registry.add, ["path"]), self.mock_call(self.registry.add, ["path"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.set_host, ["path", "example.com"]), self.mock_call(self.registry.set_host, ["path", "example.com"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -481,8 +485,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.registry.add, ["path"]), self.mock_call(self.registry.add, ["path"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.set_spa, ["path", "index.html"]), self.mock_call(self.registry.set_spa, ["path", "index.html"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -530,8 +535,9 @@ class TestRequestHandler(BaseHandlerTestCase):
), ),
self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]), self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -557,8 +563,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]), self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.set_host, ["path", "example.com"]), self.mock_call(self.registry.set_host, ["path", "example.com"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -584,8 +591,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]), self.mock_call(self.registry.set_redirect, ["path", "https://example.com"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.set_host_only, ["path", "example.com"]), self.mock_call(self.registry.set_host_only, ["path", "example.com"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -633,8 +641,9 @@ class TestRequestHandler(BaseHandlerTestCase):
), ),
self.mock_call(self.registry.set_proxy, ["path", "https://example.com"]), self.mock_call(self.registry.set_proxy, ["path", "https://example.com"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -660,8 +669,9 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.registry.set_proxy, ["path", "https://example.com"]), self.mock_call(self.registry.set_proxy, ["path", "https://example.com"]),
self.mock_call(self.token_manager.set_token, ["path", "secret"]), self.mock_call(self.token_manager.set_token, ["path", "secret"]),
self.mock_call(self.registry.set_host, ["path", "example.com"]), self.mock_call(self.registry.set_host, ["path", "example.com"]),
self.mock_call(self.registry.get_from_path, ["path"]),
self.expects_status_only( self.expects_status_only(
handler, http.HTTPStatus.CREATED, "Resource /path/ updated" handler, http.HTTPStatus.CREATED, "Resource updated"
), ),
self.seal_mocks(), self.seal_mocks(),
): ):
@@ -783,9 +793,7 @@ class TestRequestHandler(BaseHandlerTestCase):
self.mock_call(self.data_dir.exists, ["path"], True), # noqa: FBT003 self.mock_call(self.data_dir.exists, ["path"], True), # noqa: FBT003
self.mock_call(self.data_dir.remove, ["path"]), self.mock_call(self.data_dir.remove, ["path"]),
self.mock_call(self.registry.remove, ["path"]), self.mock_call(self.registry.remove, ["path"]),
self.expects_error( self.expects_error(handler, http.HTTPStatus.OK, "Resource /path/ removed"),
handler, http.HTTPStatus.NO_CONTENT, "Resource /path/ removed"
),
self.seal_mocks(), self.seal_mocks(),
): ):
handler.do_DELETE() handler.do_DELETE()
@@ -813,6 +821,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": None, "data": None,
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "localhost", "X-Forwarded-Host": "localhost",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
@@ -855,6 +864,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": b"hello", "data": b"hello",
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "localhost", "X-Forwarded-Host": "localhost",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
@@ -897,6 +907,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": None, "data": None,
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "localhost", "X-Forwarded-Host": "localhost",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
@@ -930,6 +941,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": None, "data": None,
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "localhost", "X-Forwarded-Host": "localhost",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
@@ -972,6 +984,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": None, "data": None,
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "localhost", "X-Forwarded-Host": "localhost",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
@@ -1011,6 +1024,7 @@ class TestRequestHandler(BaseHandlerTestCase):
"data": None, "data": None,
"headers": { "headers": {
"Host": "example.com", "Host": "example.com",
"Accept": "nothing",
"X-Real-IP": "127.0.0.1", "X-Real-IP": "127.0.0.1",
"X-Forwarded-Host": "host", "X-Forwarded-Host": "host",
"X-Forwarded-For": "127.0.0.1", "X-Forwarded-For": "127.0.0.1",
Generated
+1 -1
View File
@@ -142,7 +142,7 @@ wheels = [
[[package]] [[package]]
name = "stapler" name = "stapler"
version = "1.2.0" version = "1.2.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "requests" }, { name = "requests" },