TaskFlow / tests /security /test_input_validation.py
BilalCode's picture
taskflow todo app
310260a
Raw
History Blame Contribute Delete
4.96 kB
"""
Input Validation Security Tests
Tests that all tools properly validate and sanitize inputs.
"""
import pytest
from src.tools.create_task import create_task_internal
from src.tools.update_task import update_task_internal
from src.tools.mark_complete import mark_complete_internal
from src.tools.delete_task import delete_task_internal
from src.tools.get_task import get_task_internal
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_validate_input_types_and_ranges(mock_mcp_context):
"""
Test: All tools validate input types and ranges
Verifies that tools reject invalid input types and out-of-range values.
"""
# Test create_task with invalid title length
result1 = await create_task_internal(
ctx=mock_mcp_context,
title="A" * 201 # Exceeds 200 char limit
)
assert result1["status"] == "error"
# Test create_task with invalid description length
result2 = await create_task_internal(
ctx=mock_mcp_context,
title="Valid",
description="B" * 2001 # Exceeds 2000 char limit
)
assert result2["status"] == "error"
# Test update_task with invalid title length
result3 = await update_task_internal(
ctx=mock_mcp_context,
task_id=1,
title="C" * 201
)
assert result3["status"] == "error"
# Test with negative task_id
result4 = await get_task_internal(
ctx=mock_mcp_context,
task_id=-1
)
assert result4["status"] == "error"
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_reject_sql_injection_attempts(mock_mcp_context, test_session):
"""
Test: All tools reject SQL injection attempts
Verifies that tools properly sanitize inputs to prevent SQL injection.
"""
# SQL injection attempts in title
sql_injection_payloads = [
"'; DROP TABLE tasks; --",
"1' OR '1'='1",
"admin'--",
"' UNION SELECT * FROM users--"
]
for payload in sql_injection_payloads:
# Test create_task
result = await create_task_internal(
ctx=mock_mcp_context,
title=payload
)
# Should either succeed (treating as literal string) or fail validation
# But should NOT execute SQL injection
if result["status"] == "success":
# Verify the payload was stored as literal text
assert result["task"]["title"] == payload
# Verify database integrity - tasks table should still exist
from tests.utils.task_helpers import count_tasks
count = count_tasks(test_session, mock_mcp_context.user_id)
assert isinstance(count, int) # Table exists and query works
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_handle_malformed_json_gracefully(mock_mcp_context):
"""
Test: All tools handle malformed JSON gracefully
Verifies that tools handle invalid JSON inputs without crashing.
"""
# Test with None values
result1 = await create_task_internal(
ctx=mock_mcp_context,
title=None
)
assert result1["status"] == "error"
# Test with empty string
result2 = await create_task_internal(
ctx=mock_mcp_context,
title=""
)
assert result2["status"] == "error"
# Test update with no fields
result3 = await update_task_internal(
ctx=mock_mcp_context,
task_id=1
)
assert result3["status"] == "error"
@pytest.mark.security
@pytest.mark.asyncio
async def test_tools_sanitize_special_characters(mock_mcp_context, test_session):
"""
Test: Tools sanitize special characters
Verifies that tools handle special characters safely.
"""
special_chars = [
"<script>alert('xss')</script>",
"Test\x00null\x00byte",
"Test\r\nNewline",
"Test\tTab"
]
for chars in special_chars:
result = await create_task_internal(
ctx=mock_mcp_context,
title=chars
)
# Should succeed and store safely
if result["status"] == "success":
# Verify stored correctly
from tests.utils.task_helpers import get_task_by_id
task = get_task_by_id(test_session, result["task"]["id"])
assert task is not None
@pytest.mark.security
@pytest.mark.asyncio
async def test_tools_validate_task_id_format(mock_mcp_context):
"""
Test: Tools validate task_id format
Verifies that tools reject invalid task_id formats.
"""
# Test with zero
result1 = await get_task_internal(ctx=mock_mcp_context, task_id=0)
assert result1["status"] == "error"
# Test with negative
result2 = await mark_complete_internal(ctx=mock_mcp_context, task_id=-999)
assert result2["status"] == "error"
# Test with very large number
result3 = await delete_task_internal(ctx=mock_mcp_context, task_id=999999999)
assert result3["status"] == "error"