Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

299 lines
10 KiB
Python

"""
Unit tests for FileUploadValidator.
Tests cover all validation methods:
- File size validation
- File count validation
- MIME type validation
- PDF structure validation
- Comprehensive upload validation
"""
from unittest.mock import patch
from local_deep_research.security.file_upload_validator import (
FileUploadValidator,
)
class TestValidateFileSize:
"""Tests for validate_file_size method.
Oversized-path tests patch ``MAX_FILE_SIZE`` to a small value so they
can exercise the rejection branch without allocating multi-GB buffers
in CI.
"""
def test_valid_size_via_content_length(self):
"""Test that valid size passes via Content-Length header."""
is_valid, error = FileUploadValidator.validate_file_size(
content_length=1024 * 1024 # 1MB
)
assert is_valid is True
assert error is None
def test_valid_size_via_file_content(self):
"""Test that valid size passes via actual file content."""
content = b"x" * (1024 * 1024) # 1MB
is_valid, error = FileUploadValidator.validate_file_size(
content_length=None, file_content=content
)
assert is_valid is True
assert error is None
def test_oversized_via_content_length(self):
"""Test that oversized file is rejected via Content-Length."""
with patch.object(
FileUploadValidator, "MAX_FILE_SIZE", 50 * 1024 * 1024
):
is_valid, error = FileUploadValidator.validate_file_size(
content_length=51 * 1024 * 1024 # 51MB over a 50MB cap
)
assert is_valid is False
assert error is not None
assert "too large" in error.lower()
def test_oversized_via_file_content(self):
"""Test that oversized file is rejected via actual content."""
with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
content = b"x" * 2048 # 2KB over a 1KB cap
is_valid, error = FileUploadValidator.validate_file_size(
content_length=None, file_content=content
)
assert is_valid is False
assert error is not None
assert "too large" in error.lower()
def test_exactly_at_limit(self):
"""Test that file exactly at limit passes."""
is_valid, error = FileUploadValidator.validate_file_size(
content_length=FileUploadValidator.MAX_FILE_SIZE
)
assert is_valid is True
assert error is None
def test_one_byte_over_limit(self):
"""Test that file one byte over limit is rejected."""
is_valid, error = FileUploadValidator.validate_file_size(
content_length=FileUploadValidator.MAX_FILE_SIZE + 1
)
assert is_valid is False
assert error is not None
def test_both_none_returns_valid(self):
"""Test that both None values return valid (no info to validate)."""
is_valid, error = FileUploadValidator.validate_file_size(
content_length=None, file_content=None
)
assert is_valid is True
assert error is None
class TestValidateFileCount:
"""Tests for validate_file_count method."""
def test_valid_file_count(self):
"""Test that valid file count passes."""
is_valid, error = FileUploadValidator.validate_file_count(10)
assert is_valid is True
assert error is None
def test_single_file_valid(self):
"""Test that single file passes."""
is_valid, error = FileUploadValidator.validate_file_count(1)
assert is_valid is True
assert error is None
def test_max_files_valid(self):
"""Test that exactly max files passes."""
is_valid, error = FileUploadValidator.validate_file_count(
FileUploadValidator.MAX_FILES_PER_REQUEST
)
assert is_valid is True
assert error is None
def test_too_many_files(self):
"""Test that exceeding max files is rejected."""
is_valid, error = FileUploadValidator.validate_file_count(
FileUploadValidator.MAX_FILES_PER_REQUEST + 1
)
assert is_valid is False
assert error is not None
assert "too many" in error.lower()
def test_zero_files(self):
"""Test that zero files is rejected."""
is_valid, error = FileUploadValidator.validate_file_count(0)
assert is_valid is False
assert error is not None
assert "no files" in error.lower()
def test_negative_count(self):
"""Test that negative count is rejected."""
is_valid, error = FileUploadValidator.validate_file_count(-1)
assert is_valid is False
assert error is not None
class TestValidateMimeType:
"""Tests for validate_mime_type method."""
def test_valid_pdf_extension_and_magic(self):
"""Test that valid PDF passes."""
content = b"%PDF-1.4\ntest content"
is_valid, error = FileUploadValidator.validate_mime_type(
"test.pdf", content
)
assert is_valid is True
assert error is None
def test_uppercase_extension_valid(self):
"""Test that uppercase .PDF extension passes."""
content = b"%PDF-1.4\ntest content"
is_valid, error = FileUploadValidator.validate_mime_type(
"test.PDF", content
)
assert is_valid is True
assert error is None
def test_mixed_case_extension_valid(self):
"""Test that mixed case .Pdf extension passes."""
content = b"%PDF-1.4\ntest content"
is_valid, error = FileUploadValidator.validate_mime_type(
"test.PdF", content
)
assert is_valid is True
assert error is None
def test_wrong_extension_rejected(self):
"""Test that wrong extension is rejected."""
content = b"%PDF-1.4\ntest content"
is_valid, error = FileUploadValidator.validate_mime_type(
"test.txt", content
)
assert is_valid is False
assert error is not None
assert "only pdf" in error.lower()
def test_no_extension_rejected(self):
"""Test that file without extension is rejected."""
content = b"%PDF-1.4\ntest content"
is_valid, error = FileUploadValidator.validate_mime_type(
"testfile", content
)
assert is_valid is False
assert error is not None
def test_wrong_magic_bytes_rejected(self):
"""Test that wrong magic bytes are rejected."""
content = b"This is not a PDF"
is_valid, error = FileUploadValidator.validate_mime_type(
"test.pdf", content
)
assert is_valid is False
assert error is not None
assert "signature mismatch" in error.lower()
def test_empty_content_rejected(self):
"""Test that empty content is rejected."""
content = b""
is_valid, error = FileUploadValidator.validate_mime_type(
"test.pdf", content
)
assert is_valid is False
assert error is not None
def test_exe_disguised_as_pdf_rejected(self):
"""Test that executable disguised as PDF is rejected."""
# MZ header for executable
content = b"MZ" + b"\x00" * 100
is_valid, error = FileUploadValidator.validate_mime_type(
"malware.pdf", content
)
assert is_valid is False
assert error is not None
class TestValidatePdfStructure:
"""Tests for validate_pdf_structure method."""
def test_malformed_pdf_structure(self):
"""Test that malformed PDF structure is rejected."""
# Has magic bytes but invalid structure
content = b"%PDF-1.4\nthis is not valid pdf structure"
is_valid, error = FileUploadValidator.validate_pdf_structure(
"test.pdf", content
)
assert is_valid is False
assert error is not None
assert "invalid" in error.lower() or "corrupted" in error.lower()
def test_truncated_pdf_rejected(self):
"""Test that truncated PDF is rejected."""
content = b"%PDF-1.4" # Just the header
is_valid, error = FileUploadValidator.validate_pdf_structure(
"test.pdf", content
)
assert is_valid is False
assert error is not None
class TestValidateUpload:
"""Tests for validate_upload comprehensive method."""
def test_all_validations_run(self):
"""Test that all validations are executed."""
# Invalid size should fail first. Patch the cap to keep memory low.
with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
large_content = b"%PDF-1.4\n" + (b"x" * 2048) # 2KB over 1KB cap
is_valid, error = FileUploadValidator.validate_upload(
filename="test.pdf",
file_content=large_content,
)
assert is_valid is False
assert "too large" in error.lower()
def test_invalid_mime_fails_after_size(self):
"""Test that MIME validation runs after size validation."""
content = b"not a pdf"
is_valid, error = FileUploadValidator.validate_upload(
filename="test.txt", # Wrong extension
file_content=content,
)
assert is_valid is False
assert "pdf" in error.lower()
def test_returns_first_error(self):
"""Test that first validation error stops processing."""
# Large file with wrong extension - should fail on size first
with patch.object(FileUploadValidator, "MAX_FILE_SIZE", 1024):
large_content = b"x" * 2048 # 2KB over 1KB cap
is_valid, error = FileUploadValidator.validate_upload(
filename="test.txt",
file_content=large_content,
)
assert is_valid is False
# Should fail on size before checking extension
assert "too large" in error.lower()
class TestConstants:
"""Tests for class constants."""
def test_max_file_size_default_is_3gb(self):
"""Default MAX_FILE_SIZE matches the 3 GB server-config default."""
expected = 3 * 1024 * 1024 * 1024
assert FileUploadValidator.MAX_FILE_SIZE == expected
def test_max_files_is_200(self):
"""Verify MAX_FILES_PER_REQUEST is 200."""
assert FileUploadValidator.MAX_FILES_PER_REQUEST == 200
def test_pdf_magic_bytes(self):
"""Verify PDF magic bytes."""
assert FileUploadValidator.PDF_MAGIC_BYTES == b"%PDF"
def test_allowed_mime_types(self):
"""Verify allowed MIME types."""
assert "application/pdf" in FileUploadValidator.ALLOWED_MIME_TYPES