diff --git a/tests/test_frappe_docker.py b/tests/test_frappe_docker.py index 85e6f1e8..fbf40b5d 100644 --- a/tests/test_frappe_docker.py +++ b/tests/test_frappe_docker.py @@ -5,7 +5,7 @@ from typing import Any import pytest from tests.conftest import S3ServiceResult -from tests.utils import Compose, check_url_content +from tests.utils import Compose, check_url_content, wait_for_url BACKEND_SERVICES = ( "backend", @@ -81,6 +81,28 @@ def test_files_reachable(frappe_site: str, tmp_path: Path, compose: Compose): ) +def test_files_html_security_headers( + frappe_site: str, tmp_path: Path, compose: Compose +): + file_path = tmp_path / "testfile.html" + file_path.write_text("This is a Frappe Docker test html file") + + compose( + "cp", + str(file_path), + f"backend:/home/frappe/frappe-bench/sites/{frappe_site}/public/files/", + ) + + response = wait_for_url( + url=f"http://127.0.0.1/files/{file_path.name}", + site_name=frappe_site, + ) + + assert response.headers["Content-Disposition"] == "attachment" + assert response.headers["X-Frame-Options"] == "SAMEORIGIN" + assert response.headers["X-Content-Type-Options"] == "nosniff" + + @pytest.mark.parametrize("service", BACKEND_SERVICES) @pytest.mark.usefixtures("frappe_site") def test_frappe_connections_in_backends( diff --git a/tests/utils.py b/tests/utils.py index fc12198b..a58be1bd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,6 +4,7 @@ import subprocess import sys import time from contextlib import suppress +from http.client import HTTPResponse from typing import Callable, Optional from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen @@ -59,24 +60,11 @@ class Compose: def check_url_content( url: str, callback: Callable[[str], Optional[str]], site_name: str ): - request = Request(url, headers={"Host": site_name}) - - # This is needed to check https override - ctx = ssl.create_default_context() - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - for _ in range(100): try: - response = urlopen(request, context=ctx) - - except HTTPError as exc: - if exc.code not in (404, 502): - raise - - except URLError: + response = wait_for_url(url=url, site_name=site_name, attempts=1) + except RuntimeError: pass - else: text: str = response.read().decode() ret = callback(text) @@ -86,4 +74,26 @@ def check_url_content( time.sleep(0.1) + raise RuntimeError(f"Couldn't verify expected content from {url}") + + +def wait_for_url(url: str, site_name: str, attempts: int = 100) -> HTTPResponse: + request = Request(url, headers={"Host": site_name}) + + # This is needed to check https override + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + + for _ in range(attempts): + try: + return urlopen(request, context=ctx) + except HTTPError as exc: + if exc.code not in (404, 502): + raise + except URLError: + pass + + time.sleep(0.1) + raise RuntimeError(f"Couldn't ping {url}")