DNS IP custom ports for IPv4 (#113993)

* squash DNS IP enable port

* linting

* fix config entries in tests.

* fix more config entries

* fix parameter order

* Add defaults for legacy config entries

* test legacy config are not broken

* test driven migration

* define versions for future proofing

* remove defaults as should be covered by migrations in the future

* adds config migration

* spacing

* Review: remove unnecessary statements

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* make default ports the same

* test migration from future error

* linting

* Small tweaks

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
HarvsG 2024-06-18 21:24:36 +01:00 committed by GitHub
parent 9723b97f4b
commit adcd0cc2a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 246 additions and 22 deletions

View File

@ -3,9 +3,10 @@
from __future__ import annotations from __future__ import annotations
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.const import CONF_PORT
from homeassistant.core import _LOGGER, HomeAssistant
from .const import PLATFORMS from .const import CONF_PORT_IPV6, DEFAULT_PORT, PLATFORMS
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -25,3 +26,36 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload dnsip config entry.""" """Unload dnsip config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry to a newer version."""
if config_entry.version > 1:
# This means the user has downgraded from a future version
return False
if config_entry.version < 2 and config_entry.minor_version < 2:
version = config_entry.version
minor_version = config_entry.minor_version
_LOGGER.debug(
"Migrating configuration from version %s.%s",
version,
minor_version,
)
new_options = {**config_entry.options}
new_options[CONF_PORT] = DEFAULT_PORT
new_options[CONF_PORT_IPV6] = DEFAULT_PORT
hass.config_entries.async_update_entry(
config_entry, options=new_options, minor_version=2
)
_LOGGER.debug(
"Migration to configuration version %s.%s successful",
1,
2,
)
return True

View File

@ -16,7 +16,7 @@ from homeassistant.config_entries import (
ConfigFlowResult, ConfigFlowResult,
OptionsFlowWithConfigEntry, OptionsFlowWithConfigEntry,
) )
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -25,10 +25,12 @@ from .const import (
CONF_IPV4, CONF_IPV4,
CONF_IPV6, CONF_IPV6,
CONF_IPV6_V4, CONF_IPV6_V4,
CONF_PORT_IPV6,
CONF_RESOLVER, CONF_RESOLVER,
CONF_RESOLVER_IPV6, CONF_RESOLVER_IPV6,
DEFAULT_HOSTNAME, DEFAULT_HOSTNAME,
DEFAULT_NAME, DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_RESOLVER, DEFAULT_RESOLVER,
DEFAULT_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6,
DOMAIN, DOMAIN,
@ -42,32 +44,42 @@ DATA_SCHEMA = vol.Schema(
DATA_SCHEMA_ADV = vol.Schema( DATA_SCHEMA_ADV = vol.Schema(
{ {
vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string, vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
vol.Optional(CONF_RESOLVER, default=DEFAULT_RESOLVER): cv.string, vol.Optional(CONF_RESOLVER): cv.string,
vol.Optional(CONF_RESOLVER_IPV6, default=DEFAULT_RESOLVER_IPV6): cv.string, vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_RESOLVER_IPV6): cv.string,
vol.Optional(CONF_PORT_IPV6): cv.port,
} }
) )
async def async_validate_hostname( async def async_validate_hostname(
hostname: str, resolver_ipv4: str, resolver_ipv6: str hostname: str,
resolver_ipv4: str,
resolver_ipv6: str,
port: int,
port_ipv6: int,
) -> dict[str, bool]: ) -> dict[str, bool]:
"""Validate hostname.""" """Validate hostname."""
async def async_check(hostname: str, resolver: str, qtype: str) -> bool: async def async_check(
hostname: str, resolver: str, qtype: str, port: int = 53
) -> bool:
"""Return if able to resolve hostname.""" """Return if able to resolve hostname."""
result = False result = False
with contextlib.suppress(DNSError): with contextlib.suppress(DNSError):
result = bool( result = bool(
await aiodns.DNSResolver(nameservers=[resolver]).query(hostname, qtype) await aiodns.DNSResolver(
nameservers=[resolver], udp_port=port, tcp_port=port
).query(hostname, qtype)
) )
return result return result
result: dict[str, bool] = {} result: dict[str, bool] = {}
tasks = await asyncio.gather( tasks = await asyncio.gather(
async_check(hostname, resolver_ipv4, "A"), async_check(hostname, resolver_ipv4, "A", port=port),
async_check(hostname, resolver_ipv6, "AAAA"), async_check(hostname, resolver_ipv6, "AAAA", port=port_ipv6),
async_check(hostname, resolver_ipv4, "AAAA"), async_check(hostname, resolver_ipv4, "AAAA", port=port),
) )
result[CONF_IPV4] = tasks[0] result[CONF_IPV4] = tasks[0]
@ -81,6 +93,7 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for dnsip integration.""" """Handle a config flow for dnsip integration."""
VERSION = 1 VERSION = 1
MINOR_VERSION = 2
@staticmethod @staticmethod
@callback @callback
@ -102,8 +115,12 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname
resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER) resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER)
resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6) resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6)
port = user_input.get(CONF_PORT, DEFAULT_PORT)
port_ipv6 = user_input.get(CONF_PORT_IPV6, DEFAULT_PORT)
validate = await async_validate_hostname(hostname, resolver, resolver_ipv6) validate = await async_validate_hostname(
hostname, resolver, resolver_ipv6, port, port_ipv6
)
set_resolver = resolver set_resolver = resolver
if validate[CONF_IPV6]: if validate[CONF_IPV6]:
@ -129,7 +146,9 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
}, },
options={ options={
CONF_RESOLVER: resolver, CONF_RESOLVER: resolver,
CONF_PORT: port,
CONF_RESOLVER_IPV6: set_resolver, CONF_RESOLVER_IPV6: set_resolver,
CONF_PORT_IPV6: port_ipv6,
}, },
) )
@ -156,11 +175,15 @@ class DnsIPOptionsFlowHandler(OptionsFlowWithConfigEntry):
errors = {} errors = {}
if user_input is not None: if user_input is not None:
resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER) resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER)
port = user_input.get(CONF_PORT, DEFAULT_PORT)
resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6) resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6)
port_ipv6 = user_input.get(CONF_PORT_IPV6, DEFAULT_PORT)
validate = await async_validate_hostname( validate = await async_validate_hostname(
self.config_entry.data[CONF_HOSTNAME], self.config_entry.data[CONF_HOSTNAME],
resolver, resolver,
resolver_ipv6, resolver_ipv6,
port,
port_ipv6,
) )
if ( if (
@ -178,7 +201,9 @@ class DnsIPOptionsFlowHandler(OptionsFlowWithConfigEntry):
title=self.config_entry.title, title=self.config_entry.title,
data={ data={
CONF_RESOLVER: resolver, CONF_RESOLVER: resolver,
CONF_PORT: port,
CONF_RESOLVER_IPV6: resolver_ipv6, CONF_RESOLVER_IPV6: resolver_ipv6,
CONF_PORT_IPV6: port_ipv6,
}, },
) )
@ -186,7 +211,9 @@ class DnsIPOptionsFlowHandler(OptionsFlowWithConfigEntry):
vol.Schema( vol.Schema(
{ {
vol.Optional(CONF_RESOLVER): cv.string, vol.Optional(CONF_RESOLVER): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_RESOLVER_IPV6): cv.string, vol.Optional(CONF_RESOLVER_IPV6): cv.string,
vol.Optional(CONF_PORT_IPV6): cv.port,
} }
), ),
self.config_entry.options, self.config_entry.options,

View File

@ -8,6 +8,7 @@ PLATFORMS = [Platform.SENSOR]
CONF_HOSTNAME = "hostname" CONF_HOSTNAME = "hostname"
CONF_RESOLVER = "resolver" CONF_RESOLVER = "resolver"
CONF_RESOLVER_IPV6 = "resolver_ipv6" CONF_RESOLVER_IPV6 = "resolver_ipv6"
CONF_PORT_IPV6 = "port_ipv6"
CONF_IPV4 = "ipv4" CONF_IPV4 = "ipv4"
CONF_IPV6 = "ipv6" CONF_IPV6 = "ipv6"
CONF_IPV6_V4 = "ipv6_v4" CONF_IPV6_V4 = "ipv6_v4"
@ -16,4 +17,5 @@ DEFAULT_HOSTNAME = "myip.opendns.com"
DEFAULT_IPV6 = False DEFAULT_IPV6 = False
DEFAULT_NAME = "myip" DEFAULT_NAME = "myip"
DEFAULT_RESOLVER = "208.67.222.222" DEFAULT_RESOLVER = "208.67.222.222"
DEFAULT_PORT = 53
DEFAULT_RESOLVER_IPV6 = "2620:119:53::53" DEFAULT_RESOLVER_IPV6 = "2620:119:53::53"

View File

@ -11,7 +11,7 @@ from aiodns.error import DNSError
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -20,6 +20,7 @@ from .const import (
CONF_HOSTNAME, CONF_HOSTNAME,
CONF_IPV4, CONF_IPV4,
CONF_IPV6, CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER, CONF_RESOLVER,
CONF_RESOLVER_IPV6, CONF_RESOLVER_IPV6,
DOMAIN, DOMAIN,
@ -53,12 +54,14 @@ async def async_setup_entry(
resolver_ipv4 = entry.options[CONF_RESOLVER] resolver_ipv4 = entry.options[CONF_RESOLVER]
resolver_ipv6 = entry.options[CONF_RESOLVER_IPV6] resolver_ipv6 = entry.options[CONF_RESOLVER_IPV6]
port_ipv4 = entry.options[CONF_PORT]
port_ipv6 = entry.options[CONF_PORT_IPV6]
entities = [] entities = []
if entry.data[CONF_IPV4]: if entry.data[CONF_IPV4]:
entities.append(WanIpSensor(name, hostname, resolver_ipv4, False)) entities.append(WanIpSensor(name, hostname, resolver_ipv4, False, port_ipv4))
if entry.data[CONF_IPV6]: if entry.data[CONF_IPV6]:
entities.append(WanIpSensor(name, hostname, resolver_ipv6, True)) entities.append(WanIpSensor(name, hostname, resolver_ipv6, True, port_ipv6))
async_add_entities(entities, update_before_add=True) async_add_entities(entities, update_before_add=True)
@ -75,12 +78,13 @@ class WanIpSensor(SensorEntity):
hostname: str, hostname: str,
resolver: str, resolver: str,
ipv6: bool, ipv6: bool,
port: int,
) -> None: ) -> None:
"""Initialize the DNS IP sensor.""" """Initialize the DNS IP sensor."""
self._attr_name = "IPv6" if ipv6 else None self._attr_name = "IPv6" if ipv6 else None
self._attr_unique_id = f"{hostname}_{ipv6}" self._attr_unique_id = f"{hostname}_{ipv6}"
self.hostname = hostname self.hostname = hostname
self.resolver = aiodns.DNSResolver() self.resolver = aiodns.DNSResolver(tcp_port=port, udp_port=port)
self.resolver.nameservers = [resolver] self.resolver.nameservers = [resolver]
self.querytype = "AAAA" if ipv6 else "A" self.querytype = "AAAA" if ipv6 else "A"
self._retries = DEFAULT_RETRIES self._retries = DEFAULT_RETRIES

View File

@ -5,7 +5,9 @@
"data": { "data": {
"hostname": "The hostname for which to perform the DNS query", "hostname": "The hostname for which to perform the DNS query",
"resolver": "Resolver for IPV4 lookup", "resolver": "Resolver for IPV4 lookup",
"resolver_ipv6": "Resolver for IPV6 lookup" "port": "Port for IPV4 lookup",
"resolver_ipv6": "Resolver for IPV6 lookup",
"port_ipv6": "Port for IPV6 lookup"
} }
} }
}, },
@ -18,7 +20,9 @@
"init": { "init": {
"data": { "data": {
"resolver": "[%key:component::dnsip::config::step::user::data::resolver%]", "resolver": "[%key:component::dnsip::config::step::user::data::resolver%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data::resolver_ipv6%]" "port": "[%key:component::dnsip::config::step::user::data::port%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data::resolver_ipv6%]",
"port_ipv6": "[%key:component::dnsip::config::step::user::data::port_ipv6%]"
} }
} }
}, },
@ -26,7 +30,7 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}, },
"error": { "error": {
"invalid_resolver": "Invalid IP address for resolver" "invalid_resolver": "Invalid IP address or port for resolver"
} }
} }
} }

View File

@ -13,12 +13,13 @@ from homeassistant.components.dnsip.const import (
CONF_HOSTNAME, CONF_HOSTNAME,
CONF_IPV4, CONF_IPV4,
CONF_IPV6, CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER, CONF_RESOLVER,
CONF_RESOLVER_IPV6, CONF_RESOLVER_IPV6,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -66,6 +67,8 @@ async def test_form(hass: HomeAssistant) -> None:
assert result2["options"] == { assert result2["options"] == {
"resolver": "208.67.222.222", "resolver": "208.67.222.222",
"resolver_ipv6": "2620:119:53::53", "resolver_ipv6": "2620:119:53::53",
"port": 53,
"port_ipv6": 53,
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@ -96,6 +99,8 @@ async def test_form_adv(hass: HomeAssistant) -> None:
CONF_HOSTNAME: "home-assistant.io", CONF_HOSTNAME: "home-assistant.io",
CONF_RESOLVER: "8.8.8.8", CONF_RESOLVER: "8.8.8.8",
CONF_RESOLVER_IPV6: "2620:119:53::53", CONF_RESOLVER_IPV6: "2620:119:53::53",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -111,6 +116,8 @@ async def test_form_adv(hass: HomeAssistant) -> None:
assert result2["options"] == { assert result2["options"] == {
"resolver": "8.8.8.8", "resolver": "8.8.8.8",
"resolver_ipv6": "2620:119:53::53", "resolver_ipv6": "2620:119:53::53",
"port": 53,
"port_ipv6": 53,
} }
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@ -152,6 +159,8 @@ async def test_flow_already_exist(hass: HomeAssistant) -> None:
options={ options={
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::5", CONF_RESOLVER_IPV6: "2620:119:53::5",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
unique_id="home-assistant.io", unique_id="home-assistant.io",
).add_to_hass(hass) ).add_to_hass(hass)
@ -197,6 +206,8 @@ async def test_options_flow(hass: HomeAssistant) -> None:
options={ options={
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::5", CONF_RESOLVER_IPV6: "2620:119:53::5",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@ -218,6 +229,8 @@ async def test_options_flow(hass: HomeAssistant) -> None:
user_input={ user_input={
CONF_RESOLVER: "8.8.8.8", CONF_RESOLVER: "8.8.8.8",
CONF_RESOLVER_IPV6: "2001:4860:4860::8888", CONF_RESOLVER_IPV6: "2001:4860:4860::8888",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -226,6 +239,8 @@ async def test_options_flow(hass: HomeAssistant) -> None:
assert result["data"] == { assert result["data"] == {
"resolver": "8.8.8.8", "resolver": "8.8.8.8",
"resolver_ipv6": "2001:4860:4860::8888", "resolver_ipv6": "2001:4860:4860::8888",
"port": 53,
"port_ipv6": 53,
} }
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
@ -245,6 +260,8 @@ async def test_options_flow_empty_return(hass: HomeAssistant) -> None:
options={ options={
CONF_RESOLVER: "8.8.8.8", CONF_RESOLVER: "8.8.8.8",
CONF_RESOLVER_IPV6: "2620:119:53::1", CONF_RESOLVER_IPV6: "2620:119:53::1",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@ -271,6 +288,8 @@ async def test_options_flow_empty_return(hass: HomeAssistant) -> None:
assert result["data"] == { assert result["data"] == {
"resolver": "208.67.222.222", "resolver": "208.67.222.222",
"resolver_ipv6": "2620:119:53::53", "resolver_ipv6": "2620:119:53::53",
"port": 53,
"port_ipv6": 53,
} }
entry = hass.config_entries.async_get_entry(entry.entry_id) entry = hass.config_entries.async_get_entry(entry.entry_id)
@ -283,6 +302,8 @@ async def test_options_flow_empty_return(hass: HomeAssistant) -> None:
assert entry.options == { assert entry.options == {
"resolver": "208.67.222.222", "resolver": "208.67.222.222",
"resolver_ipv6": "2620:119:53::53", "resolver_ipv6": "2620:119:53::53",
"port": 53,
"port_ipv6": 53,
} }
@ -294,6 +315,8 @@ async def test_options_flow_empty_return(hass: HomeAssistant) -> None:
CONF_NAME: "home-assistant.io", CONF_NAME: "home-assistant.io",
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::5", CONF_RESOLVER_IPV6: "2620:119:53::5",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
CONF_IPV4: True, CONF_IPV4: True,
CONF_IPV6: False, CONF_IPV6: False,
}, },
@ -302,6 +325,8 @@ async def test_options_flow_empty_return(hass: HomeAssistant) -> None:
CONF_NAME: "home-assistant.io", CONF_NAME: "home-assistant.io",
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::5", CONF_RESOLVER_IPV6: "2620:119:53::5",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
CONF_IPV4: False, CONF_IPV4: False,
CONF_IPV6: True, CONF_IPV6: True,
}, },
@ -334,6 +359,8 @@ async def test_options_error(hass: HomeAssistant, p_input: dict[str, str]) -> No
{ {
CONF_RESOLVER: "192.168.200.34", CONF_RESOLVER: "192.168.200.34",
CONF_RESOLVER_IPV6: "2001:4860:4860::8888", CONF_RESOLVER_IPV6: "2001:4860:4860::8888",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -8,12 +8,14 @@ from homeassistant.components.dnsip.const import (
CONF_HOSTNAME, CONF_HOSTNAME,
CONF_IPV4, CONF_IPV4,
CONF_IPV6, CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER, CONF_RESOLVER,
CONF_RESOLVER_IPV6, CONF_RESOLVER_IPV6,
DEFAULT_PORT,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import RetrieveDNS from . import RetrieveDNS
@ -35,6 +37,8 @@ async def test_load_unload_entry(hass: HomeAssistant) -> None:
options={ options={
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53", CONF_RESOLVER_IPV6: "2620:119:53::53",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
entry_id="1", entry_id="1",
unique_id="home-assistant.io", unique_id="home-assistant.io",
@ -52,3 +56,77 @@ async def test_load_unload_entry(hass: HomeAssistant) -> None:
assert await hass.config_entries.async_unload(entry.entry_id) assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.NOT_LOADED assert entry.state is ConfigEntryState.NOT_LOADED
async def test_port_migration(
hass: HomeAssistant,
) -> None:
"""Test migration of the config entry from no ports to with ports."""
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_HOSTNAME: "home-assistant.io",
CONF_NAME: "home-assistant.io",
CONF_IPV4: True,
CONF_IPV6: True,
},
options={
CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53",
},
entry_id="1",
unique_id="home-assistant.io",
version=1,
minor_version=1,
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.dnsip.sensor.aiodns.DNSResolver",
return_value=RetrieveDNS(),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.version == 1
assert entry.minor_version == 2
assert entry.options[CONF_PORT] == DEFAULT_PORT
assert entry.options[CONF_PORT_IPV6] == DEFAULT_PORT
assert entry.state is ConfigEntryState.LOADED
async def test_migrate_error_from_future(hass: HomeAssistant) -> None:
"""Test a future version isn't migrated."""
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_HOSTNAME: "home-assistant.io",
CONF_NAME: "home-assistant.io",
CONF_IPV4: True,
CONF_IPV6: True,
"some_new_data": "new_value",
},
options={
CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53",
},
entry_id="1",
unique_id="home-assistant.io",
version=2,
minor_version=1,
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.dnsip.sensor.aiodns.DNSResolver",
return_value=RetrieveDNS(),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
entry = hass.config_entries.async_get_entry(entry.entry_id)
assert entry.state is ConfigEntryState.MIGRATION_ERROR

View File

@ -12,13 +12,14 @@ from homeassistant.components.dnsip.const import (
CONF_HOSTNAME, CONF_HOSTNAME,
CONF_IPV4, CONF_IPV4,
CONF_IPV6, CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER, CONF_RESOLVER,
CONF_RESOLVER_IPV6, CONF_RESOLVER_IPV6,
DOMAIN, DOMAIN,
) )
from homeassistant.components.dnsip.sensor import SCAN_INTERVAL from homeassistant.components.dnsip.sensor import SCAN_INTERVAL
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE from homeassistant.const import CONF_NAME, CONF_PORT, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import RetrieveDNS from . import RetrieveDNS
@ -40,6 +41,8 @@ async def test_sensor(hass: HomeAssistant) -> None:
options={ options={
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53", CONF_RESOLVER_IPV6: "2620:119:53::53",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
entry_id="1", entry_id="1",
unique_id="home-assistant.io", unique_id="home-assistant.io",
@ -67,6 +70,49 @@ async def test_sensor(hass: HomeAssistant) -> None:
] ]
async def test_legacy_sensor(hass: HomeAssistant) -> None:
"""Test the DNS IP sensor configured before the addition of ports."""
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={
CONF_HOSTNAME: "home-assistant.io",
CONF_NAME: "home-assistant.io",
CONF_IPV4: True,
CONF_IPV6: True,
},
options={
CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53",
},
entry_id="1",
unique_id="home-assistant.io",
version=1,
minor_version=1,
)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.dnsip.sensor.aiodns.DNSResolver",
return_value=RetrieveDNS(),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state1 = hass.states.get("sensor.home_assistant_io")
state2 = hass.states.get("sensor.home_assistant_io_ipv6")
assert state1.state == "1.1.1.1"
assert state1.attributes["ip_addresses"] == ["1.1.1.1", "1.2.3.4"]
assert state2.state == "2001:db8::77:dead:beef"
assert state2.attributes["ip_addresses"] == [
"2001:db8::77:dead:beef",
"2001:db8:66::dead:beef",
"2001:db8:77::dead:beef",
"2001:db8:77::face:b00c",
]
async def test_sensor_no_response( async def test_sensor_no_response(
hass: HomeAssistant, freezer: FrozenDateTimeFactory hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None: ) -> None:
@ -83,6 +129,8 @@ async def test_sensor_no_response(
options={ options={
CONF_RESOLVER: "208.67.222.222", CONF_RESOLVER: "208.67.222.222",
CONF_RESOLVER_IPV6: "2620:119:53::53", CONF_RESOLVER_IPV6: "2620:119:53::53",
CONF_PORT: 53,
CONF_PORT_IPV6: 53,
}, },
entry_id="1", entry_id="1",
unique_id="home-assistant.io", unique_id="home-assistant.io",