From 45241e57ca518e453be3707fc47ccc24df0d5eef Mon Sep 17 00:00:00 2001 From: elmurato <1382097+elmurato@users.noreply.github.com> Date: Tue, 24 Mar 2020 00:51:13 +0100 Subject: [PATCH] Add support for Minecraft SRV records (#32372) * Added support for Minecraft SRV records * Switched from dnspython to aiodns, improved server ping and log messages, use address instead of host and port in config flow * Updated component requirements --- .coveragerc | 1 + .../components/minecraft_server/__init__.py | 48 ++++-- .../minecraft_server/config_flow.py | 85 ++++++--- .../components/minecraft_server/const.py | 4 +- .../components/minecraft_server/helpers.py | 32 ++++ .../components/minecraft_server/manifest.json | 2 +- .../components/minecraft_server/strings.json | 3 +- requirements_all.txt | 1 + requirements_test_all.txt | 4 + .../minecraft_server/test_config_flow.py | 161 ++++++++++++------ 10 files changed, 245 insertions(+), 96 deletions(-) create mode 100644 homeassistant/components/minecraft_server/helpers.py diff --git a/.coveragerc b/.coveragerc index 09ff6115ce2..cc04fb03456 100644 --- a/.coveragerc +++ b/.coveragerc @@ -425,6 +425,7 @@ omit = homeassistant/components/minecraft_server/__init__.py homeassistant/components/minecraft_server/binary_sensor.py homeassistant/components/minecraft_server/const.py + homeassistant/components/minecraft_server/helpers.py homeassistant/components/minecraft_server/sensor.py homeassistant/components/minio/* homeassistant/components/mitemp_bt/sensor.py diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index a025c44e33c..3a8598d3fac 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -18,6 +18,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from . import helpers from .const import DOMAIN, MANUFACTURER, SCAN_INTERVAL, SIGNAL_NAME_PREFIX PLATFORMS = ["binary_sensor", "sensor"] @@ -37,10 +38,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) # Create and store server instance. unique_id = config_entry.unique_id _LOGGER.debug( - "Creating server instance for '%s' (host='%s', port=%s)", + "Creating server instance for '%s' (%s)", config_entry.data[CONF_NAME], config_entry.data[CONF_HOST], - config_entry.data[CONF_PORT], ) server = MinecraftServer(hass, unique_id, config_entry.data) domain_data[unique_id] = server @@ -82,7 +82,6 @@ class MinecraftServer: """Representation of a Minecraft server.""" # Private constants - _MAX_RETRIES_PING = 3 _MAX_RETRIES_STATUS = 3 def __init__( @@ -98,6 +97,7 @@ class MinecraftServer: self.port = config_data[CONF_PORT] self.online = False self._last_status_request_failed = False + self.srv_record_checked = False # 3rd party library instance self._mc_status = MCStatus(self.host, self.port) @@ -127,15 +127,36 @@ class MinecraftServer: self._stop_periodic_update() async def async_check_connection(self) -> None: - """Check server connection using a 'ping' request and store result.""" + """Check server connection using a 'status' request and store connection status.""" + # Check if host is a valid SRV record, if not already done. + if not self.srv_record_checked: + self.srv_record_checked = True + srv_record = await helpers.async_check_srv_record(self._hass, self.host) + if srv_record is not None: + _LOGGER.debug( + "'%s' is a valid Minecraft SRV record ('%s:%s')", + self.host, + srv_record[CONF_HOST], + srv_record[CONF_PORT], + ) + # Overwrite host, port and 3rd party library instance + # with data extracted out of SRV record. + self.host = srv_record[CONF_HOST] + self.port = srv_record[CONF_PORT] + self._mc_status = MCStatus(self.host, self.port) + + # Ping the server with a status request. try: await self._hass.async_add_executor_job( - self._mc_status.ping, self._MAX_RETRIES_PING + self._mc_status.status, self._MAX_RETRIES_STATUS ) self.online = True except OSError as error: _LOGGER.debug( - "Error occurred while trying to ping the server - OSError: %s", error + "Error occurred while trying to check the connection to '%s:%s' - OSError: %s", + self.host, + self.port, + error, ) self.online = False @@ -148,9 +169,9 @@ class MinecraftServer: # Inform user once about connection state changes if necessary. if server_online_old and not server_online: - _LOGGER.warning("Connection to server lost") + _LOGGER.warning("Connection to '%s:%s' lost", self.host, self.port) elif not server_online_old and server_online: - _LOGGER.info("Connection to server (re-)established") + _LOGGER.info("Connection to '%s:%s' (re-)established", self.host, self.port) # Update the server properties if server is online. if server_online: @@ -179,7 +200,11 @@ class MinecraftServer: # Inform user once about successful update if necessary. if self._last_status_request_failed: - _LOGGER.info("Updating the server properties succeeded again") + _LOGGER.info( + "Updating the properties of '%s:%s' succeeded again", + self.host, + self.port, + ) self._last_status_request_failed = False except OSError as error: # No answer to request, set all properties to unknown. @@ -193,7 +218,10 @@ class MinecraftServer: # Inform user once about failed update if necessary. if not self._last_status_request_failed: _LOGGER.warning( - "Updating the server properties failed - OSError: %s", error, + "Updating the properties of '%s:%s' failed - OSError: %s", + self.host, + self.port, + error, ) self._last_status_request_failed = True diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py index 8c6049a2c1b..a7cb0371f67 100644 --- a/homeassistant/components/minecraft_server/config_flow.py +++ b/homeassistant/components/minecraft_server/config_flow.py @@ -9,7 +9,7 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT -from . import MinecraftServer +from . import MinecraftServer, helpers from .const import ( # pylint: disable=unused-import DEFAULT_HOST, DEFAULT_NAME, @@ -29,11 +29,24 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: - # User inputs. - host = user_input[CONF_HOST] - port = user_input[CONF_PORT] + host = None + port = DEFAULT_PORT + # Split address at last occurrence of ':'. + address_left, separator, address_right = user_input[CONF_HOST].rpartition( + ":" + ) + # If no separator is found, 'rpartition' return ('', '', original_string). + if separator == "": + host = address_right + else: + host = address_left + try: + port = int(address_right) + except ValueError: + pass # 'port' is already set to default value. - unique_id = "" + # Remove '[' and ']' in case of an IPv6 address. + host = host.strip("[]") # Check if 'host' is a valid IP address and if so, get the MAC address. ip_address = None @@ -42,6 +55,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): ip_address = ipaddress.ip_address(host) except ValueError: # Host is not a valid IP address. + # Continue with host and port. pass else: # Host is a valid IP address. @@ -55,38 +69,56 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): partial(getmac.get_mac_address, **params) ) - # Validate IP address via valid MAC address. + # Validate IP address (MAC address must be available). if ip_address is not None and mac_address is None: errors["base"] = "invalid_ip" # Validate port configuration (limit to user and dynamic port range). elif (port < 1024) or (port > 65535): errors["base"] = "invalid_port" - # Validate host and port via ping request to server. + # Validate host and port by checking the server connection. else: - # Build unique_id. - if ip_address is not None: - # Since IP addresses can change and therefore are not allowed in a - # unique_id, fall back to the MAC address. - unique_id = f"{mac_address}-{port}" - else: - # Use host name in unique_id (host names should not change). - unique_id = f"{host}-{port}" - - # Abort in case the host was already configured before. - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - - # Create server instance with configuration data and try pinging the server. - server = MinecraftServer(self.hass, unique_id, user_input) + # Create server instance with configuration data and ping the server. + config_data = { + CONF_NAME: user_input[CONF_NAME], + CONF_HOST: host, + CONF_PORT: port, + } + server = MinecraftServer(self.hass, "dummy_unique_id", config_data) await server.async_check_connection() if not server.online: # Host or port invalid or server not reachable. errors["base"] = "cannot_connect" else: + # Build unique_id and config entry title. + unique_id = "" + title = f"{host}:{port}" + if ip_address is not None: + # Since IP addresses can change and therefore are not allowed in a + # unique_id, fall back to the MAC address and port (to support + # servers with same MAC address but different ports). + unique_id = f"{mac_address}-{port}" + if ip_address.version == 6: + title = f"[{host}]:{port}" + else: + # Check if 'host' is a valid SRV record. + srv_record = await helpers.async_check_srv_record( + self.hass, host + ) + if srv_record is not None: + # Use only SRV host name in unique_id (does not change). + unique_id = f"{host}-srv" + title = host + else: + # Use host name and port in unique_id (to support servers with + # same host name but different ports). + unique_id = f"{host}-{port}" + + # Abort in case the host was already configured before. + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + # Configuration data are available and no error was detected, create configuration entry. - return self.async_create_entry( - title=f"{host}:{port}", data=user_input - ) + return self.async_create_entry(title=title, data=config_data) # Show configuration form (default form in case of no user_input, # form filled with user_input and eventually with errors otherwise). @@ -107,9 +139,6 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): vol.Required( CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST) ): vol.All(str, vol.Lower), - vol.Optional( - CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT) - ): int, } ), errors=errors, diff --git a/homeassistant/components/minecraft_server/const.py b/homeassistant/components/minecraft_server/const.py index d86faf23a81..52e6ae8fd5e 100644 --- a/homeassistant/components/minecraft_server/const.py +++ b/homeassistant/components/minecraft_server/const.py @@ -2,7 +2,7 @@ ATTR_PLAYERS_LIST = "players_list" -DEFAULT_HOST = "localhost" +DEFAULT_HOST = "localhost:25565" DEFAULT_NAME = "Minecraft Server" DEFAULT_PORT = 25565 @@ -30,6 +30,8 @@ SCAN_INTERVAL = 60 SIGNAL_NAME_PREFIX = f"signal_{DOMAIN}" +SRV_RECORD_PREFIX = "_minecraft._tcp" + UNIT_PLAYERS_MAX = "players" UNIT_PLAYERS_ONLINE = "players" UNIT_PROTOCOL_VERSION = None diff --git a/homeassistant/components/minecraft_server/helpers.py b/homeassistant/components/minecraft_server/helpers.py new file mode 100644 index 00000000000..7f9380cdec2 --- /dev/null +++ b/homeassistant/components/minecraft_server/helpers.py @@ -0,0 +1,32 @@ +"""Helper functions for the Minecraft Server integration.""" + +from typing import Any, Dict + +import aiodns + +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers.typing import HomeAssistantType + +from .const import SRV_RECORD_PREFIX + + +async def async_check_srv_record(hass: HomeAssistantType, host: str) -> Dict[str, Any]: + """Check if the given host is a valid Minecraft SRV record.""" + # Check if 'host' is a valid SRV record. + return_value = None + srv_records = None + try: + srv_records = await aiodns.DNSResolver().query( + host=f"{SRV_RECORD_PREFIX}.{host}", qtype="SRV" + ) + except (aiodns.error.DNSError): + # 'host' is not a SRV record. + pass + else: + # 'host' is a valid SRV record, extract the data. + return_value = { + CONF_HOST: srv_records[0].host, + CONF_PORT: srv_records[0].port, + } + + return return_value diff --git a/homeassistant/components/minecraft_server/manifest.json b/homeassistant/components/minecraft_server/manifest.json index 1dda76dee77..0811c168f9f 100644 --- a/homeassistant/components/minecraft_server/manifest.json +++ b/homeassistant/components/minecraft_server/manifest.json @@ -3,7 +3,7 @@ "name": "Minecraft Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/minecraft_server", - "requirements": ["getmac==0.8.1", "mcstatus==2.3.0"], + "requirements": ["aiodns==2.0.0", "getmac==0.8.1", "mcstatus==2.3.0"], "dependencies": [], "codeowners": ["@elmurato"], "quality_scale": "silver" diff --git a/homeassistant/components/minecraft_server/strings.json b/homeassistant/components/minecraft_server/strings.json index 7743d940be6..3a2408694ad 100644 --- a/homeassistant/components/minecraft_server/strings.json +++ b/homeassistant/components/minecraft_server/strings.json @@ -7,8 +7,7 @@ "description": "Set up your Minecraft Server instance to allow monitoring.", "data": { "name": "Name", - "host": "Host", - "port": "Port" + "host": "Host" } } }, diff --git a/requirements_all.txt b/requirements_all.txt index 72bc9d27e7e..00a7a0a0073 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -152,6 +152,7 @@ aioautomatic==0.6.5 aiobotocore==0.11.1 # homeassistant.components.dnsip +# homeassistant.components.minecraft_server aiodns==2.0.0 # homeassistant.components.esphome diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8ed63bf8b3..231dfcd0cc7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -58,6 +58,10 @@ aioautomatic==0.6.5 # homeassistant.components.aws aiobotocore==0.11.1 +# homeassistant.components.dnsip +# homeassistant.components.minecraft_server +aiodns==2.0.0 + # homeassistant.components.esphome aioesphomeapi==2.6.1 diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index 30626fbdcb0..bc49ed08109 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -1,5 +1,8 @@ """Test the Minecraft Server config flow.""" +import asyncio + +import aiodns from asynctest import patch from mcstatus.pinger import PingResponse @@ -19,6 +22,19 @@ from homeassistant.helpers.typing import HomeAssistantType from tests.common import MockConfigEntry + +class QueryMock: + """Mock for result of aiodns.DNSResolver.query.""" + + def __init__(self): + """Set up query result mock.""" + self.host = "mc.dummyserver.com" + self.port = 23456 + self.priority = 1 + self.weight = 1 + self.ttl = None + + STATUS_RESPONSE_RAW = { "description": {"text": "Dummy Description"}, "version": {"name": "Dummy Version", "protocol": 123}, @@ -35,34 +51,34 @@ STATUS_RESPONSE_RAW = { USER_INPUT = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: "mc.dummyserver.com", - CONF_PORT: DEFAULT_PORT, + CONF_HOST: f"mc.dummyserver.com:{DEFAULT_PORT}", } +USER_INPUT_SRV = {CONF_NAME: DEFAULT_NAME, CONF_HOST: "dummyserver.com"} + USER_INPUT_IPV4 = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: "1.1.1.1", - CONF_PORT: DEFAULT_PORT, + CONF_HOST: f"1.1.1.1:{DEFAULT_PORT}", } USER_INPUT_IPV6 = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: "::ffff:0101:0101", - CONF_PORT: DEFAULT_PORT, + CONF_HOST: f"[::ffff:0101:0101]:{DEFAULT_PORT}", } USER_INPUT_PORT_TOO_SMALL = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: "mc.dummyserver.com", - CONF_PORT: 1023, + CONF_HOST: f"mc.dummyserver.com:1023", } USER_INPUT_PORT_TOO_LARGE = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: "mc.dummyserver.com", - CONF_PORT: 65536, + CONF_HOST: f"mc.dummyserver.com:65536", } +SRV_RECORDS = asyncio.Future() +SRV_RECORDS.set_result([QueryMock()]) + async def test_show_config_form(hass: HomeAssistantType) -> None: """Test if initial configuration form is shown.""" @@ -87,54 +103,96 @@ async def test_invalid_ip(hass: HomeAssistantType) -> None: async def test_same_host(hass: HomeAssistantType) -> None: """Test abort in case of same host name.""" - unique_id = f"{USER_INPUT[CONF_HOST]}-{USER_INPUT[CONF_PORT]}" - mock_config_entry = MockConfigEntry( - domain=DOMAIN, unique_id=unique_id, data=USER_INPUT - ) - mock_config_entry.add_to_hass(hass) + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): + with patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + unique_id = "mc.dummyserver.com-25565" + config_data = { + CONF_NAME: DEFAULT_NAME, + CONF_HOST: "mc.dummyserver.com", + CONF_PORT: DEFAULT_PORT, + } + mock_config_entry = MockConfigEntry( + domain=DOMAIN, unique_id=unique_id, data=config_data + ) + mock_config_entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" async def test_port_too_small(hass: HomeAssistantType) -> None: """Test error in case of a too small port.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_SMALL - ) + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_SMALL + ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_port"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_port"} async def test_port_too_large(hass: HomeAssistantType) -> None: """Test error in case of a too large port.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_LARGE - ) + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_LARGE + ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_port"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_port"} async def test_connection_failed(hass: HomeAssistantType) -> None: """Test error in case of a failed connection.""" - with patch("mcstatus.server.MinecraftServer.ping", side_effect=OSError): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): + with patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "cannot_connect"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_connection_succeeded_with_srv_record(hass: HomeAssistantType) -> None: + """Test config entry in case of a successful connection with a SRV record.""" + with patch( + "aiodns.DNSResolver.query", return_value=SRV_RECORDS, + ): + with patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_SRV[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_SRV[CONF_NAME] + assert result["data"][CONF_HOST] == USER_INPUT_SRV[CONF_HOST] async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with a host name.""" - with patch("mcstatus.server.MinecraftServer.ping", return_value=50): + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): with patch( "mcstatus.server.MinecraftServer.status", return_value=PingResponse(STATUS_RESPONSE_RAW), @@ -144,16 +202,17 @@ async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None: ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == f"{USER_INPUT[CONF_HOST]}:{USER_INPUT[CONF_PORT]}" + assert result["title"] == USER_INPUT[CONF_HOST] assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME] - assert result["data"][CONF_HOST] == USER_INPUT[CONF_HOST] - assert result["data"][CONF_PORT] == USER_INPUT[CONF_PORT] + assert result["data"][CONF_HOST] == "mc.dummyserver.com" async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv4 address.""" with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch("mcstatus.server.MinecraftServer.ping", return_value=50): + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): with patch( "mcstatus.server.MinecraftServer.status", return_value=PingResponse(STATUS_RESPONSE_RAW), @@ -163,19 +222,17 @@ async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None: ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert ( - result["title"] - == f"{USER_INPUT_IPV4[CONF_HOST]}:{USER_INPUT_IPV4[CONF_PORT]}" - ) + assert result["title"] == USER_INPUT_IPV4[CONF_HOST] assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] - assert result["data"][CONF_HOST] == USER_INPUT_IPV4[CONF_HOST] - assert result["data"][CONF_PORT] == USER_INPUT_IPV4[CONF_PORT] + assert result["data"][CONF_HOST] == "1.1.1.1" async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv6 address.""" with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch("mcstatus.server.MinecraftServer.ping", return_value=50): + with patch( + "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, + ): with patch( "mcstatus.server.MinecraftServer.status", return_value=PingResponse(STATUS_RESPONSE_RAW), @@ -185,10 +242,6 @@ async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None: ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert ( - result["title"] - == f"{USER_INPUT_IPV6[CONF_HOST]}:{USER_INPUT_IPV6[CONF_PORT]}" - ) + assert result["title"] == USER_INPUT_IPV6[CONF_HOST] assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] - assert result["data"][CONF_HOST] == USER_INPUT_IPV6[CONF_HOST] - assert result["data"][CONF_PORT] == USER_INPUT_IPV6[CONF_PORT] + assert result["data"][CONF_HOST] == "::ffff:0101:0101" -- GitLab