From 785f4cc251c55f147b33bc5719d173337304aa43 Mon Sep 17 00:00:00 2001 From: Philip Rosenberg-Watt Date: Wed, 21 Mar 2018 13:38:48 -0600 Subject: [PATCH] Fix Google Calendar caching when offline Events from Google Calendar were not firing under the following circumstances: 1. Start ha as normal with Google Calendar configured as per instructions. 2. ha loses network connectivity to Google 3. ha attempts update of Google Calendar 4. calendar/google component throws uncaught Exception causing update method to not return 5. (cached) Google Calendar event does not fire, remains "Off" Catching the Exception and returning False from the update() method causes the correct behavior (i.e., the calendar component firing the event as scheduled using cached data). --- homeassistant/components/calendar/google.py | 9 ++- requirements_test_all.txt | 3 + tests/components/calendar/test_google.py | 69 +++++++++++---------- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 098c7c70834..b62869fb940 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -8,6 +8,8 @@ https://home-assistant.io/components/binary_sensor.google_calendar/ import logging from datetime import timedelta +from httplib2 import ServerNotFoundError + from homeassistant.components.calendar import CalendarEventDevice from homeassistant.components.google import ( CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE, @@ -62,7 +64,12 @@ class GoogleCalendarData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data.""" - service = self.calendar_service.get() + try: + service = self.calendar_service.get() + except ServerNotFoundError: + _LOGGER.warning("Unable to connect to Google, using cached data") + return False + params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) params['timeMin'] = dt.now().isoformat('T') params['calendarId'] = self.calendar_id diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0a7c3b493d4..b97eab335fb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -201,3 +201,6 @@ warrant==0.6.1 # homeassistant.components.sensor.yahoo_finance yahoo-finance==1.4.0 + +# homeassistant.components.calendar.google +httplib2==0.10.3 \ No newline at end of file diff --git a/tests/components/calendar/test_google.py b/tests/components/calendar/test_google.py index 62c8ea8854f..e3046434a56 100644 --- a/tests/components/calendar/test_google.py +++ b/tests/components/calendar/test_google.py @@ -2,9 +2,10 @@ # pylint: disable=protected-access import logging import unittest -from unittest.mock import patch +from unittest.mock import patch, Mock import pytest +from httplib2 import ServerNotFoundError import homeassistant.components.calendar as calendar_base import homeassistant.components.calendar.google as calendar @@ -42,16 +43,16 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_all_day_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - week_from_today = dt_util.dt.date.today() \ - + dt_util.dt.timedelta(days=7) + week_from_today = dt_util.dt.date.today() + dt_util.dt.timedelta( + days=7) event = { 'summary': 'Test All Day Event', 'start': { 'date': week_from_today.isoformat() }, 'end': { - 'date': (week_from_today + dt_util.dt.timedelta(days=1)) - .isoformat() + 'date': (week_from_today + dt_util.dt.timedelta( + days=1)).isoformat() }, 'location': 'Test Cases', 'description': 'We\'re just testing that all day events get setup ' @@ -105,16 +106,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_future_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - one_hour_from_now = dt_util.now() \ - + dt_util.dt.timedelta(minutes=30) + one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30) event = { 'start': { 'dateTime': one_hour_from_now.isoformat() }, 'end': { 'dateTime': (one_hour_from_now - + dt_util.dt.timedelta(minutes=60)) - .isoformat() + + dt_util.dt.timedelta(minutes=60)).isoformat() }, 'summary': 'Test Event in 30 minutes', 'reminders': {'useDefault': True}, @@ -157,8 +156,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'offset_reached': False, 'start_time': one_hour_from_now.strftime(DATE_STR_FORMAT), 'end_time': - (one_hour_from_now + dt_util.dt.timedelta(minutes=60)) - .strftime(DATE_STR_FORMAT), + (one_hour_from_now + dt_util.dt.timedelta( + minutes=60)).strftime(DATE_STR_FORMAT), 'location': '', 'description': '' }) @@ -166,16 +165,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - middle_of_event = dt_util.now() \ - - dt_util.dt.timedelta(minutes=30) + middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30) event = { 'start': { 'dateTime': middle_of_event.isoformat() }, 'end': { - 'dateTime': (middle_of_event + dt_util.dt - .timedelta(minutes=60)) - .isoformat() + 'dateTime': (middle_of_event + dt_util.dt.timedelta( + minutes=60)).isoformat() }, 'summary': 'Test Event in Progress', 'reminders': {'useDefault': True}, @@ -219,8 +216,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'offset_reached': False, 'start_time': middle_of_event.strftime(DATE_STR_FORMAT), 'end_time': - (middle_of_event + dt_util.dt.timedelta(minutes=60)) - .strftime(DATE_STR_FORMAT), + (middle_of_event + dt_util.dt.timedelta(minutes=60)).strftime( + DATE_STR_FORMAT), 'location': '', 'description': '' }) @@ -228,17 +225,15 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_offset_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - middle_of_event = dt_util.now() \ - + dt_util.dt.timedelta(minutes=14) + middle_of_event = dt_util.now() + dt_util.dt.timedelta(minutes=14) event_summary = 'Test Event in Progress' event = { 'start': { 'dateTime': middle_of_event.isoformat() }, 'end': { - 'dateTime': (middle_of_event + dt_util.dt - .timedelta(minutes=60)) - .isoformat() + 'dateTime': (middle_of_event + dt_util.dt.timedelta( + minutes=60)).isoformat() }, 'summary': '{} !!-15'.format(event_summary), 'reminders': {'useDefault': True}, @@ -281,9 +276,8 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'all_day': False, 'offset_reached': True, 'start_time': middle_of_event.strftime(DATE_STR_FORMAT), - 'end_time': - (middle_of_event + dt_util.dt.timedelta(minutes=60)) - .strftime(DATE_STR_FORMAT), + 'end_time': (middle_of_event + dt_util.dt.timedelta( + minutes=60)).strftime(DATE_STR_FORMAT), 'location': '', 'description': '' }) @@ -292,8 +286,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_all_day_offset_in_progress_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - tomorrow = dt_util.dt.date.today() \ - + dt_util.dt.timedelta(days=1) + tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=1) event_summary = 'Test All Day Event Offset In Progress' event = { @@ -302,8 +295,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'date': tomorrow.isoformat() }, 'end': { - 'date': (tomorrow + dt_util.dt.timedelta(days=1)) - .isoformat() + 'date': (tomorrow + dt_util.dt.timedelta(days=1)).isoformat() }, 'location': 'Test Cases', 'description': 'We\'re just testing that all day events get setup ' @@ -358,8 +350,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_all_day_offset_event(self, mock_next_event): """Test that we can create an event trigger on device.""" - tomorrow = dt_util.dt.date.today() \ - + dt_util.dt.timedelta(days=2) + tomorrow = dt_util.dt.date.today() + dt_util.dt.timedelta(days=2) offset_hours = (1 + dt_util.now().hour) event_summary = 'Test All Day Event Offset' @@ -369,8 +360,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'date': tomorrow.isoformat() }, 'end': { - 'date': (tomorrow + dt_util.dt.timedelta(days=1)) - .isoformat() + 'date': (tomorrow + dt_util.dt.timedelta(days=1)).isoformat() }, 'location': 'Test Cases', 'description': 'We\'re just testing that all day events get setup ' @@ -421,3 +411,14 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'location': event['location'], 'description': event['description'] }) + + def test_update_false(self): + """Test that the update returns False upon Error.""" + mock_service = Mock() + mock_service.get = Mock(side_effect=ServerNotFoundError("unit test")) + + cal = calendar.GoogleCalendarEventDevice(self.hass, mock_service, None, + {'name': "test"}) + result = cal.data.update() + + self.assertFalse(result)