diff --git a/homeassistant/components/igd/__init__.py b/homeassistant/components/igd/__init__.py index b932a4f8055..191b8c1fb72 100644 --- a/homeassistant/components/igd/__init__.py +++ b/homeassistant/components/igd/__init__.py @@ -4,52 +4,34 @@ Will open a port in your router for Home Assistant and provide statistics. For more details about this component, please refer to the documentation at https://home-assistant.io/components/upnp/ """ -# XXX TODO: -# + flow: -# + discovery -# + adding device -# + removing device -# - configured: -# - adding -# - sensors: -# + adding -# + handle overflow -# - removing -# - port forward: -# - adding -# - removing -# - shutdown +import asyncio from ipaddress import IPv4Address from ipaddress import ip_address -import aiohttp -import asyncio +import aiohttp import voluptuous as vol -from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.const import CONF_URL from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import get_local_ip +from homeassistant.components.discovery import DOMAIN as DISCOVERY_DOMAIN -from .config_flow import configured_udns -from .const import CONF_PORT_FORWARD, CONF_SENSORS +from .const import CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS from .const import DOMAIN from .const import LOGGER as _LOGGER REQUIREMENTS = ['async-upnp-client==0.12.4'] -DEPENDENCIES = ['http'] +DEPENDENCIES = ['http'] # ,'discovery'] CONF_LOCAL_IP = 'local_ip' -CONF_ENABLE_PORT_MAPPING = 'port_mapping' CONF_PORTS = 'ports' CONF_UNITS = 'unit' CONF_HASS = 'hass' @@ -66,17 +48,16 @@ UNITS = { CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_URL): cv.url, - vol.Optional(CONF_ENABLE_PORT_MAPPING, default=True): cv.boolean, - vol.Optional(CONF_UNITS, default="MBytes"): vol.In(UNITS), + vol.Optional(CONF_ENABLE_PORT_MAPPING, default=False): cv.boolean, + vol.Optional(CONF_ENABLE_SENSORS, default=True): cv.boolean, vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string), - vol.Optional(CONF_PORTS): - vol.Schema({vol.Any(CONF_HASS, cv.positive_int): cv.positive_int}) + vol.Optional(CONF_UNITS, default="MBytes"): vol.In(UNITS), }), }, extra=vol.ALLOW_EXTRA) -async def _async_create_igd_device(hass: HomeAssistantType, ssdp_description: str): +async def _async_create_igd_device(hass: HomeAssistantType, + ssdp_description: str): """.""" # build requester from async_upnp_client.aiohttp import AiohttpSessionRequester @@ -111,19 +92,22 @@ def _get_device(hass: HomeAssistantType, udn): return hass.data[DOMAIN]['devices'][udn] -async def _async_create_port_forward(hass: HomeAssistantType, igd_device): - """Create a port forward.""" - _LOGGER.debug('Creating port forward: %s', igd_device) - +async def _async_add_port_mapping(hass: HomeAssistantType, + igd_device, + local_ip=None): + """Create a port mapping.""" # determine local ip, ensure sane IP - local_ip = get_local_ip() + if local_ip is None: + local_ip = get_local_ip() + if local_ip == '127.0.0.1': - _LOGGER.warning('Could not create port forward, our IP is 127.0.0.1') + _LOGGER.warning('Could not create port mapping, our IP is 127.0.0.1') return False local_ip = IPv4Address(local_ip) # create port mapping port = hass.http.server_port + _LOGGER.debug('Creating port mapping %s:%s:%s (TCP)', port, local_ip, port) await igd_device.async_add_port_mapping(remote_host=None, external_port=port, protocol='TCP', @@ -136,48 +120,52 @@ async def _async_create_port_forward(hass: HomeAssistantType, igd_device): return True -async def _async_remove_port_forward(hass: HomeAssistantType, igd_device): - """Remove a port forward.""" - _LOGGER.debug('Removing port forward: %s', igd_device) - - # remove port mapping +async def _async_delete_port_mapping(hass: HomeAssistantType, igd_device): + """Remove a port mapping.""" port = hass.http.server_port - await igd_device.async_remove_port_mapping(remote_host=None, + await igd_device.async_delete_port_mapping(remote_host=None, external_port=port, protocol='TCP') # config -async def async_setup(hass: HomeAssistantType, config): +async def async_setup(hass: HomeAssistantType, config: ConfigType): """Register a port mapping for Home Assistant via UPnP.""" - _LOGGER.debug('async_setup: config: %s', config) - conf = config.get(DOMAIN, {}) + # defaults + hass.data[DOMAIN] = { + 'auto_config': { + 'active': False, + 'port_forward': False, + 'sensors': False, + } + } - hass.data[DOMAIN] = hass.data.get(DOMAIN, {}) - configured = configured_udns(hass) - _LOGGER.debug('configured: %s', configured) + # ensure sane config + if DOMAIN not in config: + return False - # if no ssdp given: take any discovered - by flow - IGD entry - # if none discovered, raise PlatformNotReady - # if ssdp given: use the SSDP + if DISCOVERY_DOMAIN not in config: + _LOGGER.warning('IGD needs discovery, please enable it') + return False - igds = [] # XXX TODO - for igd_conf in igds: - hass.async_add_job(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT}, - data={ - 'ssdp_description': igd_conf['ssdp_description'], - } - )) + igd_config = config[DOMAIN] + if CONF_LOCAL_IP in igd_config: + hass.data[DOMAIN]['local_ip'] = igd_config[CONF_LOCAL_IP] + + hass.data[DOMAIN]['auto_config'] = { + 'active': True, + 'port_forward': igd_config[CONF_ENABLE_PORT_MAPPING], + 'sensors': igd_config[CONF_ENABLE_SENSORS], + } + _LOGGER.debug('Enabled auto_config: %s', hass.data[DOMAIN]['auto_config']) return True # config flow -async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistantType, + config_entry: ConfigEntry): """Set up a bridge from a config entry.""" - _LOGGER.debug('async_setup_entry: title: %s, data: %s', config_entry.title, config_entry.data) - data = config_entry.data ssdp_description = data['ssdp_description'] @@ -189,44 +177,49 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry): _store_device(hass, igd_device.udn, igd_device) - # port forward - if data.get(CONF_PORT_FORWARD): - await _async_create_port_forward(hass, igd_device) + # port mapping + if data.get(CONF_ENABLE_PORT_MAPPING): + local_ip = hass.data[DOMAIN].get('local_ip') + await _async_add_port_mapping(hass, igd_device, local_ip=local_ip) # sensors - if data.get(CONF_SENSORS): + if data.get(CONF_ENABLE_SENSORS): discovery_info = { 'unit': 'MBytes', 'udn': data['udn'], } hass_config = config_entry.data - hass.async_create_task(discovery.async_load_platform( - hass, 'sensor', DOMAIN, discovery_info, hass_config)) + hass.async_create_task( + discovery.async_load_platform( + hass, 'sensor', DOMAIN, discovery_info, hass_config)) async def unload_entry(event): """Unload entry on quit.""" await async_unload_entry(hass, config_entry) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unload_entry) return True -async def async_unload_entry(hass: HomeAssistantType, config_entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistantType, + config_entry: ConfigEntry): """Unload a config entry.""" - _LOGGER.debug('async_unload_entry: title: %s, data: %s', config_entry.title, config_entry.data) data = config_entry.data udn = data['udn'] - igd_device = _get_device(hass, udn) - # port forward - if data.get(CONF_PORT_FORWARD): - _LOGGER.debug('Removing port forward for: %s', igd_device) - _async_remove_port_forward(hass, igd_device) + igd_device = _get_device(hass, udn) + if igd_device is None: + return True + + # port mapping + if data.get(CONF_ENABLE_PORT_MAPPING): + await _async_delete_port_mapping(hass, igd_device) # sensors - if data.get(CONF_SENSORS): + if data.get(CONF_ENABLE_SENSORS): # XXX TODO: remove sensors pass + _store_device(hass, udn, None) + return True diff --git a/homeassistant/components/igd/config_flow.py b/homeassistant/components/igd/config_flow.py index 9ccbe79a35f..be6cf9f5f93 100644 --- a/homeassistant/components/igd/config_flow.py +++ b/homeassistant/components/igd/config_flow.py @@ -1,11 +1,12 @@ """Config flow for IGD.""" +import voluptuous as vol + from homeassistant import config_entries, data_entry_flow from homeassistant.core import callback -import voluptuous as vol - +from .const import CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS from .const import DOMAIN -from .const import LOGGER as _LOGGER + @callback def configured_udns(hass): @@ -29,24 +30,23 @@ class IgdFlowHandler(data_entry_flow.FlowHandler): @property def _discovereds(self): """Get all discovered entries.""" - if DOMAIN not in self.hass.data: - _LOGGER.debug('DOMAIN not in hass.data') - if 'discovered' not in self.hass.data.get(DOMAIN, {}): - _LOGGER.debug('discovered not in hass.data[DOMAIN]') - return self.hass.data.get(DOMAIN, {}).get('discovered', {}) def _store_discovery_info(self, discovery_info): """Add discovery info.""" udn = discovery_info['udn'] - if DOMAIN not in self.hass.data: - _LOGGER.debug('DOMAIN not in hass.data') self.hass.data[DOMAIN] = self.hass.data.get(DOMAIN, {}) - if 'discovered' not in self.hass.data[DOMAIN]: - _LOGGER.debug('Creating new discovered: %s', self.hass.data[DOMAIN]) - self.hass.data[DOMAIN]['discovered'] = self.hass.data[DOMAIN].get('discovered', {}) + self.hass.data[DOMAIN]['discovered'] = \ + self.hass.data[DOMAIN].get('discovered', {}) self.hass.data[DOMAIN]['discovered'][udn] = discovery_info + def _auto_config_settings(self): + """Check if auto_config has been enabled.""" + self.hass.data[DOMAIN] = self.hass.data.get(DOMAIN, {}) + return self.hass.data[DOMAIN].get('auto_config', { + 'active': False, + }) + async def async_step_discovery(self, discovery_info): """ Handle a discovered IGD. @@ -54,32 +54,33 @@ class IgdFlowHandler(data_entry_flow.FlowHandler): This flow is triggered by the discovery component. It will check if the host is already configured and delegate to the import step if not. """ - _LOGGER.debug('async_step_discovery %s: %s', id(self), discovery_info) - # ensure not already discovered/configured udn = discovery_info['udn'] if udn in configured_udns(self.hass): - _LOGGER.debug('Already configured: %s', discovery_info) return self.async_abort(reason='already_configured') # store discovered device self._store_discovery_info(discovery_info) - # abort --> not showing up in discovered things - # return self.async_abort(reason='user_input_required') + # auto config? + auto_config = self._auto_config_settings() + if auto_config['active']: + import_info = { + 'igd_host': discovery_info['host'], + 'sensors': auto_config['sensors'], + 'port_forward': auto_config['port_forward'], + } + + return await self._async_save_entry(import_info) - # user -> showing up in discovered things return await self.async_step_user() async def async_step_user(self, user_input=None): """Manual set up.""" - _LOGGER.debug('async_step_user %s: %s', id(self), user_input) - # if user input given, handle it user_input = user_input or {} if 'igd_host' in user_input: if not user_input['sensors'] and not user_input['port_forward']: - _LOGGER.debug('Aborting, no sensors and no portforward') return self.async_abort(reason='no_sensors_or_port_forward') configured_hosts = [ @@ -90,10 +91,9 @@ class IgdFlowHandler(data_entry_flow.FlowHandler): if user_input['igd_host'] in configured_hosts: return self.async_abort(reason='already_configured') - return await self._async_save(user_input) + return await self._async_save_entry(user_input) # let user choose from all discovered IGDs - _LOGGER.debug('Discovered devices: %s', self._discovereds) igd_hosts = [ entry['host'] for entry in self._discovereds.values() @@ -111,10 +111,12 @@ class IgdFlowHandler(data_entry_flow.FlowHandler): }) ) - async def _async_save(self, import_info): - """Store IGD as new entry.""" - _LOGGER.debug('async_step_import %s: %s', id(self), import_info) + async def async_step_import(self, import_info): + """Import a new IGD as a config entry.""" + return await self._async_save_entry(import_info) + async def _async_save_entry(self, import_info): + """Store IGD as new entry.""" # ensure we know the host igd_host = import_info['igd_host'] discovery_infos = [info @@ -129,7 +131,7 @@ class IgdFlowHandler(data_entry_flow.FlowHandler): data={ 'ssdp_description': discovery_info['ssdp_description'], 'udn': discovery_info['udn'], - 'sensors': import_info['sensors'], - 'port_forward': import_info['port_forward'], + CONF_ENABLE_SENSORS: import_info['sensors'], + CONF_ENABLE_PORT_MAPPING: import_info['port_forward'], }, ) diff --git a/homeassistant/components/igd/const.py b/homeassistant/components/igd/const.py index d1d92f7ccb5..c849e627757 100644 --- a/homeassistant/components/igd/const.py +++ b/homeassistant/components/igd/const.py @@ -3,5 +3,5 @@ import logging DOMAIN = 'igd' LOGGER = logging.getLogger('homeassistant.components.igd') -CONF_PORT_FORWARD = 'port_forward' -CONF_SENSORS = 'sensors' +CONF_ENABLE_PORT_MAPPING = 'port_forward' +CONF_ENABLE_SENSORS = 'sensors' diff --git a/homeassistant/components/sensor/igd.py b/homeassistant/components/sensor/igd.py index 1d0402520df..edd936600e3 100644 --- a/homeassistant/components/sensor/igd.py +++ b/homeassistant/components/sensor/igd.py @@ -1,15 +1,17 @@ """ -Support for UPnP Sensors (IGD). +Support for IGD Sensors. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.upnp/ +https://home-assistant.io/components/sensor.igd/ """ +# pylint: disable=invalid-name import logging from homeassistant.components import history from homeassistant.components.igd import DOMAIN, UNITS from homeassistant.helpers.entity import Entity + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['igd', 'history'] @@ -98,9 +100,6 @@ class IGDSensor(Entity): self._handle_new_value(new_value) - # _LOGGER.debug('Removing self: %s', self) - # await self.async_remove() # XXX TODO: does not remove from the UI - @property def _last_state(self): """Get the last state reported to hass.""" @@ -126,17 +125,11 @@ class IGDSensor(Entity): try: state = coercer(float(last_state.state)) * self.unit_factor except ValueError: - _LOGGER.debug('%s: value error, coercer: %s, state: %s', self.entity_id, coercer, last_state.state) - raise state = coercer(0.0) return state def _handle_new_value(self, new_value): - _LOGGER.debug('%s: handle_new_value: state: %s, new_value: %s, last_value: %s', - self.entity_id, self._state, new_value, self._last_value) - - # ❯❯❯ upnp-client --debug --pprint --device http://192.168.178.1/RootDevice.xml call-action WANCIFC/GetTotalBytesReceived if self.entity_id is None: # don't know our entity ID yet, do nothing but store value self._last_value = new_value @@ -161,7 +154,8 @@ class IGDSensor(Entity): if new_value >= 0: diff += new_value else: - # some devices don't overflow and start at 0, but somewhere to -2**32 + # some devices don't overflow and start at 0, + # but somewhere to -2**32 diff += new_value - -OVERFLOW_AT self._state += diff diff --git a/requirements_all.txt b/requirements_all.txt index ef89fb096da..97336bbae50 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -141,6 +141,7 @@ apns2==0.3.0 # homeassistant.components.asterisk_mbox asterisk_mbox==0.4.0 +# homeassistant.components.igd # homeassistant.components.media_player.dlna_dmr async-upnp-client==0.12.4 @@ -1183,9 +1184,6 @@ pytrafikverket==0.1.5.8 # homeassistant.components.device_tracker.unifi pyunifi==2.13 -# homeassistant.components.upnp -pyupnp-async==0.1.1.1 - # homeassistant.components.binary_sensor.uptimerobot pyuptimerobot==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2415d661e29..bba6b5ca46a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -177,9 +177,6 @@ pytradfri[async]==5.5.1 # homeassistant.components.device_tracker.unifi pyunifi==2.13 -# homeassistant.components.upnp -pyupnp-async==0.1.1.1 - # homeassistant.components.notify.html5 pywebpush==1.6.0 diff --git a/tests/components/igd/__init__.py b/tests/components/igd/__init__.py index d6985e0544b..4fcc4167e5b 100644 --- a/tests/components/igd/__init__.py +++ b/tests/components/igd/__init__.py @@ -1 +1 @@ -"""Tests for the IGD component.""" \ No newline at end of file +"""Tests for the IGD component.""" diff --git a/tests/components/igd/test_config_flow.py b/tests/components/igd/test_config_flow.py index 2fe2c60b19d..5530e172dae 100644 --- a/tests/components/igd/test_config_flow.py +++ b/tests/components/igd/test_config_flow.py @@ -1,13 +1,14 @@ """Tests for IGD config flow.""" from homeassistant.components import igd +from homeassistant.components.igd import config_flow as igd_config_flow from tests.common import MockConfigEntry async def test_flow_none_discovered(hass): """Test no device discovered flow.""" - flow = igd.config_flow.IgdFlowHandler() + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass result = await flow.async_step_user() @@ -17,7 +18,7 @@ async def test_flow_none_discovered(hass): async def test_flow_already_configured(hass): """Test device already configured flow.""" - flow = igd.config_flow.IgdFlowHandler() + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass # discovered device @@ -48,7 +49,7 @@ async def test_flow_already_configured(hass): async def test_flow_no_sensors_no_port_forward(hass): """Test single device, no sensors, no port_forward.""" - flow = igd.config_flow.IgdFlowHandler() + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass # discovered device @@ -79,7 +80,7 @@ async def test_flow_no_sensors_no_port_forward(hass): async def test_flow_discovered_form(hass): """Test single device discovered, show form flow.""" - flow = igd.config_flow.IgdFlowHandler() + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass # discovered device @@ -100,7 +101,7 @@ async def test_flow_discovered_form(hass): async def test_flow_two_discovered_form(hass): """Test single device discovered, show form flow.""" - flow = igd.config_flow.IgdFlowHandler() + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass # discovered device @@ -135,18 +136,18 @@ async def test_flow_two_discovered_form(hass): async def test_config_entry_created(hass): - flow = igd.config_flow.IgdFlowHandler() + """Test config entry is created.""" + flow = igd_config_flow.IgdFlowHandler() flow.hass = hass # discovered device - udn = 'uuid:device_1' hass.data[igd.DOMAIN] = { 'discovered': { - udn: { + 'uuid:device_1': { 'name': 'Test device 1', 'host': '192.168.1.1', 'ssdp_description': 'http://192.168.1.1/desc.xml', - 'udn': udn, + 'udn': 'uuid:device_1', }, }, } @@ -156,6 +157,7 @@ async def test_config_entry_created(hass): 'sensors': True, 'port_forward': False, }) + assert result['type'] == 'create_entry' assert result['data'] == { 'port_forward': False, 'sensors': True, @@ -163,3 +165,67 @@ async def test_config_entry_created(hass): 'udn': 'uuid:device_1', } assert result['title'] == 'Test device 1' + + +async def test_flow_discovery_auto_config_sensors(hass): + """Test creation of device with auto_config.""" + flow = igd_config_flow.IgdFlowHandler() + flow.hass = hass + + # auto_config active + hass.data[igd.DOMAIN] = { + 'auto_config': { + 'active': True, + 'port_forward': False, + 'sensors': True, + }, + } + + # discovered device + result = await flow.async_step_discovery({ + 'name': 'Test device 1', + 'host': '192.168.1.1', + 'ssdp_description': 'http://192.168.1.1/desc.xml', + 'udn': 'uuid:device_1', + }) + + assert result['type'] == 'create_entry' + assert result['data'] == { + 'port_forward': False, + 'sensors': True, + 'ssdp_description': 'http://192.168.1.1/desc.xml', + 'udn': 'uuid:device_1', + } + assert result['title'] == 'Test device 1' + + +async def test_flow_discovery_auto_config_sensors_port_forward(hass): + """Test creation of device with auto_config, with port forward.""" + flow = igd_config_flow.IgdFlowHandler() + flow.hass = hass + + # auto_config active, with port_forward + hass.data[igd.DOMAIN] = { + 'auto_config': { + 'active': True, + 'port_forward': True, + 'sensors': True, + }, + } + + # discovered device + result = await flow.async_step_discovery({ + 'name': 'Test device 1', + 'host': '192.168.1.1', + 'ssdp_description': 'http://192.168.1.1/desc.xml', + 'udn': 'uuid:device_1', + }) + + assert result['type'] == 'create_entry' + assert result['data'] == { + 'port_forward': True, + 'sensors': True, + 'ssdp_description': 'http://192.168.1.1/desc.xml', + 'udn': 'uuid:device_1', + } + assert result['title'] == 'Test device 1' diff --git a/tests/components/igd/test_init.py b/tests/components/igd/test_init.py index 4405cc10999..285755c687f 100644 --- a/tests/components/igd/test_init.py +++ b/tests/components/igd/test_init.py @@ -10,9 +10,96 @@ from tests.common import MockConfigEntry from tests.common import mock_coro -async def test_async_setup_entry_port_forward_created(hass): - """Test async_setup_entry.""" +async def test_async_setup_no_auto_config(hass): + """Test async_setup.""" + # setup component, enable auto_config + await async_setup_component(hass, 'igd') + assert hass.data[igd.DOMAIN]['auto_config'] == { + 'active': False, + 'port_forward': False, + 'sensors': False, + } + + +async def test_async_setup_auto_config(hass): + """Test async_setup.""" + # setup component, enable auto_config + await async_setup_component(hass, 'igd', {'igd': {}, 'discovery': {}}) + + assert hass.data[igd.DOMAIN]['auto_config'] == { + 'active': True, + 'port_forward': False, + 'sensors': True, + } + + +async def test_async_setup_auto_config_port_forward(hass): + """Test async_setup.""" + # setup component, enable auto_config + await async_setup_component(hass, 'igd', { + 'igd': {'port_forward': True}, + 'discovery': {}}) + + assert hass.data[igd.DOMAIN]['auto_config'] == { + 'active': True, + 'port_forward': True, + 'sensors': True, + } + + +async def test_async_setup_auto_config_no_sensors(hass): + """Test async_setup.""" + # setup component, enable auto_config + await async_setup_component(hass, 'igd', { + 'igd': {'sensors': False}, + 'discovery': {}}) + + assert hass.data[igd.DOMAIN]['auto_config'] == { + 'active': True, + 'port_forward': False, + 'sensors': False, + } + + +async def test_async_setup_entry_default(hass): + """Test async_setup_entry.""" + udn = 'uuid:device_1' + entry = MockConfigEntry(domain=igd.DOMAIN, data={ + 'ssdp_description': 'http://192.168.1.1/desc.xml', + 'udn': udn, + 'sensors': True, + 'port_forward': False, + }) + + # ensure hass.http is available + await async_setup_component(hass, 'igd') + + # mock async_upnp_client.igd.IgdDevice + mock_igd_device = MagicMock() + mock_igd_device.udn = udn + mock_igd_device.async_add_port_mapping.return_value = mock_coro() + mock_igd_device.async_delete_port_mapping.return_value = mock_coro() + with patch.object(igd, '_async_create_igd_device') as mock_create_device: + mock_create_device.return_value = mock_coro( + return_value=mock_igd_device) + with patch('homeassistant.components.igd.get_local_ip', + return_value='192.168.1.10'): + assert await igd.async_setup_entry(hass, entry) is True + + # ensure device is stored/used + assert hass.data[igd.DOMAIN]['devices'][udn] == mock_igd_device + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + assert hass.data[igd.DOMAIN]['devices'][udn] is None + assert len(mock_igd_device.async_add_port_mapping.mock_calls) == 0 + assert len(mock_igd_device.async_delete_port_mapping.mock_calls) == 0 + + +async def test_async_setup_entry_port_forward(hass): + """Test async_setup_entry.""" udn = 'uuid:device_1' entry = MockConfigEntry(domain=igd.DOMAIN, data={ 'ssdp_description': 'http://192.168.1.1/desc.xml', @@ -27,15 +114,20 @@ async def test_async_setup_entry_port_forward_created(hass): mock_igd_device = MagicMock() mock_igd_device.udn = udn mock_igd_device.async_add_port_mapping.return_value = mock_coro() - mock_igd_device.async_remove_port_mapping.return_value = mock_coro() + mock_igd_device.async_delete_port_mapping.return_value = mock_coro() with patch.object(igd, '_async_create_igd_device') as mock_create_device: - mock_create_device.return_value = mock_coro(return_value=mock_igd_device) - with patch('homeassistant.components.igd.get_local_ip', return_value='192.168.1.10'): + mock_create_device.return_value = mock_coro( + return_value=mock_igd_device) + with patch('homeassistant.components.igd.get_local_ip', + return_value='192.168.1.10'): assert await igd.async_setup_entry(hass, entry) is True + # ensure device is stored/used + assert hass.data[igd.DOMAIN]['devices'][udn] == mock_igd_device + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() - assert hass.data[igd.DOMAIN]['devices'][udn] == mock_igd_device + assert hass.data[igd.DOMAIN]['devices'][udn] is None assert len(mock_igd_device.async_add_port_mapping.mock_calls) > 0 assert len(mock_igd_device.async_delete_port_mapping.mock_calls) > 0