Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
G Johansson
7c103ca9ae coverage 2024-09-03 18:51:26 +00:00
G Johansson
ed8e8cbdef Add template to command args in command_line notify 2024-09-03 18:24:08 +00:00
2 changed files with 124 additions and 5 deletions

View file

@ -9,10 +9,12 @@ from typing import Any, cast
from homeassistant.components.notify import BaseNotificationService from homeassistant.components.notify import BaseNotificationService
from homeassistant.const import CONF_COMMAND from homeassistant.const import CONF_COMMAND
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.process import kill_subprocess from homeassistant.util.process import kill_subprocess
from .const import CONF_COMMAND_TIMEOUT from .const import CONF_COMMAND_TIMEOUT, LOGGER
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -44,8 +46,31 @@ class CommandLineNotificationService(BaseNotificationService):
def send_message(self, message: str = "", **kwargs: Any) -> None: def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a command line.""" """Send a message to a command line."""
command = self.command
if " " not in command:
prog = command
args = None
args_compiled = None
else:
prog, args = command.split(" ", 1)
args_compiled = Template(args, self.hass)
rendered_args = None
if args_compiled:
args_to_render = {"arguments": args}
try:
rendered_args = args_compiled.async_render(args_to_render)
except TemplateError as ex:
LOGGER.exception("Error rendering command template: %s", ex)
return
if rendered_args != args:
command = f"{prog} {rendered_args}"
LOGGER.debug("Running command: %s, with message: %s", command, message)
with subprocess.Popen( # noqa: S602 # shell by design with subprocess.Popen( # noqa: S602 # shell by design
self.command, command,
universal_newlines=True, universal_newlines=True,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
close_fds=False, # required for posix_spawn close_fds=False, # required for posix_spawn
@ -57,10 +82,10 @@ class CommandLineNotificationService(BaseNotificationService):
_LOGGER.error( _LOGGER.error(
"Command failed (with return code %s): %s", "Command failed (with return code %s): %s",
proc.returncode, proc.returncode,
self.command, command,
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
_LOGGER.error("Timeout for command: %s", self.command) _LOGGER.error("Timeout for command: %s", command)
kill_subprocess(proc) kill_subprocess(proc)
except subprocess.SubprocessError: except subprocess.SubprocessError:
_LOGGER.error("Error trying to exec command: %s", self.command) _LOGGER.error("Error trying to exec command: %s", command)

View file

@ -100,6 +100,100 @@ async def test_command_line_output(hass: HomeAssistant) -> None:
assert message == await hass.async_add_executor_job(Path(filename).read_text) assert message == await hass.async_add_executor_job(Path(filename).read_text)
async def test_command_line_output_single_command(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test the command line output."""
await setup.async_setup_component(
hass,
DOMAIN,
{
"command_line": [
{
"notify": {
"command": "echo",
"name": "Test3",
}
}
]
},
)
await hass.async_block_till_done()
assert hass.services.has_service(NOTIFY_DOMAIN, "test3")
await hass.services.async_call(
NOTIFY_DOMAIN, "test3", {"message": "test message"}, blocking=True
)
assert "Running command: echo, with message: test message" in caplog.text
async def test_command_template(hass: HomeAssistant) -> None:
"""Test the command line output using template as command."""
with tempfile.TemporaryDirectory() as tempdirname:
filename = os.path.join(tempdirname, "message.txt")
message = "one, two, testing, testing"
hass.states.async_set("sensor.test_state", filename)
await setup.async_setup_component(
hass,
DOMAIN,
{
"command_line": [
{
"notify": {
"command": "cat > {{ states.sensor.test_state.state }}",
"name": "Test3",
}
}
]
},
)
await hass.async_block_till_done()
assert hass.services.has_service(NOTIFY_DOMAIN, "test3")
await hass.services.async_call(
NOTIFY_DOMAIN, "test3", {"message": message}, blocking=True
)
assert message == await hass.async_add_executor_job(Path(filename).read_text)
async def test_command_incorrect_template(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test the command line output using template as command which isn't working."""
message = "one, two, testing, testing"
await setup.async_setup_component(
hass,
DOMAIN,
{
"command_line": [
{
"notify": {
"command": "cat > {{ this template doesn't parse ",
"name": "Test3",
}
}
]
},
)
await hass.async_block_till_done()
assert hass.services.has_service(NOTIFY_DOMAIN, "test3")
await hass.services.async_call(
NOTIFY_DOMAIN, "test3", {"message": message}, blocking=True
)
assert (
"Error rendering command template: TemplateSyntaxError: expected token"
in caplog.text
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"get_config", "get_config",
[ [