hass-core/tests/components/todoist/test_todo.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

594 lines
17 KiB
Python
Raw Normal View History

"""Unit tests for the Todoist todo platform."""
from typing import Any
from unittest.mock import AsyncMock
import pytest
from todoist_api_python.models import Due, Task
from homeassistant.components.todo import DOMAIN as TODO_DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_component import async_update_entity
from .conftest import PROJECT_ID, make_api_task
from tests.typing import WebSocketGenerator
@pytest.fixture(autouse=True)
def platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return [Platform.TODO]
@pytest.fixture(autouse=True)
async def set_time_zone(hass: HomeAssistant) -> None:
"""Set the time zone for the tests that keesp UTC-6 all year round."""
await hass.config.async_set_time_zone("America/Regina")
@pytest.mark.parametrize(
("tasks", "expected_state"),
[
([], "0"),
([make_api_task(id="12345", content="Soda", is_completed=False)], "1"),
([make_api_task(id="12345", content="Soda", is_completed=True)], "0"),
(
[
make_api_task(id="12345", content="Milk", is_completed=False),
make_api_task(id="54321", content="Soda", is_completed=False),
],
"2",
),
(
[
make_api_task(
id="12345",
content="Soda",
is_completed=False,
project_id="other-project-id",
)
],
"0",
),
(
[
make_api_task(
id="12345", content="sub-task", is_completed=False, parent_id="1"
)
],
"0",
),
],
)
async def test_todo_item_state(
hass: HomeAssistant,
setup_integration: None,
expected_state: str,
) -> None:
"""Test for a To-do List entity state."""
state = hass.states.get("todo.name")
assert state
assert state.state == expected_state
@pytest.mark.parametrize(
("tasks", "item_data", "tasks_after_update", "add_kwargs", "expected_item"),
[
(
[],
{},
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
{"content": "Soda", "due_string": "no date", "description": ""},
{"uid": "task-id-1", "summary": "Soda", "status": "needs_action"},
),
(
[],
{"due_date": "2023-11-18"},
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
due=Due(is_recurring=False, date="2023-11-18", string="today"),
)
],
{"description": "", "due_date": "2023-11-18"},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"due": "2023-11-18",
},
),
(
[],
{"due_datetime": "2023-11-18T06:30:00"},
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
due=Due(
date="2023-11-18",
is_recurring=False,
datetime="2023-11-18T12:30:00.000000Z",
string="today",
),
)
],
{
"description": "",
"due_datetime": "2023-11-18T06:30:00-06:00",
},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"due": "2023-11-18T06:30:00-06:00",
},
),
(
[],
{"description": "6-pack"},
[
make_api_task(
id="task-id-1",
content="Soda",
description="6-pack",
is_completed=False,
)
],
{"description": "6-pack", "due_string": "no date"},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"description": "6-pack",
},
),
],
ids=["summary", "due_date", "due_datetime", "description"],
)
2023-10-31 02:38:18 +01:00
async def test_add_todo_list_item(
hass: HomeAssistant,
setup_integration: None,
api: AsyncMock,
item_data: dict[str, Any],
tasks_after_update: list[Task],
add_kwargs: dict[str, Any],
expected_item: dict[str, Any],
) -> None:
2023-10-31 02:38:18 +01:00
"""Test for adding a To-do Item."""
state = hass.states.get("todo.name")
assert state
assert state.state == "0"
api.add_task = AsyncMock()
# Fake API response when state is refreshed after create
api.get_tasks.return_value = tasks_after_update
await hass.services.async_call(
TODO_DOMAIN,
2023-10-31 02:38:18 +01:00
"add_item",
{"item": "Soda", **item_data},
target={"entity_id": "todo.name"},
blocking=True,
)
args = api.add_task.call_args
assert args
assert args.kwargs == {"project_id": PROJECT_ID, "content": "Soda", **add_kwargs}
# Verify state is refreshed
state = hass.states.get("todo.name")
assert state
assert state.state == "1"
result = await hass.services.async_call(
TODO_DOMAIN,
"get_items",
{},
target={"entity_id": "todo.name"},
blocking=True,
return_response=True,
)
assert result == {"todo.name": {"items": [expected_item]}}
@pytest.mark.parametrize(
("tasks"), [[make_api_task(id="task-id-1", content="Soda", is_completed=False)]]
)
async def test_update_todo_item_status(
hass: HomeAssistant,
setup_integration: None,
api: AsyncMock,
) -> None:
"""Test for updating a To-do Item that changes the status."""
state = hass.states.get("todo.name")
assert state
assert state.state == "1"
api.close_task = AsyncMock()
api.reopen_task = AsyncMock()
# Fake API response when state is refreshed after close
api.get_tasks.return_value = [
make_api_task(id="task-id-1", content="Soda", is_completed=True)
]
await hass.services.async_call(
TODO_DOMAIN,
"update_item",
2023-10-31 02:38:18 +01:00
{"item": "task-id-1", "status": "completed"},
target={"entity_id": "todo.name"},
blocking=True,
)
assert api.close_task.called
args = api.close_task.call_args
assert args
assert args.kwargs.get("task_id") == "task-id-1"
assert not api.reopen_task.called
# Verify state is refreshed
state = hass.states.get("todo.name")
assert state
assert state.state == "0"
# Fake API response when state is refreshed after reopen
api.get_tasks.return_value = [
make_api_task(id="task-id-1", content="Soda", is_completed=False)
]
await hass.services.async_call(
TODO_DOMAIN,
"update_item",
2023-10-31 02:38:18 +01:00
{"item": "task-id-1", "status": "needs_action"},
target={"entity_id": "todo.name"},
blocking=True,
)
assert api.reopen_task.called
args = api.reopen_task.call_args
assert args
assert args.kwargs.get("task_id") == "task-id-1"
# Verify state is refreshed
state = hass.states.get("todo.name")
assert state
assert state.state == "1"
@pytest.mark.parametrize(
("tasks", "update_data", "tasks_after_update", "update_kwargs", "expected_item"),
[
(
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
description="desc",
)
],
{"rename": "Milk"},
[
make_api_task(
id="task-id-1",
content="Milk",
is_completed=False,
description="desc",
)
],
{
"task_id": "task-id-1",
"content": "Milk",
"description": "desc",
"due_string": "no date",
},
{
"uid": "task-id-1",
"summary": "Milk",
"status": "needs_action",
"description": "desc",
},
),
(
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
{"due_date": "2023-11-18"},
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
due=Due(is_recurring=False, date="2023-11-18", string="today"),
)
],
{
"task_id": "task-id-1",
"content": "Soda",
"due_date": "2023-11-18",
"description": "",
},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"due": "2023-11-18",
},
),
(
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
{"due_datetime": "2023-11-18T06:30:00"},
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
due=Due(
date="2023-11-18",
is_recurring=False,
datetime="2023-11-18T12:30:00.000000Z",
string="today",
),
)
],
{
"task_id": "task-id-1",
"content": "Soda",
"due_datetime": "2023-11-18T06:30:00-06:00",
"description": "",
},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"due": "2023-11-18T06:30:00-06:00",
},
),
(
[make_api_task(id="task-id-1", content="Soda", is_completed=False)],
{"description": "6-pack"},
[
make_api_task(
id="task-id-1",
content="Soda",
description="6-pack",
is_completed=False,
)
],
{
"task_id": "task-id-1",
"content": "Soda",
"description": "6-pack",
"due_string": "no date",
},
{
"uid": "task-id-1",
"summary": "Soda",
"status": "needs_action",
"description": "6-pack",
},
),
(
[
make_api_task(
id="task-id-1",
content="Soda",
description="6-pack",
is_completed=False,
)
],
{"description": None},
[
make_api_task(
id="task-id-1",
content="Soda",
is_completed=False,
description="",
)
],
{
"task_id": "task-id-1",
"content": "Soda",
"description": "",
"due_string": "no date",
},
{
"uid": "task-id-1",
"summary": "Soda",
"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",
],
)
async def test_update_todo_items(
hass: HomeAssistant,
setup_integration: None,
api: AsyncMock,
update_data: dict[str, Any],
tasks_after_update: list[Task],
update_kwargs: dict[str, Any],
expected_item: dict[str, Any],
) -> None:
"""Test for updating a To-do Item that changes the summary."""
state = hass.states.get("todo.name")
assert state
assert state.state == "1"
api.update_task = AsyncMock()
# Fake API response when state is refreshed after close
api.get_tasks.return_value = tasks_after_update
await hass.services.async_call(
TODO_DOMAIN,
"update_item",
{"item": "task-id-1", **update_data},
target={"entity_id": "todo.name"},
blocking=True,
)
assert api.update_task.called
args = api.update_task.call_args
assert args
assert args.kwargs == update_kwargs
result = await hass.services.async_call(
TODO_DOMAIN,
"get_items",
{},
target={"entity_id": "todo.name"},
blocking=True,
return_response=True,
)
assert result == {"todo.name": {"items": [expected_item]}}
@pytest.mark.parametrize(
("tasks"),
[
[
make_api_task(id="task-id-1", content="Soda", is_completed=False),
make_api_task(id="task-id-2", content="Milk", is_completed=False),
]
],
)
2023-10-31 02:38:18 +01:00
async def test_remove_todo_item(
hass: HomeAssistant,
setup_integration: None,
api: AsyncMock,
) -> None:
2023-10-31 02:38:18 +01:00
"""Test for removing a To-do Item."""
state = hass.states.get("todo.name")
assert state
assert state.state == "2"
api.delete_task = AsyncMock()
# Fake API response when state is refreshed after close
api.get_tasks.return_value = []
await hass.services.async_call(
TODO_DOMAIN,
2023-10-31 02:38:18 +01:00
"remove_item",
{"item": ["task-id-1", "task-id-2"]},
target={"entity_id": "todo.name"},
blocking=True,
)
assert api.delete_task.call_count == 2
args = api.delete_task.call_args_list
assert args[0].kwargs.get("task_id") == "task-id-1"
assert args[1].kwargs.get("task_id") == "task-id-2"
await async_update_entity(hass, "todo.name")
state = hass.states.get("todo.name")
assert state
assert state.state == "0"
@pytest.mark.parametrize(
("tasks"), [[make_api_task(id="task-id-1", content="Cheese", is_completed=False)]]
)
async def test_subscribe(
hass: HomeAssistant,
setup_integration: None,
api: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test for subscribing to state updates."""
# Subscribe and get the initial list
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{
"type": "todo/item/subscribe",
"entity_id": "todo.name",
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
subscription_id = msg["id"]
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
items = msg["event"].get("items")
assert items
assert len(items) == 1
assert items[0]["summary"] == "Cheese"
assert items[0]["status"] == "needs_action"
assert items[0]["uid"]
# Fake API response when state is refreshed
api.get_tasks.return_value = [
make_api_task(id="test-id-1", content="Wine", is_completed=False)
]
await hass.services.async_call(
TODO_DOMAIN,
"update_item",
{"item": "Cheese", "rename": "Wine"},
target={"entity_id": "todo.name"},
blocking=True,
)
# Verify update is published
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
items = msg["event"].get("items")
assert items
assert len(items) == 1
assert items[0]["summary"] == "Wine"
assert items[0]["status"] == "needs_action"
assert items[0]["uid"]