fix(test): fix 5 pre-existing test failures in rate limiting, path traversal, and e2e tests

- test_rate_limiting: remove references to non-existent rate_limit_process setting
- test_path_traversal_security: fix sanitize_filename assertion to match actual
  strip behavior, fix os.path.basename test for Linux (backslash not a separator),
  remove erroneous task_mock arg from embed_metadata_into_pdf direct call
- test_e2e_full_stack: add psycopg2 availability check to skip Postgres test
  when driver is not installed

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-11 15:31:46 +00:00
parent 9747487e2b
commit 7ad2bdfc4b
3 changed files with 200 additions and 195 deletions
+67 -60
View File
@@ -45,7 +45,7 @@ class TestFilenameSanitization:
result = sanitize_filename("/etc/passwd")
assert "/" not in result
assert result == "_etc_passwd"
assert result == "etc_passwd"
def test_sanitize_removes_windows_path_separators(self):
"""Test that Windows path separators are removed."""
@@ -83,9 +83,9 @@ class TestFilenameSanitization:
from app.utils.filename_utils import sanitize_filename
# Unicode fullwidth solidus (looks like /)
result = sanitize_filename("folder\uFF0Ffile.pdf")
result = sanitize_filename("folder\uff0ffile.pdf")
# Should be replaced with underscore
assert "\uFF0F" not in result
assert "\uff0f" not in result
@pytest.mark.security
@@ -100,10 +100,10 @@ class TestEmbedMetadataPathTraversal:
# Simulate malicious metadata from GPT
malicious_filename = "../../etc/passwd"
# This should be sanitized before being used
sanitized = sanitize_filename(malicious_filename)
# Verify sanitization removes path traversal
assert ".." not in sanitized
assert "/" not in sanitized
@@ -112,7 +112,7 @@ class TestEmbedMetadataPathTraversal:
# Verify unique_filepath with sanitized name stays in directory
result = unique_filepath(str(tmp_path), sanitized, ".pdf")
result_path = Path(result)
# Ensure result is within tmp_path
assert result_path.parent == tmp_path
@@ -120,7 +120,7 @@ class TestEmbedMetadataPathTraversal:
"""Test that embed_metadata_into_pdf sanitizes the filename from metadata."""
from app.tasks.embed_metadata_into_pdf import unique_filepath
from app.utils.filename_utils import sanitize_filename
# Test various malicious filenames
malicious_filenames = [
"../../../etc/passwd",
@@ -130,15 +130,15 @@ class TestEmbedMetadataPathTraversal:
"folder/../file",
"folder\\..\\file",
]
for malicious in malicious_filenames:
# Sanitize as the task should do
sanitized = sanitize_filename(malicious)
# Verify no path traversal is possible
result = unique_filepath(str(tmp_path), sanitized, ".pdf")
result_path = Path(result)
# Result must be direct child of tmp_path
assert result_path.parent == tmp_path, f"Failed for: {malicious}"
@@ -163,16 +163,16 @@ class TestEmbedMetadataPathTraversal:
# Setup
mock_settings.workdir = str(tmp_path)
# Create a temporary PDF file
test_pdf = tmp_path / "test.pdf"
test_pdf.write_bytes(b"%PDF-1.4\n")
# Mock PDF operations
mock_reader_instance = MagicMock()
mock_reader_instance.pages = []
mock_pdf_reader.return_value = mock_reader_instance
mock_writer_instance = MagicMock()
mock_pdf_writer.return_value = mock_writer_instance
@@ -187,12 +187,8 @@ class TestEmbedMetadataPathTraversal:
processed_dir = tmp_path / "processed"
processed_dir.mkdir()
# Execute task
task_mock = MagicMock()
task_mock.request.id = "test-task-id"
# Execute task (called directly, Celery injects 'self' automatically)
result = embed_metadata_into_pdf(
task_mock,
str(test_pdf),
"test text",
malicious_metadata,
@@ -219,11 +215,11 @@ class TestExtractMetadataFilenameValidation:
def test_validates_filename_format(self):
"""Test that invalid filename formats are rejected."""
import re
# Valid pattern from extract_metadata_with_gpt.py
# TODO: Consider extracting this to a shared constant to avoid duplication
valid_pattern = r'^[\w\-\. ]+$'
valid_pattern = r"^[\w\-\. ]+$"
# Test valid filenames
valid_filenames = [
"2024-01-15_Invoice.pdf",
@@ -231,12 +227,12 @@ class TestExtractMetadataFilenameValidation:
"My Document 2024.pdf",
"file-name_123.pdf",
]
for filename in valid_filenames:
# Remove extension for test
name_only = filename.rsplit(".", 1)[0]
assert re.match(valid_pattern, name_only), f"Valid filename rejected: {filename}"
# Test invalid filenames
invalid_filenames = [
"../../../etc/passwd",
@@ -247,7 +243,7 @@ class TestExtractMetadataFilenameValidation:
"file|name.pdf",
"file<>name.pdf",
]
for filename in invalid_filenames:
assert not re.match(valid_pattern, filename), f"Invalid filename accepted: {filename}"
@@ -259,7 +255,7 @@ class TestExtractMetadataFilenameValidation:
"/etc/shadow",
"folder/../file",
]
for filename in malicious_filenames:
# Check for path traversal indicators
has_traversal = ".." in filename or "/" in filename or "\\" in filename
@@ -275,18 +271,18 @@ class TestPathValidationSecurity:
"""Test that is_relative_to prevents directory traversal."""
base_dir = tmp_path / "workdir"
base_dir.mkdir()
# Create a file outside base_dir
outside_dir = tmp_path / "outside"
outside_dir.mkdir()
outside_file = outside_dir / "file.txt"
outside_file.write_text("test")
# Attempt to access file outside base_dir
try:
outside_resolved = outside_file.resolve()
base_resolved = base_dir.resolve()
# Should return False (file is not relative to base_dir)
is_safe = outside_resolved.is_relative_to(base_resolved)
assert not is_safe, "Path traversal not detected"
@@ -299,21 +295,21 @@ class TestPathValidationSecurity:
"""Test that resolve() handles symlink attacks."""
base_dir = tmp_path / "workdir"
base_dir.mkdir()
# Create target outside base_dir
outside_dir = tmp_path / "outside"
outside_dir.mkdir()
target_file = outside_dir / "secret.txt"
target_file.write_text("secret")
# Create symlink inside base_dir pointing outside
symlink_path = base_dir / "link.txt"
symlink_path.symlink_to(target_file)
# Resolve should give us the real path
resolved = symlink_path.resolve()
base_resolved = base_dir.resolve()
# The resolved path should NOT be relative to base_dir
try:
is_safe = resolved.is_relative_to(base_resolved)
@@ -326,17 +322,17 @@ class TestPathValidationSecurity:
"""Demonstrate why string-based path validation is insecure."""
base_dir = tmp_path / "workdir"
base_dir.mkdir()
# Create a similar-named directory
fake_dir = tmp_path / "workdir-fake"
fake_dir.mkdir()
fake_file = fake_dir / "file.txt"
fake_file.write_text("content")
# String-based check (insecure)
base_str = str(base_dir)
fake_str = str(fake_file)
# This would INCORRECTLY pass string.startswith() if not careful
# because "workdir-fake" starts with "workdir"
if base_str.endswith("/") or base_str.endswith("\\"):
@@ -345,7 +341,7 @@ class TestPathValidationSecurity:
else:
# Without separator, vulnerable to partial matches
string_check_unsafe = fake_str.startswith(base_str)
# Pathlib-based check (secure)
try:
pathlib_check = fake_file.resolve().is_relative_to(base_dir.resolve())
@@ -364,39 +360,50 @@ class TestFileUploadSecurity:
def test_ui_upload_uses_basename(self):
"""Test that ui_upload extracts basename to prevent path traversal."""
import os
from app.utils.filename_utils import sanitize_filename
# Simulate malicious filenames
malicious_filenames = [
"../../../etc/passwd",
"..\\..\\windows\\system32",
"/etc/shadow",
"folder/../file.pdf",
]
for malicious in malicious_filenames:
# os.path.basename should extract just the filename
basename = os.path.basename(malicious)
# Verify no path traversal remains in basename
assert ".." not in basename, f"Path traversal not removed: {malicious} -> {basename}"
assert "/" not in basename, f"Path separator not removed: {malicious} -> {basename}"
assert "\\" not in basename, f"Path separator not removed: {malicious} -> {basename}"
# Windows-style backslash paths: os.path.basename on Linux does NOT
# split on backslash, so the application also uses sanitize_filename
# to handle these. Verify the combined approach is safe.
windows_paths = [
"..\\..\\windows\\system32",
]
for malicious in windows_paths:
sanitized = sanitize_filename(os.path.basename(malicious))
assert ".." not in sanitized, f"Path traversal not removed after sanitize: {malicious} -> {sanitized}"
assert "\\" not in sanitized, f"Backslash not removed after sanitize: {malicious} -> {sanitized}"
def test_sanitize_after_basename(self):
"""Test that sanitization happens after basename extraction."""
from app.utils.filename_utils import sanitize_filename
import os
malicious = "../../../passwd.pdf"
# Step 1: Extract basename (as ui_upload does)
basename = os.path.basename(malicious)
assert basename == "passwd.pdf"
# Step 2: Sanitize (as ui_upload does)
sanitized = sanitize_filename(basename)
assert sanitized == "passwd.pdf"
# Final result is safe
assert ".." not in sanitized
assert "/" not in sanitized
@@ -410,11 +417,11 @@ class TestFileHashSecurity:
def test_hash_file_with_absolute_path_only(self, tmp_path):
"""Test that hash_file should only accept absolute paths."""
from app.utils.file_operations import hash_file
# Create a test file
test_file = tmp_path / "test.pdf"
test_file.write_bytes(b"test content")
# Should work with absolute path
result = hash_file(str(test_file))
assert isinstance(result, str)
@@ -423,7 +430,7 @@ class TestFileHashSecurity:
def test_hash_file_rejects_path_traversal(self):
"""Test that hash_file doesn't allow path traversal."""
from app.utils.file_operations import hash_file
# Attempt to hash a file using path traversal
# This should fail because the file doesn't exist
with pytest.raises(FileNotFoundError):
@@ -440,25 +447,25 @@ class TestEndToEndPathTraversal:
from app.utils.filename_utils import sanitize_filename
import os
import uuid
# Simulate ui_upload flow
malicious_upload_filename = "../../../etc/passwd"
# Step 1: Extract basename
base_filename = os.path.basename(malicious_upload_filename)
assert base_filename == "passwd"
# Step 2: Sanitize
safe_filename = sanitize_filename(base_filename)
assert safe_filename == "passwd"
# Step 3: Add UUID (as ui_upload does)
unique_id = str(uuid.uuid4())
target_filename = f"{unique_id}.{safe_filename}"
# Step 4: Join with workdir
target_path = os.path.join(str(tmp_path), target_filename)
# Verify final path is safe
final_path = Path(target_path)
assert final_path.parent == tmp_path
@@ -469,27 +476,27 @@ class TestEndToEndPathTraversal:
"""Test metadata embedding flow prevents path traversal."""
from app.utils.filename_utils import sanitize_filename
import os
# Simulate GPT returning malicious filename
gpt_metadata = {
"filename": "../../../etc/shadow",
"document_type": "Invoice",
}
# Step 1: Extract filename from metadata
suggested_filename = gpt_metadata.get("filename", "fallback")
# Step 2: Sanitize (as embed_metadata_into_pdf should do)
suggested_filename = sanitize_filename(suggested_filename)
# Step 3: Remove extension
suggested_filename = os.path.splitext(suggested_filename)[0]
# Step 4: Build final path
processed_dir = tmp_path / "processed"
processed_dir.mkdir()
final_path = os.path.join(str(processed_dir), f"{suggested_filename}.pdf")
# Verify final path is safe
result_path = Path(final_path)
assert result_path.parent == processed_dir