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
This commit is contained in:
elmurato 2020-03-24 00:51:13 +01:00 committed by GitHub
parent cd57b764ce
commit 45241e57ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 245 additions and 96 deletions

View File

@ -425,6 +425,7 @@ omit =
homeassistant/components/minecraft_server/__init__.py homeassistant/components/minecraft_server/__init__.py
homeassistant/components/minecraft_server/binary_sensor.py homeassistant/components/minecraft_server/binary_sensor.py
homeassistant/components/minecraft_server/const.py homeassistant/components/minecraft_server/const.py
homeassistant/components/minecraft_server/helpers.py
homeassistant/components/minecraft_server/sensor.py homeassistant/components/minecraft_server/sensor.py
homeassistant/components/minio/* homeassistant/components/minio/*
homeassistant/components/mitemp_bt/sensor.py homeassistant/components/mitemp_bt/sensor.py

View File

@ -18,6 +18,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import helpers
from .const import DOMAIN, MANUFACTURER, SCAN_INTERVAL, SIGNAL_NAME_PREFIX from .const import DOMAIN, MANUFACTURER, SCAN_INTERVAL, SIGNAL_NAME_PREFIX
PLATFORMS = ["binary_sensor", "sensor"] PLATFORMS = ["binary_sensor", "sensor"]
@ -37,10 +38,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry)
# Create and store server instance. # Create and store server instance.
unique_id = config_entry.unique_id unique_id = config_entry.unique_id
_LOGGER.debug( _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_NAME],
config_entry.data[CONF_HOST], config_entry.data[CONF_HOST],
config_entry.data[CONF_PORT],
) )
server = MinecraftServer(hass, unique_id, config_entry.data) server = MinecraftServer(hass, unique_id, config_entry.data)
domain_data[unique_id] = server domain_data[unique_id] = server
@ -82,7 +82,6 @@ class MinecraftServer:
"""Representation of a Minecraft server.""" """Representation of a Minecraft server."""
# Private constants # Private constants
_MAX_RETRIES_PING = 3
_MAX_RETRIES_STATUS = 3 _MAX_RETRIES_STATUS = 3
def __init__( def __init__(
@ -98,6 +97,7 @@ class MinecraftServer:
self.port = config_data[CONF_PORT] self.port = config_data[CONF_PORT]
self.online = False self.online = False
self._last_status_request_failed = False self._last_status_request_failed = False
self.srv_record_checked = False
# 3rd party library instance # 3rd party library instance
self._mc_status = MCStatus(self.host, self.port) self._mc_status = MCStatus(self.host, self.port)
@ -127,15 +127,36 @@ class MinecraftServer:
self._stop_periodic_update() self._stop_periodic_update()
async def async_check_connection(self) -> None: 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: try:
await self._hass.async_add_executor_job( 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 self.online = True
except OSError as error: except OSError as error:
_LOGGER.debug( _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 self.online = False
@ -148,9 +169,9 @@ class MinecraftServer:
# Inform user once about connection state changes if necessary. # Inform user once about connection state changes if necessary.
if server_online_old and not server_online: 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: 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. # Update the server properties if server is online.
if server_online: if server_online:
@ -179,7 +200,11 @@ class MinecraftServer:
# Inform user once about successful update if necessary. # Inform user once about successful update if necessary.
if self._last_status_request_failed: 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 self._last_status_request_failed = False
except OSError as error: except OSError as error:
# No answer to request, set all properties to unknown. # No answer to request, set all properties to unknown.
@ -193,7 +218,10 @@ class MinecraftServer:
# Inform user once about failed update if necessary. # Inform user once about failed update if necessary.
if not self._last_status_request_failed: if not self._last_status_request_failed:
_LOGGER.warning( _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 self._last_status_request_failed = True

View File

@ -9,7 +9,7 @@ from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from . import MinecraftServer from . import MinecraftServer, helpers
from .const import ( # pylint: disable=unused-import from .const import ( # pylint: disable=unused-import
DEFAULT_HOST, DEFAULT_HOST,
DEFAULT_NAME, DEFAULT_NAME,
@ -29,11 +29,24 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
errors = {} errors = {}
if user_input is not None: if user_input is not None:
# User inputs. host = None
host = user_input[CONF_HOST] port = DEFAULT_PORT
port = user_input[CONF_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. # Check if 'host' is a valid IP address and if so, get the MAC address.
ip_address = None ip_address = None
@ -42,6 +55,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
ip_address = ipaddress.ip_address(host) ip_address = ipaddress.ip_address(host)
except ValueError: except ValueError:
# Host is not a valid IP address. # Host is not a valid IP address.
# Continue with host and port.
pass pass
else: else:
# Host is a valid IP address. # Host is a valid IP address.
@ -55,38 +69,56 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
partial(getmac.get_mac_address, **params) 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: if ip_address is not None and mac_address is None:
errors["base"] = "invalid_ip" errors["base"] = "invalid_ip"
# Validate port configuration (limit to user and dynamic port range). # Validate port configuration (limit to user and dynamic port range).
elif (port < 1024) or (port > 65535): elif (port < 1024) or (port > 65535):
errors["base"] = "invalid_port" errors["base"] = "invalid_port"
# Validate host and port via ping request to server. # Validate host and port by checking the server connection.
else: else:
# Build unique_id. # Create server instance with configuration data and ping the server.
if ip_address is not None: config_data = {
# Since IP addresses can change and therefore are not allowed in a CONF_NAME: user_input[CONF_NAME],
# unique_id, fall back to the MAC address. CONF_HOST: host,
unique_id = f"{mac_address}-{port}" CONF_PORT: port,
else: }
# Use host name in unique_id (host names should not change). server = MinecraftServer(self.hass, "dummy_unique_id", config_data)
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)
await server.async_check_connection() await server.async_check_connection()
if not server.online: if not server.online:
# Host or port invalid or server not reachable. # Host or port invalid or server not reachable.
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
else: 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. # Configuration data are available and no error was detected, create configuration entry.
return self.async_create_entry( return self.async_create_entry(title=title, data=config_data)
title=f"{host}:{port}", data=user_input
)
# Show configuration form (default form in case of no user_input, # Show configuration form (default form in case of no user_input,
# form filled with user_input and eventually with errors otherwise). # form filled with user_input and eventually with errors otherwise).
@ -107,9 +139,6 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Required( vol.Required(
CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST) CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)
): vol.All(str, vol.Lower), ): vol.All(str, vol.Lower),
vol.Optional(
CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT)
): int,
} }
), ),
errors=errors, errors=errors,

View File

@ -2,7 +2,7 @@
ATTR_PLAYERS_LIST = "players_list" ATTR_PLAYERS_LIST = "players_list"
DEFAULT_HOST = "localhost" DEFAULT_HOST = "localhost:25565"
DEFAULT_NAME = "Minecraft Server" DEFAULT_NAME = "Minecraft Server"
DEFAULT_PORT = 25565 DEFAULT_PORT = 25565
@ -30,6 +30,8 @@ SCAN_INTERVAL = 60
SIGNAL_NAME_PREFIX = f"signal_{DOMAIN}" SIGNAL_NAME_PREFIX = f"signal_{DOMAIN}"
SRV_RECORD_PREFIX = "_minecraft._tcp"
UNIT_PLAYERS_MAX = "players" UNIT_PLAYERS_MAX = "players"
UNIT_PLAYERS_ONLINE = "players" UNIT_PLAYERS_ONLINE = "players"
UNIT_PROTOCOL_VERSION = None UNIT_PROTOCOL_VERSION = None

View File

@ -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

View File

@ -3,7 +3,7 @@
"name": "Minecraft Server", "name": "Minecraft Server",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/minecraft_server", "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": [], "dependencies": [],
"codeowners": ["@elmurato"], "codeowners": ["@elmurato"],
"quality_scale": "silver" "quality_scale": "silver"

View File

@ -7,8 +7,7 @@
"description": "Set up your Minecraft Server instance to allow monitoring.", "description": "Set up your Minecraft Server instance to allow monitoring.",
"data": { "data": {
"name": "Name", "name": "Name",
"host": "Host", "host": "Host"
"port": "Port"
} }
} }
}, },

View File

@ -152,6 +152,7 @@ aioautomatic==0.6.5
aiobotocore==0.11.1 aiobotocore==0.11.1
# homeassistant.components.dnsip # homeassistant.components.dnsip
# homeassistant.components.minecraft_server
aiodns==2.0.0 aiodns==2.0.0
# homeassistant.components.esphome # homeassistant.components.esphome

View File

@ -58,6 +58,10 @@ aioautomatic==0.6.5
# homeassistant.components.aws # homeassistant.components.aws
aiobotocore==0.11.1 aiobotocore==0.11.1
# homeassistant.components.dnsip
# homeassistant.components.minecraft_server
aiodns==2.0.0
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==2.6.1 aioesphomeapi==2.6.1

View File

@ -1,5 +1,8 @@
"""Test the Minecraft Server config flow.""" """Test the Minecraft Server config flow."""
import asyncio
import aiodns
from asynctest import patch from asynctest import patch
from mcstatus.pinger import PingResponse from mcstatus.pinger import PingResponse
@ -19,6 +22,19 @@ from homeassistant.helpers.typing import HomeAssistantType
from tests.common import MockConfigEntry 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 = { STATUS_RESPONSE_RAW = {
"description": {"text": "Dummy Description"}, "description": {"text": "Dummy Description"},
"version": {"name": "Dummy Version", "protocol": 123}, "version": {"name": "Dummy Version", "protocol": 123},
@ -35,34 +51,34 @@ STATUS_RESPONSE_RAW = {
USER_INPUT = { USER_INPUT = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_HOST: "mc.dummyserver.com", CONF_HOST: f"mc.dummyserver.com:{DEFAULT_PORT}",
CONF_PORT: DEFAULT_PORT,
} }
USER_INPUT_SRV = {CONF_NAME: DEFAULT_NAME, CONF_HOST: "dummyserver.com"}
USER_INPUT_IPV4 = { USER_INPUT_IPV4 = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_HOST: "1.1.1.1", CONF_HOST: f"1.1.1.1:{DEFAULT_PORT}",
CONF_PORT: DEFAULT_PORT,
} }
USER_INPUT_IPV6 = { USER_INPUT_IPV6 = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_HOST: "::ffff:0101:0101", CONF_HOST: f"[::ffff:0101:0101]:{DEFAULT_PORT}",
CONF_PORT: DEFAULT_PORT,
} }
USER_INPUT_PORT_TOO_SMALL = { USER_INPUT_PORT_TOO_SMALL = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_HOST: "mc.dummyserver.com", CONF_HOST: f"mc.dummyserver.com:1023",
CONF_PORT: 1023,
} }
USER_INPUT_PORT_TOO_LARGE = { USER_INPUT_PORT_TOO_LARGE = {
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_HOST: "mc.dummyserver.com", CONF_HOST: f"mc.dummyserver.com:65536",
CONF_PORT: 65536,
} }
SRV_RECORDS = asyncio.Future()
SRV_RECORDS.set_result([QueryMock()])
async def test_show_config_form(hass: HomeAssistantType) -> None: async def test_show_config_form(hass: HomeAssistantType) -> None:
"""Test if initial configuration form is shown.""" """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: async def test_same_host(hass: HomeAssistantType) -> None:
"""Test abort in case of same host name.""" """Test abort in case of same host name."""
unique_id = f"{USER_INPUT[CONF_HOST]}-{USER_INPUT[CONF_PORT]}" with patch(
mock_config_entry = MockConfigEntry( "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,
domain=DOMAIN, unique_id=unique_id, data=USER_INPUT ):
) with patch(
mock_config_entry.add_to_hass(hass) "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( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
) )
assert result["type"] == RESULT_TYPE_ABORT assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_port_too_small(hass: HomeAssistantType) -> None: async def test_port_too_small(hass: HomeAssistantType) -> None:
"""Test error in case of a too small port.""" """Test error in case of a too small port."""
result = await hass.config_entries.flow.async_init( with patch(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_SMALL "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["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "invalid_port"} assert result["errors"] == {"base": "invalid_port"}
async def test_port_too_large(hass: HomeAssistantType) -> None: async def test_port_too_large(hass: HomeAssistantType) -> None:
"""Test error in case of a too large port.""" """Test error in case of a too large port."""
result = await hass.config_entries.flow.async_init( with patch(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_LARGE "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["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "invalid_port"} assert result["errors"] == {"base": "invalid_port"}
async def test_connection_failed(hass: HomeAssistantType) -> None: async def test_connection_failed(hass: HomeAssistantType) -> None:
"""Test error in case of a failed connection.""" """Test error in case of a failed connection."""
with patch("mcstatus.server.MinecraftServer.ping", side_effect=OSError): with patch(
result = await hass.config_entries.flow.async_init( "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT ):
) 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["type"] == RESULT_TYPE_FORM
assert result["errors"] == {"base": "cannot_connect"} 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: async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None:
"""Test config entry in case of a successful connection with a host name.""" """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( with patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.MinecraftServer.status",
return_value=PingResponse(STATUS_RESPONSE_RAW), 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["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_NAME] == USER_INPUT[CONF_NAME]
assert result["data"][CONF_HOST] == USER_INPUT[CONF_HOST] assert result["data"][CONF_HOST] == "mc.dummyserver.com"
assert result["data"][CONF_PORT] == USER_INPUT[CONF_PORT]
async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None: async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None:
"""Test config entry in case of a successful connection with an IPv4 address.""" """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("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( with patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.MinecraftServer.status",
return_value=PingResponse(STATUS_RESPONSE_RAW), 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["type"] == RESULT_TYPE_CREATE_ENTRY
assert ( assert result["title"] == USER_INPUT_IPV4[CONF_HOST]
result["title"]
== f"{USER_INPUT_IPV4[CONF_HOST]}:{USER_INPUT_IPV4[CONF_PORT]}"
)
assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME]
assert result["data"][CONF_HOST] == USER_INPUT_IPV4[CONF_HOST] assert result["data"][CONF_HOST] == "1.1.1.1"
assert result["data"][CONF_PORT] == USER_INPUT_IPV4[CONF_PORT]
async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None: async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None:
"""Test config entry in case of a successful connection with an IPv6 address.""" """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("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( with patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.MinecraftServer.status",
return_value=PingResponse(STATUS_RESPONSE_RAW), 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["type"] == RESULT_TYPE_CREATE_ENTRY
assert ( assert result["title"] == USER_INPUT_IPV6[CONF_HOST]
result["title"]
== f"{USER_INPUT_IPV6[CONF_HOST]}:{USER_INPUT_IPV6[CONF_PORT]}"
)
assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME]
assert result["data"][CONF_HOST] == USER_INPUT_IPV6[CONF_HOST] assert result["data"][CONF_HOST] == "::ffff:0101:0101"
assert result["data"][CONF_PORT] == USER_INPUT_IPV6[CONF_PORT]