TaskFlow / tests /unit /tools /test_mark_complete.py
BilalCode's picture
taskflow todo app
310260a
Raw
History Blame Contribute Delete
6.28 kB
"""
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.
"""
# Setup: Create a pending task
task = create_test_task(test_session, mock_mcp_context.user_id, title="Test Task", completed=False)
# Execute
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert
assert result["status"] == "success"
assert result["task"]["id"] == task.id
assert result["task"]["title"] == "Test Task"
assert result["task"]["completed"] is True
# Verify task is marked complete in database
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.
"""
# Setup: Create a completed task
task = create_test_task(test_session, mock_mcp_context.user_id, title="Completed Task", completed=True)
# Execute
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert - should succeed (idempotent)
assert result["status"] == "success"
assert result["task"]["completed"] is True
# Verify task remains completed
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.
"""
# Execute with non-existent task_id
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=99999
)
# Assert
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.
"""
# Setup: Create task for user 2
task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="User 2 Task")
# Execute with user 1 context (trying to complete user 2's task)
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert - should fail (unauthorized)
assert result["status"] == "error"
assert "error" in result
assert "not found" in result["error"].lower() # Returns "not found" to prevent information disclosure
# Verify task remains unchanged
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.
"""
# Setup: Create task with old timestamp
task = create_test_task(test_session, mock_mcp_context.user_id, title="Test Task")
original_updated_at = task.updated_at
# Wait a moment to ensure timestamp difference
import time
time.sleep(0.1)
# Execute
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert
assert result["status"] == "success"
# Verify updated_at was updated
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.
"""
# Setup: Create a completed task
task = create_test_task(test_session, mock_mcp_context.user_id, title="Completed Task", completed=True)
# Execute mark_complete (should toggle to incomplete)
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert - task should be toggled to incomplete
assert result["status"] == "success"
assert result["task"]["completed"] is False
# Verify in database
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.
"""
# Setup
task = create_test_task(test_session, mock_mcp_context.user_id, title="My Task")
# Execute
result = await mark_complete_internal(
ctx=mock_mcp_context,
task_id=task.id
)
# Assert response contains required fields
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"