diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index a383c49f34893d..6ccd6a4f10b1f5 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -2,7 +2,7 @@ "domain": "accuweather", "name": "AccuWeather", "documentation": "https://www.home-assistant.io/integrations/accuweather/", - "requirements": ["accuweather==0.0.10"], + "requirements": ["accuweather==0.0.11"], "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum" diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index ceb926f326eb11..959d53a01ae35e 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,11 +3,11 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==35"], + "requirements": ["axis==37"], "zeroconf": [ - {"type":"_axis-video._tcp.local.","macaddress":"00408C*"}, - {"type":"_axis-video._tcp.local.","macaddress":"ACCC8E*"}, - {"type":"_axis-video._tcp.local.","macaddress":"B8A44F*"} + { "type": "_axis-video._tcp.local.", "macaddress": "00408C*" }, + { "type": "_axis-video._tcp.local.", "macaddress": "ACCC8E*" }, + { "type": "_axis-video._tcp.local.", "macaddress": "B8A44F*" } ], "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"] diff --git a/homeassistant/components/bom/weather.py b/homeassistant/components/bom/weather.py index 94b9960c851bce..9229d0c11d4356 100644 --- a/homeassistant/components/bom/weather.py +++ b/homeassistant/components/bom/weather.py @@ -54,7 +54,9 @@ def name(self): @property def condition(self): """Return the current condition.""" - return self.bom_data.get_reading("weather") + return self.bom_data.get_reading("weather") or self.bom_data.get_reading( + "cloud" + ) # Now implement the WeatherEntity interface diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index d0d0304a02a562..724f9393fbfaa1 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -48,9 +48,9 @@ async def validate_connect(self, hass: core.HomeAssistant) -> bool: """Test if we can validate connection with the device.""" def update_telegram(telegram): - self._telegram = telegram - - transport.close() + if obis_ref.EQUIPMENT_IDENTIFIER in telegram: + self._telegram = telegram + transport.close() if self._host is None: reader_factory = partial( diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 4368bff8d0f789..d0c86f2cdf598d 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200918.0"], + "requirements": ["home-assistant-frontend==20200918.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json index a12058d38d0705..893294da25e699 100644 --- a/homeassistant/components/gogogate2/manifest.json +++ b/homeassistant/components/gogogate2/manifest.json @@ -3,7 +3,7 @@ "name": "Gogogate2 and iSmartGate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gogogate2", - "requirements": ["gogogate2-api==2.0.2"], + "requirements": ["gogogate2-api==2.0.3"], "codeowners": ["@vangorra"], "homekit": { "models": [ diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index b0d0b8b7bd2480..ab2ad65713de9e 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -39,7 +39,7 @@ STATE_OPEN, STATE_OPENING, ) -from homeassistant.core import State +from homeassistant.core import CoreState, State import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event @@ -162,6 +162,10 @@ async def async_added_to_hass(self): self.hass, self._entities, self._update_supported_features_event ) ) + + if self.hass.state == CoreState.running: + await self.async_update() + return await super().async_added_to_hass() @property diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 289bb8df3f0962..007e05edfbb415 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -36,7 +36,7 @@ STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import State +from homeassistant.core import CoreState, State import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -111,6 +111,11 @@ async def async_state_changed_listener(event): self.hass, self._entity_ids, async_state_changed_listener ) ) + + if self.hass.state == CoreState.running: + await self.async_update() + return + await super().async_added_to_hass() @property diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index f57db0ed56a9cc..a7377ffe43e0c3 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -145,7 +145,7 @@ def _check_same_state(_, _2, new_st: State): else: cur_value = new_st.attributes.get(attribute) - if CONF_TO not in config: + if CONF_FROM in config and CONF_TO not in config: return cur_value != old_value return cur_value == new_value diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index adacc939870aa1..5f03fb593d623a 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -36,7 +36,7 @@ def __call__(self, value): if isinstance(value, str) and value.startswith("/"): number = int(value[1:]) else: - number = int(value) + value = number = int(value) if not (0 <= number <= self.maximum): raise vol.Invalid(f"must be a value between 0 and {self.maximum}") diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 871629b68775c9..d20f56054b3a59 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,7 +2,7 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.7"], + "requirements": ["pyinsteon==1.0.8"], "codeowners": ["@teharris1"], "config_flow": true } \ No newline at end of file diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index 067ee8be476f85..c11255aba874e2 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -202,6 +202,10 @@ async def async_step_ws_port(self, user_input=None): if user_input is not None: self._ws_port = user_input.get(CONF_WS_PORT) + # optional ints return 0 rather than None when empty + if self._ws_port == 0: + self._ws_port = None + try: await validate_ws(self.hass, self._get_data()) except WSCannotConnect: diff --git a/homeassistant/components/luci/device_tracker.py b/homeassistant/components/luci/device_tracker.py index fe64c90bf4c2b0..d4fb1d5f7bc388 100644 --- a/homeassistant/components/luci/device_tracker.py +++ b/homeassistant/components/luci/device_tracker.py @@ -94,7 +94,12 @@ def _update_info(self): last_results = [] for device in result: - if device.reachable: + if ( + not hasattr(self.router.router.owrt_version, "release") + or not self.router.router.owrt_version.release + or self.router.router.owrt_version.release[0] < 19 + or device.reachable + ): last_results.append(device) self.last_results = last_results diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index 3355c497aab4af..e4c64f9aedab21 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -5,6 +5,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TIME, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -209,13 +211,17 @@ def forecast(self): met_forecast = self.coordinator.data.hourly_forecast else: met_forecast = self.coordinator.data.daily_forecast + required_keys = {ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME} ha_forecast = [] for met_item in met_forecast: + if not set(met_item).issuperset(required_keys): + continue ha_item = { k: met_item[v] for k, v in FORECAST_MAP.items() if met_item.get(v) } - ha_item[ATTR_FORECAST_CONDITION] = format_condition( - ha_item[ATTR_FORECAST_CONDITION] - ) + if ha_item.get(ATTR_FORECAST_CONDITION): + ha_item[ATTR_FORECAST_CONDITION] = format_condition( + ha_item[ATTR_FORECAST_CONDITION] + ) ha_forecast.append(ha_item) return ha_forecast diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index c238e105659f3e..fa5b42807b0a8f 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -158,20 +158,23 @@ def update(self): """Update the state of the switch.""" self._is_on = self._read_coil(self._coil) - def _read_coil(self, coil) -> Optional[bool]: + def _read_coil(self, coil) -> bool: """Read coil using the Modbus hub slave.""" try: result = self._hub.read_coils(self._slave, coil, 1) except ConnectionException: self._available = False - return + return False if isinstance(result, (ModbusException, ExceptionResponse)): self._available = False - return + return False self._available = True - return bool(result.bits[coil]) + # bits[0] select the lowest bit in result, + # is_on for a binary_sensor is true if the bit are 1 + # The other bits are not considered. + return bool(result.bits[0]) def _write_coil(self, coil, value): """Write coil using the Modbus hub slave.""" diff --git a/homeassistant/components/nextcloud/__init__.py b/homeassistant/components/nextcloud/__init__.py index ff94fd708db655..1a773040980781 100644 --- a/homeassistant/components/nextcloud/__init__.py +++ b/homeassistant/components/nextcloud/__init__.py @@ -100,6 +100,7 @@ def setup(hass, config): _LOGGER.error("Nextcloud setup failed - Check configuration") hass.data[DOMAIN] = get_data_points(ncm.data) + hass.data[DOMAIN]["instance"] = conf[CONF_URL] def nextcloud_update(event_time): """Update data from nextcloud api.""" diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 754f09fa19999c..c3f7151431a394 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -77,6 +77,8 @@ def _precheck_image(image, opts): if imgfmt not in ("PNG", "JPEG"): _LOGGER.warning("Image is of unsupported type: %s", imgfmt) raise ValueError() + if not img.mode == "RGB": + img = img.convert("RGB") return img diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 83d5d7b9f3afcc..c3b701449c201c 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -86,7 +86,7 @@ async def _async_update_data(self): try: async with async_timeout.timeout(5): return await self.device.update() - except aiocoap_error.Error as err: + except (aiocoap_error.Error, OSError) as err: raise update_coordinator.UpdateFailed("Error fetching data") from err @property diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 3888b8bf53662c..2b085d1ba4034b 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -267,7 +267,8 @@ def update(self): """Get the latest inventory data and update state and attributes.""" self.data_service.update() attr = self.data_service.attributes.get(self._json_key) - self._state = attr["soc"] + if attr and "soc" in attr: + self._state = attr["soc"] class SolarEdgeDataService: diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index b76896b815a44c..9e036a764f859d 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -77,6 +77,9 @@ def _stream_worker_internal(hass, stream, quit_event): # compatible with empty_moov and manual bitstream filters not in PyAV if container.format.name in {"hls", "mpegts"}: audio_stream = None + # Some audio streams do not have a profile and throw errors when remuxing + if audio_stream and audio_stream.profile is None: + audio_stream = None # The presentation timestamps of the first packet in each stream we receive # Use to adjust before muxing or outputting, but we don't adjust internally @@ -113,7 +116,11 @@ def empty_stream_dict(): # Get to first video keyframe while first_packet[video_stream] is None: packet = next(container.demux()) - if packet.stream == video_stream and packet.is_keyframe: + if ( + packet.stream == video_stream + and packet.is_keyframe + and packet.dts is not None + ): first_packet[video_stream] = packet initial_packets.append(packet) # Get first_pts from subsequent frame to first keyframe @@ -121,6 +128,8 @@ def empty_stream_dict(): [pts is None for pts in {**first_packet, **first_pts}.values()] ) and (len(initial_packets) < PACKETS_TO_WAIT_FOR_AUDIO): packet = next(container.demux((video_stream, audio_stream))) + if packet.dts is None: + continue # Discard packets with no dts if ( first_packet[packet.stream] is None ): # actually video already found above so only for audio diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index c4a81db1ff44a6..5f29043a1fe84e 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -67,7 +67,7 @@ async def async_create_device(cls, hass: HomeAssistantType, ssdp_location: str): """Create UPnP/IGD device.""" # build async_upnp_client requester session = async_get_clientsession(hass) - requester = AiohttpSessionRequester(session, True) + requester = AiohttpSessionRequester(session, True, 10) # create async_upnp_client device factory = UpnpFactory(requester, disable_state_variable_validation=True) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index e53e31856516d9..66645ee1fb940b 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -305,7 +305,9 @@ def supported_features(self): """Flag media player features that are supported.""" supported = SUPPORT_WEBOSTV - if self._client.sound_output == "external_arc": + if (self._client.sound_output == "external_arc") or ( + self._client.sound_output == "external_speaker" + ): supported = supported | SUPPORT_WEBOSTV_VOLUME elif self._client.sound_output != "lineout": supported = supported | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET diff --git a/homeassistant/const.py b/homeassistant/const.py index 818f4a715a4b06..86a73b5308ec57 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 115 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0f88f88469be1d..0894039d99aa78 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.37.0 -home-assistant-frontend==20200918.0 +home-assistant-frontend==20200918.2 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 6aea10a43cf1d9..1ec5be4ab3a8a7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -102,7 +102,7 @@ YesssSMS==0.4.1 abodepy==1.1.0 # homeassistant.components.accuweather -accuweather==0.0.10 +accuweather==0.0.11 # homeassistant.components.mcp23017 adafruit-blinka==3.9.0 @@ -309,7 +309,7 @@ av==8.0.2 avri-api==0.1.7 # homeassistant.components.axis -axis==35 +axis==37 # homeassistant.components.azure_event_hub azure-eventhub==5.1.0 @@ -669,7 +669,7 @@ glances_api==0.2.0 gntp==1.0.3 # homeassistant.components.gogogate2 -gogogate2-api==2.0.2 +gogogate2-api==2.0.3 # homeassistant.components.google google-api-python-client==1.6.4 @@ -747,7 +747,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200918.0 +home-assistant-frontend==20200918.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 @@ -1401,7 +1401,7 @@ pyialarm==0.3 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.7 +pyinsteon==1.0.8 # homeassistant.components.intesishome pyintesishome==1.7.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d5bd1f036b257b..d904e74a08c209 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -45,7 +45,7 @@ YesssSMS==0.4.1 abodepy==1.1.0 # homeassistant.components.accuweather -accuweather==0.0.10 +accuweather==0.0.11 # homeassistant.components.androidtv adb-shell[async]==0.2.1 @@ -174,7 +174,7 @@ av==8.0.2 avri-api==0.1.7 # homeassistant.components.axis -axis==35 +axis==37 # homeassistant.components.azure_event_hub azure-eventhub==5.1.0 @@ -334,7 +334,7 @@ gios==0.1.4 glances_api==0.2.0 # homeassistant.components.gogogate2 -gogogate2-api==2.0.2 +gogogate2-api==2.0.3 # homeassistant.components.google google-api-python-client==1.6.4 @@ -370,7 +370,7 @@ hole==0.5.1 holidays==0.10.3 # homeassistant.components.frontend -home-assistant-frontend==20200918.0 +home-assistant-frontend==20200918.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 @@ -674,7 +674,7 @@ pyhomematic==0.1.68 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.7 +pyinsteon==1.0.8 # homeassistant.components.ipma pyipma==2.0.5 diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index a22c56b4bfc017..ba8fecbed32fee 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -737,6 +737,11 @@ async def test_reload_with_base_integration_platform_not_setup(hass): }, ) await hass.async_block_till_done() + hass.states.async_set("light.master_hall_lights", STATE_ON) + hass.states.async_set("light.master_hall_lights_2", STATE_OFF) + + hass.states.async_set("light.outside_patio_lights", STATE_OFF) + hass.states.async_set("light.outside_patio_lights_2", STATE_OFF) yaml_path = path.join( _get_fixtures_base_path(), @@ -755,6 +760,8 @@ async def test_reload_with_base_integration_platform_not_setup(hass): assert hass.states.get("light.light_group") is None assert hass.states.get("light.master_hall_lights_g") is not None assert hass.states.get("light.outside_patio_lights_g") is not None + assert hass.states.get("light.master_hall_lights_g").state == STATE_ON + assert hass.states.get("light.outside_patio_lights_g").state == STATE_OFF def _get_fixtures_base_path(): diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index ce9ecaba1b0a5b..990fc9cc95644e 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -538,6 +538,39 @@ async def test_if_fires_on_entity_change_with_for_without_to(hass, calls): assert len(calls) == 1 +async def test_if_does_not_fires_on_entity_change_with_for_without_to_2(hass, calls): + """Test for firing on entity change with for.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "state", + "entity_id": "test.entity", + "for": {"seconds": 5}, + }, + "action": {"service": "test.automation"}, + } + }, + ) + await hass.async_block_till_done() + + utcnow = dt_util.utcnow() + with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow: + mock_utcnow.return_value = utcnow + + for i in range(10): + hass.states.async_set("test.entity", str(i)) + await hass.async_block_till_done() + + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + + assert len(calls) == 0 + + async def test_if_fires_on_entity_creation_and_removal(hass, calls): """Test for firing on entity creation and removal, with to/from constraints.""" # set automations for multiple combinations to/from diff --git a/tests/components/homeassistant/triggers/test_time_pattern.py b/tests/components/homeassistant/triggers/test_time_pattern.py index 0ef071aadb60c6..3d32748c176338 100644 --- a/tests/components/homeassistant/triggers/test_time_pattern.py +++ b/tests/components/homeassistant/triggers/test_time_pattern.py @@ -1,4 +1,6 @@ """The tests for the time_pattern automation.""" +from datetime import timedelta + import pytest import voluptuous as vol @@ -123,6 +125,39 @@ async def test_if_fires_when_second_matches(hass, calls): assert len(calls) == 1 +async def test_if_fires_when_second_as_string_matches(hass, calls): + """Test for firing if seconds are matching.""" + now = dt_util.utcnow() + time_that_will_not_match_right_away = dt_util.utcnow().replace( + year=now.year + 1, second=15 + ) + with patch( + "homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "time_pattern", + "hours": "*", + "minutes": "*", + "seconds": "30", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_time_changed( + hass, time_that_will_not_match_right_away + timedelta(seconds=15) + ) + + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_when_all_matches(hass, calls): """Test for firing if everything matches.""" now = dt_util.utcnow() diff --git a/tests/components/kodi/test_config_flow.py b/tests/components/kodi/test_config_flow.py index 4fd61ede8bad7c..71c2bce13074a8 100644 --- a/tests/components/kodi/test_config_flow.py +++ b/tests/components/kodi/test_config_flow.py @@ -165,6 +165,51 @@ async def test_form_valid_ws_port(hass, user_flow): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_empty_ws_port(hass, user_flow): + """Test we handle an empty websocket port input.""" + with patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ): + result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) + + assert result["type"] == "form" + assert result["step_id"] == "ws_port" + assert result["errors"] == {} + + with patch( + "homeassistant.components.kodi.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.kodi.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"ws_port": 0} + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_HOST["host"] + assert result["data"] == { + **TEST_HOST, + "ws_port": None, + "password": None, + "username": None, + "name": None, + "timeout": DEFAULT_TIMEOUT, + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_form_invalid_auth(hass, user_flow): """Test we handle invalid auth.""" with patch(