mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-11 06:14:04 +02:00
Fix causal context links for intercepted calls (#1559)
This commit is contained in:
parent
9525d2f243
commit
1f137d2b36
2 changed files with 110 additions and 6 deletions
|
|
@ -2119,7 +2119,7 @@ class AdaptiveLightingManager:
|
|||
|
||||
for eid in _entity_ids:
|
||||
# Must add a new context otherwise _adapt_light will bail out
|
||||
context = switch.create_context("intercept")
|
||||
context = switch.create_context("intercept", parent=call.context)
|
||||
self.clear_proactively_adapting(eid)
|
||||
self.set_proactively_adapting(context.id, eid)
|
||||
_LOGGER.debug(
|
||||
|
|
@ -2140,7 +2140,7 @@ class AdaptiveLightingManager:
|
|||
assert set(skipped) == set(entity_ids)
|
||||
return # The call will be intercepted with the original data
|
||||
# Call light turn_on service for skipped entities
|
||||
context = self.create_context("skipped")
|
||||
context = self.create_context("skipped", parent=call.context)
|
||||
_LOGGER.debug(
|
||||
"(5) _service_interceptor_turn_on_handler: calling `light.turn_on` with skipped='%s', service_data: '%s', context='%s'",
|
||||
skipped,
|
||||
|
|
@ -2180,6 +2180,7 @@ class AdaptiveLightingManager:
|
|||
adaptation_data = await switch.prepare_adaptation_data(
|
||||
entity_ids[0],
|
||||
transition,
|
||||
context=switch.create_context("adapt_lights", parent=call.context),
|
||||
)
|
||||
if adaptation_data is None:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import pytest
|
|||
import ulid_transform
|
||||
import voluptuous.error
|
||||
from flaky import flaky
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.components.adaptive_lighting.adaptation_utils import (
|
||||
AdaptationData,
|
||||
LightControlAttributes,
|
||||
|
|
@ -2332,11 +2333,11 @@ async def test_service_calls_task_cancellation(hass):
|
|||
|
||||
async def _turn_on_and_track_event_contexts(
|
||||
hass: HomeAssistant,
|
||||
context_id: str,
|
||||
context_id: str | Context,
|
||||
entity_id,
|
||||
return_full_events: bool = False,
|
||||
):
|
||||
context = Context(id=context_id)
|
||||
context = context_id if isinstance(context_id, Context) else Context(id=context_id)
|
||||
event_context_ids = []
|
||||
events = []
|
||||
|
||||
|
|
@ -2359,6 +2360,105 @@ async def _turn_on_and_track_event_contexts(
|
|||
return event_context_ids
|
||||
|
||||
|
||||
async def _admin_context(hass: HomeAssistant, context_id: str) -> Context:
|
||||
"""Create a user-originated context accepted by entity service checks."""
|
||||
user = await hass.auth.async_create_user(context_id, group_ids=[GROUP_ID_ADMIN])
|
||||
return Context(
|
||||
id=context_id,
|
||||
parent_id="automation_origin",
|
||||
user_id=user.id,
|
||||
)
|
||||
|
||||
|
||||
async def test_apply_service_context_links_to_origin(hass):
|
||||
"""The apply service links its light call to the originating service call."""
|
||||
switch, (_, _, light) = await setup_lights_and_switch(hass)
|
||||
origin = await _admin_context(hass, "apply_origin")
|
||||
events: list[Event] = []
|
||||
|
||||
async def listener(event: Event) -> None:
|
||||
if (
|
||||
event.data.get("domain") == LIGHT_DOMAIN
|
||||
and event.data.get("service") == SERVICE_TURN_ON
|
||||
):
|
||||
events.append(event)
|
||||
|
||||
remove_listener = hass.bus.async_listen(EVENT_CALL_SERVICE, listener)
|
||||
try:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_APPLY,
|
||||
{
|
||||
ATTR_ENTITY_ID: switch.entity_id,
|
||||
CONF_LIGHTS: [light.entity_id],
|
||||
CONF_TURN_ON_LIGHTS: True,
|
||||
},
|
||||
blocking=True,
|
||||
context=origin,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
finally:
|
||||
remove_listener()
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].context.parent_id == origin.id
|
||||
assert events[0].context.user_id is None
|
||||
|
||||
|
||||
async def test_single_light_intercept_keeps_origin_context(hass):
|
||||
"""A directly intercepted call keeps the original context unchanged."""
|
||||
await setup_lights_and_switch(hass, {CONF_INTERCEPT: True}, True)
|
||||
origin = await _admin_context(hass, "single_intercept_origin")
|
||||
|
||||
events = await _turn_on_and_track_event_contexts(
|
||||
hass,
|
||||
origin,
|
||||
ENTITY_LIGHT_3,
|
||||
return_full_events=True,
|
||||
)
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].context.id == origin.id
|
||||
assert events[0].context.parent_id == origin.parent_id
|
||||
assert events[0].context.user_id == origin.user_id
|
||||
|
||||
|
||||
async def test_multi_profile_intercept_context_links_to_origin(hass):
|
||||
"""A secondary profile adaptation links its new call to the origin."""
|
||||
lights, _, _ = await setup_proactive_multiple_lights_two_switches(hass)
|
||||
origin = await _admin_context(hass, "multi_profile_origin")
|
||||
|
||||
events = await _turn_on_and_track_event_contexts(
|
||||
hass,
|
||||
origin,
|
||||
lights[:2],
|
||||
return_full_events=True,
|
||||
)
|
||||
secondary_events = [event for event in events if ":ntrc:" in event.context.id]
|
||||
|
||||
assert len(secondary_events) == 1
|
||||
assert secondary_events[0].context.parent_id == origin.id
|
||||
assert secondary_events[0].context.user_id is None
|
||||
|
||||
|
||||
async def test_skipped_light_context_links_to_origin(hass):
|
||||
"""A split call for an unmanaged light links its new call to the origin."""
|
||||
lights, _, _ = await setup_proactive_multiple_lights_two_switches(hass)
|
||||
origin = await _admin_context(hass, "skipped_light_origin")
|
||||
|
||||
events = await _turn_on_and_track_event_contexts(
|
||||
hass,
|
||||
origin,
|
||||
[lights[0], lights[2]],
|
||||
return_full_events=True,
|
||||
)
|
||||
skipped_events = [event for event in events if ":skpp:" in event.context.id]
|
||||
|
||||
assert len(skipped_events) == 1
|
||||
assert skipped_events[0].context.parent_id == origin.id
|
||||
assert skipped_events[0].context.user_id is None
|
||||
|
||||
|
||||
def _mock_sun_light_settings(switch: AdaptiveSwitch, settings: dict[str, Any]):
|
||||
sun_light_settings_mock = Mock()
|
||||
sun_light_settings_mock.get_settings = Mock(return_value=settings)
|
||||
|
|
@ -2415,9 +2515,10 @@ async def test_proactive_adaptation_with_separate_commands(hass):
|
|||
},
|
||||
)
|
||||
|
||||
origin = await _admin_context(hass, "separate_commands_origin")
|
||||
events = await _turn_on_and_track_event_contexts(
|
||||
hass,
|
||||
"test_context",
|
||||
origin,
|
||||
ENTITY_LIGHT_3,
|
||||
return_full_events=True,
|
||||
)
|
||||
|
|
@ -2428,8 +2529,10 @@ async def test_proactive_adaptation_with_separate_commands(hass):
|
|||
|
||||
# Expect two service calls
|
||||
assert len(event_context_ids) == 2, event_context_ids
|
||||
assert event_context_ids[0] == "test_context"
|
||||
assert event_context_ids[0] == origin.id
|
||||
assert is_our_context_id(event_context_ids[1])
|
||||
assert events[1].context.parent_id == origin.id
|
||||
assert events[1].context.user_id is None
|
||||
|
||||
# Expect adapted light state
|
||||
state = hass.states.get(ENTITY_LIGHT_3)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue