mirror of https://github.com/home-assistant/core
810 lines
22 KiB
Python
810 lines
22 KiB
Python
"""The tests for the webdav todo component."""
|
|
|
|
from datetime import UTC, date, datetime
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, Mock
|
|
|
|
from caldav.lib.error import DAVError, NotFoundError
|
|
from caldav.objects import Todo
|
|
import pytest
|
|
|
|
from homeassistant.components.todo import (
|
|
ATTR_DESCRIPTION,
|
|
ATTR_DUE_DATE,
|
|
ATTR_DUE_DATETIME,
|
|
ATTR_ITEM,
|
|
ATTR_RENAME,
|
|
ATTR_STATUS,
|
|
DOMAIN as TODO_DOMAIN,
|
|
TodoServices,
|
|
)
|
|
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
|
|
from tests.common import MockConfigEntry
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
CALENDAR_NAME = "My Tasks"
|
|
ENTITY_NAME = "My tasks"
|
|
TEST_ENTITY = "todo.my_tasks"
|
|
SUPPORTED_FEATURES = 119
|
|
|
|
TODO_NO_STATUS = """BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//E-Corp.//CalDAV Client//EN
|
|
BEGIN:VTODO
|
|
UID:1
|
|
DTSTAMP:20231125T000000Z
|
|
SUMMARY:Milk
|
|
END:VTODO
|
|
END:VCALENDAR"""
|
|
|
|
TODO_NEEDS_ACTION = """BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//E-Corp.//CalDAV Client//EN
|
|
BEGIN:VTODO
|
|
UID:2
|
|
DTSTAMP:20171125T000000Z
|
|
SUMMARY:Cheese
|
|
STATUS:NEEDS-ACTION
|
|
END:VTODO
|
|
END:VCALENDAR"""
|
|
|
|
RESULT_ITEM = {
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
}
|
|
|
|
TODO_COMPLETED = """BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//E-Corp.//CalDAV Client//EN
|
|
BEGIN:VTODO
|
|
UID:3
|
|
DTSTAMP:20231125T000000Z
|
|
SUMMARY:Wine
|
|
STATUS:COMPLETED
|
|
END:VTODO
|
|
END:VCALENDAR"""
|
|
|
|
|
|
TODO_NO_SUMMARY = """BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//E-Corp.//CalDAV Client//EN
|
|
BEGIN:VTODO
|
|
UID:4
|
|
DTSTAMP:20171126T000000Z
|
|
STATUS:NEEDS-ACTION
|
|
END:VTODO
|
|
END:VCALENDAR"""
|
|
|
|
TODO_ALL_FIELDS = """BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//E-Corp.//CalDAV Client//EN
|
|
BEGIN:VTODO
|
|
UID:2
|
|
DTSTAMP:20171125T000000Z
|
|
SUMMARY:Cheese
|
|
DESCRIPTION:Any kind will do
|
|
STATUS:NEEDS-ACTION
|
|
DUE:20171126
|
|
END:VTODO
|
|
END:VCALENDAR"""
|
|
|
|
|
|
@pytest.fixture
|
|
def platforms() -> list[Platform]:
|
|
"""Fixture to set up config entry platforms."""
|
|
return [Platform.TODO]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def set_tz(hass: HomeAssistant) -> None:
|
|
"""Fixture to set timezone with fixed offset year round."""
|
|
await hass.config.async_set_time_zone("America/Regina")
|
|
|
|
|
|
@pytest.fixture(name="todos")
|
|
def mock_todos() -> list[str]:
|
|
"""Fixture to return VTODO objects for the calendar."""
|
|
return []
|
|
|
|
|
|
@pytest.fixture(name="supported_components")
|
|
def mock_supported_components() -> list[str]:
|
|
"""Fixture to set supported components of the calendar."""
|
|
return ["VTODO"]
|
|
|
|
|
|
@pytest.fixture(name="calendar")
|
|
def mock_calendar(supported_components: list[str]) -> Mock:
|
|
"""Fixture to create the primary calendar for the test."""
|
|
calendar = Mock()
|
|
calendar.search = MagicMock(return_value=[])
|
|
calendar.name = CALENDAR_NAME
|
|
calendar.get_supported_components = MagicMock(return_value=supported_components)
|
|
return calendar
|
|
|
|
|
|
def create_todo(calendar: Mock, idx: str, ics: str) -> Todo:
|
|
"""Create a caldav Todo object."""
|
|
return Todo(client=None, url=f"{idx}.ics", data=ics, parent=calendar, id=idx)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_search_items(calendar: Mock, todos: list[str]) -> None:
|
|
"""Fixture to add search results to the test calendar."""
|
|
calendar.search.return_value = [
|
|
create_todo(calendar, str(idx), item) for idx, item in enumerate(todos)
|
|
]
|
|
|
|
|
|
@pytest.fixture(name="calendars")
|
|
def mock_calendars(calendar: Mock) -> list[Mock]:
|
|
"""Fixture to create calendars for the test."""
|
|
return [calendar]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def mock_add_to_hass(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
) -> None:
|
|
"""Fixture to add the ConfigEntry."""
|
|
config_entry.add_to_hass(hass)
|
|
|
|
|
|
IGNORE_COMPONENTS = ["BEGIN", "END", "DTSTAMP", "PRODID", "UID", "VERSION"]
|
|
|
|
|
|
def compact_ics(ics: str) -> list[str]:
|
|
"""Pull out parts of the rfc5545 content useful for assertions in tests."""
|
|
return [
|
|
line
|
|
for line in ics.split("\n")
|
|
if line and not any(filter(line.startswith, IGNORE_COMPONENTS))
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("todos", "expected_state"),
|
|
[
|
|
([], "0"),
|
|
(
|
|
[TODO_NEEDS_ACTION],
|
|
"1",
|
|
),
|
|
(
|
|
[TODO_NO_STATUS],
|
|
"1",
|
|
),
|
|
([TODO_COMPLETED], "0"),
|
|
([TODO_NO_STATUS, TODO_NEEDS_ACTION, TODO_COMPLETED], "2"),
|
|
([TODO_NO_SUMMARY], "0"),
|
|
],
|
|
ids=(
|
|
"empty",
|
|
"needs_action",
|
|
"no_status",
|
|
"completed",
|
|
"all",
|
|
"no_summary",
|
|
),
|
|
)
|
|
async def test_todo_list_state(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
expected_state: str,
|
|
) -> None:
|
|
"""Test a calendar entity from a config entry."""
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.name == ENTITY_NAME
|
|
assert state.state == expected_state
|
|
assert dict(state.attributes) == {
|
|
"friendly_name": ENTITY_NAME,
|
|
"supported_features": SUPPORTED_FEATURES,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("supported_components", "has_entity"),
|
|
[([], False), (["VTODO"], True), (["VEVENT"], False), (["VEVENT", "VTODO"], True)],
|
|
)
|
|
async def test_supported_components(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
has_entity: bool,
|
|
) -> None:
|
|
"""Test a calendar supported components matches VTODO."""
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert (state is not None) == has_entity
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("item_data", "expcted_save_args", "expected_item"),
|
|
[
|
|
(
|
|
{},
|
|
{"status": "NEEDS-ACTION", "summary": "Cheese"},
|
|
RESULT_ITEM,
|
|
),
|
|
(
|
|
{ATTR_DUE_DATE: "2023-11-18"},
|
|
{"status": "NEEDS-ACTION", "summary": "Cheese", "due": date(2023, 11, 18)},
|
|
{**RESULT_ITEM, "due": "2023-11-18"},
|
|
),
|
|
(
|
|
{ATTR_DUE_DATETIME: "2023-11-18T08:30:00-06:00"},
|
|
{
|
|
"status": "NEEDS-ACTION",
|
|
"summary": "Cheese",
|
|
"due": datetime(2023, 11, 18, 14, 30, 00, tzinfo=UTC),
|
|
},
|
|
{**RESULT_ITEM, "due": "2023-11-18T08:30:00-06:00"},
|
|
),
|
|
(
|
|
{ATTR_DESCRIPTION: "Make sure to get Swiss"},
|
|
{
|
|
"status": "NEEDS-ACTION",
|
|
"summary": "Cheese",
|
|
"description": "Make sure to get Swiss",
|
|
},
|
|
{**RESULT_ITEM, "description": "Make sure to get Swiss"},
|
|
),
|
|
],
|
|
ids=[
|
|
"summary",
|
|
"due_date",
|
|
"due_datetime",
|
|
"description",
|
|
],
|
|
)
|
|
async def test_add_item(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
item_data: dict[str, Any],
|
|
expcted_save_args: dict[str, Any],
|
|
expected_item: dict[str, Any],
|
|
) -> None:
|
|
"""Test adding an item to the list."""
|
|
calendar.search.return_value = []
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.state == "0"
|
|
|
|
# Simulate return value for the state update after the service call
|
|
calendar.search.return_value = [create_todo(calendar, "2", TODO_NEEDS_ACTION)]
|
|
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.ADD_ITEM,
|
|
{ATTR_ITEM: "Cheese", **item_data},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
assert calendar.save_todo.call_args
|
|
assert calendar.save_todo.call_args.kwargs == expcted_save_args
|
|
|
|
# Verify state was updated
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.state == "1"
|
|
|
|
|
|
async def test_add_item_failure(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
calendar: Mock,
|
|
) -> None:
|
|
"""Test failure when adding an item to the list."""
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
calendar.save_todo.side_effect = DAVError()
|
|
|
|
with pytest.raises(HomeAssistantError, match="CalDAV save error"):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.ADD_ITEM,
|
|
{ATTR_ITEM: "Cheese"},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("update_data", "expected_ics", "expected_state", "expected_item"),
|
|
[
|
|
(
|
|
{ATTR_RENAME: "Swiss Cheese"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;VALUE=DATE:20171126",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Swiss Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Swiss Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
"due": "2017-11-26",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_STATUS: "needs_action"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;VALUE=DATE:20171126",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
"due": "2017-11-26",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_STATUS: "completed"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;VALUE=DATE:20171126",
|
|
"STATUS:COMPLETED",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"0",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "completed",
|
|
"description": "Any kind will do",
|
|
"due": "2017-11-26",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_RENAME: "Swiss Cheese", ATTR_STATUS: "needs_action"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;VALUE=DATE:20171126",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Swiss Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Swiss Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
"due": "2017-11-26",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_DUE_DATE: "2023-11-18"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;VALUE=DATE:20231118",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
"due": "2023-11-18",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_DUE_DATETIME: "2023-11-18T08:30:00-06:00"},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"DUE;TZID=America/Regina:20231118T083000",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
"due": "2023-11-18T08:30:00-06:00",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_DUE_DATETIME: None},
|
|
[
|
|
"DESCRIPTION:Any kind will do",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"description": "Any kind will do",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_DESCRIPTION: "Make sure to get Swiss"},
|
|
[
|
|
"DESCRIPTION:Make sure to get Swiss",
|
|
"DUE;VALUE=DATE:20171126",
|
|
"STATUS:NEEDS-ACTION",
|
|
"SUMMARY:Cheese",
|
|
],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"due": "2017-11-26",
|
|
"description": "Make sure to get Swiss",
|
|
},
|
|
),
|
|
(
|
|
{ATTR_DESCRIPTION: None},
|
|
["DUE;VALUE=DATE:20171126", "STATUS:NEEDS-ACTION", "SUMMARY:Cheese"],
|
|
"1",
|
|
{
|
|
"uid": "2",
|
|
"summary": "Cheese",
|
|
"status": "needs_action",
|
|
"due": "2017-11-26",
|
|
},
|
|
),
|
|
],
|
|
ids=[
|
|
"rename",
|
|
"status_needs_action",
|
|
"status_completed",
|
|
"rename_status",
|
|
"due_date",
|
|
"due_datetime",
|
|
"clear_due_date",
|
|
"description",
|
|
"clear_description",
|
|
],
|
|
)
|
|
async def test_update_item(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
update_data: dict[str, Any],
|
|
expected_ics: list[str],
|
|
expected_state: str,
|
|
expected_item: dict[str, Any],
|
|
) -> None:
|
|
"""Test updating an item on the list."""
|
|
|
|
item = Todo(dav_client, None, TODO_ALL_FIELDS, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.state == "1"
|
|
|
|
calendar.todo_by_uid = MagicMock(return_value=item)
|
|
|
|
dav_client.put.return_value.status = 204
|
|
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.UPDATE_ITEM,
|
|
{
|
|
ATTR_ITEM: "Cheese",
|
|
**update_data,
|
|
},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
assert dav_client.put.call_args
|
|
ics = dav_client.put.call_args.args[1]
|
|
assert compact_ics(ics) == expected_ics
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.state == expected_state
|
|
|
|
result = await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.GET_ITEMS,
|
|
{},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
return_response=True,
|
|
)
|
|
assert result == {TEST_ENTITY: {"items": [expected_item]}}
|
|
|
|
|
|
async def test_update_item_failure(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
) -> None:
|
|
"""Test failure when updating an item on the list."""
|
|
|
|
item = Todo(dav_client, None, TODO_NEEDS_ACTION, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
calendar.todo_by_uid = MagicMock(return_value=item)
|
|
dav_client.put.side_effect = DAVError()
|
|
|
|
with pytest.raises(HomeAssistantError, match="CalDAV save error"):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.UPDATE_ITEM,
|
|
{
|
|
ATTR_ITEM: "Cheese",
|
|
ATTR_STATUS: "completed",
|
|
},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("side_effect", "match"),
|
|
[(DAVError, "CalDAV lookup error"), (NotFoundError, "Could not find")],
|
|
)
|
|
async def test_update_item_lookup_failure(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
side_effect: Any,
|
|
match: str,
|
|
) -> None:
|
|
"""Test failure when looking up an item to update."""
|
|
|
|
item = Todo(dav_client, None, TODO_NEEDS_ACTION, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
calendar.todo_by_uid.side_effect = side_effect
|
|
|
|
with pytest.raises(HomeAssistantError, match=match):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.UPDATE_ITEM,
|
|
{
|
|
ATTR_ITEM: "Cheese",
|
|
ATTR_STATUS: "completed",
|
|
},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("uids_to_delete", "expect_item1_delete_called", "expect_item2_delete_called"),
|
|
[
|
|
([], False, False),
|
|
(["Cheese"], True, False),
|
|
(["Wine"], False, True),
|
|
(["Wine", "Cheese"], True, True),
|
|
],
|
|
ids=("none", "item1-only", "item2-only", "both-items"),
|
|
)
|
|
async def test_remove_item(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
uids_to_delete: list[str],
|
|
expect_item1_delete_called: bool,
|
|
expect_item2_delete_called: bool,
|
|
) -> None:
|
|
"""Test removing an item on the list."""
|
|
|
|
item1 = Todo(dav_client, None, TODO_NEEDS_ACTION, calendar, "2")
|
|
item2 = Todo(dav_client, None, TODO_COMPLETED, calendar, "3")
|
|
calendar.search = MagicMock(return_value=[item1, item2])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
state = hass.states.get(TEST_ENTITY)
|
|
assert state
|
|
assert state.state == "1"
|
|
|
|
def lookup(uid: str) -> Mock:
|
|
assert uid in ("2", "3")
|
|
if uid == "2":
|
|
return item1
|
|
return item2
|
|
|
|
calendar.todo_by_uid = Mock(side_effect=lookup)
|
|
item1.delete = Mock()
|
|
item2.delete = Mock()
|
|
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.REMOVE_ITEM,
|
|
{ATTR_ITEM: uids_to_delete},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
assert item1.delete.called == expect_item1_delete_called
|
|
assert item2.delete.called == expect_item2_delete_called
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("todos", "side_effect", "match"),
|
|
[
|
|
([TODO_NEEDS_ACTION], DAVError, "CalDAV lookup error"),
|
|
([TODO_NEEDS_ACTION], NotFoundError, "Could not find"),
|
|
],
|
|
)
|
|
async def test_remove_item_lookup_failure(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
calendar: Mock,
|
|
side_effect: Any,
|
|
match: str,
|
|
) -> None:
|
|
"""Test failure while removing an item from the list."""
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
calendar.todo_by_uid.side_effect = side_effect
|
|
|
|
with pytest.raises(HomeAssistantError, match=match):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.REMOVE_ITEM,
|
|
{ATTR_ITEM: "Cheese"},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
async def test_remove_item_failure(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
) -> None:
|
|
"""Test removing an item on the list."""
|
|
|
|
item = Todo(dav_client, "2.ics", TODO_NEEDS_ACTION, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
def lookup(uid: str) -> Mock:
|
|
return item
|
|
|
|
calendar.todo_by_uid = Mock(side_effect=lookup)
|
|
dav_client.delete.return_value.status = 500
|
|
|
|
with pytest.raises(HomeAssistantError, match="CalDAV delete error"):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.REMOVE_ITEM,
|
|
{ATTR_ITEM: "Cheese"},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
async def test_remove_item_not_found(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
) -> None:
|
|
"""Test removing an item on the list."""
|
|
|
|
item = Todo(dav_client, "2.ics", TODO_NEEDS_ACTION, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
def lookup(uid: str) -> Mock:
|
|
return item
|
|
|
|
calendar.todo_by_uid.side_effect = NotFoundError()
|
|
|
|
with pytest.raises(HomeAssistantError, match="Could not find"):
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.REMOVE_ITEM,
|
|
{ATTR_ITEM: "Cheese"},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
async def test_subscribe(
|
|
hass: HomeAssistant,
|
|
config_entry: MockConfigEntry,
|
|
dav_client: Mock,
|
|
calendar: Mock,
|
|
hass_ws_client: WebSocketGenerator,
|
|
) -> None:
|
|
"""Test subscription to item updates."""
|
|
|
|
item = Todo(dav_client, None, TODO_NEEDS_ACTION, calendar, "2")
|
|
calendar.search = MagicMock(return_value=[item])
|
|
|
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
|
|
# Subscribe and get the initial list
|
|
client = await hass_ws_client(hass)
|
|
await client.send_json_auto_id(
|
|
{
|
|
"type": "todo/item/subscribe",
|
|
"entity_id": TEST_ENTITY,
|
|
}
|
|
)
|
|
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"]
|
|
|
|
calendar.todo_by_uid = MagicMock(return_value=item)
|
|
dav_client.put.return_value.status = 204
|
|
# Reflect update for state refresh after update
|
|
calendar.search.return_value = [
|
|
Todo(
|
|
dav_client, None, TODO_NEEDS_ACTION.replace("Cheese", "Milk"), calendar, "2"
|
|
)
|
|
]
|
|
await hass.services.async_call(
|
|
TODO_DOMAIN,
|
|
TodoServices.UPDATE_ITEM,
|
|
{
|
|
ATTR_ITEM: "Cheese",
|
|
ATTR_RENAME: "Milk",
|
|
},
|
|
target={ATTR_ENTITY_ID: TEST_ENTITY},
|
|
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"] == "Milk"
|
|
assert items[0]["status"] == "needs_action"
|
|
assert items[0]["uid"]
|