| """ |
| Unit Tests for create_task MCP Tool |
| |
| Tests the create_task tool functionality including: |
| - Valid task creation with title only |
| - Task creation with title and description |
| - Validation errors for empty title |
| - Validation errors for title exceeding 200 chars |
| - Validation errors for description exceeding 2000 chars |
| - User_id scoping from MCPContext |
| """ |
|
|
| import pytest |
| from datetime import datetime |
|
|
| from src.tools.create_task import create_task_internal |
| from tests.utils.task_helpers import get_task_by_id, count_tasks |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_valid_title(mock_mcp_context, test_session): |
| """ |
| Test: create_task with valid title |
| |
| Verifies that create_task successfully creates a task with just a title. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Buy groceries" |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert "task" in result |
| assert result["task"]["title"] == "Buy groceries" |
| assert result["task"]["description"] is None |
| assert result["task"]["completed"] is False |
| assert "id" in result["task"] |
| assert "created_at" in result["task"] |
| assert "updated_at" in result["task"] |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
| assert task is not None |
| assert task.title == "Buy groceries" |
| assert task.user_id == mock_mcp_context.user_id |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_title_and_description(mock_mcp_context, test_session): |
| """ |
| Test: create_task with title and description |
| |
| Verifies that create_task successfully creates a task with both title and description. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Review PR", |
| description="Check code quality and test coverage" |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["title"] == "Review PR" |
| assert result["task"]["description"] == "Check code quality and test coverage" |
| assert result["task"]["completed"] is False |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
| assert task is not None |
| assert task.title == "Review PR" |
| assert task.description == "Check code quality and test coverage" |
| assert task.user_id == mock_mcp_context.user_id |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_empty_title_returns_error(mock_mcp_context): |
| """ |
| Test: create_task with empty title returns error |
| |
| Verifies that create_task returns validation error for empty title. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="" |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "Title is required" in result["error"] |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_whitespace_only_title_returns_error(mock_mcp_context): |
| """ |
| Test: create_task with whitespace-only title returns error |
| |
| Verifies that create_task returns validation error for whitespace-only title. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title=" " |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "Title is required" in result["error"] |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_title_exceeding_200_chars_returns_error(mock_mcp_context): |
| """ |
| Test: create_task with title exceeding 200 chars returns error |
| |
| Verifies that create_task returns validation error for title exceeding 200 characters. |
| """ |
| |
| long_title = "A" * 201 |
|
|
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title=long_title |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "Title exceeds 200 characters" in result["error"] |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_description_exceeding_2000_chars_returns_error(mock_mcp_context): |
| """ |
| Test: create_task with description exceeding 2000 chars returns error |
| |
| Verifies that create_task returns validation error for description exceeding 2000 characters. |
| """ |
| |
| long_description = "B" * 2001 |
|
|
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Valid title", |
| description=long_description |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "Description exceeds 2000 characters" in result["error"] |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_persists_user_id_from_mcp_context(mock_mcp_context, test_session): |
| """ |
| Test: create_task persists user_id from MCPContext |
| |
| Verifies that create_task correctly uses user_id from MCPContext for data scoping. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Test task for user scoping" |
| ) |
|
|
| |
| assert result["status"] == "success" |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
| assert task is not None |
| assert task.user_id == mock_mcp_context.user_id |
|
|
| |
| task_count = count_tasks(test_session, mock_mcp_context.user_id) |
| assert task_count == 1 |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_max_valid_title_length(mock_mcp_context, test_session): |
| """ |
| Test: create_task with maximum valid title length (200 chars) |
| |
| Verifies that create_task accepts title with exactly 200 characters. |
| """ |
| |
| max_title = "A" * 200 |
|
|
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title=max_title |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["title"] == max_title |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
| assert task is not None |
| assert len(task.title) == 200 |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_create_task_with_max_valid_description_length(mock_mcp_context, test_session): |
| """ |
| Test: create_task with maximum valid description length (2000 chars) |
| |
| Verifies that create_task accepts description with exactly 2000 characters. |
| """ |
| |
| max_description = "B" * 2000 |
|
|
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Valid title", |
| description=max_description |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["description"] == max_description |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
| assert task is not None |
| assert len(task.description) == 2000 |
|
|