Stop updating google_calendars.yaml if it does not already exist (#72340)

* Stop updating google_calendars.yaml if it does not already exist

* Add additional test coverage to make CI pass

* Add test for no updates to google_calendar.yaml

* Add parameter to test for expecting write calls

* Missing call argument

* Remove conditional and replace with inline assert
pull/72485/head
Allen Porter 2022-05-24 23:59:27 -07:00 committed by GitHub
parent 9591d5366e
commit 71bc650ac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 15 deletions

View File

@ -300,6 +300,7 @@ async def async_setup_services(
calendars = await hass.async_add_executor_job( calendars = await hass.async_add_executor_job(
load_config, hass.config.path(YAML_DEVICES) load_config, hass.config.path(YAML_DEVICES)
) )
calendars_file_lock = asyncio.Lock()
async def _found_calendar(calendar_item: Calendar) -> None: async def _found_calendar(calendar_item: Calendar) -> None:
calendar = get_calendar_info( calendar = get_calendar_info(
@ -307,15 +308,19 @@ async def async_setup_services(
calendar_item.dict(exclude_unset=True), calendar_item.dict(exclude_unset=True),
) )
calendar_id = calendar_item.id calendar_id = calendar_item.id
# Populate the yaml file with all discovered calendars # If the google_calendars.yaml file already exists, populate it for
if calendar_id not in calendars: # backwards compatibility, but otherwise do not create it if it does
calendars[calendar_id] = calendar # not exist.
await hass.async_add_executor_job( if calendars:
update_config, hass.config.path(YAML_DEVICES), calendar if calendar_id not in calendars:
) calendars[calendar_id] = calendar
else: async with calendars_file_lock:
# Prefer entity/name information from yaml, overriding api await hass.async_add_executor_job(
calendar = calendars[calendar_id] update_config, hass.config.path(YAML_DEVICES), calendar
)
else:
# Prefer entity/name information from yaml, overriding api
calendar = calendars[calendar_id]
async_dispatcher_send(hass, DISCOVER_CALENDAR, calendar) async_dispatcher_send(hass, DISCOVER_CALENDAR, calendar)
created_calendars = set() created_calendars = set()

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
import datetime import datetime
from typing import Any, Generator, TypeVar from typing import Any, Generator, TypeVar
from unittest.mock import mock_open, patch from unittest.mock import Mock, mock_open, patch
from aiohttp.client_exceptions import ClientError from aiohttp.client_exceptions import ClientError
from gcal_sync.auth import API_BASE_URL from gcal_sync.auth import API_BASE_URL
@ -104,11 +104,11 @@ def calendars_config(calendars_config_entity: dict[str, Any]) -> list[dict[str,
def mock_calendars_yaml( def mock_calendars_yaml(
hass: HomeAssistant, hass: HomeAssistant,
calendars_config: list[dict[str, Any]], calendars_config: list[dict[str, Any]],
) -> None: ) -> Generator[Mock, None, None]:
"""Fixture that prepares the google_calendars.yaml mocks.""" """Fixture that prepares the google_calendars.yaml mocks."""
mocked_open_function = mock_open(read_data=yaml.dump(calendars_config)) mocked_open_function = mock_open(read_data=yaml.dump(calendars_config))
with patch("homeassistant.components.google.open", mocked_open_function): with patch("homeassistant.components.google.open", mocked_open_function):
yield yield mocked_open_function
class FakeStorage: class FakeStorage:
@ -170,10 +170,17 @@ def config_entry_token_expiry(token_expiry: datetime.datetime) -> float:
return token_expiry.timestamp() return token_expiry.timestamp()
@pytest.fixture
def config_entry_options() -> dict[str, Any] | None:
"""Fixture to set initial config entry options."""
return None
@pytest.fixture @pytest.fixture
def config_entry( def config_entry(
token_scopes: list[str], token_scopes: list[str],
config_entry_token_expiry: float, config_entry_token_expiry: float,
config_entry_options: dict[str, Any] | None,
) -> MockConfigEntry: ) -> MockConfigEntry:
"""Fixture to create a config entry for the integration.""" """Fixture to create a config entry for the integration."""
return MockConfigEntry( return MockConfigEntry(
@ -188,6 +195,7 @@ def config_entry(
"expires_at": config_entry_token_expiry, "expires_at": config_entry_token_expiry,
}, },
}, },
options=config_entry_options,
) )

View File

@ -572,14 +572,16 @@ async def test_opaque_event(
assert (len(events) > 0) == expect_visible_event assert (len(events) > 0) == expect_visible_event
@pytest.mark.parametrize("mock_test_setup", [None])
async def test_scan_calendar_error( async def test_scan_calendar_error(
hass, hass,
component_setup, component_setup,
test_api_calendar, test_api_calendar,
mock_calendars_list, mock_calendars_list,
config_entry,
): ):
"""Test that the calendar update handles a server error.""" """Test that the calendar update handles a server error."""
config_entry.add_to_hass(hass)
mock_calendars_list({}, exc=ClientError()) mock_calendars_list({}, exc=ClientError())
assert await component_setup() assert await component_setup()

View File

@ -6,7 +6,7 @@ import datetime
import http import http
import time import time
from typing import Any from typing import Any
from unittest.mock import patch from unittest.mock import Mock, patch
import pytest import pytest
@ -19,6 +19,7 @@ from homeassistant.components.google import (
SERVICE_ADD_EVENT, SERVICE_ADD_EVENT,
SERVICE_SCAN_CALENDARS, SERVICE_SCAN_CALENDARS,
) )
from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_OFF from homeassistant.const import STATE_OFF
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
@ -229,7 +230,10 @@ async def test_found_calendar_from_api(
assert not hass.states.get(TEST_YAML_ENTITY) assert not hass.states.get(TEST_YAML_ENTITY)
@pytest.mark.parametrize("calendars_config,google_config", [([], {})]) @pytest.mark.parametrize(
"calendars_config,google_config,config_entry_options",
[([], {}, {CONF_CALENDAR_ACCESS: "read_write"})],
)
async def test_load_application_credentials( async def test_load_application_credentials(
hass: HomeAssistant, hass: HomeAssistant,
component_setup: ComponentSetup, component_setup: ComponentSetup,
@ -604,3 +608,48 @@ async def test_expired_token_requires_reauth(
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1 assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm" assert flows[0]["step_id"] == "reauth_confirm"
@pytest.mark.parametrize(
"calendars_config,expect_write_calls",
[
(
[
{
"cal_id": "ignored",
"entities": {"device_id": "existing", "name": "existing"},
}
],
True,
),
([], False),
],
ids=["has_yaml", "no_yaml"],
)
async def test_calendar_yaml_update(
hass: HomeAssistant,
component_setup: ComponentSetup,
mock_calendars_yaml: Mock,
mock_calendars_list: ApiResult,
test_api_calendar: dict[str, Any],
mock_events_list: ApiResult,
setup_config_entry: MockConfigEntry,
calendars_config: dict[str, Any],
expect_write_calls: bool,
) -> None:
"""Test updating the yaml file with a new calendar."""
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
mock_calendars_yaml().read.assert_called()
mock_calendars_yaml().write.called is expect_write_calls
state = hass.states.get(TEST_API_ENTITY)
assert state
assert state.name == TEST_API_ENTITY_NAME
assert state.state == STATE_OFF
# No yaml config loaded that overwrites the entity name
assert not hass.states.get(TEST_YAML_ENTITY)