From aea9f9f9d22126cccbc395fd509b5c09d61b1b17 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Sun, 11 Oct 2026 06:26:42 +0100 Subject: [PATCH] test(config): pin the Jellyfin name tiers and the address redaction (t41) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two acceptance items that had no test of their own: * the Jellyfin auto-detect tiers. t41 replaced the entity-derived search key (the Android TV Remote entity's friendly_name) with the names setup already knows — the name the user typed, then the discovery device name. Three tests pin it, the third being the regression: a Jellyfin player matching the *atv* friendly_name, with a serving state, used to be auto-detected; now only the typed/discovered names search. Red proof on the pre-t41 tree (60d884b): `_find_jellyfin_entity(hass, "jellyfin shield")` DID match media_player.jellyfin_shield there. * diagnostics redaction of the renamed address field. The G14 field list already carried "host" (alongside the legacy spellings), but the test only exercised the legacy keys — so a future edit could have dropped the live one unnoticed. The fixture and assertion now cover both spellings. Verified: 337 passed / 2 skipped, 338 / 1 with androidtvremote2, ruff clean. --- tests/test_command_routing.py | 4 +- tests/test_config_flow_ux.py | 90 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/tests/test_command_routing.py b/tests/test_command_routing.py index 49286eb..a5e6dd3 100644 --- a/tests/test_command_routing.py +++ b/tests/test_command_routing.py @@ -1034,7 +1034,8 @@ class TestEntryDiagnostics: options={ "adb_command_enabled": True, "protocol_pairing_code": "4821", - "adb_host": "192.168.1.50", + "host": "192.168.1.50", # t41: the one address field + "adb_host": "192.168.1.50", # legacy spellings stay redacted too "protocol_host": "192.168.1.50", }, title="Shield", @@ -1052,6 +1053,7 @@ class TestEntryDiagnostics: # old import pointed at homeassistant.util.diagnostic, which does not # exist, so this pass silently did nothing and only the marker scan # (pairing-/key-shaped names) masked anything. + assert payload["entry"]["options"]["host"] == "**REDACTED**" assert payload["entry"]["options"]["adb_host"] == "**REDACTED**" assert payload["entry"]["options"]["protocol_host"] == "**REDACTED**" assert payload["entry"]["data"]["nested"]["adb_host"] == "**REDACTED**" diff --git a/tests/test_config_flow_ux.py b/tests/test_config_flow_ux.py index 04c6212..6cb7c75 100644 --- a/tests/test_config_flow_ux.py +++ b/tests/test_config_flow_ux.py @@ -33,6 +33,7 @@ from nsh.const import ( CONF_ADB_ENABLED, CONF_ADB_PORT, CONF_HOST, + CONF_JELLYFIN_ENTITY, CONF_PAIRING_CODE, CONF_PROTOCOL_ENABLED, CONF_SKIP_PAIRING, @@ -581,6 +582,95 @@ class TestDiscoveryPrefill: assert defaults[CONF_HOST] == DISCOVERED_HOST +class TestJellyfinAutoDetect: + """t41: auto-detect searches with the names setup already knows. + + The removed tier resolved the Android TV Remote entity's ``friendly_name`` + and searched the Jellyfin registry with THAT. What remains is the name the + user typed and the device name from discovery — and another integration's + entity is never consulted, not even as a search term. + """ + + def _patch_registry(self, monkeypatch, entity_ids): + er = sys.modules["homeassistant.helpers.entity_registry"] + entities = { + eid: SimpleNamespace( + domain="media_player", + platform="jellyfin", + entity_id=eid, + original_name=eid.split(".", 1)[1].replace("_", " "), + disabled_by=None, + ) + for eid in entity_ids + } + monkeypatch.setattr( + er, "async_get", lambda hass: SimpleNamespace(entities=entities) + ) + + async def test_the_typed_name_finds_the_jellyfin_player(self, tmp_path, monkeypatch): + module = _form_module() + _patch_protocol(monkeypatch, module) + self._patch_registry(monkeypatch, ["media_player.jellyfin_living_room"]) + flow, hass = _new_flow(module, tmp_path) + state = SimpleNamespace(state="idle") + hass.states = SimpleNamespace( + get=lambda eid: state if eid == "media_player.jellyfin_living_room" else None + ) + + await flow.async_step_user( + _user_input(**{"name": "Jellyfin Living Room", CONF_PROTOCOL_ENABLED: True}) + ) + + assert flow._user_data[CONF_JELLYFIN_ENTITY] == "media_player.jellyfin_living_room" + + async def test_the_discovered_device_name_is_the_second_tier( + self, tmp_path, monkeypatch + ): + module = _form_module() + _patch_protocol(monkeypatch, module) + self._patch_registry(monkeypatch, ["media_player.jellyfin_living_room"]) + flow, hass = _new_flow(module, tmp_path) + state = SimpleNamespace(state="idle") + hass.states = SimpleNamespace( + get=lambda eid: state if eid == "media_player.jellyfin_living_room" else None + ) + flow._discovered_name = "jellyfin living room" + + await flow.async_step_user( + _user_input(**{"name": "Living Room TV", CONF_PROTOCOL_ENABLED: True}) + ) + + assert flow._user_data[CONF_JELLYFIN_ENTITY] == "media_player.jellyfin_living_room" + + async def test_another_integrations_entity_is_not_a_search_term( + self, tmp_path, monkeypatch + ): + """The regression pin: the old tier searched with the atv friendly_name. + + A Jellyfin player matches THAT name, and the state machine serves it, so + the pre-t41 code auto-detected it. Only the typed name may search now. + """ + module = _form_module() + _patch_protocol(monkeypatch, module) + self._patch_registry(monkeypatch, ["media_player.jellyfin_shield"]) + flow, hass = _new_flow(module, tmp_path) + atv_state = SimpleNamespace( + state="idle", attributes={"friendly_name": "jellyfin shield"} + ) + state = SimpleNamespace(state="idle") + hass.states = SimpleNamespace( + get=lambda eid: state + if eid == "media_player.jellyfin_shield" + else (atv_state if eid == "media_player.shield" else None) + ) + + await flow.async_step_user( + _user_input(**{"name": "Living Room", CONF_PROTOCOL_ENABLED: True}) + ) + + assert flow._user_data[CONF_JELLYFIN_ENTITY] == "" + + class TestTransportsDefaultOn: """t41: the two native transports are OURS — both start enabled.