| """ |
| Unit Tests for mark_complete MCP Tool |
| |
| Tests the mark_complete tool functionality including: |
| - Toggles task to completed |
| - Idempotent behavior on already completed tasks |
| - Error handling for non-existent task_id |
| - Task ownership validation |
| - Updates updated_at timestamp |
| """ |
|
|
| import pytest |
| from datetime import datetime, timedelta |
|
|
| from src.tools.mark_complete import mark_complete_internal |
| from tests.utils.task_helpers import create_test_task, get_task_by_id |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_toggles_task_to_completed(mock_mcp_context, test_session): |
| """ |
| Test: mark_complete toggles task to completed |
| |
| Verifies that mark_complete successfully marks a pending task as completed. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context.user_id, title="Test Task", completed=False) |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["id"] == task.id |
| assert result["task"]["title"] == "Test Task" |
| assert result["task"]["completed"] is True |
|
|
| |
| updated_task = get_task_by_id(test_session, task.id) |
| assert updated_task.completed is True |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_on_already_completed_task_is_idempotent(mock_mcp_context, test_session): |
| """ |
| Test: mark_complete on already completed task is idempotent |
| |
| Verifies that marking an already completed task as complete succeeds without error. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context.user_id, title="Completed Task", completed=True) |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["completed"] is True |
|
|
| |
| updated_task = get_task_by_id(test_session, task.id) |
| assert updated_task.completed is True |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_with_non_existent_task_id_returns_error(mock_mcp_context): |
| """ |
| Test: mark_complete with non-existent task_id returns error |
| |
| Verifies that mark_complete returns error for non-existent task. |
| """ |
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=99999 |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "not found" in result["error"].lower() |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_validates_task_ownership(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: mark_complete validates task ownership |
| |
| Verifies that mark_complete returns error when trying to complete another user's task. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="User 2 Task") |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "error" in result |
| assert "not found" in result["error"].lower() |
|
|
| |
| unchanged_task = get_task_by_id(test_session, task.id) |
| assert unchanged_task.completed is False |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_updates_updated_at_timestamp(mock_mcp_context, test_session): |
| """ |
| Test: mark_complete updates updated_at timestamp |
| |
| Verifies that mark_complete updates the updated_at timestamp. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context.user_id, title="Test Task") |
| original_updated_at = task.updated_at |
|
|
| |
| import time |
| time.sleep(0.1) |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "success" |
|
|
| |
| updated_task = get_task_by_id(test_session, task.id) |
| assert updated_task.updated_at > original_updated_at |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_toggles_completed_to_incomplete(mock_mcp_context, test_session): |
| """ |
| Test: mark_complete toggles completed task back to incomplete |
| |
| Verifies that mark_complete can toggle a completed task back to incomplete. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context.user_id, title="Completed Task", completed=True) |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert result["task"]["completed"] is False |
|
|
| |
| updated_task = get_task_by_id(test_session, task.id) |
| assert updated_task.completed is False |
|
|
|
|
| @pytest.mark.unit |
| @pytest.mark.asyncio |
| async def test_mark_complete_returns_task_details(mock_mcp_context, test_session): |
| """ |
| Test: mark_complete returns task details in response |
| |
| Verifies that mark_complete returns complete task information. |
| """ |
| |
| task = create_test_task(test_session, mock_mcp_context.user_id, title="My Task") |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=task.id |
| ) |
|
|
| |
| assert result["status"] == "success" |
| assert "task" in result |
|
|
| task_data = result["task"] |
| assert "id" in task_data |
| assert "title" in task_data |
| assert "completed" in task_data |
| assert "updated_at" in task_data |
|
|
| assert task_data["id"] == task.id |
| assert task_data["title"] == "My Task" |
|
|