File size: 4,961 Bytes
310260a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
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"