Skip to content

Commit 2ad470d

Browse files
Petro31frenck
authored andcommitted
Fix optimistic set to false for template entities (home-assistant#150421)
1 parent 8b2fce9 commit 2ad470d

File tree

11 files changed

+322
-5
lines changed

11 files changed

+322
-5
lines changed

homeassistant/components/template/entity.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@ def __init__(
3434
self._action_scripts: dict[str, Script] = {}
3535

3636
if self._optimistic_entity:
37+
optimistic = config.get(CONF_OPTIMISTIC)
38+
3739
self._template = config.get(CONF_STATE)
3840

39-
optimistic = self._template is None
41+
assumed_optimistic = self._template is None
4042
if self._extra_optimistic_options:
41-
optimistic = optimistic and all(
43+
assumed_optimistic = assumed_optimistic and all(
4244
config.get(option) is None
4345
for option in self._extra_optimistic_options
4446
)
4547

46-
self._attr_assumed_state = optimistic or config.get(CONF_OPTIMISTIC, False)
48+
self._attr_assumed_state = optimistic or (
49+
optimistic is None and assumed_optimistic
50+
)
4751

4852
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
4953
self.entity_id = async_generate_entity_id(

homeassistant/components/template/template_entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102

103103

104104
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA = {
105-
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
105+
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
106106
}
107107

108108

tests/components/template/test_alarm_control_panel.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,3 +973,35 @@ async def test_optimistic(hass: HomeAssistant) -> None:
973973

974974
state = hass.states.get(TEST_ENTITY_ID)
975975
assert state.state == AlarmControlPanelState.ARMED_HOME
976+
977+
978+
@pytest.mark.parametrize(
979+
("count", "panel_config"),
980+
[
981+
(
982+
1,
983+
{
984+
"name": TEST_OBJECT_ID,
985+
"state": "{{ states('alarm_control_panel.test') }}",
986+
**OPTIMISTIC_TEMPLATE_ALARM_CONFIG,
987+
"optimistic": False,
988+
},
989+
)
990+
],
991+
)
992+
@pytest.mark.parametrize(
993+
"style",
994+
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
995+
)
996+
@pytest.mark.usefixtures("setup_panel")
997+
async def test_not_optimistic(hass: HomeAssistant) -> None:
998+
"""Test optimistic yaml option set to false."""
999+
await hass.services.async_call(
1000+
ALARM_DOMAIN,
1001+
"alarm_arm_away",
1002+
{"entity_id": TEST_ENTITY_ID, "code": "1234"},
1003+
blocking=True,
1004+
)
1005+
1006+
state = hass.states.get(TEST_ENTITY_ID)
1007+
assert state.state == STATE_UNKNOWN

tests/components/template/test_cover.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,11 +628,38 @@ async def test_template_position(
628628
],
629629
)
630630
@pytest.mark.usefixtures("setup_cover")
631-
async def test_template_not_optimistic(hass: HomeAssistant) -> None:
631+
async def test_template_not_optimistic(
632+
hass: HomeAssistant,
633+
calls: list[ServiceCall],
634+
) -> None:
632635
"""Test the is_closed attribute."""
633636
state = hass.states.get(TEST_ENTITY_ID)
634637
assert state.state == STATE_UNKNOWN
635638

639+
# Test to make sure optimistic is not set with only a position template.
640+
await hass.services.async_call(
641+
COVER_DOMAIN,
642+
SERVICE_OPEN_COVER,
643+
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
644+
blocking=True,
645+
)
646+
await hass.async_block_till_done()
647+
648+
state = hass.states.get(TEST_ENTITY_ID)
649+
assert state.state == STATE_UNKNOWN
650+
651+
# Test to make sure optimistic is not set with only a position template.
652+
await hass.services.async_call(
653+
COVER_DOMAIN,
654+
SERVICE_CLOSE_COVER,
655+
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
656+
blocking=True,
657+
)
658+
await hass.async_block_till_done()
659+
660+
state = hass.states.get(TEST_ENTITY_ID)
661+
assert state.state == STATE_UNKNOWN
662+
636663

637664
@pytest.mark.parametrize(("count", "state_template"), [(1, "{{ 1 == 1 }}")])
638665
@pytest.mark.parametrize(

tests/components/template/test_fan.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,6 +1885,39 @@ async def test_optimistic_option(hass: HomeAssistant) -> None:
18851885
assert state.state == STATE_OFF
18861886

18871887

1888+
@pytest.mark.parametrize(
1889+
("count", "fan_config"),
1890+
[
1891+
(
1892+
1,
1893+
{
1894+
"name": TEST_OBJECT_ID,
1895+
"state": "{{ is_state('sensor.test_sensor', 'on') }}",
1896+
"turn_on": [],
1897+
"turn_off": [],
1898+
"optimistic": False,
1899+
},
1900+
)
1901+
],
1902+
)
1903+
@pytest.mark.parametrize(
1904+
"style",
1905+
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
1906+
)
1907+
@pytest.mark.usefixtures("setup_fan")
1908+
async def test_not_optimistic(hass: HomeAssistant) -> None:
1909+
"""Test optimistic yaml option set to false."""
1910+
await hass.services.async_call(
1911+
fan.DOMAIN,
1912+
"turn_on",
1913+
{"entity_id": TEST_ENTITY_ID},
1914+
blocking=True,
1915+
)
1916+
1917+
state = hass.states.get(TEST_ENTITY_ID)
1918+
assert state.state == STATE_OFF
1919+
1920+
18881921
async def test_setup_config_entry(
18891922
hass: HomeAssistant,
18901923
snapshot: SnapshotAssertion,

tests/components/template/test_light.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2795,6 +2795,42 @@ async def test_optimistic_option(hass: HomeAssistant) -> None:
27952795
assert state.state == STATE_OFF
27962796

27972797

2798+
@pytest.mark.parametrize(
2799+
("count", "light_config"),
2800+
[
2801+
(
2802+
1,
2803+
{
2804+
"name": TEST_OBJECT_ID,
2805+
"state": "{{ is_state('light.test_state', 'on') }}",
2806+
"turn_on": [],
2807+
"turn_off": [],
2808+
"optimistic": False,
2809+
},
2810+
)
2811+
],
2812+
)
2813+
@pytest.mark.parametrize(
2814+
("style", "expected"),
2815+
[
2816+
(ConfigurationStyle.MODERN, STATE_OFF),
2817+
(ConfigurationStyle.TRIGGER, STATE_UNKNOWN),
2818+
],
2819+
)
2820+
@pytest.mark.usefixtures("setup_light")
2821+
async def test_not_optimistic(hass: HomeAssistant, expected: str) -> None:
2822+
"""Test optimistic yaml option set to false."""
2823+
await hass.services.async_call(
2824+
light.DOMAIN,
2825+
"turn_on",
2826+
{"entity_id": TEST_ENTITY_ID},
2827+
blocking=True,
2828+
)
2829+
2830+
state = hass.states.get(TEST_ENTITY_ID)
2831+
assert state.state == expected
2832+
2833+
27982834
async def test_setup_config_entry(
27992835
hass: HomeAssistant,
28002836
snapshot: SnapshotAssertion,

tests/components/template/test_lock.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,39 @@ async def test_optimistic(hass: HomeAssistant) -> None:
11901190
assert state.state == LockState.UNLOCKED
11911191

11921192

1193+
@pytest.mark.parametrize(
1194+
("count", "lock_config"),
1195+
[
1196+
(
1197+
1,
1198+
{
1199+
"name": TEST_OBJECT_ID,
1200+
"state": "{{ is_state('sensor.test_state', 'on') }}",
1201+
"lock": [],
1202+
"unlock": [],
1203+
"optimistic": False,
1204+
},
1205+
)
1206+
],
1207+
)
1208+
@pytest.mark.parametrize(
1209+
"style",
1210+
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
1211+
)
1212+
@pytest.mark.usefixtures("setup_lock")
1213+
async def test_not_optimistic(hass: HomeAssistant) -> None:
1214+
"""Test optimistic yaml option set to false."""
1215+
await hass.services.async_call(
1216+
lock.DOMAIN,
1217+
lock.SERVICE_LOCK,
1218+
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
1219+
blocking=True,
1220+
)
1221+
1222+
state = hass.states.get(TEST_ENTITY_ID)
1223+
assert state.state == LockState.UNLOCKED
1224+
1225+
11931226
async def test_setup_config_entry(
11941227
hass: HomeAssistant,
11951228
snapshot: SnapshotAssertion,

tests/components/template/test_number.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,37 @@ async def test_optimistic(hass: HomeAssistant) -> None:
605605
assert float(state.state) == 2
606606

607607

608+
@pytest.mark.parametrize(
609+
("count", "number_config"),
610+
[
611+
(
612+
1,
613+
{
614+
"state": "{{ states('sensor.test_state') }}",
615+
"optimistic": False,
616+
"set_value": [],
617+
},
618+
)
619+
],
620+
)
621+
@pytest.mark.parametrize(
622+
"style",
623+
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
624+
)
625+
@pytest.mark.usefixtures("setup_number")
626+
async def test_not_optimistic(hass: HomeAssistant) -> None:
627+
"""Test optimistic yaml option set to false."""
628+
await hass.services.async_call(
629+
number.DOMAIN,
630+
number.SERVICE_SET_VALUE,
631+
{ATTR_ENTITY_ID: _TEST_NUMBER, "value": 4},
632+
blocking=True,
633+
)
634+
635+
state = hass.states.get(_TEST_NUMBER)
636+
assert state.state == STATE_UNKNOWN
637+
638+
608639
@pytest.mark.parametrize(
609640
("count", "number_config"),
610641
[

tests/components/template/test_select.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,42 @@ async def test_optimistic(hass: HomeAssistant) -> None:
601601
assert state.state == "yes"
602602

603603

604+
@pytest.mark.parametrize(
605+
("count", "select_config"),
606+
[
607+
(
608+
1,
609+
{
610+
"state": "{{ states('select.test_state') }}",
611+
"optimistic": False,
612+
"options": "{{ ['test', 'yes', 'no'] }}",
613+
"select_option": [],
614+
},
615+
)
616+
],
617+
)
618+
@pytest.mark.parametrize(
619+
"style",
620+
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
621+
)
622+
@pytest.mark.usefixtures("setup_select")
623+
async def test_not_optimistic(hass: HomeAssistant) -> None:
624+
"""Test optimistic yaml option set to false."""
625+
# Ensure Trigger template entities update the options list
626+
hass.states.async_set(TEST_STATE_ENTITY_ID, "anything")
627+
await hass.async_block_till_done()
628+
629+
await hass.services.async_call(
630+
select.DOMAIN,
631+
select.SERVICE_SELECT_OPTION,
632+
{ATTR_ENTITY_ID: _TEST_SELECT, "option": "test"},
633+
blocking=True,
634+
)
635+
636+
state = hass.states.get(_TEST_SELECT)
637+
assert state.state == STATE_UNKNOWN
638+
639+
604640
@pytest.mark.parametrize(
605641
("count", "select_config"),
606642
[

tests/components/template/test_switch.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
STATE_OFF,
1515
STATE_ON,
1616
STATE_UNAVAILABLE,
17+
STATE_UNKNOWN,
1718
)
1819
from homeassistant.core import CoreState, HomeAssistant, ServiceCall, State
1920
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -1267,3 +1268,39 @@ async def test_optimistic_option(hass: HomeAssistant) -> None:
12671268

12681269
state = hass.states.get(TEST_ENTITY_ID)
12691270
assert state.state == STATE_OFF
1271+
1272+
1273+
@pytest.mark.parametrize(
1274+
("count", "switch_config"),
1275+
[
1276+
(
1277+
1,
1278+
{
1279+
"name": TEST_OBJECT_ID,
1280+
"state": "{{ is_state('switch.test_state', 'on') }}",
1281+
"turn_on": [],
1282+
"turn_off": [],
1283+
"optimistic": False,
1284+
},
1285+
)
1286+
],
1287+
)
1288+
@pytest.mark.parametrize(
1289+
("style", "expected"),
1290+
[
1291+
(ConfigurationStyle.MODERN, STATE_OFF),
1292+
(ConfigurationStyle.TRIGGER, STATE_UNKNOWN),
1293+
],
1294+
)
1295+
@pytest.mark.usefixtures("setup_switch")
1296+
async def test_not_optimistic(hass: HomeAssistant, expected: str) -> None:
1297+
"""Test optimistic yaml option set to false."""
1298+
await hass.services.async_call(
1299+
switch.DOMAIN,
1300+
"turn_on",
1301+
{"entity_id": TEST_ENTITY_ID},
1302+
blocking=True,
1303+
)
1304+
1305+
state = hass.states.get(TEST_ENTITY_ID)
1306+
assert state.state == expected

0 commit comments

Comments
 (0)