Skip to content

Commit

Permalink
Revert "Remove Workday YAML configuration (home-assistant#94102)" (ho…
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck authored Jun 29, 2023
1 parent ed16fff commit 34ac541
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 2 deletions.
82 changes: 80 additions & 2 deletions homeassistant/components/workday/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@
from __future__ import annotations

from datetime import date, timedelta
from typing import Any

import holidays
from holidays import DateLike, HolidayBase
import voluptuous as vol

from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
BinarySensorEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util

from .const import (
Expand All @@ -24,11 +32,81 @@
CONF_PROVINCE,
CONF_REMOVE_HOLIDAYS,
CONF_WORKDAYS,
DEFAULT_EXCLUDES,
DEFAULT_NAME,
DEFAULT_OFFSET,
DEFAULT_WORKDAYS,
DOMAIN,
LOGGER,
)


def valid_country(value: Any) -> str:
"""Validate that the given country is supported."""
value = cv.string(value)
all_supported_countries = holidays.list_supported_countries()

try:
raw_value = value.encode("utf-8")
except UnicodeError as err:
raise vol.Invalid(
"The country name or the abbreviation must be a valid UTF-8 string."
) from err
if not raw_value:
raise vol.Invalid("Country name or the abbreviation must not be empty.")
if value not in all_supported_countries:
raise vol.Invalid("Country is not supported.")
return value


PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_COUNTRY): valid_country,
vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): vol.All(
cv.ensure_list, [vol.In(ALLOWED_DAYS)]
),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
vol.Optional(CONF_PROVINCE): cv.string,
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): vol.All(
cv.ensure_list, [vol.In(ALLOWED_DAYS)]
),
vol.Optional(CONF_ADD_HOLIDAYS, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(CONF_REMOVE_HOLIDAYS, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
}
)


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Workday sensor."""
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2023.11.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
)

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
Expand Down
27 changes: 27 additions & 0 deletions homeassistant/components/workday/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,33 @@ def async_get_options_flow(
"""Get the options flow for this handler."""
return WorkdayOptionsFlowHandler(config_entry)

async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Import a configuration from config.yaml."""

abort_match = {
CONF_COUNTRY: config[CONF_COUNTRY],
CONF_EXCLUDES: config[CONF_EXCLUDES],
CONF_OFFSET: config[CONF_OFFSET],
CONF_WORKDAYS: config[CONF_WORKDAYS],
CONF_ADD_HOLIDAYS: config[CONF_ADD_HOLIDAYS],
CONF_REMOVE_HOLIDAYS: config[CONF_REMOVE_HOLIDAYS],
CONF_PROVINCE: config.get(CONF_PROVINCE),
}
new_config = config.copy()
new_config[CONF_PROVINCE] = config.get(CONF_PROVINCE)
LOGGER.debug("Importing with %s", new_config)

self._async_abort_entries_match(abort_match)

self.data[CONF_NAME] = config.get(CONF_NAME, DEFAULT_NAME)
self.data[CONF_COUNTRY] = config[CONF_COUNTRY]
LOGGER.debug(
"No duplicate, next step with name %s for country %s",
self.data[CONF_NAME],
self.data[CONF_COUNTRY],
)
return await self.async_step_options(user_input=new_config)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/workday/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
"already_configured": "Service with this configuration already exist"
}
},
"issues": {
"deprecated_yaml": {
"title": "The Workday YAML configuration is being removed",
"description": "Configuring Workday using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Workday YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
}
},
"selector": {
"province": {
"options": {
Expand Down
45 changes: 45 additions & 0 deletions tests/components/workday/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

from freezegun.api import FrozenDateTimeFactory
import pytest
import voluptuous as vol

from homeassistant.components.workday import binary_sensor
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import UTC
Expand All @@ -28,6 +30,21 @@
)


async def test_valid_country_yaml() -> None:
"""Test valid country from yaml."""
# Invalid UTF-8, must not contain U+D800 to U+DFFF
with pytest.raises(vol.Invalid):
binary_sensor.valid_country("\ud800")
with pytest.raises(vol.Invalid):
binary_sensor.valid_country("\udfff")
# Country MUST NOT be empty
with pytest.raises(vol.Invalid):
binary_sensor.valid_country("")
# Country must be supported by holidays
with pytest.raises(vol.Invalid):
binary_sensor.valid_country("HomeAssistantLand")


@pytest.mark.parametrize(
("config", "expected_state"),
[
Expand Down Expand Up @@ -62,6 +79,34 @@ async def test_setup(
}


async def test_setup_from_import(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test setup from various configs."""
freezer.move_to(datetime(2022, 4, 15, 12, tzinfo=UTC)) # Monday
await async_setup_component(
hass,
"binary_sensor",
{
"binary_sensor": {
"platform": "workday",
"country": "DE",
}
},
)
await hass.async_block_till_done()

state = hass.states.get("binary_sensor.workday_sensor")
assert state.state == "off"
assert state.attributes == {
"friendly_name": "Workday Sensor",
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
}


async def test_setup_with_invalid_province_from_yaml(hass: HomeAssistant) -> None:
"""Test setup invalid province with import."""

Expand Down
139 changes: 139 additions & 0 deletions tests/components/workday/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CONF_REMOVE_HOLIDAYS,
CONF_WORKDAYS,
DEFAULT_EXCLUDES,
DEFAULT_NAME,
DEFAULT_OFFSET,
DEFAULT_WORKDAYS,
DOMAIN,
Expand All @@ -23,6 +24,8 @@

from . import init_integration

from tests.common import MockConfigEntry

pytestmark = pytest.mark.usefixtures("mock_setup_entry")


Expand Down Expand Up @@ -111,6 +114,142 @@ async def test_form_no_subdivision(hass: HomeAssistant) -> None:
}


async def test_import_flow_success(hass: HomeAssistant) -> None:
"""Test a successful import of yaml."""

result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_NAME: DEFAULT_NAME,
CONF_COUNTRY: "DE",
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: [],
CONF_REMOVE_HOLIDAYS: [],
},
)
await hass.async_block_till_done()

assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Workday Sensor"
assert result["options"] == {
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": None,
}

result2 = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_NAME: "Workday Sensor 2",
CONF_COUNTRY: "DE",
CONF_PROVINCE: "BW",
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: [],
CONF_REMOVE_HOLIDAYS: [],
},
)
await hass.async_block_till_done()

assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Workday Sensor 2"
assert result2["options"] == {
"name": "Workday Sensor 2",
"country": "DE",
"province": "BW",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
}


async def test_import_flow_already_exist(hass: HomeAssistant) -> None:
"""Test import of yaml already exist."""

entry = MockConfigEntry(
domain=DOMAIN,
data={},
options={
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": None,
},
)
entry.add_to_hass(hass)

result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_NAME: "Workday sensor 2",
CONF_COUNTRY: "DE",
CONF_EXCLUDES: ["sat", "sun", "holiday"],
CONF_OFFSET: 0,
CONF_WORKDAYS: ["mon", "tue", "wed", "thu", "fri"],
CONF_ADD_HOLIDAYS: [],
CONF_REMOVE_HOLIDAYS: [],
},
)
await hass.async_block_till_done()

assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"


async def test_import_flow_province_no_conflict(hass: HomeAssistant) -> None:
"""Test import of yaml with province."""

entry = MockConfigEntry(
domain=DOMAIN,
data={},
options={
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
},
)
entry.add_to_hass(hass)

result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_NAME: "Workday sensor 2",
CONF_COUNTRY: "DE",
CONF_PROVINCE: "BW",
CONF_EXCLUDES: ["sat", "sun", "holiday"],
CONF_OFFSET: 0,
CONF_WORKDAYS: ["mon", "tue", "wed", "thu", "fri"],
CONF_ADD_HOLIDAYS: [],
CONF_REMOVE_HOLIDAYS: [],
},
)
await hass.async_block_till_done()

assert result["type"] == FlowResultType.CREATE_ENTRY


async def test_options_form(hass: HomeAssistant) -> None:
"""Test we get the form in options."""

Expand Down

0 comments on commit 34ac541

Please sign in to comment.