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)