fix(availability): F3 — any usable transport keeps the entities up (t40)
Reported from a live install: with ADB enabled but unreachable and a paired, healthy protocol transport, the media player went unavailable even though the protocol still served power/app/volume. The old rule was ``manager.connected or manager.awake is not None or not manager.available`` — i.e. "a configured ADB manager that is down ⇒ the whole entity is down" (t5-F2), which ignored transport B entirely. The ladder is now: * protocol transport wired and not reported unreachable — usable; * or ADB connected — usable; * or fresh wake data (the poller has spoken) — usable; * or a passive manager (no ADB configured at all) — usable, because nothing to transport is not a broken transport. Unavailable only when a CONFIGURED transport is actively down and no usable data exists. One implementation, ``entity_available`` (media_player.py), is used by the media player, the remote (whose ladder previously duplicated a weaker version of this rule) and the diagnostics payload. Diagnostics grew ``transport.entity_available`` plus ``adb.configured`` so a support dump answers "why is my entity unavailable?" — the ladder inputs and its verdict are now in the same place. Pinned by tests: the exact user case (ADB configured+down, protocol up ⇒ available), all-down (⇒ unavailable), wake-data rescue, connected ADB without a protocol, the passive manager, a configured-but-down transport with no data, the remote following the same ladder, and the diagnostics verdict. Verified: 310 passed / 2 skipped, 311 passed / 1 skipped (androidtvremote2), 16 passed in the HA drift suite, ruff clean.
This commit is contained in:
1 parent
dd23d01a88
commit
60d884b76a
4 files changed
+144
-17
No files matched your search
@@ -1174,6 +1174,19 @@ class TestBrowseMediaDirectory:
|
||||
assert protocol.launch_calls == [selected.media_content_id] # C-LAUNCH
|
||||
|
||||
|
||||
class DownAdbPoller(FakeAdbPoller):
|
||||
"""A CONFIGURED ADB poller that cannot reach the device (t40 repro)."""
|
||||
|
||||
connected = False
|
||||
|
||||
|
||||
def make_manager_with(*, poller=None, protocol=None):
|
||||
"""A manager with exactly these transports (None = not configured)."""
|
||||
return ShieldConnectionManager(
|
||||
hass=None, entry=None, poller=poller, protocol=protocol, store=None
|
||||
)
|
||||
|
||||
|
||||
def make_app_sensor(modules, manager, entry, *, atv_state=None):
|
||||
"""A Current App sensor over the given manager/entry (t40)."""
|
||||
from types import SimpleNamespace
|
||||
@@ -1264,3 +1277,84 @@ class TestCurrentAppSensorFriendlyName:
|
||||
player._manager._on_current_app("ar.tvplayer.tv")
|
||||
|
||||
assert sensor.native_value == player.app_name == "TiviMate"
|
||||
|
||||
|
||||
class TestAvailabilityLadder:
|
||||
"""t40: ADB enabled-but-unreachable + a healthy protocol = AVAILABLE.
|
||||
|
||||
The t5-F2 rule took the whole entity down whenever a configured ADB manager
|
||||
was disconnected, even though the paired protocol transport was still
|
||||
serving power/app/volume.
|
||||
"""
|
||||
|
||||
def _player(self, modules, *, poller=None, protocol=None):
|
||||
player, _served, _intents = make_player(
|
||||
modules, manager=make_manager_with(poller=poller, protocol=protocol)
|
||||
)
|
||||
player._entry.runtime_data = player._manager
|
||||
return player
|
||||
|
||||
def test_the_users_case_keeps_the_entity_available(self, modules):
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=FakeProtocolClient())
|
||||
player._manager._on_is_available(True)
|
||||
|
||||
assert player._manager.available is True # ADB is configured ...
|
||||
assert player._manager.connected is False # ... but unreachable
|
||||
assert player.available is True # the protocol carries the entity
|
||||
|
||||
def test_unavailable_when_every_configured_transport_is_down(self, modules):
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=FakeProtocolClient())
|
||||
player._manager._on_is_available(False)
|
||||
|
||||
assert player.available is False
|
||||
|
||||
def test_fresh_wake_data_is_enough(self, modules):
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=FakeProtocolClient())
|
||||
player._manager._on_is_available(False)
|
||||
player._manager.store.set("awake", False)
|
||||
|
||||
assert player.available is True
|
||||
|
||||
def test_a_connected_adb_carries_a_protocol_less_entry(self, modules):
|
||||
player = self._player(modules, poller=FakeAdbPoller(), protocol=None)
|
||||
|
||||
assert player._manager.protocol_active is False
|
||||
assert player.available is True
|
||||
|
||||
def test_a_passive_manager_stays_available(self, modules):
|
||||
player = self._player(modules, poller=None, protocol=None)
|
||||
|
||||
assert player._manager.available is False # no ADB configured at all
|
||||
assert player.available is True
|
||||
|
||||
def test_a_configured_but_down_transport_is_unavailable(self, modules):
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=None)
|
||||
|
||||
assert player.available is False
|
||||
|
||||
def test_the_remote_follows_the_same_ladder(self, modules):
|
||||
from types import SimpleNamespace
|
||||
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=FakeProtocolClient())
|
||||
player._manager._on_is_available(True)
|
||||
remote = modules["remote"].NvidiaShieldRemoteEntity(
|
||||
SimpleNamespace(), player._entry
|
||||
)
|
||||
|
||||
assert remote.available is True
|
||||
|
||||
def test_the_diagnostics_dump_explains_the_ladder(self, modules):
|
||||
player = self._player(modules, poller=DownAdbPoller(), protocol=FakeProtocolClient())
|
||||
player._manager._on_is_available(True)
|
||||
|
||||
from nsh.diagnostics import async_get_config_entry_diagnostics
|
||||
|
||||
entry = player._entry
|
||||
entry.data = {}
|
||||
entry.options = {}
|
||||
payload = asyncio.run(async_get_config_entry_diagnostics(None, entry))
|
||||
|
||||
transport = payload["transport"]
|
||||
assert transport["entity_available"] is True
|
||||
assert transport["adb"] == {"configured": True, "connected": False, "awake": None}
|
||||
assert transport["protocol"]["active"] is True
|
||||
Reference in new issue
Block a user