File size: 4,449 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
"""
Contract Tests for list_tasks Tool

Validates that list_tasks tool adheres to its defined contract:
- Input schema validation
- Output schema validation
- Error schema validation
"""

import pytest
import json

from src.tools.list_tasks import list_tasks_internal, get_tool_definition
from tests.utils.task_helpers import create_multiple_tasks


@pytest.mark.contract
@pytest.mark.asyncio
async def test_list_tasks_input_schema_validation(mock_mcp_context):
    """
    Test: list_tasks input schema validation

    Verifies that list_tasks accepts inputs matching the defined schema.
    """
    # Get tool definition
    tool_def = get_tool_definition()

    # Verify tool definition structure
    assert tool_def["type"] == "function"
    assert "function" in tool_def
    assert tool_def["function"]["name"] == "list_tasks"

    # Verify parameters schema (no parameters required)
    params = tool_def["function"]["parameters"]
    assert params["type"] == "object"
    assert params["properties"] == {}
    assert params["required"] == []

    # Test valid input (no parameters)
    result = await list_tasks_internal(ctx=mock_mcp_context)
    assert "tasks" in result


@pytest.mark.contract
@pytest.mark.asyncio
async def test_list_tasks_output_schema_validation(mock_mcp_context, test_session):
    """
    Test: list_tasks output schema validation

    Verifies that list_tasks returns outputs matching the defined schema.
    """
    # Setup: Create tasks
    create_multiple_tasks(test_session, mock_mcp_context.user_id, count=2, completed=False)
    create_multiple_tasks(test_session, mock_mcp_context.user_id, count=1, completed=True)

    # Execute
    result = await list_tasks_internal(ctx=mock_mcp_context)

    # Verify output schema
    assert "tasks" in result
    assert isinstance(result["tasks"], list)

    assert "total" in result
    assert isinstance(result["total"], int)
    assert result["total"] == 3

    assert "completed_count" in result
    assert isinstance(result["completed_count"], int)
    assert result["completed_count"] == 1

    assert "pending_count" in result
    assert isinstance(result["pending_count"], int)
    assert result["pending_count"] == 2

    # Verify task object schema
    for task in result["tasks"]:
        assert "id" in task
        assert isinstance(task["id"], int)

        assert "title" in task
        assert isinstance(task["title"], str)

        assert "description" in task
        # description can be string or None

        assert "completed" in task
        assert isinstance(task["completed"], bool)

        assert "created_at" in task
        assert isinstance(task["created_at"], str)

        assert "updated_at" in task
        assert isinstance(task["updated_at"], str)


@pytest.mark.contract
@pytest.mark.asyncio
async def test_list_tasks_error_schema_validation(mock_mcp_context):
    """
    Test: list_tasks error schema validation

    Verifies that list_tasks returns errors matching the defined schema.
    """
    # list_tasks typically doesn't fail with validation errors
    # but it should handle database errors gracefully

    # Execute (should succeed)
    result = await list_tasks_internal(ctx=mock_mcp_context)

    # If error occurs, verify error schema
    if "status" in result and result["status"] == "error":
        assert "error" in result
        assert isinstance(result["error"], str)
        assert len(result["error"]) > 0


@pytest.mark.contract
def test_list_tasks_tool_definition_matches_contract():
    """
    Test: list_tasks tool definition matches contract

    Verifies that the tool definition matches the contract specification.
    """
    # Load contract from file
    import os
    contract_path = os.path.join(
        os.path.dirname(__file__),
        "../../../specs/001-mcp-server-tools/contracts/list_tasks.json"
    )

    with open(contract_path, "r") as f:
        contract = json.load(f)

    # Get tool definition
    tool_def = get_tool_definition()

    # Verify tool name matches
    assert tool_def["function"]["name"] == contract["tool_name"]

    # Verify input schema structure matches (no parameters)
    tool_params = tool_def["function"]["parameters"]
    contract_input = contract["input_schema"]

    assert tool_params["type"] == contract_input["type"]
    assert tool_params["properties"] == contract_input["properties"]
    assert tool_params["required"] == contract_input["required"]