Fix conversation ID validation to return 400 instead of 500 for long IDs (#10496)

This commit is contained in:
sp.wack
2025-08-19 18:03:05 +04:00
committed by GitHub
parent 476954f3a4
commit 0297b3da18
3 changed files with 171 additions and 8 deletions

View File

@@ -0,0 +1,118 @@
"""
Test cases for conversation ID validation to ensure proper error handling.
This addresses GitHub issue #10489 where long conversation IDs were returning
500 Internal Server Error instead of proper 4xx errors.
"""
import pytest
from fastapi import HTTPException, status
from openhands.server.utils import validate_conversation_id
class TestConversationIdValidation:
"""Test conversation ID validation function."""
def test_valid_conversation_id(self):
"""Test that valid conversation IDs pass validation."""
# Test normal UUID hex format (32 characters)
valid_id = 'a1b2c3d4e5f6789012345678901234ab'
result = validate_conversation_id(valid_id)
assert result == valid_id
# Test shorter valid ID
valid_id = 'abc123'
result = validate_conversation_id(valid_id)
assert result == valid_id
# Test alphanumeric with hyphens (UUID format)
valid_id = 'a1b2c3d4-e5f6-7890-1234-5678901234ab'
result = validate_conversation_id(valid_id)
assert result == valid_id
def test_long_conversation_id_rejected(self):
"""Test that very long conversation IDs are rejected with 400 Bad Request.
This is the main test case for GitHub issue #10489.
"""
# Test with 1000 character ID (similar to the reported issue)
long_id = 'a' * 1000
with pytest.raises(HTTPException) as exc_info:
validate_conversation_id(long_id)
assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
assert 'too long' in exc_info.value.detail
def test_conversation_id_with_null_bytes_rejected(self):
"""Test that conversation IDs with null bytes are rejected."""
invalid_id = 'valid\x00id'
with pytest.raises(HTTPException) as exc_info:
validate_conversation_id(invalid_id)
assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
assert 'invalid characters' in exc_info.value.detail
def test_conversation_id_with_path_traversal_rejected(self):
"""Test that conversation IDs with path traversal attempts are rejected."""
invalid_ids = [
'../../../etc/passwd',
'id/../other',
'id\\..\\other',
'id/with/slashes',
'id\\with\\backslashes',
]
for invalid_id in invalid_ids:
with pytest.raises(HTTPException) as exc_info:
validate_conversation_id(invalid_id)
assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
assert 'invalid path characters' in exc_info.value.detail
def test_conversation_id_with_control_characters_rejected(self):
"""Test that conversation IDs with control characters are rejected."""
invalid_ids = [
'id\nwith\nnewlines',
'id\twith\ttabs',
'id\rwith\rcarriage',
'id\x01with\x02control',
]
for invalid_id in invalid_ids:
with pytest.raises(HTTPException) as exc_info:
validate_conversation_id(invalid_id)
assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
assert 'control characters' in exc_info.value.detail
def test_conversation_id_boundary_length(self):
"""Test conversation ID length boundaries."""
# Test exactly 100 characters (should pass)
boundary_id = 'a' * 100
result = validate_conversation_id(boundary_id)
assert result == boundary_id
# Test 101 characters (should fail)
too_long_id = 'a' * 101
with pytest.raises(HTTPException) as exc_info:
validate_conversation_id(too_long_id)
assert exc_info.value.status_code == status.HTTP_400_BAD_REQUEST
assert 'too long' in exc_info.value.detail
def test_empty_conversation_id(self):
"""Test that empty conversation ID is handled."""
# Empty string should pass validation (will fail later in business logic)
result = validate_conversation_id('')
assert result == ''
def test_conversation_id_with_spaces(self):
"""Test that conversation IDs with spaces are allowed."""
# Spaces are printable characters (ASCII 32), so they should be allowed
# The business logic might reject them, but validation should pass
id_with_spaces = 'id with spaces'
result = validate_conversation_id(id_with_spaces)
assert result == id_with_spaces