From cc8b4f059aa99e777b7ac4595cd0cccd3cef5893 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 23 Sep 2019 19:57:31 -0400 Subject: [PATCH 01/19] Added apprise notification component --- homeassistant/components/apprise/__init__.py | 1 + .../components/apprise/manifest.json | 10 +++ homeassistant/components/apprise/notify.py | 71 +++++++++++++++++++ requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/apprise/__init__.py | 1 + tests/components/apprise/test_notify.py | 68 ++++++++++++++++++ 7 files changed, 157 insertions(+) create mode 100644 homeassistant/components/apprise/__init__.py create mode 100644 homeassistant/components/apprise/manifest.json create mode 100644 homeassistant/components/apprise/notify.py create mode 100644 tests/components/apprise/__init__.py create mode 100644 tests/components/apprise/test_notify.py diff --git a/homeassistant/components/apprise/__init__.py b/homeassistant/components/apprise/__init__.py new file mode 100644 index 00000000000000..6ffdaf690d9409 --- /dev/null +++ b/homeassistant/components/apprise/__init__.py @@ -0,0 +1 @@ +"""The apprise component.""" diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json new file mode 100644 index 00000000000000..8385a1709815fb --- /dev/null +++ b/homeassistant/components/apprise/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "apprise", + "name": "Apprise", + "documentation": "https://www.home-assistant.io/components/apprise", + "requirements": [ + "apprise==0.8.0" + ], + "dependencies": [], + "codeowners": [] +} diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py new file mode 100644 index 00000000000000..eb715572ebbc46 --- /dev/null +++ b/homeassistant/components/apprise/notify.py @@ -0,0 +1,71 @@ +"""Apprise platform for notify component.""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.notify import ( + ATTR_TITLE, + ATTR_TITLE_DEFAULT, + PLATFORM_SCHEMA, + BaseNotificationService, +) + +_LOGGER = logging.getLogger(__name__) + +CONF_FILE = "config" +CONF_URL = "url" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_URL): vol.All(cv.ensure_list, [str]), + vol.Optional(CONF_FILE): cv.string + } +) + + +def get_service(hass, config, discovery_info=None): + """Get the Apprise notification service.""" + from apprise import Apprise + from apprise import AppriseConfig + + # Create our object + a = Apprise() + + if config.get(CONF_FILE): + # Sourced from a Configuration File + ac = AppriseConfig() + if not ac.add(config[CONF_FILE]): + _LOGGER.error("Invalid Apprise config url provided") + return None + + elif not a.add(ac): + _LOGGER.error("Invalid Apprise config url provided") + return None + + if config.get(CONF_URL): + # Ordered list of URLs + if not a.add(config[CONF_URL]): + _LOGGER.error("Invalid Apprise URL(s) supplied") + return None + + if len(a) == 0: + _LOGGER.error("No Apprise services were loaded.") + return None + + return AppriseNotificationService(a) + + +class AppriseNotificationService(BaseNotificationService): + """Implement the notification service for Apprise.""" + + def __init__(self, a_obj): + """Initialize the service.""" + self.apprise = a_obj + + def send_message(self, message="", **kwargs): + """Send a message to a specified target. + """ + title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + self.apprise.notify(body=message, title=title) diff --git a/requirements_all.txt b/requirements_all.txt index 9a9540c87473d2..d80c848397b5a7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -217,6 +217,9 @@ apcaccess==0.0.13 # homeassistant.components.apns apns2==0.3.0 +# homeassistant.components.apprise +apprise==0.8.0 + # homeassistant.components.aprs aprslib==0.6.46 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4c3a6e7721bccb..164991edb1ce77 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -103,6 +103,9 @@ androidtv==0.0.30 # homeassistant.components.apns apns2==0.3.0 +# homeassistant.components.apprise +apprise==0.8.0 + # homeassistant.components.aprs aprslib==0.6.46 diff --git a/tests/components/apprise/__init__.py b/tests/components/apprise/__init__.py new file mode 100644 index 00000000000000..ffebc35b4e1469 --- /dev/null +++ b/tests/components/apprise/__init__.py @@ -0,0 +1 @@ +"""Tests for the apprise component.""" diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py new file mode 100644 index 00000000000000..20175ae239c60f --- /dev/null +++ b/tests/components/apprise/test_notify.py @@ -0,0 +1,68 @@ +"""The tests for the apprise notification platform.""" +import unittest +from unittest.mock import patch + +from apprise import Apprise +import requests_mock + +from homeassistant.setup import setup_component +import homeassistant.components.notify as notify +from tests.common import assert_setup_component, get_test_home_assistant + + +class TestApprise(unittest.TestCase): + """Tests the Apprise Component.""" + + def setUp(self): + """Initialize values for this test case class.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Stop everything that we started.""" + self.hass.stop() + + @patch.object(Apprise, "notify", return_value=True) + def test_apprise_config(self, mock__get_data): + """Test setup.""" + config = { + notify.DOMAIN: { + "name": "test", + "platform": "apprise", + "url": "dbus://", + } + } + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert handle_config[notify.DOMAIN] + + def test_apprise_config_bad(self): + """Test set up the platform with bad/missing configuration.""" + config = { + notify.DOMAIN: { + "name": "test", + "platform": "apprise", + # Expects a list + "url": 1, + } + } + with assert_setup_component(0) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert not handle_config[notify.DOMAIN] + + @requests_mock.Mocker() + @patch.object(Apprise, "notify", return_value=True) + def test_apprise_push_default(self, mock, mock__get_data): + """Test simple apprise push.""" + config = { + notify.DOMAIN: { + "name": "test", + "platform": "apprise", + "url": "windows://", + } + } + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert handle_config[notify.DOMAIN] + data = {"title": "Test Title", "message": "Test Message"} + assert not self.hass.services.call(notify.DOMAIN, "test", data) + self.hass.block_till_done() From 4cd785a5861c296006faafe0284d4f0ed03bc4a7 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 23 Sep 2019 20:19:59 -0400 Subject: [PATCH 02/19] flake-8 fixes; black formatting + import merged to 1 line --- homeassistant/components/apprise/notify.py | 8 +++----- tests/components/apprise/test_notify.py | 12 ++---------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index eb715572ebbc46..c95f31c3d904e8 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -20,15 +20,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_URL): vol.All(cv.ensure_list, [str]), - vol.Optional(CONF_FILE): cv.string + vol.Optional(CONF_FILE): cv.string, } ) def get_service(hass, config, discovery_info=None): """Get the Apprise notification service.""" - from apprise import Apprise - from apprise import AppriseConfig + from apprise import Apprise, AppriseConfig # Create our object a = Apprise() @@ -65,7 +64,6 @@ def __init__(self, a_obj): self.apprise = a_obj def send_message(self, message="", **kwargs): - """Send a message to a specified target. - """ + """Send a message to a specified target.""" title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) self.apprise.notify(body=message, title=title) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 20175ae239c60f..e6528112025c5c 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -25,11 +25,7 @@ def tearDown(self): # pylint: disable=invalid-name def test_apprise_config(self, mock__get_data): """Test setup.""" config = { - notify.DOMAIN: { - "name": "test", - "platform": "apprise", - "url": "dbus://", - } + notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "dbus://"} } with assert_setup_component(1) as handle_config: assert setup_component(self.hass, notify.DOMAIN, config) @@ -54,11 +50,7 @@ def test_apprise_config_bad(self): def test_apprise_push_default(self, mock, mock__get_data): """Test simple apprise push.""" config = { - notify.DOMAIN: { - "name": "test", - "platform": "apprise", - "url": "windows://", - } + notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "windows://"} } with assert_setup_component(1) as handle_config: assert setup_component(self.hass, notify.DOMAIN, config) From c94e2f6abdb7d1f5159cb991e3047fbf17287703 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 23 Sep 2019 21:25:17 -0400 Subject: [PATCH 03/19] pylint issues resolved --- homeassistant/components/apprise/notify.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index c95f31c3d904e8..2b5e36f10117fb 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -30,30 +30,32 @@ def get_service(hass, config, discovery_info=None): from apprise import Apprise, AppriseConfig # Create our object - a = Apprise() + a_obj = Apprise() if config.get(CONF_FILE): # Sourced from a Configuration File - ac = AppriseConfig() - if not ac.add(config[CONF_FILE]): + a_config = AppriseConfig() + if not a_config.add(config[CONF_FILE]): _LOGGER.error("Invalid Apprise config url provided") return None - elif not a.add(ac): + if not a_obj.add(a_config): _LOGGER.error("Invalid Apprise config url provided") return None if config.get(CONF_URL): # Ordered list of URLs - if not a.add(config[CONF_URL]): + if not a_obj.add(config[CONF_URL]): _LOGGER.error("Invalid Apprise URL(s) supplied") return None - if len(a) == 0: + loaded = len(a_obj) + if not loaded: _LOGGER.error("No Apprise services were loaded.") return None - return AppriseNotificationService(a) + _LOGGER.info("Loaded %d Apprise service(s).", loaded) + return AppriseNotificationService(a_obj) class AppriseNotificationService(BaseNotificationService): From 71cab55aca2a76b00f9bc3a510bb0938680d4423 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 24 Sep 2019 09:10:08 -0400 Subject: [PATCH 04/19] added github name to manifest.json --- homeassistant/components/apprise/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index 8385a1709815fb..e724e8e4bf2b34 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -6,5 +6,5 @@ "apprise==0.8.0" ], "dependencies": [], - "codeowners": [] + "codeowners": ["@caronc"] } From ba3ba13c36083dea4c7bd0881e874ce7795143b8 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 24 Sep 2019 09:17:31 -0400 Subject: [PATCH 05/19] import moved to top as per code review request --- homeassistant/components/apprise/notify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 2b5e36f10117fb..5ff9301f2d02e6 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -3,6 +3,8 @@ import voluptuous as vol +from apprise import Apprise, AppriseConfig + import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( @@ -27,7 +29,6 @@ def get_service(hass, config, discovery_info=None): """Get the Apprise notification service.""" - from apprise import Apprise, AppriseConfig # Create our object a_obj = Apprise() From dd20556bbbd6910dfa8a75e6df8122ac81cbb1a8 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 24 Sep 2019 10:48:05 -0400 Subject: [PATCH 06/19] manifest formatting to avoid failing ci --- CODEOWNERS | 1 + homeassistant/components/apprise/manifest.json | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index ea50d24095c1ba..8e52210cec7480 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,7 @@ homeassistant/components/ambient_station/* @bachya homeassistant/components/androidtv/* @JeffLIrion homeassistant/components/apache_kafka/* @bachya homeassistant/components/api/* @home-assistant/core +homeassistant/components/apprise/* @caronc homeassistant/components/aprs/* @PhilRW homeassistant/components/arcam_fmj/* @elupus homeassistant/components/arduino/* @fabaff diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index e724e8e4bf2b34..51873b55cc3677 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -6,5 +6,7 @@ "apprise==0.8.0" ], "dependencies": [], - "codeowners": ["@caronc"] + "codeowners": [ + "@caronc" + ] } From e064234ba931ccaaa6030390e018dd7e41c19fe2 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 24 Sep 2019 14:19:37 -0400 Subject: [PATCH 07/19] .coveragerc updated to include apprise --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 145350b6b19955..49de1c640448aa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -47,6 +47,7 @@ omit = homeassistant/components/apache_kafka/* homeassistant/components/apcupsd/* homeassistant/components/apple_tv/* + homeassistant/components/apprise/* homeassistant/components/aqualogic/* homeassistant/components/aquostv/media_player.py homeassistant/components/arcam_fmj/media_player.py From ef4f7e1621fc1972a1712c0853643f9a2875c2f1 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Fri, 27 Sep 2019 17:49:25 -0400 Subject: [PATCH 08/19] removed block for written tests --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 49de1c640448aa..145350b6b19955 100644 --- a/.coveragerc +++ b/.coveragerc @@ -47,7 +47,6 @@ omit = homeassistant/components/apache_kafka/* homeassistant/components/apcupsd/* homeassistant/components/apple_tv/* - homeassistant/components/apprise/* homeassistant/components/aqualogic/* homeassistant/components/aquostv/media_player.py homeassistant/components/arcam_fmj/media_player.py From 693667a5e3e1e5dad55de4be8283ad1912d8f254 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Fri, 27 Sep 2019 19:52:28 -0400 Subject: [PATCH 09/19] more test coverage --- tests/components/apprise/test_notify.py | 67 +++++++++++++++++++++---- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index e6528112025c5c..e1f9aab61d8fcc 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,9 +1,11 @@ """The tests for the apprise notification platform.""" +import os import unittest from unittest.mock import patch +from tempfile import mkdtemp from apprise import Apprise -import requests_mock +from apprise import AppriseConfig from homeassistant.setup import setup_component import homeassistant.components.notify as notify @@ -13,31 +15,58 @@ class TestApprise(unittest.TestCase): """Tests the Apprise Component.""" + tmp_dir = None + def setUp(self): """Initialize values for this test case class.""" self.hass = get_test_home_assistant() + self.tmp_dir = mkdtemp() def tearDown(self): # pylint: disable=invalid-name """Stop everything that we started.""" self.hass.stop() + for fname in os.listdir(self.tmp_dir): + os.remove(os.path.join(self.tmp_dir, fname)) + os.rmdir(self.tmp_dir) - @patch.object(Apprise, "notify", return_value=True) - def test_apprise_config(self, mock__get_data): - """Test setup.""" + @patch.object(AppriseConfig, "add", return_value=False) + def test_apprise_config_bad_conf_add(self, __mockcfg_add): + """Test set up the platform with bad/missing file configuration.""" config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "dbus://"} + notify.DOMAIN: {"name": "test", "platform": "apprise", "config": "/path/"} + } + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert handle_config[notify.DOMAIN] + + @patch.object(Apprise, "add", return_value=False) + @patch.object(AppriseConfig, "add", return_value=True) + def test_apprise_config_bad_conf_save(self, __mock_add, __mockcfg_add): + """Test set up the platform failing to save config data.""" + config = { + notify.DOMAIN: {"name": "test", "platform": "apprise", "config": "/path/"} } with assert_setup_component(1) as handle_config: assert setup_component(self.hass, notify.DOMAIN, config) assert handle_config[notify.DOMAIN] - def test_apprise_config_bad(self): + @patch.object(Apprise, "add", return_value=False) + def test_apprise_bad_url(self, __mock_notify): """Test set up the platform with bad/missing configuration.""" + config = { + notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "dbus://"} + } + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert handle_config[notify.DOMAIN] + + def test_apprise_config_bad_url(self): + """Test set up the platform with bad/missing url configuration.""" config = { notify.DOMAIN: { "name": "test", "platform": "apprise", - # Expects a list + # Expects an actual URL "url": 1, } } @@ -45,10 +74,9 @@ def test_apprise_config_bad(self): assert setup_component(self.hass, notify.DOMAIN, config) assert not handle_config[notify.DOMAIN] - @requests_mock.Mocker() @patch.object(Apprise, "notify", return_value=True) - def test_apprise_push_default(self, mock, mock__get_data): - """Test simple apprise push.""" + def test_apprise_url(self, __mock_notify): + """Test simple apprise push using URL config.""" config = { notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "windows://"} } @@ -58,3 +86,22 @@ def test_apprise_push_default(self, mock, mock__get_data): data = {"title": "Test Title", "message": "Test Message"} assert not self.hass.services.call(notify.DOMAIN, "test", data) self.hass.block_till_done() + + @patch.object(Apprise, "notify", return_value=True) + def test_apprise_config(self, __mock_notify): + """Test simple apprise push using AppriseConfig.""" + tmp_cfg = os.path.join(self.tmp_dir, "apprise.txt") + config = { + notify.DOMAIN: {"name": "test", "platform": "apprise", "config": tmp_cfg} + } + + with open(tmp_cfg, "w+") as f: + # Write a simple text based configuration file + f.write("windows://") + + with assert_setup_component(1) as handle_config: + assert setup_component(self.hass, notify.DOMAIN, config) + assert handle_config[notify.DOMAIN] + data = {"title": "Test Title", "message": "Test Message"} + assert not self.hass.services.call(notify.DOMAIN, "test", data) + self.hass.block_till_done() From ffa6e457154f66d09892b7687b07766129e96052 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 28 Sep 2019 09:03:33 -0400 Subject: [PATCH 10/19] formatting as per code review --- homeassistant/components/apprise/notify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 5ff9301f2d02e6..734fa3e39ede41 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -52,10 +52,10 @@ def get_service(hass, config, discovery_info=None): loaded = len(a_obj) if not loaded: - _LOGGER.error("No Apprise services were loaded.") + _LOGGER.error("No Apprise services were loaded") return None - _LOGGER.info("Loaded %d Apprise service(s).", loaded) + _LOGGER.debug("Loaded %d Apprise service(s).", loaded) return AppriseNotificationService(a_obj) From 5e25965f8dbd0982f51cee603d1fc66bd7e90d3a Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 30 Sep 2019 19:34:25 -0400 Subject: [PATCH 11/19] tests converted to async style as per code review --- homeassistant/components/apprise/notify.py | 12 +- tests/components/apprise/test_notify.py | 156 +++++++++------------ 2 files changed, 66 insertions(+), 102 deletions(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 734fa3e39ede41..ab568dd7cb25c1 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -3,7 +3,7 @@ import voluptuous as vol -from apprise import Apprise, AppriseConfig +import apprise import homeassistant.helpers.config_validation as cv @@ -31,11 +31,11 @@ def get_service(hass, config, discovery_info=None): """Get the Apprise notification service.""" # Create our object - a_obj = Apprise() + a_obj = apprise.Apprise() if config.get(CONF_FILE): # Sourced from a Configuration File - a_config = AppriseConfig() + a_config = apprise.AppriseConfig() if not a_config.add(config[CONF_FILE]): _LOGGER.error("Invalid Apprise config url provided") return None @@ -50,12 +50,6 @@ def get_service(hass, config, discovery_info=None): _LOGGER.error("Invalid Apprise URL(s) supplied") return None - loaded = len(a_obj) - if not loaded: - _LOGGER.error("No Apprise services were loaded") - return None - - _LOGGER.debug("Loaded %d Apprise service(s).", loaded) return AppriseNotificationService(a_obj) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index e1f9aab61d8fcc..295dc4a2092ebb 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,107 +1,77 @@ """The tests for the apprise notification platform.""" -import os -import unittest from unittest.mock import patch -from tempfile import mkdtemp -from apprise import Apprise -from apprise import AppriseConfig +from homeassistant.setup import async_setup_component -from homeassistant.setup import setup_component -import homeassistant.components.notify as notify -from tests.common import assert_setup_component, get_test_home_assistant +BASE_COMPONENT = "notify" -class TestApprise(unittest.TestCase): - """Tests the Apprise Component.""" +async def test_apprise_config_load_fail(hass): + """Test apprise configuration failures.""" - tmp_dir = None + config = { + BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} + } - def setUp(self): - """Initialize values for this test case class.""" - self.hass = get_test_home_assistant() - self.tmp_dir = mkdtemp() + with patch("apprise.AppriseConfig.add", return_value=False): + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that we started.""" - self.hass.stop() - for fname in os.listdir(self.tmp_dir): - os.remove(os.path.join(self.tmp_dir, fname)) - os.rmdir(self.tmp_dir) + with patch("apprise.AppriseConfig.add", return_value=True): + with patch("apprise.Apprise.add", return_value=False): + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() - @patch.object(AppriseConfig, "add", return_value=False) - def test_apprise_config_bad_conf_add(self, __mockcfg_add): - """Test set up the platform with bad/missing file configuration.""" - config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "config": "/path/"} - } - with assert_setup_component(1) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - - @patch.object(Apprise, "add", return_value=False) - @patch.object(AppriseConfig, "add", return_value=True) - def test_apprise_config_bad_conf_save(self, __mock_add, __mockcfg_add): - """Test set up the platform failing to save config data.""" - config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "config": "/path/"} - } - with assert_setup_component(1) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - - @patch.object(Apprise, "add", return_value=False) - def test_apprise_bad_url(self, __mock_notify): - """Test set up the platform with bad/missing configuration.""" - config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "dbus://"} - } - with assert_setup_component(1) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - - def test_apprise_config_bad_url(self): - """Test set up the platform with bad/missing url configuration.""" - config = { - notify.DOMAIN: { - "name": "test", - "platform": "apprise", - # Expects an actual URL - "url": 1, - } - } - with assert_setup_component(0) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - @patch.object(Apprise, "notify", return_value=True) - def test_apprise_url(self, __mock_notify): - """Test simple apprise push using URL config.""" - config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "url": "windows://"} + +async def test_apprise_config_load_okay(hass, tmp_path): + """Test apprise configuration failures.""" + + # Test cases where our URL is invalid + d = tmp_path / "apprise-config" + d.mkdir() + f = d / "apprise" + f.write_text("mailto://user:pass@example.com/") + + config = {BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": str(f)}} + + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() + + +async def test_apprise_url_load_fail(hass): + """Test apprise url failure.""" + + config = { + BASE_COMPONENT: { + "name": "test", + "platform": "apprise", + "url": "mailto://user:pass@example.com", } - with assert_setup_component(1) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - data = {"title": "Test Title", "message": "Test Message"} - assert not self.hass.services.call(notify.DOMAIN, "test", data) - self.hass.block_till_done() - - @patch.object(Apprise, "notify", return_value=True) - def test_apprise_config(self, __mock_notify): - """Test simple apprise push using AppriseConfig.""" - tmp_cfg = os.path.join(self.tmp_dir, "apprise.txt") - config = { - notify.DOMAIN: {"name": "test", "platform": "apprise", "config": tmp_cfg} + } + with patch("apprise.Apprise.add", return_value=False): + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() + + +async def test_apprise_notification(hass): + """Test apprise notification.""" + + config = { + BASE_COMPONENT: { + "name": "test", + "platform": "apprise", + "url": "mailto://user:pass@example.com", } + } + + # Our Message + data = {"title": "Test Title", "message": "Test Message"} - with open(tmp_cfg, "w+") as f: - # Write a simple text based configuration file - f.write("windows://") + with patch("apprise.Apprise") as mock_apprise: + mock_apprise.notify.return_value = True + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() - with assert_setup_component(1) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert handle_config[notify.DOMAIN] - data = {"title": "Test Title", "message": "Test Message"} - assert not self.hass.services.call(notify.DOMAIN, "test", data) - self.hass.block_till_done() + # Test the call to our underlining notify() call + await hass.services.async_call(BASE_COMPONENT, "test", data) + await hass.async_block_till_done() From 5d54fbe5c271d85927a7131a30754b8413789975 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Thu, 10 Oct 2019 15:15:33 -0400 Subject: [PATCH 12/19] increased coverage --- tests/components/apprise/test_notify.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 295dc4a2092ebb..946c8d23dd9ac7 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -6,8 +6,8 @@ BASE_COMPONENT = "notify" -async def test_apprise_config_load_fail(hass): - """Test apprise configuration failures.""" +async def test_apprise_config_load_fail01(hass): + """Test apprise configuration failures 1.""" config = { BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} @@ -17,8 +17,16 @@ async def test_apprise_config_load_fail(hass): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() - with patch("apprise.AppriseConfig.add", return_value=True): - with patch("apprise.Apprise.add", return_value=False): + +async def test_apprise_config_load_fail02(hass): + """Test apprise configuration failures 2.""" + + config = { + BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} + } + + with patch("apprise.Apprise.add", return_value=False): + with patch("apprise.AppriseConfig.add", return_value=True): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() From 8e199bd7a211b5acdca486dd81a87dbe18e1ac36 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 13 Oct 2019 22:26:28 -0400 Subject: [PATCH 13/19] bumped version of apprise to 0.8.1 --- homeassistant/components/apprise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index 51873b55cc3677..3e971a96e7eac8 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -3,7 +3,7 @@ "name": "Apprise", "documentation": "https://www.home-assistant.io/components/apprise", "requirements": [ - "apprise==0.8.0" + "apprise==0.8.1" ], "dependencies": [], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index d80c848397b5a7..0abd9d726e6141 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ apcaccess==0.0.13 apns2==0.3.0 # homeassistant.components.apprise -apprise==0.8.0 +apprise==0.8.1 # homeassistant.components.aprs aprslib==0.6.46 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 164991edb1ce77..234d7982073feb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -104,7 +104,7 @@ androidtv==0.0.30 apns2==0.3.0 # homeassistant.components.apprise -apprise==0.8.0 +apprise==0.8.1 # homeassistant.components.aprs aprslib==0.6.46 From bbf85b1327e18596619da7e7931a6e20b5b1ead4 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 16:33:21 -0400 Subject: [PATCH 14/19] test that mocked entries are called --- tests/components/apprise/test_notify.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 946c8d23dd9ac7..278e1a51ba3f42 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,5 +1,6 @@ """The tests for the apprise notification platform.""" from unittest.mock import patch +from unittest.mock import MagicMock from homeassistant.setup import async_setup_component @@ -76,10 +77,19 @@ async def test_apprise_notification(hass): data = {"title": "Test Title", "message": "Test Message"} with patch("apprise.Apprise") as mock_apprise: - mock_apprise.notify.return_value = True + obj = MagicMock() + obj.add.return_value = True + obj.notify.return_value = True + mock_apprise.return_value = obj assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() # Test the call to our underlining notify() call await hass.services.async_call(BASE_COMPONENT, "test", data) await hass.async_block_till_done() + + # Validate calls were made under the hood correctly + obj.add.assert_called_once_with([config[BASE_COMPONENT]["url"]]) + obj.notify.assert_called_once_with( + **{"body": data["message"], "title": data["title"]} + ) From 2fd82641392617eb9a4324a16805659ebe3edcb8 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 16:41:06 -0400 Subject: [PATCH 15/19] added tests for hass.service loading --- tests/components/apprise/test_notify.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 278e1a51ba3f42..c59751b19106b7 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -18,6 +18,9 @@ async def test_apprise_config_load_fail01(hass): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() + # Test that our service failed to load + assert hass.services.has_service(BASE_COMPONENT, "test") is False + async def test_apprise_config_load_fail02(hass): """Test apprise configuration failures 2.""" @@ -31,6 +34,9 @@ async def test_apprise_config_load_fail02(hass): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() + # Test that our service failed to load + assert hass.services.has_service(BASE_COMPONENT, "test") is False + async def test_apprise_config_load_okay(hass, tmp_path): """Test apprise configuration failures.""" @@ -46,6 +52,9 @@ async def test_apprise_config_load_okay(hass, tmp_path): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() + # Valid configuration was loaded; our service is good + assert hass.services.has_service(BASE_COMPONENT, "test") is True + async def test_apprise_url_load_fail(hass): """Test apprise url failure.""" @@ -61,6 +70,9 @@ async def test_apprise_url_load_fail(hass): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() + # Test that our service failed to load + assert hass.services.has_service(BASE_COMPONENT, "test") is False + async def test_apprise_notification(hass): """Test apprise notification.""" @@ -84,6 +96,9 @@ async def test_apprise_notification(hass): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() + # Test the existance of our service + assert hass.services.has_service(BASE_COMPONENT, "test") is True + # Test the call to our underlining notify() call await hass.services.async_call(BASE_COMPONENT, "test", data) await hass.async_block_till_done() From 6b06d3fdabec111d471a430b3cb8c03d91965aab Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 17:08:06 -0400 Subject: [PATCH 16/19] support tags for those who identify the TARGET option --- homeassistant/components/apprise/notify.py | 11 ++++-- tests/components/apprise/test_notify.py | 40 +++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index ab568dd7cb25c1..bea6dc9e961caf 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -8,6 +8,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, @@ -61,6 +62,12 @@ def __init__(self, a_obj): self.apprise = a_obj def send_message(self, message="", **kwargs): - """Send a message to a specified target.""" + """Send a message to a specified target. + + If no target/tags are specified, then services are notified as is + However, if any tags are specified, then they will be applied + to the notification causing filtering (if set up that way) + """ + targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) - self.apprise.notify(body=message, title=title) + self.apprise.notify(body=message, title=title, tag=targets) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index c59751b19106b7..98f1293cd0b900 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -106,5 +106,43 @@ async def test_apprise_notification(hass): # Validate calls were made under the hood correctly obj.add.assert_called_once_with([config[BASE_COMPONENT]["url"]]) obj.notify.assert_called_once_with( - **{"body": data["message"], "title": data["title"]} + **{"body": data["message"], "title": data["title"], "tag": None} + ) + + +async def test_apprise_notification_with_target(hass, tmp_path): + """Test apprise notification with a target.""" + + # Test cases where our URL is invalid + d = tmp_path / "apprise-config" + d.mkdir() + f = d / "apprise" + + # Write 2 config entries each assigned to different tags + f.write_text("devops=mailto://user:pass@example.com/\r\n") + f.write_text("system,alert=syslog://\r\n") + + config = {BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": str(f)}} + + # Our Message, only notify the services tagged with "devops" + data = {"title": "Test Title", "message": "Test Message", "target": ["devops"]} + + with patch("apprise.Apprise") as mock_apprise: + obj = MagicMock() + obj.add.return_value = True + obj.notify.return_value = True + mock_apprise.return_value = obj + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() + + # Test the existance of our service + assert hass.services.has_service(BASE_COMPONENT, "test") is True + + # Test the call to our underlining notify() call + await hass.services.async_call(BASE_COMPONENT, "test", data) + await hass.async_block_till_done() + + # Validate calls were made under the hood correctly + obj.notify.assert_called_once_with( + **{"body": data["message"], "title": data["title"], "tag": data["target"]} ) From 3d1cd2a5f881b1f941a8d71d505431e14145522f Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 17:12:41 -0400 Subject: [PATCH 17/19] renamed variable as per code review --- tests/components/apprise/test_notify.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 98f1293cd0b900..8e33ef9e34cb3d 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -128,10 +128,10 @@ async def test_apprise_notification_with_target(hass, tmp_path): data = {"title": "Test Title", "message": "Test Message", "target": ["devops"]} with patch("apprise.Apprise") as mock_apprise: - obj = MagicMock() - obj.add.return_value = True - obj.notify.return_value = True - mock_apprise.return_value = obj + apprise_obj = MagicMock() + apprise_obj.add.return_value = True + apprise_obj.notify.return_value = True + mock_apprise.return_value = apprise_obj assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() @@ -143,6 +143,6 @@ async def test_apprise_notification_with_target(hass, tmp_path): await hass.async_block_till_done() # Validate calls were made under the hood correctly - obj.notify.assert_called_once_with( + apprise_obj.notify.assert_called_once_with( **{"body": data["message"], "title": data["title"], "tag": data["target"]} ) From 16b5ace39573177c62f1344796601a102a72d801 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 17:14:09 -0400 Subject: [PATCH 18/19] 'assert not' used instead of 'is False' --- tests/components/apprise/test_notify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 8e33ef9e34cb3d..237f99de676faa 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -19,7 +19,7 @@ async def test_apprise_config_load_fail01(hass): await hass.async_block_till_done() # Test that our service failed to load - assert hass.services.has_service(BASE_COMPONENT, "test") is False + assert not hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_config_load_fail02(hass): @@ -35,7 +35,7 @@ async def test_apprise_config_load_fail02(hass): await hass.async_block_till_done() # Test that our service failed to load - assert hass.services.has_service(BASE_COMPONENT, "test") is False + assert not hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_config_load_okay(hass, tmp_path): @@ -53,7 +53,7 @@ async def test_apprise_config_load_okay(hass, tmp_path): await hass.async_block_till_done() # Valid configuration was loaded; our service is good - assert hass.services.has_service(BASE_COMPONENT, "test") is True + assert hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_url_load_fail(hass): @@ -71,7 +71,7 @@ async def test_apprise_url_load_fail(hass): await hass.async_block_till_done() # Test that our service failed to load - assert hass.services.has_service(BASE_COMPONENT, "test") is False + assert not hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_notification(hass): @@ -97,7 +97,7 @@ async def test_apprise_notification(hass): await hass.async_block_till_done() # Test the existance of our service - assert hass.services.has_service(BASE_COMPONENT, "test") is True + assert hass.services.has_service(BASE_COMPONENT, "test") # Test the call to our underlining notify() call await hass.services.async_call(BASE_COMPONENT, "test", data) @@ -136,7 +136,7 @@ async def test_apprise_notification_with_target(hass, tmp_path): await hass.async_block_till_done() # Test the existance of our service - assert hass.services.has_service(BASE_COMPONENT, "test") is True + assert hass.services.has_service(BASE_COMPONENT, "test") # Test the call to our underlining notify() call await hass.services.async_call(BASE_COMPONENT, "test", data) From 8a584e238132328b866c955cedad4946297cf9fe Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 14 Oct 2019 17:28:33 -0400 Subject: [PATCH 19/19] added period (in case linter isn't happy) --- homeassistant/components/apprise/notify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index bea6dc9e961caf..662cc9c1ab6ef0 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -66,7 +66,7 @@ def send_message(self, message="", **kwargs): If no target/tags are specified, then services are notified as is However, if any tags are specified, then they will be applied - to the notification causing filtering (if set up that way) + to the notification causing filtering (if set up that way). """ targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)