mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 09:08:46 +02:00
read_pdf_upload() bounds reads at 10 MB and requires the %PDF magic before anything reaches pdftotext (SEC-06); rejections land in the per-file errors list with the filename. Parsing now runs via asyncio.to_thread so a slow PDF no longer blocks the event loop for up to 30s (BE-14). Review notes: temp files were already context-managed (BE-09 overstated) and the pension batch already commits atomically (BE-10 overstated). 69 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42 lines
1.2 KiB
Python
42 lines
1.2 KiB
Python
"""Upload validation: size cap and PDF magic-byte check (SEC-06)."""
|
|
|
|
import asyncio
|
|
from io import BytesIO
|
|
|
|
import pytest
|
|
from fastapi import UploadFile
|
|
|
|
from app.uploads import read_pdf_upload
|
|
|
|
|
|
def make_upload(data: bytes, filename: str = "test.pdf") -> UploadFile:
|
|
return UploadFile(file=BytesIO(data), filename=filename)
|
|
|
|
|
|
def test_valid_pdf_accepted():
|
|
data = b"%PDF-1.7 fake content"
|
|
out = asyncio.run(read_pdf_upload(make_upload(data)))
|
|
assert out == data
|
|
|
|
|
|
def test_non_pdf_rejected_with_filename():
|
|
with pytest.raises(ValueError, match=r"evil\.pdf: not a PDF"):
|
|
asyncio.run(read_pdf_upload(make_upload(b"MZ\x90\x00binary", "evil.pdf")))
|
|
|
|
|
|
def test_oversized_rejected():
|
|
big = b"%PDF" + b"x" * 100
|
|
with pytest.raises(ValueError, match="exceeds the 0 MB limit"):
|
|
asyncio.run(read_pdf_upload(make_upload(big), max_bytes=50))
|
|
|
|
|
|
def test_exact_limit_accepted():
|
|
data = b"%PDF" + b"x" * 46 # exactly 50 bytes
|
|
out = asyncio.run(read_pdf_upload(make_upload(data), max_bytes=50))
|
|
assert len(out) == 50
|
|
|
|
|
|
def test_empty_file_rejected():
|
|
with pytest.raises(ValueError, match="not a PDF"):
|
|
asyncio.run(read_pdf_upload(make_upload(b"")))
|