From dc0ff6c1a870bb0e01f35c653303da08fc19485b Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Fri, 19 Dec 2025 17:24:44 +0100 Subject: feat: implement task modification functionality in TaskWarrior server - Added `modify_task` method to allow modification of tasks based on a filter expression, supporting parameters for project, priority, due date, description, and tags. - Implemented validation for modification parameters, ensuring at least one parameter is provided and that priority and tag formats are correct. - Introduced corresponding tests to validate the functionality of task modifications, including edge cases for invalid inputs and multiple modifications. - Enhanced docstrings for clarity and detailed usage examples. --- servers/taskwarrior/tests/test_server.py | 165 +++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) (limited to 'servers/taskwarrior/tests/test_server.py') diff --git a/servers/taskwarrior/tests/test_server.py b/servers/taskwarrior/tests/test_server.py index 97f7268..601a535 100644 --- a/servers/taskwarrior/tests/test_server.py +++ b/servers/taskwarrior/tests/test_server.py @@ -212,3 +212,168 @@ class TestTaskWarriorServer: await taskwarrior_server.manage_context("set") assert "Context name is required" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_modify_task_project(self, taskwarrior_server): + """Test modifying a task's project.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + project="NewProject" + ) + assert result["filter_expr"] == "uuid:test-uuid-123" + assert result["modifications"]["project"] == "NewProject" + + @pytest.mark.asyncio + async def test_modify_task_priority(self, taskwarrior_server): + """Test modifying a task's priority.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + priority="H" + ) + assert result["modifications"]["priority"] == "H" + + @pytest.mark.asyncio + async def test_modify_task_clear_priority(self, taskwarrior_server): + """Test clearing a task's priority.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + priority="" + ) + assert result["modifications"]["priority"] is None + + @pytest.mark.asyncio + async def test_modify_task_due(self, taskwarrior_server): + """Test modifying a task's due date.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + due="tomorrow" + ) + assert result["modifications"]["due"] == "tomorrow" + + @pytest.mark.asyncio + async def test_modify_task_description(self, taskwarrior_server): + """Test modifying a task's description.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + description="New description" + ) + assert result["modifications"]["description"] == "New description" + + @pytest.mark.asyncio + async def test_modify_task_tags(self, taskwarrior_server): + """Test modifying a task's tags.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + tags=["+urgent", "-later"] + ) + assert result["modifications"]["tags"] == ["+urgent", "-later"] + + @pytest.mark.asyncio + async def test_modify_task_multiple_fields(self, taskwarrior_server): + """Test modifying multiple fields at once.""" + mock_output = "Modified 1 task." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "+work", + project="Work", + priority="H", + tags=["+urgent"] + ) + assert result["filter_expr"] == "+work" + assert result["modifications"]["project"] == "Work" + assert result["modifications"]["priority"] == "H" + assert result["modifications"]["tags"] == ["+urgent"] + + @pytest.mark.asyncio + async def test_modify_task_no_modifications(self, taskwarrior_server): + """Test that modifying without any parameters raises ValueError.""" + with pytest.raises(ValueError) as exc_info: + await taskwarrior_server.modify_task("uuid:test-uuid-123") + + assert "At least one modification parameter must be provided" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_modify_task_invalid_priority(self, taskwarrior_server): + """Test that invalid priority raises ValueError.""" + with pytest.raises(ValueError) as exc_info: + await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + priority="X" + ) + + assert "Priority must be H, M, or L" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_modify_task_invalid_tag_format(self, taskwarrior_server): + """Test that tags without +/- prefix raise ValueError.""" + with pytest.raises(ValueError) as exc_info: + await taskwarrior_server.modify_task( + "uuid:test-uuid-123", + tags=["invalidtag"] + ) + + assert "Tags must start with + or -" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_modify_task_with_filter_expression(self, taskwarrior_server): + """Test modifying tasks with complex filter expression.""" + mock_output = "Modified 2 tasks." + + with patch.object( + taskwarrior_server, '_run_task_command', + new_callable=AsyncMock, + return_value=(mock_output, '', 0) + ): + result = await taskwarrior_server.modify_task( + "project:Home status:pending", + priority="M" + ) + assert result["filter_expr"] == "project:Home status:pending" + assert result["modifications"]["priority"] == "M" -- cgit v1.2.3