From 71e636572fa85d37e384745049eee313c4189d07 Mon Sep 17 00:00:00 2001
From: Aaron Godfrey <me@aarongodfrey.dev>
Date: Sun, 21 Jan 2024 15:09:08 -0800
Subject: [PATCH] Send recurrence data when updating a task in todoist
 (#108269)

* Send recurrence data when updating a task in todoist

* Update tests/components/todoist/test_todo.py

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* Move logic into _task_api_data.

* Add comment about sending potentinally stale data.

---------

Co-authored-by: Allen Porter <allen.porter@gmail.com>
---
 homeassistant/components/todoist/todo.py | 13 +++++--
 tests/components/todoist/test_todo.py    | 46 +++++++++++++++++++++++-
 2 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/todoist/todo.py b/homeassistant/components/todoist/todo.py
index 5067e98642e..490e4ad9f1a 100644
--- a/homeassistant/components/todoist/todo.py
+++ b/homeassistant/components/todoist/todo.py
@@ -4,6 +4,8 @@ import asyncio
 import datetime
 from typing import Any, cast
 
+from todoist_api_python.models import Task
+
 from homeassistant.components.todo import (
     TodoItem,
     TodoItemStatus,
@@ -32,7 +34,7 @@ async def async_setup_entry(
     )
 
 
-def _task_api_data(item: TodoItem) -> dict[str, Any]:
+def _task_api_data(item: TodoItem, api_data: Task | None = None) -> dict[str, Any]:
     """Convert a TodoItem to the set of add or update arguments."""
     item_data: dict[str, Any] = {
         "content": item.summary,
@@ -44,6 +46,12 @@ def _task_api_data(item: TodoItem) -> dict[str, Any]:
             item_data["due_datetime"] = due.isoformat()
         else:
             item_data["due_date"] = due.isoformat()
+        # In order to not lose any recurrence metadata for the task, we need to
+        # ensure that we send the `due_string` param if the task has it set.
+        # NOTE: It's ok to send stale data for non-recurring tasks. Any provided
+        # date/datetime will override this string.
+        if api_data and api_data.due:
+            item_data["due_string"] = api_data.due.string
     else:
         # Special flag "no date" clears the due date/datetime.
         # See https://developer.todoist.com/rest/v2/#update-a-task for more.
@@ -126,7 +134,8 @@ class TodoistTodoListEntity(CoordinatorEntity[TodoistCoordinator], TodoListEntit
     async def async_update_todo_item(self, item: TodoItem) -> None:
         """Update a To-do item."""
         uid: str = cast(str, item.uid)
-        if update_data := _task_api_data(item):
+        api_data = next((d for d in self.coordinator.data if d.id == uid), None)
+        if update_data := _task_api_data(item, api_data):
             await self.coordinator.api.update_task(task_id=uid, **update_data)
         if item.status is not None:
             # Only update status if changed
diff --git a/tests/components/todoist/test_todo.py b/tests/components/todoist/test_todo.py
index 5aa1e2af9de..a227ec858e4 100644
--- a/tests/components/todoist/test_todo.py
+++ b/tests/components/todoist/test_todo.py
@@ -402,8 +402,52 @@ async def test_update_todo_item_status(
                 "status": "needs_action",
             },
         ),
+        (
+            [
+                make_api_task(
+                    id="task-id-1",
+                    content="Soda",
+                    description="6-pack",
+                    is_completed=False,
+                    # Create a mock task with a string value in the Due object and verify it
+                    # gets preserved when verifying the kwargs to update below
+                    due=Due(date="2024-01-01", is_recurring=True, string="every day"),
+                )
+            ],
+            {"due_date": "2024-02-01"},
+            [
+                make_api_task(
+                    id="task-id-1",
+                    content="Soda",
+                    description="6-pack",
+                    is_completed=False,
+                    due=Due(date="2024-02-01", is_recurring=True, string="every day"),
+                )
+            ],
+            {
+                "task_id": "task-id-1",
+                "content": "Soda",
+                "description": "6-pack",
+                "due_date": "2024-02-01",
+                "due_string": "every day",
+            },
+            {
+                "uid": "task-id-1",
+                "summary": "Soda",
+                "status": "needs_action",
+                "description": "6-pack",
+                "due": "2024-02-01",
+            },
+        ),
+    ],
+    ids=[
+        "rename",
+        "due_date",
+        "due_datetime",
+        "description",
+        "clear_description",
+        "due_date_with_recurrence",
     ],
-    ids=["rename", "due_date", "due_datetime", "description", "clear_description"],
 )
 async def test_update_todo_items(
     hass: HomeAssistant,
-- 
GitLab