test(nginx): add regression coverage for /files headers

This commit is contained in:
Rocket-Quack 2026-03-17 16:08:25 +01:00
parent 00c3475943
commit 3ffa8b720c
2 changed files with 48 additions and 16 deletions

View file

@ -5,7 +5,7 @@ from typing import Any
import pytest import pytest
from tests.conftest import S3ServiceResult 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_SERVICES = (
"backend", "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("<html><body>This is a Frappe Docker test html file</body></html>")
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.parametrize("service", BACKEND_SERVICES)
@pytest.mark.usefixtures("frappe_site") @pytest.mark.usefixtures("frappe_site")
def test_frappe_connections_in_backends( def test_frappe_connections_in_backends(

View file

@ -4,6 +4,7 @@ import subprocess
import sys import sys
import time import time
from contextlib import suppress from contextlib import suppress
from http.client import HTTPResponse
from typing import Callable, Optional from typing import Callable, Optional
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
@ -59,24 +60,11 @@ class Compose:
def check_url_content( def check_url_content(
url: str, callback: Callable[[str], Optional[str]], site_name: str 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): for _ in range(100):
try: try:
response = urlopen(request, context=ctx) response = wait_for_url(url=url, site_name=site_name, attempts=1)
except RuntimeError:
except HTTPError as exc:
if exc.code not in (404, 502):
raise
except URLError:
pass pass
else: else:
text: str = response.read().decode() text: str = response.read().decode()
ret = callback(text) ret = callback(text)
@ -86,4 +74,26 @@ def check_url_content(
time.sleep(0.1) 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}") raise RuntimeError(f"Couldn't ping {url}")