security fixes
All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 26s
All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 26s
This commit is contained in:
229
tests/test_safe_dispatch.py
Normal file
229
tests/test_safe_dispatch.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""Tests for safe dispatch utility."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# Add tools directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "tools" / "ai-review"))
|
||||
|
||||
from utils.safe_dispatch import (
|
||||
MAX_EVENT_SIZE,
|
||||
load_event_data,
|
||||
safe_dispatch,
|
||||
)
|
||||
|
||||
|
||||
class TestLoadEventData:
|
||||
"""Test event data loading and validation."""
|
||||
|
||||
def test_load_valid_json(self):
|
||||
"""Test loading valid JSON."""
|
||||
event_json = '{"action": "created", "issue": {"number": 123}}'
|
||||
|
||||
data = load_event_data(event_json)
|
||||
|
||||
assert data["action"] == "created"
|
||||
assert data["issue"]["number"] == 123
|
||||
|
||||
def test_reject_invalid_json(self):
|
||||
"""Test that invalid JSON is rejected."""
|
||||
invalid_json = '{"action": "created", invalid}'
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid JSON"):
|
||||
load_event_data(invalid_json)
|
||||
|
||||
def test_reject_too_large_data(self):
|
||||
"""Test that data exceeding size limit is rejected."""
|
||||
# Create JSON larger than MAX_EVENT_SIZE
|
||||
large_data = {"data": "x" * (MAX_EVENT_SIZE + 1)}
|
||||
large_json = json.dumps(large_data)
|
||||
|
||||
with pytest.raises(ValueError, match="Event data too large"):
|
||||
load_event_data(large_json)
|
||||
|
||||
def test_reject_non_object_json(self):
|
||||
"""Test that non-object JSON is rejected."""
|
||||
# JSON array
|
||||
with pytest.raises(ValueError, match="must be a JSON object"):
|
||||
load_event_data('["array"]')
|
||||
|
||||
# JSON string
|
||||
with pytest.raises(ValueError, match="must be a JSON object"):
|
||||
load_event_data('"string"')
|
||||
|
||||
# JSON number
|
||||
load_event_data("123")
|
||||
|
||||
def test_accept_empty_object(self):
|
||||
"""Test that empty object is valid."""
|
||||
data = load_event_data("{}")
|
||||
assert data == {}
|
||||
|
||||
|
||||
class TestSafeDispatch:
|
||||
"""Test safe dispatch functionality."""
|
||||
|
||||
@patch("utils.safe_dispatch.get_dispatcher")
|
||||
def test_successful_dispatch(self, mock_get_dispatcher):
|
||||
"""Test successful event dispatch."""
|
||||
# Mock dispatcher
|
||||
mock_dispatcher = Mock()
|
||||
mock_result = Mock()
|
||||
mock_result.errors = []
|
||||
mock_result.agents_run = ["PRAgent"]
|
||||
mock_result.results = [Mock(success=True, message="Success")]
|
||||
mock_dispatcher.dispatch.return_value = mock_result
|
||||
mock_get_dispatcher.return_value = mock_dispatcher
|
||||
|
||||
event_json = json.dumps(
|
||||
{
|
||||
"action": "created",
|
||||
"issue": {"number": 123},
|
||||
"comment": {"body": "test"},
|
||||
}
|
||||
)
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo", event_json)
|
||||
|
||||
assert exit_code == 0
|
||||
mock_dispatcher.dispatch.assert_called_once()
|
||||
|
||||
@patch("utils.safe_dispatch.get_dispatcher")
|
||||
def test_dispatch_with_errors(self, mock_get_dispatcher):
|
||||
"""Test dispatch that encounters errors."""
|
||||
# Mock dispatcher with errors
|
||||
mock_dispatcher = Mock()
|
||||
mock_result = Mock()
|
||||
mock_result.errors = ["Agent failed"]
|
||||
mock_result.agents_run = ["PRAgent"]
|
||||
mock_result.results = [Mock(success=False, message="Failed")]
|
||||
mock_dispatcher.dispatch.return_value = mock_result
|
||||
mock_get_dispatcher.return_value = mock_dispatcher
|
||||
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo", event_json)
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
def test_invalid_repository_format(self):
|
||||
"""Test that invalid repository format returns error."""
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "invalid-repo", event_json)
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
def test_path_traversal_rejection(self):
|
||||
"""Test that path traversal attempts are rejected."""
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/../../etc/passwd", event_json)
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
def test_shell_injection_rejection(self):
|
||||
"""Test that shell injection attempts are rejected."""
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo; rm -rf /", event_json)
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
def test_invalid_json_rejection(self):
|
||||
"""Test that invalid JSON returns error."""
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo", "invalid json")
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
@patch("utils.safe_dispatch.get_dispatcher")
|
||||
def test_sanitization_applied(self, mock_get_dispatcher):
|
||||
"""Test that data is sanitized before dispatch."""
|
||||
mock_dispatcher = Mock()
|
||||
mock_result = Mock()
|
||||
mock_result.errors = []
|
||||
mock_result.agents_run = []
|
||||
mock_result.results = []
|
||||
mock_dispatcher.dispatch.return_value = mock_result
|
||||
mock_get_dispatcher.return_value = mock_dispatcher
|
||||
|
||||
# Event with sensitive data
|
||||
event_json = json.dumps(
|
||||
{
|
||||
"action": "created",
|
||||
"issue": {
|
||||
"number": 123,
|
||||
"user": {
|
||||
"login": "testuser",
|
||||
"email": "secret@example.com", # Should be sanitized
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
safe_dispatch("issue_comment", "owner/repo", event_json)
|
||||
|
||||
# Check that dispatch was called
|
||||
call_args = mock_dispatcher.dispatch.call_args
|
||||
dispatched_data = call_args[1]["event_data"]
|
||||
|
||||
# Sensitive data should not be in the minimal context
|
||||
assert "email" not in str(dispatched_data)
|
||||
|
||||
@patch("utils.safe_dispatch.get_dispatcher")
|
||||
def test_exception_handling(self, mock_get_dispatcher):
|
||||
"""Test that unexpected exceptions are handled."""
|
||||
mock_dispatcher = Mock()
|
||||
mock_dispatcher.dispatch.side_effect = Exception("Unexpected error")
|
||||
mock_get_dispatcher.return_value = mock_dispatcher
|
||||
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo", event_json)
|
||||
|
||||
assert exit_code == 1
|
||||
|
||||
|
||||
class TestInputValidation:
|
||||
"""Test input validation edge cases."""
|
||||
|
||||
def test_repository_with_special_chars(self):
|
||||
"""Test repository names with allowed special characters."""
|
||||
event_json = '{"action": "created"}'
|
||||
|
||||
# Underscores and hyphens are allowed
|
||||
with patch("utils.safe_dispatch.get_dispatcher") as mock:
|
||||
mock_dispatcher = Mock()
|
||||
mock_result = Mock(errors=[], agents_run=[], results=[])
|
||||
mock_dispatcher.dispatch.return_value = mock_result
|
||||
mock.return_value = mock_dispatcher
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "my-org/my_repo", event_json)
|
||||
assert exit_code == 0
|
||||
|
||||
def test_unicode_in_event_data(self):
|
||||
"""Test handling of Unicode in event data."""
|
||||
event_json = json.dumps(
|
||||
{
|
||||
"action": "created",
|
||||
"comment": {"body": "Hello 世界 🌍"},
|
||||
}
|
||||
)
|
||||
|
||||
with patch("utils.safe_dispatch.get_dispatcher") as mock:
|
||||
with patch('utils.safe_dispatch.get_dispatcher') as mock:
|
||||
mock_dispatcher = Mock()
|
||||
mock_result = Mock(errors=[], agents_run=[], results=[])
|
||||
mock_dispatcher.dispatch.return_value = mock_result
|
||||
mock.return_value = mock_dispatcher
|
||||
|
||||
exit_code = safe_dispatch("issue_comment", "owner/repo", event_json)
|
||||
assert exit_code == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user