Nest notify payload fields under the action's data key
The modern HA action schema passes title and message to the notify service only under data; fix the morning summary and afternoon reminder blueprints and the test accessors accordingly.
This commit is contained in:
@@ -69,6 +69,7 @@ conditions:
|
|||||||
|
|
||||||
actions:
|
actions:
|
||||||
- action: !input notify_target
|
- action: !input notify_target
|
||||||
|
data:
|
||||||
title: NestQuest afternoon reminder
|
title: NestQuest afternoon reminder
|
||||||
message: |
|
message: |
|
||||||
{%- set ns = namespace(lines=[]) -%}
|
{%- set ns = namespace(lines=[]) -%}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ conditions:
|
|||||||
|
|
||||||
actions:
|
actions:
|
||||||
- action: !input notify_target
|
- action: !input notify_target
|
||||||
|
data:
|
||||||
title: NestQuest morning summary
|
title: NestQuest morning summary
|
||||||
message: |
|
message: |
|
||||||
{%- set ns = namespace(lines=[]) -%}
|
{%- set ns = namespace(lines=[]) -%}
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ def morning_action(bp: dict) -> dict:
|
|||||||
return single_action(bp)
|
return single_action(bp)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_data(bp: dict, index: int = 0) -> dict:
|
||||||
|
"""Return a notify action's service ``data`` (title/message live
|
||||||
|
there in the modern HA action schema, not at the action's top level)."""
|
||||||
|
action = bp["actions"][index]
|
||||||
|
assert isinstance(action.get("data"), dict), (
|
||||||
|
"notify payload fields must nest under data:"
|
||||||
|
)
|
||||||
|
return action["data"]
|
||||||
|
|
||||||
|
|
||||||
def iter_strings(node: object):
|
def iter_strings(node: object):
|
||||||
"""Yield every string value in a parsed YAML structure, recursively."""
|
"""Yield every string value in a parsed YAML structure, recursively."""
|
||||||
if isinstance(node, str):
|
if isinstance(node, str):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import re
|
|||||||
from custom_components.nestquest.const import DEFAULT_AFTERNOON_REMINDER_TIME
|
from custom_components.nestquest.const import DEFAULT_AFTERNOON_REMINDER_TIME
|
||||||
|
|
||||||
from tests.blueprint_helpers import (
|
from tests.blueprint_helpers import (
|
||||||
|
notify_data,
|
||||||
BlueprintInput,
|
BlueprintInput,
|
||||||
blueprint,
|
blueprint,
|
||||||
iter_strings,
|
iter_strings,
|
||||||
@@ -80,8 +81,9 @@ def test_afternoon_reminder_wires_inputs_not_literals() -> None:
|
|||||||
|
|
||||||
action = single_action(bp)
|
action = single_action(bp)
|
||||||
assert action["action"] == BlueprintInput("notify_target")
|
assert action["action"] == BlueprintInput("notify_target")
|
||||||
assert action["title"] == AFTERNOON_TITLE
|
data = notify_data(afternoon_reminder())
|
||||||
assert isinstance(action["message"], str) and action["message"].strip()
|
assert data["title"] == AFTERNOON_TITLE
|
||||||
|
assert isinstance(data["message"], str) and data["message"].strip()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -125,7 +127,7 @@ def test_afternoon_reminder_lists_only_present_children_with_open_quests() -> No
|
|||||||
TemplateState("sensor.nestquest_household_quests_due_today", "7"),
|
TemplateState("sensor.nestquest_household_quests_due_today", "7"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(single_action(afternoon_reminder())["message"], states=states)
|
rendered = render(notify_data(afternoon_reminder())["message"], states=states)
|
||||||
assert rendered == "Alice still has 2 quest(s) open."
|
assert rendered == "Alice still has 2 quest(s) open."
|
||||||
assert "Carol" not in rendered
|
assert "Carol" not in rendered
|
||||||
assert "Bob" not in rendered
|
assert "Bob" not in rendered
|
||||||
@@ -218,7 +220,7 @@ def test_afternoon_reminder_collision_suffixed_duplicate_names() -> None:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(single_action(afternoon_reminder())["message"], states=states)
|
rendered = render(notify_data(afternoon_reminder())["message"], states=states)
|
||||||
assert rendered.split("\n") == [
|
assert rendered.split("\n") == [
|
||||||
"Ada still has 1 quest(s) open.",
|
"Ada still has 1 quest(s) open.",
|
||||||
"Ada still has 3 quest(s) open.",
|
"Ada still has 3 quest(s) open.",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from types import SimpleNamespace
|
|||||||
from custom_components.nestquest.const import DEFAULT_MORNING_SUMMARY_TIME
|
from custom_components.nestquest.const import DEFAULT_MORNING_SUMMARY_TIME
|
||||||
|
|
||||||
from tests.blueprint_helpers import (
|
from tests.blueprint_helpers import (
|
||||||
|
notify_data,
|
||||||
ALLOWED_ENTITY_SHAPES,
|
ALLOWED_ENTITY_SHAPES,
|
||||||
BlueprintInput,
|
BlueprintInput,
|
||||||
_ENTITY_LITERAL_RE,
|
_ENTITY_LITERAL_RE,
|
||||||
@@ -88,8 +89,9 @@ def test_morning_summary_wires_inputs_not_literals() -> None:
|
|||||||
|
|
||||||
action = morning_action(bp)
|
action = morning_action(bp)
|
||||||
assert action["action"] == BlueprintInput("notify_target")
|
assert action["action"] == BlueprintInput("notify_target")
|
||||||
assert action["title"] == MORNING_TITLE
|
data = notify_data(bp)
|
||||||
assert isinstance(action["message"], str) and action["message"].strip()
|
assert data["title"] == MORNING_TITLE
|
||||||
|
assert isinstance(data["message"], str) and data["message"].strip()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -139,7 +141,7 @@ def test_due_sensor_regex_selects_only_nestquest_due_sensors() -> None:
|
|||||||
including HA collision-suffixed duplicates (a second child with
|
including HA collision-suffixed duplicates (a second child with
|
||||||
the same name gets entity ids ending _2) — and rejects everything
|
the same name gets entity ids ending _2) — and rejects everything
|
||||||
else."""
|
else."""
|
||||||
message = morning_action(morning_summary())["message"]
|
message = notify_data(morning_summary())["message"]
|
||||||
match = re.search(
|
match = re.search(
|
||||||
r"\^(sensor\\\\\.nestquest_\.\*_quests_due_today\(_\\\\d\+\)\?\$)", message
|
r"\^(sensor\\\\\.nestquest_\.\*_quests_due_today\(_\\\\d\+\)\?\$)", message
|
||||||
)
|
)
|
||||||
@@ -216,7 +218,7 @@ def morning_fixture() -> SimpleNamespace:
|
|||||||
|
|
||||||
def test_morning_summary_lists_present_child_and_omits_absent() -> None:
|
def test_morning_summary_lists_present_child_and_omits_absent() -> None:
|
||||||
"""Present Alice appears with her count; absent Bob and decoys never do."""
|
"""Present Alice appears with her count; absent Bob and decoys never do."""
|
||||||
message = morning_action(morning_summary())["message"]
|
message = notify_data(morning_summary())["message"]
|
||||||
rendered = render(message, states=morning_fixture())
|
rendered = render(message, states=morning_fixture())
|
||||||
assert rendered == "Alice owes 2 quest(s) today."
|
assert rendered == "Alice owes 2 quest(s) today."
|
||||||
assert "Bob" not in rendered
|
assert "Bob" not in rendered
|
||||||
@@ -250,7 +252,7 @@ def test_morning_summary_present_child_zero_remaining_is_all_clear() -> None:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(morning_action(morning_summary())["message"], states=states)
|
rendered = render(notify_data(morning_summary())["message"], states=states)
|
||||||
assert "Alice owes 2 quest(s) today." in rendered
|
assert "Alice owes 2 quest(s) today." in rendered
|
||||||
assert "Carol is all clear today (0 quests remaining)." in rendered
|
assert "Carol is all clear today (0 quests remaining)." in rendered
|
||||||
|
|
||||||
@@ -267,7 +269,7 @@ def test_morning_summary_present_child_without_sensors_is_flagged() -> None:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(morning_action(morning_summary())["message"], states=states)
|
rendered = render(notify_data(morning_summary())["message"], states=states)
|
||||||
assert rendered == "Frances's quest count is unavailable today."
|
assert rendered == "Frances's quest count is unavailable today."
|
||||||
|
|
||||||
|
|
||||||
@@ -299,7 +301,7 @@ def test_morning_summary_collision_suffixed_duplicate_names() -> None:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(morning_action(morning_summary())["message"], states=states)
|
rendered = render(notify_data(morning_summary())["message"], states=states)
|
||||||
lines = rendered.split("\n")
|
lines = rendered.split("\n")
|
||||||
assert lines == [
|
assert lines == [
|
||||||
"Ada owes 1 quest(s) today.",
|
"Ada owes 1 quest(s) today.",
|
||||||
@@ -323,7 +325,7 @@ def test_morning_summary_no_present_children() -> None:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
rendered = render(morning_action(morning_summary())["message"], states=states)
|
rendered = render(notify_data(morning_summary())["message"], states=states)
|
||||||
assert rendered == "No children present today."
|
assert rendered == "No children present today."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user