Add due date and description to Google Tasks (#104654)
* Add tests for config validation function * Add Google Tasks due date and description * Revert test timezone * Update changes after upstream * Update homeassistant/components/google_tasks/todo.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add google tasks tests for creating --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
8e64eff626
commit
c8aed06438
3 changed files with 114 additions and 68 deletions
|
@ -1,7 +1,7 @@
|
|||
"""Google Tasks todo platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any, cast
|
||||
|
||||
from homeassistant.components.todo import (
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DOMAIN
|
||||
|
@ -35,9 +36,31 @@ def _convert_todo_item(item: TodoItem) -> dict[str, str]:
|
|||
result["title"] = item.summary
|
||||
if item.status is not None:
|
||||
result["status"] = TODO_STATUS_MAP_INV[item.status]
|
||||
if (due := item.due) is not None:
|
||||
# due API field is a timestamp string, but with only date resolution
|
||||
result["due"] = dt_util.start_of_local_day(due).isoformat()
|
||||
if (description := item.description) is not None:
|
||||
result["notes"] = description
|
||||
return result
|
||||
|
||||
|
||||
def _convert_api_item(item: dict[str, str]) -> TodoItem:
|
||||
"""Convert tasks API items into a TodoItem."""
|
||||
due: date | None = None
|
||||
if (due_str := item.get("due")) is not None:
|
||||
due = datetime.fromisoformat(due_str).date()
|
||||
return TodoItem(
|
||||
summary=item["title"],
|
||||
uid=item["id"],
|
||||
status=TODO_STATUS_MAP.get(
|
||||
item.get("status", ""),
|
||||
TodoItemStatus.NEEDS_ACTION,
|
||||
),
|
||||
due=due,
|
||||
description=item.get("notes"),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
|
@ -68,6 +91,8 @@ class GoogleTaskTodoListEntity(
|
|||
TodoListEntityFeature.CREATE_TODO_ITEM
|
||||
| TodoListEntityFeature.UPDATE_TODO_ITEM
|
||||
| TodoListEntityFeature.DELETE_TODO_ITEM
|
||||
| TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
|
||||
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
@ -88,17 +113,7 @@ class GoogleTaskTodoListEntity(
|
|||
"""Get the current set of To-do items."""
|
||||
if self.coordinator.data is None:
|
||||
return None
|
||||
return [
|
||||
TodoItem(
|
||||
summary=item["title"],
|
||||
uid=item["id"],
|
||||
status=TODO_STATUS_MAP.get(
|
||||
item.get("status"), # type: ignore[arg-type]
|
||||
TodoItemStatus.NEEDS_ACTION,
|
||||
),
|
||||
)
|
||||
for item in _order_tasks(self.coordinator.data)
|
||||
]
|
||||
return [_convert_api_item(item) for item in _order_tasks(self.coordinator.data)]
|
||||
|
||||
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||
"""Add an item to the To-do list."""
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
# serializer version: 1
|
||||
# name: test_create_todo_list_item[api_responses0]
|
||||
# name: test_create_todo_list_item[description]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks?alt=json',
|
||||
'POST',
|
||||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[api_responses0].1
|
||||
# name: test_create_todo_list_item[description].1
|
||||
'{"title": "Soda", "status": "needsAction", "notes": "6-pack"}'
|
||||
# ---
|
||||
# name: test_create_todo_list_item[due]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks?alt=json',
|
||||
'POST',
|
||||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[due].1
|
||||
'{"title": "Soda", "status": "needsAction", "due": "2023-11-18T00:00:00-08:00"}'
|
||||
# ---
|
||||
# name: test_create_todo_list_item[summary]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks?alt=json',
|
||||
'POST',
|
||||
)
|
||||
# ---
|
||||
# name: test_create_todo_list_item[summary].1
|
||||
'{"title": "Soda", "status": "needsAction"}'
|
||||
# ---
|
||||
# name: test_delete_todo_list_item[_handler]
|
||||
|
@ -38,6 +56,33 @@
|
|||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_partial_update[description]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[description].1
|
||||
'{"notes": "6-pack"}'
|
||||
# ---
|
||||
# name: test_partial_update[due_date]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[due_date].1
|
||||
'{"due": "2023-11-18T00:00:00-08:00"}'
|
||||
# ---
|
||||
# name: test_partial_update[rename]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update[rename].1
|
||||
'{"title": "Soda"}'
|
||||
# ---
|
||||
# name: test_partial_update_status[api_responses0]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
|
@ -47,15 +92,6 @@
|
|||
# name: test_partial_update_status[api_responses0].1
|
||||
'{"status": "needsAction"}'
|
||||
# ---
|
||||
# name: test_partial_update_title[api_responses0]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
'PATCH',
|
||||
)
|
||||
# ---
|
||||
# name: test_partial_update_title[api_responses0].1
|
||||
'{"title": "Soda"}'
|
||||
# ---
|
||||
# name: test_update_todo_list_item[api_responses0]
|
||||
tuple(
|
||||
'https://tasks.googleapis.com/tasks/v1/lists/task-list-id-1/tasks/some-task-id?alt=json',
|
||||
|
|
|
@ -19,13 +19,12 @@ from homeassistant.exceptions import HomeAssistantError
|
|||
from tests.typing import WebSocketGenerator
|
||||
|
||||
ENTITY_ID = "todo.my_tasks"
|
||||
ITEM = {
|
||||
"id": "task-list-id-1",
|
||||
"title": "My tasks",
|
||||
}
|
||||
LIST_TASK_LIST_RESPONSE = {
|
||||
"items": [
|
||||
{
|
||||
"id": "task-list-id-1",
|
||||
"title": "My tasks",
|
||||
},
|
||||
]
|
||||
"items": [ITEM],
|
||||
}
|
||||
EMPTY_RESPONSE = {}
|
||||
LIST_TASKS_RESPONSE = {
|
||||
|
@ -76,6 +75,20 @@ LIST_TASKS_RESPONSE_MULTIPLE = {
|
|||
],
|
||||
}
|
||||
|
||||
# API responses when testing update methods
|
||||
UPDATE_API_RESPONSES = [
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE_WATER,
|
||||
EMPTY_RESPONSE, # update
|
||||
LIST_TASKS_RESPONSE, # refresh after update
|
||||
]
|
||||
CREATE_API_RESPONSES = [
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE,
|
||||
EMPTY_RESPONSE, # create
|
||||
LIST_TASKS_RESPONSE, # refresh
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[str]:
|
||||
|
@ -207,12 +220,14 @@ def mock_http_response(response_handler: list | Callable) -> Mock:
|
|||
"title": "Task 1",
|
||||
"status": "needsAction",
|
||||
"position": "0000000000000001",
|
||||
"due": "2023-11-18T00:00:00+00:00",
|
||||
},
|
||||
{
|
||||
"id": "task-2",
|
||||
"title": "Task 2",
|
||||
"status": "completed",
|
||||
"position": "0000000000000002",
|
||||
"notes": "long description",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -238,11 +253,13 @@ async def test_get_items(
|
|||
"uid": "task-1",
|
||||
"summary": "Task 1",
|
||||
"status": "needs_action",
|
||||
"due": "2023-11-18",
|
||||
},
|
||||
{
|
||||
"uid": "task-2",
|
||||
"summary": "Task 2",
|
||||
"status": "completed",
|
||||
"description": "long description",
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -333,21 +350,20 @@ async def test_task_items_error_response(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_responses",
|
||||
("api_responses", "item_data"),
|
||||
[
|
||||
[
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE,
|
||||
EMPTY_RESPONSE, # create
|
||||
LIST_TASKS_RESPONSE, # refresh after delete
|
||||
]
|
||||
(CREATE_API_RESPONSES, {}),
|
||||
(CREATE_API_RESPONSES, {"due_date": "2023-11-18"}),
|
||||
(CREATE_API_RESPONSES, {"description": "6-pack"}),
|
||||
],
|
||||
ids=["summary", "due", "description"],
|
||||
)
|
||||
async def test_create_todo_list_item(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
mock_http_response: Mock,
|
||||
item_data: dict[str, Any],
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test for creating a To-do Item."""
|
||||
|
@ -361,7 +377,7 @@ async def test_create_todo_list_item(
|
|||
await hass.services.async_call(
|
||||
TODO_DOMAIN,
|
||||
"add_item",
|
||||
{"item": "Soda"},
|
||||
{"item": "Soda", **item_data},
|
||||
target={"entity_id": "todo.my_tasks"},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -407,17 +423,7 @@ async def test_create_todo_list_item_error(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_responses",
|
||||
[
|
||||
[
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE_WATER,
|
||||
EMPTY_RESPONSE, # update
|
||||
LIST_TASKS_RESPONSE, # refresh after update
|
||||
]
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("api_responses", [UPDATE_API_RESPONSES])
|
||||
async def test_update_todo_list_item(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
|
@ -483,21 +489,20 @@ async def test_update_todo_list_item_error(
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_responses",
|
||||
("api_responses", "item_data"),
|
||||
[
|
||||
[
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE_WATER,
|
||||
EMPTY_RESPONSE, # update
|
||||
LIST_TASKS_RESPONSE, # refresh after update
|
||||
]
|
||||
(UPDATE_API_RESPONSES, {"rename": "Soda"}),
|
||||
(UPDATE_API_RESPONSES, {"due_date": "2023-11-18"}),
|
||||
(UPDATE_API_RESPONSES, {"description": "6-pack"}),
|
||||
],
|
||||
ids=("rename", "due_date", "description"),
|
||||
)
|
||||
async def test_partial_update_title(
|
||||
async def test_partial_update(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
mock_http_response: Any,
|
||||
item_data: dict[str, Any],
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test for partial update with title only."""
|
||||
|
@ -511,7 +516,7 @@ async def test_partial_update_title(
|
|||
await hass.services.async_call(
|
||||
TODO_DOMAIN,
|
||||
"update_item",
|
||||
{"item": "some-task-id", "rename": "Soda"},
|
||||
{"item": "some-task-id", **item_data},
|
||||
target={"entity_id": "todo.my_tasks"},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -522,17 +527,7 @@ async def test_partial_update_title(
|
|||
assert call.kwargs.get("body") == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"api_responses",
|
||||
[
|
||||
[
|
||||
LIST_TASK_LIST_RESPONSE,
|
||||
LIST_TASKS_RESPONSE_WATER,
|
||||
EMPTY_RESPONSE, # update
|
||||
LIST_TASKS_RESPONSE, # refresh after update
|
||||
]
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("api_responses", [UPDATE_API_RESPONSES])
|
||||
async def test_partial_update_status(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
|
|
Loading…
Add table
Reference in a new issue