Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions homeassistant/components/homematicip_cloud/hap.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ async def get_auth(self, hass: HomeAssistant, hapid, pin):
_LOGGER.debug("Connection request result: %s", result)
except HmipConnectionError:
return None
# a rejected SGTIN or PIN comes back as a failed result, not an exception
if not result.success:
return None
return auth


Expand Down
9 changes: 7 additions & 2 deletions homeassistant/components/todoist/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,15 @@ async def async_get_events(
start = parse_due_date(task.due)
if start is None:
continue
duration = (
timedelta(minutes=30)
if isinstance(start, datetime)
else timedelta(days=1)
)
event = CalendarEvent(
summary=task.content,
start=start,
end=start + timedelta(days=1),
end=start + duration,
)
if (
event.start_datetime_local is not None
Expand All @@ -530,7 +535,7 @@ async def async_get_events(
continue
if (
event.end_datetime_local is not None
and event.end_datetime_local < start_date
and event.end_datetime_local <= start_date
):
continue
events.append(event)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zwave_js/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2844,7 +2844,7 @@ def _get_controller_statistics_dict(
"nak": statistics.nak,
"can": statistics.can,
"timeout_ack": statistics.timeout_ack,
"timout_response": statistics.timeout_response,
"timeout_response": statistics.timeout_response,
"timeout_callback": statistics.timeout_callback,
}

Expand Down
36 changes: 35 additions & 1 deletion tests/components/homematicip_cloud/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Tests for HomematicIP Cloud config flow."""

from unittest.mock import patch
from unittest.mock import AsyncMock, patch

from homematicip.connection.connection_context import ConnectionContext
from homematicip.connection.rest_connection import RestResult

from homeassistant import config_entries
from homeassistant.components.homematicip_cloud.const import (
Expand Down Expand Up @@ -104,6 +107,37 @@ async def test_flow_init_connection_error(hass: HomeAssistant) -> None:
assert result["errors"] == {"base": "invalid_sgtin_or_pin"}


async def test_flow_init_rejected_pin(
hass: HomeAssistant, simple_mock_auth: AsyncMock
) -> None:
"""Test the flow stays on the first step when the cloud rejects the PIN."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)

# the library hands a 400 back as a failed result instead of raising
simple_mock_auth.connection_request.return_value = RestResult(
status=400, text='{"errorCode":"INVALID_PIN"}'
)
with (
patch(
"homeassistant.components.homematicip_cloud.hap.Auth",
return_value=simple_mock_auth,
),
patch(
"homeassistant.components.homematicip_cloud.hap.ConnectionContextBuilder.build_context_async",
return_value=ConnectionContext(),
),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=DEFAULT_CONFIG
)

assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
assert result["errors"] == {"base": "invalid_sgtin_or_pin"}


async def test_flow_link_connection_error(hass: HomeAssistant) -> None:
"""Test config flow client registration connection error."""
result = await hass.config_entries.flow.async_init(
Expand Down
25 changes: 25 additions & 0 deletions tests/components/homematicip_cloud/test_hap.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from homematicip.auth import Auth
from homematicip.connection.connection_context import ConnectionContext
from homematicip.connection.rest_connection import RestResult
from homematicip.exceptions.connection_exceptions import (
HmipAuthenticationError,
HmipConnectionError,
Expand Down Expand Up @@ -197,6 +198,7 @@ async def test_auth_create(hass: HomeAssistant, simple_mock_auth) -> None:
"""Mock AsyncAuth to execute get_auth."""
config = {HMIPC_HAPID: HAPID, HMIPC_PIN: HAPPIN, HMIPC_NAME: "hmip"}
hmip_auth = HomematicipAuth(hass, config)
simple_mock_auth.connection_request.return_value = RestResult(status=200)
assert hmip_auth

with (
Expand Down Expand Up @@ -245,6 +247,29 @@ async def test_auth_create_exception(hass: HomeAssistant, simple_mock_auth) -> N
assert not await hmip_auth.get_auth(hass, HAPID, HAPPIN)


async def test_auth_create_rejected(
hass: HomeAssistant, simple_mock_auth: AsyncMock
) -> None:
"""Test a connection request the cloud rejects does not yield an auth."""
config = {HMIPC_HAPID: HAPID, HMIPC_PIN: HAPPIN, HMIPC_NAME: "hmip"}
hmip_auth = HomematicipAuth(hass, config)
# a wrong or missing PIN is answered with a 400, which the library returns
simple_mock_auth.connection_request.return_value = RestResult(
status=400, text='{"errorCode":"INVALID_PIN"}'
)
with (
patch(
"homeassistant.components.homematicip_cloud.hap.Auth",
return_value=simple_mock_auth,
),
patch(
"homeassistant.components.homematicip_cloud.hap.ConnectionContextBuilder.build_context_async",
return_value=ConnectionContext(),
),
):
assert not await hmip_auth.async_setup()


async def test_get_state_after_disconnect(
hass: HomeAssistant, hmip_config_entry: MockConfigEntry, simple_mock_home
) -> None:
Expand Down
74 changes: 65 additions & 9 deletions tests/components/todoist/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,39 @@ async def test_calendar_custom_project_unique_id(
assert entity is None


@pytest.mark.parametrize(
("due", "expected_start", "expected_end"),
[
(
make_api_due(date="2023-03-30", string="Mar 30"),
{"date": "2023-03-30"},
{"date": "2023-03-31"},
),
(
make_api_due(date="2023-03-30T18:00:00", string="Mar 30 6:00 PM"),
{"dateTime": "2023-03-30T18:00:00-06:00"},
{"dateTime": "2023-03-30T18:30:00-06:00"},
),
],
ids=("all_day", "timed"),
)
async def test_event_duration(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
expected_start: dict[str, str],
expected_end: dict[str, str],
) -> None:
"""Test date-only tasks last one day and timed tasks last 30 minutes."""
client = await hass_client()
response = await client.get(
get_events_url(
"calendar.name", "2023-03-30T00:00:00.000Z", "2023-04-01T00:00:00.000Z"
)
)
assert response.status == HTTPStatus.OK
assert await response.json() == [get_events_response(expected_start, expected_end)]


@pytest.mark.parametrize(
("due", "start", "end", "expected_response"),
[
Expand Down Expand Up @@ -254,14 +287,28 @@ async def test_calendar_custom_project_unique_id(
"2023-04-01T08:00:00.000Z",
[],
),
(
make_api_due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-31T06:00:00.000Z",
"2023-04-01T06:00:00.000Z",
[],
),
(
make_api_due(date="2023-03-30", is_recurring=False, string="Mar 30"),
"2023-03-29T06:00:00.000Z",
"2023-03-30T06:00:00.000Z",
[],
),
],
ids=("included", "exact", "overlap_start", "overlap_end", "after", "before"),
ids=(
"included",
"exact",
"overlap_start",
"overlap_end",
"after",
"end_boundary",
"before",
),
)
async def test_all_day_event(
hass: HomeAssistant,
Expand Down Expand Up @@ -374,14 +421,14 @@ async def test_task_due_datetime(
has_task_response = [
get_events_response(
{"dateTime": "2023-03-30T18:00:00-06:00"},
{"dateTime": "2023-03-31T18:00:00-06:00"},
{"dateTime": "2023-03-30T18:30:00-06:00"},
)
]

# Completely includes the start/end of the task
response = await client.get(
get_events_url(
"calendar.name", "2023-03-30T08:00:00.000Z", "2023-03-31T08:00:00.000Z"
"calendar.name", "2023-03-30T23:00:00.000Z", "2023-03-31T01:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand All @@ -390,7 +437,7 @@ async def test_task_due_datetime(
# Overlap with the start of the event
response = await client.get(
get_events_url(
"calendar.name", "2023-03-29T20:00:00.000Z", "2023-03-31T02:00:00.000Z"
"calendar.name", "2023-03-30T23:30:00.000Z", "2023-03-31T00:15:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand All @@ -399,7 +446,7 @@ async def test_task_due_datetime(
# Overlap with the end of the event
response = await client.get(
get_events_url(
"calendar.name", "2023-03-31T20:00:00.000Z", "2023-04-01T02:00:00.000Z"
"calendar.name", "2023-03-31T00:15:00.000Z", "2023-03-31T01:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand All @@ -408,7 +455,7 @@ async def test_task_due_datetime(
# Task is active, but range does not include start/end
response = await client.get(
get_events_url(
"calendar.name", "2023-03-31T10:00:00.000Z", "2023-03-31T11:00:00.000Z"
"calendar.name", "2023-03-31T00:10:00.000Z", "2023-03-31T00:20:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand All @@ -417,7 +464,16 @@ async def test_task_due_datetime(
# Query is before the task starts (no results)
response = await client.get(
get_events_url(
"calendar.name", "2023-03-28T00:00:00.000Z", "2023-03-29T00:00:00.000Z"
"calendar.name", "2023-03-30T22:00:00.000Z", "2023-03-30T23:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
assert await response.json() == []

# Query starts exactly when the task ends (no results)
response = await client.get(
get_events_url(
"calendar.name", "2023-03-31T00:30:00.000Z", "2023-03-31T01:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand All @@ -426,7 +482,7 @@ async def test_task_due_datetime(
# Query is after the task ends (no results)
response = await client.get(
get_events_url(
"calendar.name", "2023-04-01T07:00:00.000Z", "2023-04-02T07:00:00.000Z"
"calendar.name", "2023-03-31T01:00:00.000Z", "2023-03-31T02:00:00.000Z"
),
)
assert response.status == HTTPStatus.OK
Expand Down Expand Up @@ -535,6 +591,6 @@ async def test_config_entry(
assert await response.json() == [
get_events_response(
{"dateTime": "2023-03-30T18:00:00-06:00"},
{"dateTime": "2023-03-31T18:00:00-06:00"},
{"dateTime": "2023-03-30T18:30:00-06:00"},
)
]
Loading
Loading