Bump pycfdns from 2.0.1 to 3.0.0 (#103426)

This commit is contained in:
Joakim Sørensen 2023-11-06 11:05:44 +01:00 committed by GitHub
parent 779b19ca46
commit 6d567c3e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 225 additions and 135 deletions

View File

@ -1,17 +1,12 @@
"""Update the IP addresses of your Cloudflare DNS records.""" """Update the IP addresses of your Cloudflare DNS records."""
from __future__ import annotations from __future__ import annotations
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from aiohttp import ClientSession from aiohttp import ClientSession
from pycfdns import CloudflareUpdater import pycfdns
from pycfdns.exceptions import (
CloudflareAuthenticationException,
CloudflareConnectionException,
CloudflareException,
CloudflareZoneException,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_TOKEN, CONF_ZONE from homeassistant.const import CONF_API_TOKEN, CONF_ZONE
@ -37,32 +32,43 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Cloudflare from a config entry.""" """Set up Cloudflare from a config entry."""
session = async_get_clientsession(hass) session = async_get_clientsession(hass)
cfupdate = CloudflareUpdater( client = pycfdns.Client(
session, api_token=entry.data[CONF_API_TOKEN],
entry.data[CONF_API_TOKEN], client_session=session,
entry.data[CONF_ZONE],
entry.data[CONF_RECORDS],
) )
try: try:
zone_id = await cfupdate.get_zone_id() dns_zones = await client.list_zones()
except CloudflareAuthenticationException as error: dns_zone = next(
zone for zone in dns_zones if zone["name"] == entry.data[CONF_ZONE]
)
except pycfdns.AuthenticationException as error:
raise ConfigEntryAuthFailed from error raise ConfigEntryAuthFailed from error
except (CloudflareConnectionException, CloudflareZoneException) as error: except pycfdns.ComunicationException as error:
raise ConfigEntryNotReady from error raise ConfigEntryNotReady from error
async def update_records(now): async def update_records(now):
"""Set up recurring update.""" """Set up recurring update."""
try: try:
await _async_update_cloudflare(session, cfupdate, zone_id) await _async_update_cloudflare(
except CloudflareException as error: session, client, dns_zone, entry.data[CONF_RECORDS]
)
except (
pycfdns.AuthenticationException,
pycfdns.ComunicationException,
) as error:
_LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error) _LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error)
async def update_records_service(call: ServiceCall) -> None: async def update_records_service(call: ServiceCall) -> None:
"""Set up service for manual trigger.""" """Set up service for manual trigger."""
try: try:
await _async_update_cloudflare(session, cfupdate, zone_id) await _async_update_cloudflare(
except CloudflareException as error: session, client, dns_zone, entry.data[CONF_RECORDS]
)
except (
pycfdns.AuthenticationException,
pycfdns.ComunicationException,
) as error:
_LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error) _LOGGER.error("Error updating zone %s: %s", entry.data[CONF_ZONE], error)
update_interval = timedelta(minutes=DEFAULT_UPDATE_INTERVAL) update_interval = timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
@ -87,12 +93,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _async_update_cloudflare( async def _async_update_cloudflare(
session: ClientSession, session: ClientSession,
cfupdate: CloudflareUpdater, client: pycfdns.Client,
zone_id: str, dns_zone: pycfdns.ZoneModel,
target_records: list[str],
) -> None: ) -> None:
_LOGGER.debug("Starting update for zone %s", cfupdate.zone) _LOGGER.debug("Starting update for zone %s", dns_zone["name"])
records = await cfupdate.get_record_info(zone_id) records = await client.list_dns_records(zone_id=dns_zone["id"], type="A")
_LOGGER.debug("Records: %s", records) _LOGGER.debug("Records: %s", records)
location_info = await async_detect_location_info(session) location_info = await async_detect_location_info(session)
@ -100,5 +107,28 @@ async def _async_update_cloudflare(
if not location_info or not is_ipv4_address(location_info.ip): if not location_info or not is_ipv4_address(location_info.ip):
raise HomeAssistantError("Could not get external IPv4 address") raise HomeAssistantError("Could not get external IPv4 address")
await cfupdate.update_records(zone_id, records, location_info.ip) filtered_records = [
_LOGGER.debug("Update for zone %s is complete", cfupdate.zone) record
for record in records
if record["name"] in target_records and record["content"] != location_info.ip
]
if len(filtered_records) == 0:
_LOGGER.debug("All target records are up to date")
return
await asyncio.gather(
*[
client.update_dns_record(
zone_id=dns_zone["id"],
record_id=record["id"],
record_content=location_info.ip,
record_name=record["name"],
record_type=record["type"],
record_proxied=record["proxied"],
)
for record in filtered_records
]
)
_LOGGER.debug("Update for zone %s is complete", dns_zone["name"])

View File

@ -5,12 +5,7 @@ from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
from pycfdns import CloudflareUpdater import pycfdns
from pycfdns.exceptions import (
CloudflareAuthenticationException,
CloudflareConnectionException,
CloudflareZoneException,
)
import voluptuous as vol import voluptuous as vol
from homeassistant.components import persistent_notification from homeassistant.components import persistent_notification
@ -23,6 +18,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_RECORDS, DOMAIN from .const import CONF_RECORDS, DOMAIN
from .helpers import get_zone_id
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -33,54 +29,45 @@ DATA_SCHEMA = vol.Schema(
) )
def _zone_schema(zones: list[str] | None = None) -> vol.Schema: def _zone_schema(zones: list[pycfdns.ZoneModel] | None = None) -> vol.Schema:
"""Zone selection schema.""" """Zone selection schema."""
zones_list = [] zones_list = []
if zones is not None: if zones is not None:
zones_list = zones zones_list = [zones["name"] for zones in zones]
return vol.Schema({vol.Required(CONF_ZONE): vol.In(zones_list)}) return vol.Schema({vol.Required(CONF_ZONE): vol.In(zones_list)})
def _records_schema(records: list[str] | None = None) -> vol.Schema: def _records_schema(records: list[pycfdns.RecordModel] | None = None) -> vol.Schema:
"""Zone records selection schema.""" """Zone records selection schema."""
records_dict = {} records_dict = {}
if records: if records:
records_dict = {name: name for name in records} records_dict = {name["name"]: name["name"] for name in records}
return vol.Schema({vol.Required(CONF_RECORDS): cv.multi_select(records_dict)}) return vol.Schema({vol.Required(CONF_RECORDS): cv.multi_select(records_dict)})
async def _validate_input( async def _validate_input(
hass: HomeAssistant, data: dict[str, Any] hass: HomeAssistant,
) -> dict[str, list[str] | None]: data: dict[str, Any],
) -> dict[str, Any]:
"""Validate the user input allows us to connect. """Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user. Data has the keys from DATA_SCHEMA with values provided by the user.
""" """
zone = data.get(CONF_ZONE) zone = data.get(CONF_ZONE)
records: list[str] | None = None records: list[pycfdns.RecordModel] = []
cfupdate = CloudflareUpdater( client = pycfdns.Client(
async_get_clientsession(hass), api_token=data[CONF_API_TOKEN],
data[CONF_API_TOKEN], client_session=async_get_clientsession(hass),
zone,
[],
) )
try: zones = await client.list_zones()
zones: list[str] | None = await cfupdate.get_zones() if zone and (zone_id := get_zone_id(zone, zones)) is not None:
if zone: records = await client.list_dns_records(zone_id=zone_id, type="A")
zone_id = await cfupdate.get_zone_id()
records = await cfupdate.get_zone_records(zone_id, "A")
except CloudflareConnectionException as error:
raise CannotConnect from error
except CloudflareAuthenticationException as error:
raise InvalidAuth from error
except CloudflareZoneException as error:
raise InvalidZone from error
return {"zones": zones, "records": records} return {"zones": zones, "records": records}
@ -95,8 +82,8 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the Cloudflare config flow.""" """Initialize the Cloudflare config flow."""
self.cloudflare_config: dict[str, Any] = {} self.cloudflare_config: dict[str, Any] = {}
self.zones: list[str] | None = None self.zones: list[pycfdns.ZoneModel] | None = None
self.records: list[str] | None = None self.records: list[pycfdns.RecordModel] | None = None
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle initiation of re-authentication with Cloudflare.""" """Handle initiation of re-authentication with Cloudflare."""
@ -195,18 +182,16 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN):
async def _async_validate_or_error( async def _async_validate_or_error(
self, config: dict[str, Any] self, config: dict[str, Any]
) -> tuple[dict[str, list[str] | None], dict[str, str]]: ) -> tuple[dict[str, list[Any]], dict[str, str]]:
errors: dict[str, str] = {} errors: dict[str, str] = {}
info = {} info = {}
try: try:
info = await _validate_input(self.hass, config) info = await _validate_input(self.hass, config)
except CannotConnect: except pycfdns.ComunicationException:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuth: except pycfdns.AuthenticationException:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except InvalidZone:
errors["base"] = "invalid_zone"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
@ -220,7 +205,3 @@ class CannotConnect(HomeAssistantError):
class InvalidAuth(HomeAssistantError): class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth.""" """Error to indicate there is invalid auth."""
class InvalidZone(HomeAssistantError):
"""Error to indicate we cannot validate zone exists in account."""

View File

@ -0,0 +1,10 @@
"""Helpers for the CloudFlare integration."""
import pycfdns
def get_zone_id(target_zone_name: str, zones: list[pycfdns.ZoneModel]) -> str | None:
"""Get the zone ID for the target zone name."""
for zone in zones:
if zone["name"] == target_zone_name:
return zone["id"]
return None

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/cloudflare", "documentation": "https://www.home-assistant.io/integrations/cloudflare",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["pycfdns"], "loggers": ["pycfdns"],
"requirements": ["pycfdns==2.0.1"] "requirements": ["pycfdns==3.0.0"]
} }

View File

@ -30,8 +30,7 @@
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
"invalid_zone": "Invalid zone"
}, },
"abort": { "abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",

View File

@ -1630,7 +1630,7 @@ pybravia==0.3.3
pycarwings2==2.14 pycarwings2==2.14
# homeassistant.components.cloudflare # homeassistant.components.cloudflare
pycfdns==2.0.1 pycfdns==3.0.0
# homeassistant.components.channels # homeassistant.components.channels
pychannels==1.2.3 pychannels==1.2.3

View File

@ -1242,7 +1242,7 @@ pybotvac==0.0.24
pybravia==0.3.3 pybravia==0.3.3
# homeassistant.components.cloudflare # homeassistant.components.cloudflare
pycfdns==2.0.1 pycfdns==3.0.0
# homeassistant.components.comfoconnect # homeassistant.components.comfoconnect
pycomfoconnect==0.5.1 pycomfoconnect==0.5.1

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from pycfdns import CFRecord import pycfdns
from homeassistant.components.cloudflare.const import CONF_RECORDS, DOMAIN from homeassistant.components.cloudflare.const import CONF_RECORDS, DOMAIN
from homeassistant.const import CONF_API_TOKEN, CONF_ZONE from homeassistant.const import CONF_API_TOKEN, CONF_ZONE
@ -26,9 +26,8 @@ USER_INPUT_ZONE = {CONF_ZONE: "mock.com"}
USER_INPUT_RECORDS = {CONF_RECORDS: ["ha.mock.com", "homeassistant.mock.com"]} USER_INPUT_RECORDS = {CONF_RECORDS: ["ha.mock.com", "homeassistant.mock.com"]}
MOCK_ZONE = "mock.com" MOCK_ZONE: pycfdns.ZoneModel = {"name": "mock.com", "id": "mock-zone-id"}
MOCK_ZONE_ID = "mock-zone-id" MOCK_ZONE_RECORDS: list[pycfdns.RecordModel] = [
MOCK_ZONE_RECORDS = [
{ {
"id": "zone-record-id", "id": "zone-record-id",
"type": "A", "type": "A",
@ -77,21 +76,12 @@ async def init_integration(
return entry return entry
def _get_mock_cfupdate( def _get_mock_client(zone: str = MOCK_ZONE, records: list = MOCK_ZONE_RECORDS):
zone: str = MOCK_ZONE, client: pycfdns.Client = AsyncMock()
zone_id: str = MOCK_ZONE_ID,
records: list = MOCK_ZONE_RECORDS,
):
client = AsyncMock()
zone_records = [record["name"] for record in records] client.list_zones = AsyncMock(return_value=[zone])
cf_records = [CFRecord(record) for record in records] client.list_dns_records = AsyncMock(return_value=records)
client.update_dns_record = AsyncMock(return_value=None)
client.get_zones = AsyncMock(return_value=[zone])
client.get_zone_records = AsyncMock(return_value=zone_records)
client.get_record_info = AsyncMock(return_value=cf_records)
client.get_zone_id = AsyncMock(return_value=zone_id)
client.update_records = AsyncMock(return_value=None)
return client return client

View File

@ -3,15 +3,15 @@ from unittest.mock import patch
import pytest import pytest
from . import _get_mock_cfupdate from . import _get_mock_client
@pytest.fixture @pytest.fixture
def cfupdate(hass): def cfupdate(hass):
"""Mock the CloudflareUpdater for easier testing.""" """Mock the CloudflareUpdater for easier testing."""
mock_cfupdate = _get_mock_cfupdate() mock_cfupdate = _get_mock_client()
with patch( with patch(
"homeassistant.components.cloudflare.CloudflareUpdater", "homeassistant.components.cloudflare.pycfdns.Client",
return_value=mock_cfupdate, return_value=mock_cfupdate,
) as mock_api: ) as mock_api:
yield mock_api yield mock_api
@ -20,9 +20,9 @@ def cfupdate(hass):
@pytest.fixture @pytest.fixture
def cfupdate_flow(hass): def cfupdate_flow(hass):
"""Mock the CloudflareUpdater for easier config flow testing.""" """Mock the CloudflareUpdater for easier config flow testing."""
mock_cfupdate = _get_mock_cfupdate() mock_cfupdate = _get_mock_client()
with patch( with patch(
"homeassistant.components.cloudflare.config_flow.CloudflareUpdater", "homeassistant.components.cloudflare.pycfdns.Client",
return_value=mock_cfupdate, return_value=mock_cfupdate,
) as mock_api: ) as mock_api:
yield mock_api yield mock_api

View File

@ -1,9 +1,5 @@
"""Test the Cloudflare config flow.""" """Test the Cloudflare config flow."""
from pycfdns.exceptions import ( import pycfdns
CloudflareAuthenticationException,
CloudflareConnectionException,
CloudflareZoneException,
)
from homeassistant.components.cloudflare.const import CONF_RECORDS, DOMAIN from homeassistant.components.cloudflare.const import CONF_RECORDS, DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
@ -81,7 +77,7 @@ async def test_user_form_cannot_connect(hass: HomeAssistant, cfupdate_flow) -> N
DOMAIN, context={CONF_SOURCE: SOURCE_USER} DOMAIN, context={CONF_SOURCE: SOURCE_USER}
) )
instance.get_zones.side_effect = CloudflareConnectionException() instance.list_zones.side_effect = pycfdns.ComunicationException()
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
@ -99,7 +95,7 @@ async def test_user_form_invalid_auth(hass: HomeAssistant, cfupdate_flow) -> Non
DOMAIN, context={CONF_SOURCE: SOURCE_USER} DOMAIN, context={CONF_SOURCE: SOURCE_USER}
) )
instance.get_zones.side_effect = CloudflareAuthenticationException() instance.list_zones.side_effect = pycfdns.AuthenticationException()
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,
@ -109,24 +105,6 @@ async def test_user_form_invalid_auth(hass: HomeAssistant, cfupdate_flow) -> Non
assert result["errors"] == {"base": "invalid_auth"} assert result["errors"] == {"base": "invalid_auth"}
async def test_user_form_invalid_zone(hass: HomeAssistant, cfupdate_flow) -> None:
"""Test we handle invalid zone error."""
instance = cfupdate_flow.return_value
result = await hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_USER}
)
instance.get_zones.side_effect = CloudflareZoneException()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
USER_INPUT,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "invalid_zone"}
async def test_user_form_unexpected_exception( async def test_user_form_unexpected_exception(
hass: HomeAssistant, cfupdate_flow hass: HomeAssistant, cfupdate_flow
) -> None: ) -> None:
@ -137,7 +115,7 @@ async def test_user_form_unexpected_exception(
DOMAIN, context={CONF_SOURCE: SOURCE_USER} DOMAIN, context={CONF_SOURCE: SOURCE_USER}
) )
instance.get_zones.side_effect = Exception() instance.list_zones.side_effect = Exception()
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
USER_INPUT, USER_INPUT,

View File

@ -0,0 +1,13 @@
"""Test Cloudflare integration helpers."""
from homeassistant.components.cloudflare.helpers import get_zone_id
def test_get_zone_id():
"""Test get_zone_id."""
zones = [
{"id": "1", "name": "example.com"},
{"id": "2", "name": "example.org"},
]
assert get_zone_id("example.com", zones) == "1"
assert get_zone_id("example.org", zones) == "2"
assert get_zone_id("example.net", zones) is None

View File

@ -1,22 +1,25 @@
"""Test the Cloudflare integration.""" """Test the Cloudflare integration."""
from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from pycfdns.exceptions import ( import pycfdns
CloudflareAuthenticationException,
CloudflareConnectionException,
CloudflareZoneException,
)
import pytest import pytest
from homeassistant.components.cloudflare.const import DOMAIN, SERVICE_UPDATE_RECORDS from homeassistant.components.cloudflare.const import (
CONF_RECORDS,
DEFAULT_UPDATE_INTERVAL,
DOMAIN,
SERVICE_UPDATE_RECORDS,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.util.dt as dt_util
from homeassistant.util.location import LocationInfo from homeassistant.util.location import LocationInfo
from . import ENTRY_CONFIG, init_integration from . import ENTRY_CONFIG, init_integration
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, async_fire_time_changed
async def test_unload_entry(hass: HomeAssistant, cfupdate) -> None: async def test_unload_entry(hass: HomeAssistant, cfupdate) -> None:
@ -35,10 +38,7 @@ async def test_unload_entry(hass: HomeAssistant, cfupdate) -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"side_effect", "side_effect",
( (pycfdns.ComunicationException(),),
CloudflareConnectionException(),
CloudflareZoneException(),
),
) )
async def test_async_setup_raises_entry_not_ready( async def test_async_setup_raises_entry_not_ready(
hass: HomeAssistant, cfupdate, side_effect hass: HomeAssistant, cfupdate, side_effect
@ -49,7 +49,7 @@ async def test_async_setup_raises_entry_not_ready(
entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG) entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG)
entry.add_to_hass(hass) entry.add_to_hass(hass)
instance.get_zone_id.side_effect = side_effect instance.list_zones.side_effect = side_effect
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_RETRY assert entry.state is ConfigEntryState.SETUP_RETRY
@ -64,7 +64,7 @@ async def test_async_setup_raises_entry_auth_failed(
entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG) entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG)
entry.add_to_hass(hass) entry.add_to_hass(hass)
instance.get_zone_id.side_effect = CloudflareAuthenticationException() instance.list_zones.side_effect = pycfdns.AuthenticationException()
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_ERROR assert entry.state is ConfigEntryState.SETUP_ERROR
@ -81,7 +81,7 @@ async def test_async_setup_raises_entry_auth_failed(
assert flow["context"]["entry_id"] == entry.entry_id assert flow["context"]["entry_id"] == entry.entry_id
async def test_integration_services(hass: HomeAssistant, cfupdate) -> None: async def test_integration_services(hass: HomeAssistant, cfupdate, caplog) -> None:
"""Test integration services.""" """Test integration services."""
instance = cfupdate.return_value instance = cfupdate.return_value
@ -112,7 +112,8 @@ async def test_integration_services(hass: HomeAssistant, cfupdate) -> None:
) )
await hass.async_block_till_done() await hass.async_block_till_done()
instance.update_records.assert_called_once() assert len(instance.update_dns_record.mock_calls) == 2
assert "All target records are up to date" not in caplog.text
async def test_integration_services_with_issue(hass: HomeAssistant, cfupdate) -> None: async def test_integration_services_with_issue(hass: HomeAssistant, cfupdate) -> None:
@ -134,4 +135,92 @@ async def test_integration_services_with_issue(hass: HomeAssistant, cfupdate) ->
) )
await hass.async_block_till_done() await hass.async_block_till_done()
instance.update_records.assert_not_called() instance.update_dns_record.assert_not_called()
async def test_integration_services_with_nonexisting_record(
hass: HomeAssistant, cfupdate, caplog
) -> None:
"""Test integration services."""
instance = cfupdate.return_value
entry = await init_integration(
hass, data={**ENTRY_CONFIG, CONF_RECORDS: ["nonexisting.example.com"]}
)
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.cloudflare.async_detect_location_info",
return_value=LocationInfo(
"0.0.0.0",
"US",
"USD",
"CA",
"California",
"San Diego",
"92122",
"America/Los_Angeles",
32.8594,
-117.2073,
True,
),
):
await hass.services.async_call(
DOMAIN,
SERVICE_UPDATE_RECORDS,
{},
blocking=True,
)
await hass.async_block_till_done()
instance.update_dns_record.assert_not_called()
assert "All target records are up to date" in caplog.text
async def test_integration_update_interval(
hass: HomeAssistant,
cfupdate,
caplog,
) -> None:
"""Test integration update interval."""
instance = cfupdate.return_value
entry = await init_integration(hass)
assert entry.state is ConfigEntryState.LOADED
with patch(
"homeassistant.components.cloudflare.async_detect_location_info",
return_value=LocationInfo(
"0.0.0.0",
"US",
"USD",
"CA",
"California",
"San Diego",
"92122",
"America/Los_Angeles",
32.8594,
-117.2073,
True,
),
):
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
assert len(instance.update_dns_record.mock_calls) == 2
assert "All target records are up to date" not in caplog.text
instance.list_dns_records.side_effect = pycfdns.AuthenticationException()
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
assert len(instance.update_dns_record.mock_calls) == 2
instance.list_dns_records.side_effect = pycfdns.ComunicationException()
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
)
await hass.async_block_till_done()
assert len(instance.update_dns_record.mock_calls) == 2