Simplify Minecraft Server SRV handling (#100726)
This commit is contained in:
parent
803d24ad1a
commit
84451e858e
12 changed files with 245 additions and 306 deletions
|
@ -4,8 +4,10 @@ from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from mcstatus import JavaServer
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, Platform
|
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_PORT, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
import homeassistant.helpers.device_registry as dr
|
import homeassistant.helpers.device_registry as dr
|
||||||
import homeassistant.helpers.entity_registry as er
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
@ -20,20 +22,14 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Minecraft Server from a config entry."""
|
"""Set up Minecraft Server from a config entry."""
|
||||||
_LOGGER.debug(
|
|
||||||
"Creating coordinator instance for '%s' (%s)",
|
|
||||||
entry.data[CONF_NAME],
|
|
||||||
entry.data[CONF_HOST],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create coordinator instance.
|
# Create coordinator instance.
|
||||||
config_entry_id = entry.entry_id
|
coordinator = MinecraftServerCoordinator(hass, entry)
|
||||||
coordinator = MinecraftServerCoordinator(hass, config_entry_id, entry.data)
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
# Store coordinator instance.
|
# Store coordinator instance.
|
||||||
domain_data = hass.data.setdefault(DOMAIN, {})
|
domain_data = hass.data.setdefault(DOMAIN, {})
|
||||||
domain_data[config_entry_id] = coordinator
|
domain_data[entry.entry_id] = coordinator
|
||||||
|
|
||||||
# Set up platforms.
|
# Set up platforms.
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
@ -43,7 +39,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Unload Minecraft Server config entry."""
|
"""Unload Minecraft Server config entry."""
|
||||||
config_entry_id = config_entry.entry_id
|
|
||||||
|
|
||||||
# Unload platforms.
|
# Unload platforms.
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||||
|
@ -51,17 +46,18 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clean up.
|
# Clean up.
|
||||||
hass.data[DOMAIN].pop(config_entry_id)
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Migrate old config entry to a new format."""
|
"""Migrate old config entry to a new format."""
|
||||||
_LOGGER.debug("Migrating from version %s", config_entry.version)
|
|
||||||
|
|
||||||
# 1 --> 2: Use config entry ID as base for unique IDs.
|
# 1 --> 2: Use config entry ID as base for unique IDs.
|
||||||
if config_entry.version == 1:
|
if config_entry.version == 1:
|
||||||
|
_LOGGER.debug("Migrating from version 1")
|
||||||
|
|
||||||
old_unique_id = config_entry.unique_id
|
old_unique_id = config_entry.unique_id
|
||||||
assert old_unique_id
|
assert old_unique_id
|
||||||
config_entry_id = config_entry.entry_id
|
config_entry_id = config_entry.entry_id
|
||||||
|
@ -78,7 +74,52 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||||
# Migrate entities.
|
# Migrate entities.
|
||||||
await er.async_migrate_entries(hass, config_entry_id, _migrate_entity_unique_id)
|
await er.async_migrate_entries(hass, config_entry_id, _migrate_entity_unique_id)
|
||||||
|
|
||||||
_LOGGER.debug("Migration to version %s successful", config_entry.version)
|
_LOGGER.debug("Migration to version 2 successful")
|
||||||
|
|
||||||
|
# 2 --> 3: Use address instead of host and port in config entry.
|
||||||
|
if config_entry.version == 2:
|
||||||
|
_LOGGER.debug("Migrating from version 2")
|
||||||
|
|
||||||
|
config_data = config_entry.data
|
||||||
|
|
||||||
|
# Migrate config entry.
|
||||||
|
try:
|
||||||
|
address = config_data[CONF_HOST]
|
||||||
|
JavaServer.lookup(address)
|
||||||
|
host_only_lookup_success = True
|
||||||
|
except ValueError as error:
|
||||||
|
host_only_lookup_success = False
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Hostname (without port) cannot be parsed (error: %s), trying again with port",
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not host_only_lookup_success:
|
||||||
|
try:
|
||||||
|
address = f"{config_data[CONF_HOST]}:{config_data[CONF_PORT]}"
|
||||||
|
JavaServer.lookup(address)
|
||||||
|
except ValueError as error:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Can't migrate configuration entry due to error while parsing server address (error: %s), try again later",
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating config entry, replacing host '%s' and port '%s' with address '%s'",
|
||||||
|
config_data[CONF_HOST],
|
||||||
|
config_data[CONF_PORT],
|
||||||
|
address,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_data = config_data.copy()
|
||||||
|
new_data[CONF_ADDRESS] = address
|
||||||
|
del new_data[CONF_HOST]
|
||||||
|
del new_data[CONF_PORT]
|
||||||
|
config_entry.version = 3
|
||||||
|
hass.config_entries.async_update_entry(config_entry, data=new_data)
|
||||||
|
|
||||||
|
_LOGGER.debug("Migration to version 3 successful")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
"""Config flow for Minecraft Server integration."""
|
"""Config flow for Minecraft Server integration."""
|
||||||
from contextlib import suppress
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mcstatus import JavaServer
|
from mcstatus import JavaServer
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
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_ADDRESS, CONF_NAME
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
from . import helpers
|
from .const import DEFAULT_NAME, DOMAIN
|
||||||
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN
|
|
||||||
|
|
||||||
DEFAULT_HOST = "localhost:25565"
|
DEFAULT_ADDRESS = "localhost:25565"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -20,51 +18,22 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
|
class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Minecraft Server."""
|
"""Handle a config flow for Minecraft Server."""
|
||||||
|
|
||||||
VERSION = 2
|
VERSION = 3
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None) -> FlowResult:
|
async def async_step_user(self, user_input=None) -> FlowResult:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input:
|
||||||
host = None
|
address = user_input[CONF_ADDRESS]
|
||||||
port = DEFAULT_PORT
|
|
||||||
title = user_input[CONF_HOST]
|
|
||||||
|
|
||||||
# Split address at last occurrence of ':'.
|
if await self._async_is_server_online(address):
|
||||||
address_left, separator, address_right = user_input[CONF_HOST].rpartition(
|
# No error was detected, create configuration entry.
|
||||||
":"
|
config_data = {CONF_NAME: user_input[CONF_NAME], CONF_ADDRESS: address}
|
||||||
)
|
return self.async_create_entry(title=address, data=config_data)
|
||||||
|
|
||||||
# If no separator is found, 'rpartition' returns ('', '', original_string).
|
# Host or port invalid or server not reachable.
|
||||||
if separator == "":
|
errors["base"] = "cannot_connect"
|
||||||
host = address_right
|
|
||||||
else:
|
|
||||||
host = address_left
|
|
||||||
with suppress(ValueError):
|
|
||||||
port = int(address_right)
|
|
||||||
|
|
||||||
# Remove '[' and ']' in case of an IPv6 address.
|
|
||||||
host = host.strip("[]")
|
|
||||||
|
|
||||||
# Validate port configuration (limit to user and dynamic port range).
|
|
||||||
if (port < 1024) or (port > 65535):
|
|
||||||
errors["base"] = "invalid_port"
|
|
||||||
# Validate host and port by checking the server connection.
|
|
||||||
else:
|
|
||||||
# Create server instance with configuration data and ping the server.
|
|
||||||
config_data = {
|
|
||||||
CONF_NAME: user_input[CONF_NAME],
|
|
||||||
CONF_HOST: host,
|
|
||||||
CONF_PORT: port,
|
|
||||||
}
|
|
||||||
if await self._async_is_server_online(host, port):
|
|
||||||
# Configuration data are available and no error was detected,
|
|
||||||
# create configuration entry.
|
|
||||||
return self.async_create_entry(title=title, data=config_data)
|
|
||||||
|
|
||||||
# Host or port invalid or server not reachable.
|
|
||||||
errors["base"] = "cannot_connect"
|
|
||||||
|
|
||||||
# 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).
|
||||||
|
@ -83,24 +52,32 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)
|
CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)
|
||||||
): str,
|
): str,
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)
|
CONF_ADDRESS,
|
||||||
|
default=user_input.get(CONF_ADDRESS, DEFAULT_ADDRESS),
|
||||||
): vol.All(str, vol.Lower),
|
): vol.All(str, vol.Lower),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_is_server_online(self, host: str, port: int) -> bool:
|
async def _async_is_server_online(self, address: str) -> bool:
|
||||||
"""Check server connection using a 'status' request and return result."""
|
"""Check server connection using a 'status' request and return result."""
|
||||||
|
|
||||||
# Check if host is a SRV record. If so, update server data.
|
# Parse and check server address.
|
||||||
if srv_record := await helpers.async_check_srv_record(host):
|
try:
|
||||||
# Use extracted host and port from SRV record.
|
server = await JavaServer.async_lookup(address)
|
||||||
host = srv_record[CONF_HOST]
|
except ValueError as error:
|
||||||
port = srv_record[CONF_PORT]
|
_LOGGER.debug(
|
||||||
|
(
|
||||||
|
"Error occurred while parsing server address '%s' -"
|
||||||
|
" ValueError: %s"
|
||||||
|
),
|
||||||
|
address,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
# Send a status request to the server.
|
# Send a status request to the server.
|
||||||
server = JavaServer(host, port)
|
|
||||||
try:
|
try:
|
||||||
await server.async_status()
|
await server.async_status()
|
||||||
return True
|
return True
|
||||||
|
@ -110,8 +87,8 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"Error occurred while trying to check the connection to '%s:%s' -"
|
"Error occurred while trying to check the connection to '%s:%s' -"
|
||||||
" OSError: %s"
|
" OSError: %s"
|
||||||
),
|
),
|
||||||
host,
|
server.address.host,
|
||||||
port,
|
server.address.port,
|
||||||
error,
|
error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""Constants for the Minecraft Server integration."""
|
"""Constants for the Minecraft Server integration."""
|
||||||
|
|
||||||
DEFAULT_NAME = "Minecraft Server"
|
DEFAULT_NAME = "Minecraft Server"
|
||||||
DEFAULT_PORT = 25565
|
|
||||||
|
|
||||||
DOMAIN = "minecraft_server"
|
DOMAIN = "minecraft_server"
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
"""The Minecraft Server integration."""
|
"""The Minecraft Server integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from mcstatus.server import JavaServer
|
from mcstatus.server import JavaServer
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_ADDRESS, CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from . import helpers
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -36,12 +34,11 @@ class MinecraftServerData:
|
||||||
class MinecraftServerCoordinator(DataUpdateCoordinator[MinecraftServerData]):
|
class MinecraftServerCoordinator(DataUpdateCoordinator[MinecraftServerData]):
|
||||||
"""Minecraft Server data update coordinator."""
|
"""Minecraft Server data update coordinator."""
|
||||||
|
|
||||||
_srv_record_checked = False
|
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, hass: HomeAssistant, unique_id: str, config_data: Mapping[str, Any]
|
|
||||||
) -> None:
|
|
||||||
"""Initialize coordinator instance."""
|
"""Initialize coordinator instance."""
|
||||||
|
config_data = config_entry.data
|
||||||
|
self.unique_id = config_entry.entry_id
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass=hass,
|
hass=hass,
|
||||||
name=config_data[CONF_NAME],
|
name=config_data[CONF_NAME],
|
||||||
|
@ -49,34 +46,20 @@ class MinecraftServerCoordinator(DataUpdateCoordinator[MinecraftServerData]):
|
||||||
update_interval=SCAN_INTERVAL,
|
update_interval=SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Server data
|
try:
|
||||||
self.unique_id = unique_id
|
self._server = JavaServer.lookup(config_data[CONF_ADDRESS])
|
||||||
self._host = config_data[CONF_HOST]
|
except ValueError as error:
|
||||||
self._port = config_data[CONF_PORT]
|
raise HomeAssistantError(
|
||||||
|
f"Address in configuration entry cannot be parsed (error: {error}), please remove this device and add it again"
|
||||||
# 3rd party library instance
|
) from error
|
||||||
self._server = JavaServer(self._host, self._port)
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> MinecraftServerData:
|
async def _async_update_data(self) -> MinecraftServerData:
|
||||||
"""Get server data from 3rd party library and update properties."""
|
"""Get server data from 3rd party library and update properties."""
|
||||||
|
|
||||||
# Check once if host is a valid Minecraft SRV record.
|
|
||||||
if not self._srv_record_checked:
|
|
||||||
self._srv_record_checked = True
|
|
||||||
if srv_record := await helpers.async_check_srv_record(self._host):
|
|
||||||
# Overwrite host, port and 3rd party library instance
|
|
||||||
# with data extracted out of the SRV record.
|
|
||||||
self._host = srv_record[CONF_HOST]
|
|
||||||
self._port = srv_record[CONF_PORT]
|
|
||||||
self._server = JavaServer(self._host, self._port)
|
|
||||||
|
|
||||||
# Send status request to the server.
|
|
||||||
try:
|
try:
|
||||||
status_response = await self._server.async_status()
|
status_response = await self._server.async_status()
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(error) from error
|
||||||
|
|
||||||
# Got answer to request, update properties.
|
|
||||||
players_list = []
|
players_list = []
|
||||||
if players := status_response.players.sample:
|
if players := status_response.players.sample:
|
||||||
for player in players:
|
for player in players:
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""Helper functions of Minecraft Server integration."""
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import aiodns
|
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
|
||||||
|
|
||||||
SRV_RECORD_PREFIX = "_minecraft._tcp"
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_check_srv_record(host: str) -> dict[str, Any] | None:
|
|
||||||
"""Check if the given host is a valid Minecraft SRV record."""
|
|
||||||
srv_record = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
srv_query = await aiodns.DNSResolver().query(
|
|
||||||
host=f"{SRV_RECORD_PREFIX}.{host}", qtype="SRV"
|
|
||||||
)
|
|
||||||
except aiodns.error.DNSError:
|
|
||||||
# 'host' is not a Minecraft SRV record.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# 'host' is a valid Minecraft SRV record, extract the data.
|
|
||||||
srv_record = {
|
|
||||||
CONF_HOST: srv_query[0].host,
|
|
||||||
CONF_PORT: srv_query[0].port,
|
|
||||||
}
|
|
||||||
_LOGGER.debug(
|
|
||||||
"'%s' is a valid Minecraft SRV record ('%s:%s')",
|
|
||||||
host,
|
|
||||||
srv_record[CONF_HOST],
|
|
||||||
srv_record[CONF_PORT],
|
|
||||||
)
|
|
||||||
|
|
||||||
return srv_record
|
|
|
@ -7,5 +7,5 @@
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["dnspython", "mcstatus"],
|
"loggers": ["dnspython", "mcstatus"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["aiodns==3.0.0", "mcstatus==11.0.0"]
|
"requirements": ["mcstatus==11.0.0"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
"description": "Set up your Minecraft Server instance to allow monitoring.",
|
"description": "Set up your Minecraft Server instance to allow monitoring.",
|
||||||
"data": {
|
"data": {
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
"name": "[%key:common::config_flow::data::name%]",
|
||||||
"host": "[%key:common::config_flow::data::host%]"
|
"address": "Server address"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_port": "Port must be in range from 1024 to 65535. Please correct it and try again.",
|
"cannot_connect": "Failed to connect to server. Please check the address and try again. If a port was provided, it must be within a valid range. Also ensure that you are running at least version 1.7 of Minecraft Java Edition on your server."
|
||||||
"cannot_connect": "Failed to connect to server. Please check the host and port and try again. Also ensure that you are running at least Minecraft version 1.7 on your server."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
|
|
@ -216,7 +216,6 @@ aiocomelit==0.0.8
|
||||||
aiodiscover==1.5.1
|
aiodiscover==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.dnsip
|
# homeassistant.components.dnsip
|
||||||
# homeassistant.components.minecraft_server
|
|
||||||
aiodns==3.0.0
|
aiodns==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.eafm
|
# homeassistant.components.eafm
|
||||||
|
|
|
@ -197,7 +197,6 @@ aiocomelit==0.0.8
|
||||||
aiodiscover==1.5.1
|
aiodiscover==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.dnsip
|
# homeassistant.components.dnsip
|
||||||
# homeassistant.components.minecraft_server
|
|
||||||
aiodns==3.0.0
|
aiodns==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.eafm
|
# homeassistant.components.eafm
|
||||||
|
|
|
@ -7,6 +7,8 @@ from mcstatus.status_response import (
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_HOST = "mc.dummyserver.com"
|
TEST_HOST = "mc.dummyserver.com"
|
||||||
|
TEST_PORT = 25566
|
||||||
|
TEST_ADDRESS = f"{TEST_HOST}:{TEST_PORT}"
|
||||||
|
|
||||||
TEST_JAVA_STATUS_RESPONSE_RAW = {
|
TEST_JAVA_STATUS_RESPONSE_RAW = {
|
||||||
"description": {"text": "Dummy Description"},
|
"description": {"text": "Dummy Description"},
|
||||||
|
|
|
@ -1,59 +1,20 @@
|
||||||
"""Tests for the Minecraft Server config flow."""
|
"""Tests for the Minecraft Server config flow."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import aiodns
|
from mcstatus import JavaServer
|
||||||
|
|
||||||
from homeassistant.components.minecraft_server.const import (
|
from homeassistant.components.minecraft_server.const import DEFAULT_NAME, DOMAIN
|
||||||
DEFAULT_NAME,
|
|
||||||
DEFAULT_PORT,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
from homeassistant.const import CONF_ADDRESS, CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
from .const import TEST_HOST, TEST_JAVA_STATUS_RESPONSE
|
from .const import TEST_ADDRESS, TEST_HOST, TEST_JAVA_STATUS_RESPONSE, TEST_PORT
|
||||||
|
|
||||||
|
|
||||||
class QueryMock:
|
|
||||||
"""Mock for result of aiodns.DNSResolver.query."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""Set up query result mock."""
|
|
||||||
self.host = TEST_HOST
|
|
||||||
self.port = 23456
|
|
||||||
self.priority = 1
|
|
||||||
self.weight = 1
|
|
||||||
self.ttl = None
|
|
||||||
|
|
||||||
|
|
||||||
USER_INPUT = {
|
USER_INPUT = {
|
||||||
CONF_NAME: DEFAULT_NAME,
|
CONF_NAME: DEFAULT_NAME,
|
||||||
CONF_HOST: f"{TEST_HOST}:{DEFAULT_PORT}",
|
CONF_ADDRESS: TEST_ADDRESS,
|
||||||
}
|
|
||||||
|
|
||||||
USER_INPUT_SRV = {CONF_NAME: DEFAULT_NAME, CONF_HOST: TEST_HOST}
|
|
||||||
|
|
||||||
USER_INPUT_IPV4 = {
|
|
||||||
CONF_NAME: DEFAULT_NAME,
|
|
||||||
CONF_HOST: f"1.1.1.1:{DEFAULT_PORT}",
|
|
||||||
}
|
|
||||||
|
|
||||||
USER_INPUT_IPV6 = {
|
|
||||||
CONF_NAME: DEFAULT_NAME,
|
|
||||||
CONF_HOST: f"[::ffff:0101:0101]:{DEFAULT_PORT}",
|
|
||||||
}
|
|
||||||
|
|
||||||
USER_INPUT_PORT_TOO_SMALL = {
|
|
||||||
CONF_NAME: DEFAULT_NAME,
|
|
||||||
CONF_HOST: f"{TEST_HOST}:1023",
|
|
||||||
}
|
|
||||||
|
|
||||||
USER_INPUT_PORT_TOO_LARGE = {
|
|
||||||
CONF_NAME: DEFAULT_NAME,
|
|
||||||
CONF_HOST: f"{TEST_HOST}:65536",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,39 +28,25 @@ async def test_show_config_form(hass: HomeAssistant) -> None:
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
|
||||||
async def test_port_too_small(hass: HomeAssistant) -> None:
|
async def test_lookup_failed(hass: HomeAssistant) -> None:
|
||||||
"""Test error in case of a too small port."""
|
"""Test error in case of a failed connection."""
|
||||||
with patch(
|
with patch(
|
||||||
"aiodns.DNSResolver.query",
|
"mcstatus.server.JavaServer.async_lookup",
|
||||||
side_effect=aiodns.error.DNSError,
|
side_effect=ValueError,
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_PORT_TOO_SMALL
|
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["errors"] == {"base": "invalid_port"}
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
async def test_port_too_large(hass: HomeAssistant) -> None:
|
|
||||||
"""Test error in case of a too large port."""
|
|
||||||
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"] == FlowResultType.FORM
|
|
||||||
assert result["errors"] == {"base": "invalid_port"}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_failed(hass: HomeAssistant) -> None:
|
async def test_connection_failed(hass: HomeAssistant) -> None:
|
||||||
"""Test error in case of a failed connection."""
|
"""Test error in case of a failed connection."""
|
||||||
with patch(
|
with patch(
|
||||||
"aiodns.DNSResolver.query",
|
"mcstatus.server.JavaServer.async_lookup",
|
||||||
side_effect=aiodns.error.DNSError,
|
return_value=JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
), patch("mcstatus.server.JavaServer.async_status", side_effect=OSError):
|
), patch("mcstatus.server.JavaServer.async_status", side_effect=OSError):
|
||||||
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
|
||||||
|
@ -109,30 +56,11 @@ async def test_connection_failed(hass: HomeAssistant) -> None:
|
||||||
assert result["errors"] == {"base": "cannot_connect"}
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_succeeded_with_srv_record(hass: HomeAssistant) -> None:
|
async def test_connection_succeeded(hass: HomeAssistant) -> None:
|
||||||
"""Test config entry in case of a successful connection with a SRV record."""
|
|
||||||
with patch(
|
|
||||||
"aiodns.DNSResolver.query",
|
|
||||||
side_effect=AsyncMock(return_value=[QueryMock()]),
|
|
||||||
), patch(
|
|
||||||
"mcstatus.server.JavaServer.async_status",
|
|
||||||
return_value=TEST_JAVA_STATUS_RESPONSE,
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.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: HomeAssistant) -> 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(
|
with patch(
|
||||||
"aiodns.DNSResolver.query",
|
"mcstatus.server.JavaServer.async_lookup",
|
||||||
side_effect=aiodns.error.DNSError,
|
return_value=JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
), patch(
|
), patch(
|
||||||
"mcstatus.server.JavaServer.async_status",
|
"mcstatus.server.JavaServer.async_status",
|
||||||
return_value=TEST_JAVA_STATUS_RESPONSE,
|
return_value=TEST_JAVA_STATUS_RESPONSE,
|
||||||
|
@ -142,44 +70,6 @@ async def test_connection_succeeded_with_host(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == USER_INPUT[CONF_HOST]
|
assert result["title"] == USER_INPUT[CONF_ADDRESS]
|
||||||
assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME]
|
assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME]
|
||||||
assert result["data"][CONF_HOST] == TEST_HOST
|
assert result["data"][CONF_ADDRESS] == TEST_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_succeeded_with_ip4(hass: HomeAssistant) -> None:
|
|
||||||
"""Test config entry in case of a successful connection with an IPv4 address."""
|
|
||||||
with patch(
|
|
||||||
"aiodns.DNSResolver.query",
|
|
||||||
side_effect=aiodns.error.DNSError,
|
|
||||||
), patch(
|
|
||||||
"mcstatus.server.JavaServer.async_status",
|
|
||||||
return_value=TEST_JAVA_STATUS_RESPONSE,
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
|
||||||
assert result["title"] == USER_INPUT_IPV4[CONF_HOST]
|
|
||||||
assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME]
|
|
||||||
assert result["data"][CONF_HOST] == "1.1.1.1"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_succeeded_with_ip6(hass: HomeAssistant) -> None:
|
|
||||||
"""Test config entry in case of a successful connection with an IPv6 address."""
|
|
||||||
with patch(
|
|
||||||
"aiodns.DNSResolver.query",
|
|
||||||
side_effect=aiodns.error.DNSError,
|
|
||||||
), patch(
|
|
||||||
"mcstatus.server.JavaServer.async_status",
|
|
||||||
return_value=TEST_JAVA_STATUS_RESPONSE,
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
|
||||||
assert result["title"] == USER_INPUT_IPV6[CONF_HOST]
|
|
||||||
assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME]
|
|
||||||
assert result["data"][CONF_HOST] == "::ffff:0101:0101"
|
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
"""Tests for the Minecraft Server integration."""
|
"""Tests for the Minecraft Server integration."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import aiodns
|
from mcstatus import JavaServer
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||||
from homeassistant.components.minecraft_server.const import (
|
from homeassistant.components.minecraft_server.const import DEFAULT_NAME, DOMAIN
|
||||||
DEFAULT_NAME,
|
|
||||||
DEFAULT_PORT,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_NAME, CONF_PORT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from .const import TEST_HOST, TEST_JAVA_STATUS_RESPONSE
|
from .const import TEST_ADDRESS, TEST_HOST, TEST_JAVA_STATUS_RESPONSE, TEST_PORT
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
TEST_UNIQUE_ID = f"{TEST_HOST}-{DEFAULT_PORT}"
|
TEST_UNIQUE_ID = f"{TEST_HOST}-{TEST_PORT}"
|
||||||
|
|
||||||
SENSOR_KEYS = [
|
SENSOR_KEYS = [
|
||||||
{"v1": "Latency Time", "v2": "latency"},
|
{"v1": "Latency Time", "v2": "latency"},
|
||||||
|
@ -32,43 +28,54 @@ SENSOR_KEYS = [
|
||||||
BINARY_SENSOR_KEYS = {"v1": "Status", "v2": "status"}
|
BINARY_SENSOR_KEYS = {"v1": "Status", "v2": "status"}
|
||||||
|
|
||||||
|
|
||||||
async def test_entry_migration_v1_to_v2(hass: HomeAssistant) -> None:
|
def create_v1_mock_config_entry(hass: HomeAssistant) -> int:
|
||||||
"""Test entry migration from version 1 to 2."""
|
"""Create mock config entry."""
|
||||||
|
|
||||||
# Create mock config entry.
|
|
||||||
config_entry_v1 = MockConfigEntry(
|
config_entry_v1 = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id=TEST_UNIQUE_ID,
|
unique_id=TEST_UNIQUE_ID,
|
||||||
data={
|
data={
|
||||||
CONF_NAME: DEFAULT_NAME,
|
CONF_NAME: DEFAULT_NAME,
|
||||||
CONF_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
CONF_PORT: DEFAULT_PORT,
|
CONF_PORT: TEST_PORT,
|
||||||
},
|
},
|
||||||
version=1,
|
version=1,
|
||||||
)
|
)
|
||||||
config_entry_id = config_entry_v1.entry_id
|
config_entry_id = config_entry_v1.entry_id
|
||||||
config_entry_v1.add_to_hass(hass)
|
config_entry_v1.add_to_hass(hass)
|
||||||
|
|
||||||
# Create mock device entry.
|
return config_entry_id
|
||||||
|
|
||||||
|
|
||||||
|
def create_v1_mock_device_entry(hass: HomeAssistant, config_entry_id: int) -> int:
|
||||||
|
"""Create mock device entry."""
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
device_entry_v1 = device_registry.async_get_or_create(
|
device_entry_v1 = device_registry.async_get_or_create(
|
||||||
config_entry_id=config_entry_id,
|
config_entry_id=config_entry_id,
|
||||||
identifiers={(DOMAIN, TEST_UNIQUE_ID)},
|
identifiers={(DOMAIN, TEST_UNIQUE_ID)},
|
||||||
)
|
)
|
||||||
device_entry_id = device_entry_v1.id
|
device_entry_id = device_entry_v1.id
|
||||||
|
|
||||||
assert device_entry_v1
|
assert device_entry_v1
|
||||||
assert device_entry_id
|
assert device_entry_id
|
||||||
|
|
||||||
# Create mock sensor entity entries.
|
return device_entry_id
|
||||||
|
|
||||||
|
|
||||||
|
def create_v1_mock_sensor_entity_entries(
|
||||||
|
hass: HomeAssistant, config_entry_id: int, device_entry_id: int
|
||||||
|
) -> list[dict]:
|
||||||
|
"""Create mock sensor entity entries."""
|
||||||
sensor_entity_id_key_mapping_list = []
|
sensor_entity_id_key_mapping_list = []
|
||||||
|
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
for sensor_key in SENSOR_KEYS:
|
for sensor_key in SENSOR_KEYS:
|
||||||
entity_unique_id = f"{TEST_UNIQUE_ID}-{sensor_key['v1']}"
|
entity_unique_id = f"{TEST_UNIQUE_ID}-{sensor_key['v1']}"
|
||||||
entity_entry_v1 = entity_registry.async_get_or_create(
|
entity_entry_v1 = entity_registry.async_get_or_create(
|
||||||
SENSOR_DOMAIN,
|
SENSOR_DOMAIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
unique_id=entity_unique_id,
|
unique_id=entity_unique_id,
|
||||||
config_entry=config_entry_v1,
|
config_entry=config_entry,
|
||||||
device_id=device_entry_id,
|
device_id=device_entry_id,
|
||||||
)
|
)
|
||||||
assert entity_entry_v1.unique_id == entity_unique_id
|
assert entity_entry_v1.unique_id == entity_unique_id
|
||||||
|
@ -76,25 +83,51 @@ async def test_entry_migration_v1_to_v2(hass: HomeAssistant) -> None:
|
||||||
{"entity_id": entity_entry_v1.entity_id, "key": sensor_key["v2"]}
|
{"entity_id": entity_entry_v1.entity_id, "key": sensor_key["v2"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create mock binary sensor entity entry.
|
return sensor_entity_id_key_mapping_list
|
||||||
|
|
||||||
|
|
||||||
|
def create_v1_mock_binary_sensor_entity_entry(
|
||||||
|
hass: HomeAssistant, config_entry_id: int, device_entry_id: int
|
||||||
|
) -> dict:
|
||||||
|
"""Create mock binary sensor entity entry."""
|
||||||
|
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
entity_unique_id = f"{TEST_UNIQUE_ID}-{BINARY_SENSOR_KEYS['v1']}"
|
entity_unique_id = f"{TEST_UNIQUE_ID}-{BINARY_SENSOR_KEYS['v1']}"
|
||||||
entity_entry_v1 = entity_registry.async_get_or_create(
|
entity_entry = entity_registry.async_get_or_create(
|
||||||
BINARY_SENSOR_DOMAIN,
|
BINARY_SENSOR_DOMAIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
unique_id=entity_unique_id,
|
unique_id=entity_unique_id,
|
||||||
config_entry=config_entry_v1,
|
config_entry=config_entry,
|
||||||
device_id=device_entry_id,
|
device_id=device_entry_id,
|
||||||
)
|
)
|
||||||
assert entity_entry_v1.unique_id == entity_unique_id
|
assert entity_entry.unique_id == entity_unique_id
|
||||||
binary_sensor_entity_id_key_mapping = {
|
binary_sensor_entity_id_key_mapping = {
|
||||||
"entity_id": entity_entry_v1.entity_id,
|
"entity_id": entity_entry.entity_id,
|
||||||
"key": BINARY_SENSOR_KEYS["v2"],
|
"key": BINARY_SENSOR_KEYS["v2"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return binary_sensor_entity_id_key_mapping
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entry_migration(hass: HomeAssistant) -> None:
|
||||||
|
"""Test entry migration from version 1 to 3, where host and port is required for the connection to the server."""
|
||||||
|
config_entry_id = create_v1_mock_config_entry(hass)
|
||||||
|
device_entry_id = create_v1_mock_device_entry(hass, config_entry_id)
|
||||||
|
sensor_entity_id_key_mapping_list = create_v1_mock_sensor_entity_entries(
|
||||||
|
hass, config_entry_id, device_entry_id
|
||||||
|
)
|
||||||
|
binary_sensor_entity_id_key_mapping = create_v1_mock_binary_sensor_entity_entry(
|
||||||
|
hass, config_entry_id, device_entry_id
|
||||||
|
)
|
||||||
|
|
||||||
# Trigger migration.
|
# Trigger migration.
|
||||||
with patch(
|
with patch(
|
||||||
"aiodns.DNSResolver.query",
|
"mcstatus.server.JavaServer.lookup",
|
||||||
side_effect=aiodns.error.DNSError,
|
side_effect=[
|
||||||
|
ValueError,
|
||||||
|
JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
|
JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
|
],
|
||||||
), patch(
|
), patch(
|
||||||
"mcstatus.server.JavaServer.async_status",
|
"mcstatus.server.JavaServer.async_status",
|
||||||
return_value=TEST_JAVA_STATUS_RESPONSE,
|
return_value=TEST_JAVA_STATUS_RESPONSE,
|
||||||
|
@ -103,29 +136,84 @@ async def test_entry_migration_v1_to_v2(hass: HomeAssistant) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Test migrated config entry.
|
# Test migrated config entry.
|
||||||
config_entry_v2 = hass.config_entries.async_get_entry(config_entry_id)
|
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||||
assert config_entry_v2.unique_id is None
|
assert config_entry.unique_id is None
|
||||||
assert config_entry_v2.data == {
|
assert config_entry.data == {
|
||||||
CONF_NAME: DEFAULT_NAME,
|
CONF_NAME: DEFAULT_NAME,
|
||||||
CONF_HOST: TEST_HOST,
|
CONF_ADDRESS: TEST_ADDRESS,
|
||||||
CONF_PORT: DEFAULT_PORT,
|
|
||||||
}
|
}
|
||||||
assert config_entry_v2.version == 2
|
assert config_entry.version == 3
|
||||||
|
|
||||||
# Test migrated device entry.
|
# Test migrated device entry.
|
||||||
device_entry_v2 = device_registry.async_get(device_entry_id)
|
device_registry = dr.async_get(hass)
|
||||||
assert device_entry_v2.identifiers == {(DOMAIN, config_entry_id)}
|
device_entry = device_registry.async_get(device_entry_id)
|
||||||
|
assert device_entry.identifiers == {(DOMAIN, config_entry_id)}
|
||||||
|
|
||||||
# Test migrated sensor entity entries.
|
# Test migrated sensor entity entries.
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
for mapping in sensor_entity_id_key_mapping_list:
|
for mapping in sensor_entity_id_key_mapping_list:
|
||||||
entity_entry_v2 = entity_registry.async_get(mapping["entity_id"])
|
entity_entry = entity_registry.async_get(mapping["entity_id"])
|
||||||
assert entity_entry_v2.unique_id == f"{config_entry_id}-{mapping['key']}"
|
assert entity_entry.unique_id == f"{config_entry_id}-{mapping['key']}"
|
||||||
|
|
||||||
# Test migrated binary sensor entity entry.
|
# Test migrated binary sensor entity entry.
|
||||||
entity_entry_v2 = entity_registry.async_get(
|
entity_entry = entity_registry.async_get(
|
||||||
binary_sensor_entity_id_key_mapping["entity_id"]
|
binary_sensor_entity_id_key_mapping["entity_id"]
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
entity_entry_v2.unique_id
|
entity_entry.unique_id
|
||||||
== f"{config_entry_id}-{binary_sensor_entity_id_key_mapping['key']}"
|
== f"{config_entry_id}-{binary_sensor_entity_id_key_mapping['key']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entry_migration_host_only(hass: HomeAssistant) -> None:
|
||||||
|
"""Test entry migration from version 1 to 3, where host alone is sufficient for the connection to the server."""
|
||||||
|
config_entry_id = create_v1_mock_config_entry(hass)
|
||||||
|
device_entry_id = create_v1_mock_device_entry(hass, config_entry_id)
|
||||||
|
create_v1_mock_sensor_entity_entries(hass, config_entry_id, device_entry_id)
|
||||||
|
create_v1_mock_binary_sensor_entity_entry(hass, config_entry_id, device_entry_id)
|
||||||
|
|
||||||
|
# Trigger migration.
|
||||||
|
with patch(
|
||||||
|
"mcstatus.server.JavaServer.lookup",
|
||||||
|
side_effect=[
|
||||||
|
JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
|
JavaServer(host=TEST_HOST, port=TEST_PORT),
|
||||||
|
],
|
||||||
|
), patch(
|
||||||
|
"mcstatus.server.JavaServer.async_status",
|
||||||
|
return_value=TEST_JAVA_STATUS_RESPONSE,
|
||||||
|
):
|
||||||
|
assert await hass.config_entries.async_setup(config_entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test migrated config entry.
|
||||||
|
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||||
|
assert config_entry.unique_id is None
|
||||||
|
assert config_entry.data == {
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_ADDRESS: TEST_HOST,
|
||||||
|
}
|
||||||
|
assert config_entry.version == 3
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entry_migration_v3_failure(hass: HomeAssistant) -> None:
|
||||||
|
"""Test failed entry migration from version 2 to 3."""
|
||||||
|
config_entry_id = create_v1_mock_config_entry(hass)
|
||||||
|
device_entry_id = create_v1_mock_device_entry(hass, config_entry_id)
|
||||||
|
create_v1_mock_sensor_entity_entries(hass, config_entry_id, device_entry_id)
|
||||||
|
create_v1_mock_binary_sensor_entity_entry(hass, config_entry_id, device_entry_id)
|
||||||
|
|
||||||
|
# Trigger migration.
|
||||||
|
with patch(
|
||||||
|
"mcstatus.server.JavaServer.lookup",
|
||||||
|
side_effect=[
|
||||||
|
ValueError,
|
||||||
|
ValueError,
|
||||||
|
],
|
||||||
|
):
|
||||||
|
assert not await hass.config_entries.async_setup(config_entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test config entry.
|
||||||
|
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||||
|
assert config_entry.version == 2
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue