fix(files): handle empty json uploads safely (#434)
* fix(files): handle empty json uploads safely * fix(files): normalize invalid json upload errors * fix(files): restore file processing error import --------- Co-authored-by: LRRuan <lrruan@users.noreply.github.com>
This commit is contained in:
parent
09a980c2fb
commit
2097c2cbcf
|
|
@ -10,7 +10,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from src import schemas
|
||||
from src.config import settings
|
||||
from src.exceptions import FileProcessingError, UnsupportedFileTypeError
|
||||
from src.exceptions import (
|
||||
FileProcessingError,
|
||||
UnsupportedFileTypeError,
|
||||
ValidationException,
|
||||
)
|
||||
from src.schemas import Message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -58,7 +62,19 @@ class JSONProcessor:
|
|||
async def extract_text(self, content: bytes) -> str:
|
||||
import json
|
||||
|
||||
data = json.loads(content.decode("utf-8"))
|
||||
try:
|
||||
decoded_content = content.decode("utf-8")
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ValidationException("JSON uploads must be UTF-8 encoded") from exc
|
||||
|
||||
if not decoded_content.strip():
|
||||
return ""
|
||||
|
||||
try:
|
||||
data = json.loads(decoded_content)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValidationException("Uploaded JSON is invalid") from exc
|
||||
|
||||
# Convert JSON to readable text format
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,33 @@ async def test_create_messages_with_json_file(
|
|||
assert message["session_id"] == session_name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_messages_with_empty_json_file(
|
||||
client: TestClient,
|
||||
db_session: AsyncSession,
|
||||
sample_data: tuple[Workspace, Peer],
|
||||
):
|
||||
"""Test that empty JSON uploads do not crash and create empty content."""
|
||||
test_workspace, test_peer = sample_data
|
||||
|
||||
test_session = await _create_test_session(db_session, test_workspace)
|
||||
session_name = test_session.name
|
||||
|
||||
file_data = io.BytesIO(b"")
|
||||
files = {"file": ("empty.json", file_data, "application/json")}
|
||||
form_data = {"peer_id": test_peer.name}
|
||||
|
||||
url = _get_upload_url(test_workspace.name, session_name)
|
||||
response = client.post(url, files=files, data=form_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["content"] == ""
|
||||
assert data[0]["peer_id"] == test_peer.name
|
||||
assert data[0]["session_id"] == session_name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_messages_with_unsupported_file_type(
|
||||
client: TestClient,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from src.exceptions import ValidationException
|
||||
from src.utils.files import JSONProcessor
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_processor_returns_empty_string_for_blank_content():
|
||||
processor = JSONProcessor()
|
||||
|
||||
assert await processor.extract_text(b"") == ""
|
||||
assert await processor.extract_text(b" \n\t") == ""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_processor_preserves_valid_json_behavior():
|
||||
processor = JSONProcessor()
|
||||
|
||||
result = await processor.extract_text(b'{"name": "test", "count": 1}')
|
||||
|
||||
assert json.loads(result) == {"name": "test", "count": 1}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_processor_rejects_non_utf8_content():
|
||||
processor = JSONProcessor()
|
||||
|
||||
with pytest.raises(ValidationException, match="UTF-8"):
|
||||
await processor.extract_text(b"\xff\xfe\x00{")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_json_processor_rejects_invalid_json_content():
|
||||
processor = JSONProcessor()
|
||||
|
||||
with pytest.raises(ValidationException, match="invalid"):
|
||||
await processor.extract_text(b'{"name": }')
|
||||
Loading…
Reference in New Issue