Add options flow to File (#120269)

* Add options flow to File

* Review comments
This commit is contained in:
G Johansson 2024-08-15 18:21:07 +02:00 committed by GitHub
parent e39bfeac08
commit 24a20c75eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 307 additions and 38 deletions

View File

@ -1,5 +1,8 @@
"""The file component.""" """The file component."""
from copy import deepcopy
from typing import Any
from homeassistant.components.notify import migrate_notify_issue from homeassistant.components.notify import migrate_notify_issue
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -84,7 +87,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a file component entry.""" """Set up a file component entry."""
config = dict(entry.data) config = {**entry.data, **entry.options}
filepath: str = config[CONF_FILE_PATH] filepath: str = config[CONF_FILE_PATH]
if filepath and not await hass.async_add_executor_job( if filepath and not await hass.async_add_executor_job(
hass.config.is_allowed_path, filepath hass.config.is_allowed_path, filepath
@ -98,6 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups( await hass.config_entries.async_forward_entry_setups(
entry, [Platform(entry.data[CONF_PLATFORM])] entry, [Platform(entry.data[CONF_PLATFORM])]
) )
entry.async_on_unload(entry.add_update_listener(update_listener))
if entry.data[CONF_PLATFORM] == Platform.NOTIFY and CONF_NAME in entry.data: if entry.data[CONF_PLATFORM] == Platform.NOTIFY and CONF_NAME in entry.data:
# New notify entities are being setup through the config entry, # New notify entities are being setup through the config entry,
# but during the deprecation period we want to keep the legacy notify platform, # but during the deprecation period we want to keep the legacy notify platform,
@ -121,3 +125,29 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await hass.config_entries.async_unload_platforms( return await hass.config_entries.async_unload_platforms(
entry, [entry.data[CONF_PLATFORM]] entry, [entry.data[CONF_PLATFORM]]
) )
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate config entry."""
if config_entry.version > 2:
# Downgraded from future
return False
if config_entry.version < 2:
# Move optional fields from data to options in config entry
data: dict[str, Any] = deepcopy(dict(config_entry.data))
options = {}
for key, value in config_entry.data.items():
if key not in (CONF_FILE_PATH, CONF_PLATFORM, CONF_NAME):
data.pop(key)
options[key] = value
hass.config_entries.async_update_entry(
config_entry, version=2, data=data, options=options
)
return True

View File

@ -1,11 +1,18 @@
"""Config flow for file integration.""" """Config flow for file integration."""
from copy import deepcopy
import os import os
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
OptionsFlowWithConfigEntry,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_FILE_PATH, CONF_FILE_PATH,
CONF_FILENAME, CONF_FILENAME,
@ -15,6 +22,7 @@ from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
Platform, Platform,
) )
from homeassistant.core import callback
from homeassistant.helpers.selector import ( from homeassistant.helpers.selector import (
BooleanSelector, BooleanSelector,
BooleanSelectorConfig, BooleanSelectorConfig,
@ -31,27 +39,44 @@ BOOLEAN_SELECTOR = BooleanSelector(BooleanSelectorConfig())
TEMPLATE_SELECTOR = TemplateSelector(TemplateSelectorConfig()) TEMPLATE_SELECTOR = TemplateSelector(TemplateSelectorConfig())
TEXT_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT)) TEXT_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT))
FILE_FLOW_SCHEMAS = { FILE_OPTIONS_SCHEMAS = {
Platform.SENSOR.value: vol.Schema( Platform.SENSOR.value: vol.Schema(
{ {
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR,
vol.Optional(CONF_VALUE_TEMPLATE): TEMPLATE_SELECTOR, vol.Optional(CONF_VALUE_TEMPLATE): TEMPLATE_SELECTOR,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): TEXT_SELECTOR, vol.Optional(CONF_UNIT_OF_MEASUREMENT): TEXT_SELECTOR,
} }
), ),
Platform.NOTIFY.value: vol.Schema( Platform.NOTIFY.value: vol.Schema(
{ {
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR,
vol.Optional(CONF_TIMESTAMP, default=False): BOOLEAN_SELECTOR, vol.Optional(CONF_TIMESTAMP, default=False): BOOLEAN_SELECTOR,
} }
), ),
} }
FILE_FLOW_SCHEMAS = {
Platform.SENSOR.value: vol.Schema(
{
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR,
}
).extend(FILE_OPTIONS_SCHEMAS[Platform.SENSOR.value].schema),
Platform.NOTIFY.value: vol.Schema(
{
vol.Required(CONF_FILE_PATH): TEXT_SELECTOR,
}
).extend(FILE_OPTIONS_SCHEMAS[Platform.NOTIFY.value].schema),
}
class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN): class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a file config flow.""" """Handle a file config flow."""
VERSION = 1 VERSION = 2
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
return FileOptionsFlowHandler(config_entry)
async def validate_file_path(self, file_path: str) -> bool: async def validate_file_path(self, file_path: str) -> bool:
"""Ensure the file path is valid.""" """Ensure the file path is valid."""
@ -80,7 +105,13 @@ class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN):
errors[CONF_FILE_PATH] = "not_allowed" errors[CONF_FILE_PATH] = "not_allowed"
else: else:
title = f"{DEFAULT_NAME} [{user_input[CONF_FILE_PATH]}]" title = f"{DEFAULT_NAME} [{user_input[CONF_FILE_PATH]}]"
return self.async_create_entry(data=user_input, title=title) data = deepcopy(user_input)
options = {}
for key, value in user_input.items():
if key not in (CONF_FILE_PATH, CONF_PLATFORM, CONF_NAME):
data.pop(key)
options[key] = value
return self.async_create_entry(data=data, title=title, options=options)
return self.async_show_form( return self.async_show_form(
step_id=platform, data_schema=FILE_FLOW_SCHEMAS[platform], errors=errors step_id=platform, data_schema=FILE_FLOW_SCHEMAS[platform], errors=errors
@ -114,4 +145,29 @@ class FileConfigFlowHandler(ConfigFlow, domain=DOMAIN):
else: else:
file_path = import_data[CONF_FILE_PATH] file_path = import_data[CONF_FILE_PATH]
title = f"{name} [{file_path}]" title = f"{name} [{file_path}]"
return self.async_create_entry(title=title, data=import_data) data = deepcopy(import_data)
options = {}
for key, value in import_data.items():
if key not in (CONF_FILE_PATH, CONF_PLATFORM, CONF_NAME):
data.pop(key)
options[key] = value
return self.async_create_entry(title=title, data=data, options=options)
class FileOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Handle File options."""
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage File options."""
if user_input:
return self.async_create_entry(data=user_input)
platform = self.config_entry.data[CONF_PLATFORM]
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
FILE_OPTIONS_SCHEMAS[platform], self.config_entry.options or {}
),
)

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from functools import partial from functools import partial
import logging import logging
import os import os
from types import MappingProxyType
from typing import Any, TextIO from typing import Any, TextIO
import voluptuous as vol import voluptuous as vol
@ -109,7 +108,7 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up notify entity.""" """Set up notify entity."""
unique_id = entry.entry_id unique_id = entry.entry_id
async_add_entities([FileNotifyEntity(unique_id, entry.data)]) async_add_entities([FileNotifyEntity(unique_id, {**entry.data, **entry.options})])
class FileNotifyEntity(NotifyEntity): class FileNotifyEntity(NotifyEntity):
@ -118,7 +117,7 @@ class FileNotifyEntity(NotifyEntity):
_attr_icon = FILE_ICON _attr_icon = FILE_ICON
_attr_supported_features = NotifyEntityFeature.TITLE _attr_supported_features = NotifyEntityFeature.TITLE
def __init__(self, unique_id: str, config: MappingProxyType[str, Any]) -> None: def __init__(self, unique_id: str, config: dict[str, Any]) -> None:
"""Initialize the service.""" """Initialize the service."""
self._file_path: str = config[CONF_FILE_PATH] self._file_path: str = config[CONF_FILE_PATH]
self._add_timestamp: bool = config.get(CONF_TIMESTAMP, False) self._add_timestamp: bool = config.get(CONF_TIMESTAMP, False)

View File

@ -60,14 +60,15 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the file sensor.""" """Set up the file sensor."""
config = dict(entry.data) config = dict(entry.data)
options = dict(entry.options)
file_path: str = config[CONF_FILE_PATH] file_path: str = config[CONF_FILE_PATH]
unique_id: str = entry.entry_id unique_id: str = entry.entry_id
name: str = config.get(CONF_NAME, DEFAULT_NAME) name: str = config.get(CONF_NAME, DEFAULT_NAME)
unit: str | None = config.get(CONF_UNIT_OF_MEASUREMENT) unit: str | None = options.get(CONF_UNIT_OF_MEASUREMENT)
value_template: Template | None = None value_template: Template | None = None
if CONF_VALUE_TEMPLATE in config: if CONF_VALUE_TEMPLATE in options:
value_template = Template(config[CONF_VALUE_TEMPLATE], hass) value_template = Template(options[CONF_VALUE_TEMPLATE], hass)
async_add_entities( async_add_entities(
[FileSensor(unique_id, name, file_path, unit, value_template)], True [FileSensor(unique_id, name, file_path, unit, value_template)], True

View File

@ -42,6 +42,22 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
} }
}, },
"options": {
"step": {
"init": {
"data": {
"value_template": "[%key:component::file::config::step::sensor::data::value_template%]",
"unit_of_measurement": "[%key:component::file::config::step::sensor::data::unit_of_measurement%]",
"timestamp": "[%key:component::file::config::step::notify::data::timestamp%]"
},
"data_description": {
"value_template": "[%key:component::file::config::step::sensor::data_description::value_template%]",
"unit_of_measurement": "[%key:component::file::config::step::sensor::data_description::unit_of_measurement%]",
"timestamp": "[%key:component::file::config::step::notify::data_description::timestamp%]"
}
}
}
},
"exceptions": { "exceptions": {
"dir_not_allowed": { "dir_not_allowed": {
"message": "Access to {filename} is not allowed." "message": "Access to {filename} is not allowed."

View File

@ -7,6 +7,7 @@ import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.file import DOMAIN from homeassistant.components.file import DOMAIN
from homeassistant.const import CONF_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -15,20 +16,22 @@ from tests.common import MockConfigEntry
MOCK_CONFIG_NOTIFY = { MOCK_CONFIG_NOTIFY = {
"platform": "notify", "platform": "notify",
"file_path": "some_file", "file_path": "some_file",
"timestamp": True,
} }
MOCK_OPTIONS_NOTIFY = {"timestamp": True}
MOCK_CONFIG_SENSOR = { MOCK_CONFIG_SENSOR = {
"platform": "sensor", "platform": "sensor",
"file_path": "some/path", "file_path": "some/path",
"value_template": "{{ value | round(1) }}",
} }
MOCK_OPTIONS_SENSOR = {"value_template": "{{ value | round(1) }}"}
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("platform", "data"), ("platform", "data", "options"),
[("sensor", MOCK_CONFIG_SENSOR), ("notify", MOCK_CONFIG_NOTIFY)], [
("sensor", MOCK_CONFIG_SENSOR, MOCK_OPTIONS_SENSOR),
("notify", MOCK_CONFIG_NOTIFY, MOCK_OPTIONS_NOTIFY),
],
) )
async def test_form( async def test_form(
hass: HomeAssistant, hass: HomeAssistant,
@ -36,6 +39,7 @@ async def test_form(
mock_is_allowed_path: bool, mock_is_allowed_path: bool,
platform: str, platform: str,
data: dict[str, Any], data: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -50,7 +54,7 @@ async def test_form(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
user_input = dict(data) user_input = {**data, **options}
user_input.pop("platform") user_input.pop("platform")
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=user_input result["flow_id"], user_input=user_input
@ -59,12 +63,17 @@ async def test_form(
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == data assert result2["data"] == data
assert result2["options"] == options
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("platform", "data"), ("platform", "data", "options"),
[("sensor", MOCK_CONFIG_SENSOR), ("notify", MOCK_CONFIG_NOTIFY)], [
("sensor", MOCK_CONFIG_SENSOR, MOCK_OPTIONS_SENSOR),
("notify", MOCK_CONFIG_NOTIFY, MOCK_OPTIONS_NOTIFY),
],
) )
async def test_already_configured( async def test_already_configured(
hass: HomeAssistant, hass: HomeAssistant,
@ -72,9 +81,10 @@ async def test_already_configured(
mock_is_allowed_path: bool, mock_is_allowed_path: bool,
platform: str, platform: str,
data: dict[str, Any], data: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test aborting if the entry is already configured.""" """Test aborting if the entry is already configured."""
entry = MockConfigEntry(domain=DOMAIN, data=data) entry = MockConfigEntry(domain=DOMAIN, data=data, options=options)
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -91,7 +101,7 @@ async def test_already_configured(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == platform assert result["step_id"] == platform
user_input = dict(data) user_input = {**data, **options}
user_input.pop("platform") user_input.pop("platform")
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -103,10 +113,14 @@ async def test_already_configured(
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize("is_allowed", [False], ids=["not_allowed"]) @pytest.mark.parametrize("is_allowed", [False], ids=["not_allowed"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
("platform", "data"), ("platform", "data", "options"),
[("sensor", MOCK_CONFIG_SENSOR), ("notify", MOCK_CONFIG_NOTIFY)], [
("sensor", MOCK_CONFIG_SENSOR, MOCK_OPTIONS_SENSOR),
("notify", MOCK_CONFIG_NOTIFY, MOCK_OPTIONS_NOTIFY),
],
) )
async def test_not_allowed( async def test_not_allowed(
hass: HomeAssistant, hass: HomeAssistant,
@ -114,6 +128,7 @@ async def test_not_allowed(
mock_is_allowed_path: bool, mock_is_allowed_path: bool,
platform: str, platform: str,
data: dict[str, Any], data: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test aborting if the file path is not allowed.""" """Test aborting if the file path is not allowed."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -130,7 +145,7 @@ async def test_not_allowed(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == platform assert result["step_id"] == platform
user_input = dict(data) user_input = {**data, **options}
user_input.pop("platform") user_input.pop("platform")
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -140,3 +155,49 @@ async def test_not_allowed(
assert result2["type"] is FlowResultType.FORM assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"file_path": "not_allowed"} assert result2["errors"] == {"file_path": "not_allowed"}
@pytest.mark.parametrize(
("platform", "data", "options", "new_options"),
[
(
"sensor",
MOCK_CONFIG_SENSOR,
MOCK_OPTIONS_SENSOR,
{CONF_UNIT_OF_MEASUREMENT: "mm"},
),
("notify", MOCK_CONFIG_NOTIFY, MOCK_OPTIONS_NOTIFY, {"timestamp": False}),
],
)
async def test_options_flow(
hass: HomeAssistant,
mock_is_allowed_path: bool,
platform: str,
data: dict[str, Any],
options: dict[str, Any],
new_options: dict[str, Any],
) -> None:
"""Test options config flow."""
entry = MockConfigEntry(domain=DOMAIN, data=data, options=options, version=2)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=new_options,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == new_options
entry = hass.config_entries.async_get_entry(entry.entry_id)
assert entry.state is config_entries.ConfigEntryState.LOADED
assert entry.options == new_options

View File

@ -0,0 +1,65 @@
"""The tests for local file init."""
from unittest.mock import MagicMock, Mock, patch
from homeassistant.components.file import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, get_fixture_path
@patch("os.path.isfile", Mock(return_value=True))
@patch("os.access", Mock(return_value=True))
async def test_migration_to_version_2(
hass: HomeAssistant, mock_is_allowed_path: MagicMock
) -> None:
"""Test the File sensor with JSON entries."""
data = {
"platform": "sensor",
"name": "file2",
"file_path": get_fixture_path("file_value_template.txt", "file"),
"value_template": "{{ value_json.temperature }}",
}
entry = MockConfigEntry(
domain=DOMAIN,
version=1,
data=data,
title=f"test [{data['file_path']}]",
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
assert entry.version == 2
assert entry.data == {
"platform": "sensor",
"name": "file2",
"file_path": get_fixture_path("file_value_template.txt", "file"),
}
assert entry.options == {
"value_template": "{{ value_json.temperature }}",
}
@patch("os.path.isfile", Mock(return_value=True))
@patch("os.access", Mock(return_value=True))
async def test_migration_from_future_version(
hass: HomeAssistant, mock_is_allowed_path: MagicMock
) -> None:
"""Test the File sensor with JSON entries."""
data = {
"platform": "sensor",
"name": "file2",
"file_path": get_fixture_path("file_value_template.txt", "file"),
"value_template": "{{ value_json.temperature }}",
}
entry = MockConfigEntry(
domain=DOMAIN, version=3, data=data, title=f"test [{data['file_path']}]"
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.MIGRATION_ERROR

View File

@ -174,7 +174,7 @@ async def test_legacy_notify_file_exception(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("timestamp", "data"), ("timestamp", "data", "options"),
[ [
( (
False, False,
@ -182,6 +182,8 @@ async def test_legacy_notify_file_exception(
"name": "test", "name": "test",
"platform": "notify", "platform": "notify",
"file_path": "mock_file", "file_path": "mock_file",
},
{
"timestamp": False, "timestamp": False,
}, },
), ),
@ -191,6 +193,8 @@ async def test_legacy_notify_file_exception(
"name": "test", "name": "test",
"platform": "notify", "platform": "notify",
"file_path": "mock_file", "file_path": "mock_file",
},
{
"timestamp": True, "timestamp": True,
}, },
), ),
@ -203,6 +207,7 @@ async def test_legacy_notify_file_entry_only_setup(
timestamp: bool, timestamp: bool,
mock_is_allowed_path: MagicMock, mock_is_allowed_path: MagicMock,
data: dict[str, Any], data: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test the legacy notify file output in entry only setup.""" """Test the legacy notify file output in entry only setup."""
filename = "mock_file" filename = "mock_file"
@ -213,7 +218,11 @@ async def test_legacy_notify_file_entry_only_setup(
message = params["message"] message = params["message"]
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options=options,
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -252,7 +261,7 @@ async def test_legacy_notify_file_entry_only_setup(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("is_allowed", "config"), ("is_allowed", "config", "options"),
[ [
( (
False, False,
@ -260,6 +269,8 @@ async def test_legacy_notify_file_entry_only_setup(
"name": "test", "name": "test",
"platform": "notify", "platform": "notify",
"file_path": "mock_file", "file_path": "mock_file",
},
{
"timestamp": False, "timestamp": False,
}, },
), ),
@ -271,10 +282,15 @@ async def test_legacy_notify_file_not_allowed(
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
mock_is_allowed_path: MagicMock, mock_is_allowed_path: MagicMock,
config: dict[str, Any], config: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test legacy notify file output not allowed.""" """Test legacy notify file output not allowed."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=config, title=f"test [{config['file_path']}]" domain=DOMAIN,
data=config,
version=2,
options=options,
title=f"test [{config['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry.entry_id) assert not await hass.config_entries.async_setup(entry.entry_id)
@ -293,13 +309,15 @@ async def test_legacy_notify_file_not_allowed(
], ],
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
("data", "is_allowed"), ("data", "options", "is_allowed"),
[ [
( (
{ {
"name": "test", "name": "test",
"platform": "notify", "platform": "notify",
"file_path": "mock_file", "file_path": "mock_file",
},
{
"timestamp": False, "timestamp": False,
}, },
True, True,
@ -314,12 +332,17 @@ async def test_notify_file_write_access_failed(
service: str, service: str,
params: dict[str, Any], params: dict[str, Any],
data: dict[str, Any], data: dict[str, Any],
options: dict[str, Any],
) -> None: ) -> None:
"""Test the notify file fails.""" """Test the notify file fails."""
domain = notify.DOMAIN domain = notify.DOMAIN
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options=options,
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)

View File

@ -47,7 +47,11 @@ async def test_file_value_entry_setup(
} }
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options={},
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -66,11 +70,17 @@ async def test_file_value_template(
"platform": "sensor", "platform": "sensor",
"name": "file2", "name": "file2",
"file_path": get_fixture_path("file_value_template.txt", "file"), "file_path": get_fixture_path("file_value_template.txt", "file"),
}
options = {
"value_template": "{{ value_json.temperature }}", "value_template": "{{ value_json.temperature }}",
} }
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options=options,
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -90,7 +100,11 @@ async def test_file_empty(hass: HomeAssistant, mock_is_allowed_path: MagicMock)
} }
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options={},
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
@ -113,7 +127,11 @@ async def test_file_path_invalid(
} }
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=data, title=f"test [{data['file_path']}]" domain=DOMAIN,
data=data,
version=2,
options={},
title=f"test [{data['file_path']}]",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)