mirror of
https://github.com/home-assistant/core.git
synced 2026-07-04 21:25:26 +01:00
Fix ESPHome update entity stuck on for project versions with build suffix (#172571)
This commit is contained in:
@@ -284,6 +284,19 @@ class ESPHomeUpdateEntity(EsphomeEntity[UpdateInfo, UpdateState], UpdateEntity):
|
||||
UpdateDeviceClass, static_info.device_class
|
||||
)
|
||||
|
||||
def version_is_newer(self, latest_version: str, installed_version: str) -> bool:
|
||||
"""Return True if latest_version is newer than installed_version.
|
||||
|
||||
ESPHome project versions can carry a build suffix (e.g.
|
||||
2025.11.5_c51f7548) that AwesomeVersion cannot parse. Without stripping
|
||||
it the base comparison raises and the entity is forced on for every
|
||||
build mismatch. Drop the suffix so the versions compare cleanly and we
|
||||
only report genuinely newer firmware.
|
||||
"""
|
||||
return super().version_is_newer(
|
||||
latest_version.partition("_")[0], installed_version.partition("_")[0]
|
||||
)
|
||||
|
||||
@property
|
||||
@esphome_state_property
|
||||
def installed_version(self) -> str:
|
||||
|
||||
@@ -5,6 +5,8 @@ from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from aioesphomeapi import APIClient, UpdateCommand, UpdateInfo, UpdateState
|
||||
from awesomeversion import AwesomeVersion
|
||||
from awesomeversion.exceptions import AwesomeVersionCompareException
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.esphome.dashboard import async_get_dashboard
|
||||
@@ -547,6 +549,128 @@ async def test_generic_device_update_entity_has_update(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("current_version", "latest_version"),
|
||||
[
|
||||
("2025.11.5_c51f7548", "2025.11.6_aabbccdd"),
|
||||
("2025.11.5_c51f7548", "2025.11.5_aabbccdd"),
|
||||
("2025.11.6_aabbccdd", "2025.11.5_c51f7548"),
|
||||
],
|
||||
ids=["newer_base", "same_base_new_build", "older_base"],
|
||||
)
|
||||
def test_awesomeversion_cannot_compare_project_versions(
|
||||
current_version: str, latest_version: str
|
||||
) -> None:
|
||||
"""Prove AwesomeVersion raises on ESPHome project versions.
|
||||
|
||||
ESPHome project versions carry a build suffix (e.g. 2025.11.5_c51f7548).
|
||||
AwesomeVersion cannot parse these, so the base UpdateEntity comparison would
|
||||
raise and force the entity on, which is why ESPHomeUpdateEntity mirrors the
|
||||
device by comparing with a plain string inequality instead.
|
||||
"""
|
||||
with pytest.raises(AwesomeVersionCompareException):
|
||||
assert AwesomeVersion(latest_version) > current_version
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("current_version", "latest_version", "expected_state"),
|
||||
[
|
||||
("2025.11.5_c51f7548", "2025.11.6_aabbccdd", STATE_ON),
|
||||
("2025.11.5_c51f7548", "2025.11.5_aabbccdd", STATE_OFF),
|
||||
("2025.11.6_aabbccdd", "2025.11.5_c51f7548", STATE_OFF),
|
||||
("2025.11.5_c51f7548", "2025.11.5_c51f7548", STATE_OFF),
|
||||
],
|
||||
ids=["newer_base", "same_base_new_build", "older_base", "identical"],
|
||||
)
|
||||
async def test_generic_device_update_entity_project_version(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
||||
current_version: str,
|
||||
latest_version: str,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test version comparison for ESPHome project versions.
|
||||
|
||||
AwesomeVersion cannot parse the build suffix, so the entity strips it and
|
||||
compares the real versions: only a genuinely newer base version is offered;
|
||||
a different build of the same version or an older version is not.
|
||||
"""
|
||||
entity_info = [
|
||||
UpdateInfo(
|
||||
object_id="myupdate",
|
||||
key=1,
|
||||
name="my update",
|
||||
)
|
||||
]
|
||||
states = [
|
||||
UpdateState(
|
||||
key=1,
|
||||
current_version=current_version,
|
||||
latest_version=latest_version,
|
||||
title="ESPHome Project",
|
||||
release_summary=RELEASE_SUMMARY,
|
||||
release_url=RELEASE_URL,
|
||||
)
|
||||
]
|
||||
await mock_generic_device_entry(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
states=states,
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
async def test_generic_device_update_entity_clears_after_ota(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: MockESPHomeDeviceType,
|
||||
) -> None:
|
||||
"""Test a project version update clears once the device runs the new build."""
|
||||
entity_info = [
|
||||
UpdateInfo(
|
||||
object_id="myupdate",
|
||||
key=1,
|
||||
name="my update",
|
||||
)
|
||||
]
|
||||
states = [
|
||||
UpdateState(
|
||||
key=1,
|
||||
current_version="2025.11.5_c51f7548",
|
||||
latest_version="2025.11.6_aabbccdd",
|
||||
title="ESPHome Project",
|
||||
release_summary=RELEASE_SUMMARY,
|
||||
release_url=RELEASE_URL,
|
||||
)
|
||||
]
|
||||
mock_device = await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
states=states,
|
||||
)
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
mock_device.set_state(
|
||||
UpdateState(
|
||||
key=1,
|
||||
current_version="2025.11.6_aabbccdd",
|
||||
latest_version="2025.11.6_aabbccdd",
|
||||
title="ESPHome Project",
|
||||
release_summary=RELEASE_SUMMARY,
|
||||
release_url=RELEASE_URL,
|
||||
)
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_update_entity_release_notes(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
|
||||
Reference in New Issue
Block a user