Add config flow to Workday (#72558)

* Initial commit Workday Config Flow

* Add tests

* Remove day_to_string

* new entity name, new depr. version, clean

* Use repairs for depr. warning

* Fix issue_registry moved

* tweaks

* hassfest

* Fix CI

* FlowResultType

* breaking version

* remove translation

* Fixes

* naming

* duplicates

* abort entries match

* add_suggested_values_to_schema

* various

* validate country

* abort_entries_match in option flow

* Remove country test

* remove country not exist string

* docstring exceptions

* easier

* break version

* unneeded check

* slim tests

* Fix import test

* Fix province in abort_match

* review comments

* Fix import province

* Add review fixes

* fix reviews

* Review fixes
This commit is contained in:
G Johansson
2023-04-19 11:50:11 +02:00
committed by GitHub
parent a511e7d6bc
commit f74103c57e
13 changed files with 1093 additions and 20 deletions
+20 -5
View File
@@ -8,22 +8,37 @@ from homeassistant.components.workday.const import (
DEFAULT_NAME,
DEFAULT_OFFSET,
DEFAULT_WORKDAYS,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def init_integration(
hass: HomeAssistant,
config: dict[str, Any],
) -> None:
"""Set up the Workday integration in Home Assistant."""
entry_id: str = "1",
source: str = SOURCE_USER,
) -> MockConfigEntry:
"""Set up the Scrape integration in Home Assistant."""
await async_setup_component(
hass, "binary_sensor", {"binary_sensor": {"platform": "workday", **config}}
config_entry = MockConfigEntry(
domain=DOMAIN,
source=source,
data={},
options=config,
entry_id=entry_id,
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
TEST_CONFIG_WITH_PROVINCE = {
"name": DEFAULT_NAME,
+14
View File
@@ -0,0 +1,14 @@
"""Fixtures for Workday integration tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.workday.async_setup_entry", return_value=True
) as mock_setup:
yield mock_setup
@@ -79,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."""
@@ -0,0 +1,488 @@
"""Test the Workday config flow."""
from __future__ import annotations
import pytest
from homeassistant import config_entries
from homeassistant.components.workday.const import (
CONF_ADD_HOLIDAYS,
CONF_COUNTRY,
CONF_EXCLUDES,
CONF_OFFSET,
CONF_PROVINCE,
CONF_REMOVE_HOLIDAYS,
CONF_WORKDAYS,
DEFAULT_EXCLUDES,
DEFAULT_NAME,
DEFAULT_OFFSET,
DEFAULT_WORKDAYS,
DOMAIN,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import init_integration
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant) -> None:
"""Test we get the forms."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: "Workday Sensor",
CONF_COUNTRY: "DE",
},
)
await hass.async_block_till_done()
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: [],
CONF_REMOVE_HOLIDAYS: [],
CONF_PROVINCE: "none",
},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Workday Sensor"
assert result3["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,
}
async def test_form_no_subdivision(hass: HomeAssistant) -> None:
"""Test we get the forms correctly without subdivision."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: "Workday Sensor",
CONF_COUNTRY: "SE",
},
)
await hass.async_block_till_done()
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
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 result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Workday Sensor"
assert result3["options"] == {
"name": "Workday Sensor",
"country": "SE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": 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."""
entry = await init_integration(
hass,
{
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": None,
},
)
result = await hass.config_entries.options.async_init(entry.entry_id)
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": "BW",
},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["data"] == {
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": "BW",
}
async def test_form_incorrect_dates(hass: HomeAssistant) -> None:
"""Test errors in setup entry."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: "Workday Sensor",
CONF_COUNTRY: "DE",
},
)
await hass.async_block_till_done()
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: ["2022-xx-12"],
CONF_REMOVE_HOLIDAYS: [],
CONF_PROVINCE: "none",
},
)
await hass.async_block_till_done()
assert result3["errors"] == {"add_holidays": "add_holiday_error"}
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: ["2022-12-12"],
CONF_REMOVE_HOLIDAYS: ["Does not exist"],
CONF_PROVINCE: "none",
},
)
await hass.async_block_till_done()
assert result3["errors"] == {"remove_holidays": "remove_holiday_error"}
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
CONF_EXCLUDES: DEFAULT_EXCLUDES,
CONF_OFFSET: DEFAULT_OFFSET,
CONF_WORKDAYS: DEFAULT_WORKDAYS,
CONF_ADD_HOLIDAYS: ["2022-12-12"],
CONF_REMOVE_HOLIDAYS: ["Weihnachtstag"],
CONF_PROVINCE: "none",
},
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Workday Sensor"
assert result3["options"] == {
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2022-12-12"],
"remove_holidays": ["Weihnachtstag"],
"province": None,
}
async def test_options_form_incorrect_dates(hass: HomeAssistant) -> None:
"""Test errors in options."""
entry = await init_integration(
hass,
{
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": None,
},
)
result = await hass.config_entries.options.async_init(entry.entry_id)
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2022-xx-12"],
"remove_holidays": [],
"province": "BW",
},
)
assert result2["errors"] == {"add_holidays": "add_holiday_error"}
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2022-12-12"],
"remove_holidays": ["Does not exist"],
"province": "BW",
},
)
assert result2["errors"] == {"remove_holidays": "remove_holiday_error"}
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2022-12-12"],
"remove_holidays": ["Weihnachtstag"],
"province": "BW",
},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["data"] == {
"name": "Workday Sensor",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2022-12-12"],
"remove_holidays": ["Weihnachtstag"],
"province": "BW",
}
async def test_options_form_abort_duplicate(hass: HomeAssistant) -> None:
"""Test errors in options for duplicates."""
await init_integration(
hass,
{
"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_id="1",
)
entry2 = await init_integration(
hass,
{
"name": "Workday Sensor2",
"country": "DE",
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": ["2023-03-28"],
"remove_holidays": [],
"province": None,
},
entry_id="2",
)
result = await hass.config_entries.options.async_init(entry2.entry_id)
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"excludes": ["sat", "sun", "holiday"],
"days_offset": 0.0,
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"add_holidays": [],
"remove_holidays": [],
"province": "none",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "already_configured"}
+51
View File
@@ -0,0 +1,51 @@
"""Test Workday component setup process."""
from __future__ import annotations
from datetime import datetime
from freezegun.api import FrozenDateTimeFactory
from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import UTC
from . import TEST_CONFIG_EXAMPLE_1, TEST_CONFIG_WITH_PROVINCE, init_integration
async def test_load_unload_entry(hass: HomeAssistant) -> None:
"""Test load and unload entry."""
entry = await init_integration(hass, TEST_CONFIG_EXAMPLE_1)
state = hass.states.get("binary_sensor.workday_sensor")
assert state
await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.workday_sensor")
assert not state
async def test_update_options(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test options update and config entry is reloaded."""
freezer.move_to(datetime(2023, 4, 12, 12, tzinfo=UTC)) # Monday
entry = await init_integration(hass, TEST_CONFIG_WITH_PROVINCE)
assert entry.state == config_entries.ConfigEntryState.LOADED
assert entry.update_listeners is not None
state = hass.states.get("binary_sensor.workday_sensor")
assert state.state == "on"
new_options = TEST_CONFIG_WITH_PROVINCE.copy()
new_options["add_holidays"] = ["2023-04-12"]
hass.config_entries.async_update_entry(entry, options=new_options)
await hass.async_block_till_done()
entry_check = hass.config_entries.async_get_entry("1")
assert entry_check.state == config_entries.ConfigEntryState.LOADED
state = hass.states.get("binary_sensor.workday_sensor")
assert state.state == "off"