test: command-routing and play_media(channel) orchestration — digit sequence with cancel guard, fallback ordering protocol→ADB→legacy, remote send_command holds/repeats, volume readout, is_on ladder
This commit is contained in:
1 parent
f071d540bd
commit
b451638c65
4 files changed
+581
-1
No files matched your search
@@ -0,0 +1,191 @@
|
||||
"""Home-Assistant object stubs sized to unit-test the entity modules (t10).
|
||||
|
||||
Import-only stubs: MediaPlayerEntity/RemoteEntity bases, the feature/state
|
||||
enum values the integration actually uses, and the event helpers. Tests load
|
||||
their entity module through the synthetic ``nsh`` package AFTER calling
|
||||
:func:`install` (and the per-file fixture pops everything at teardown).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
import sys
|
||||
import types
|
||||
|
||||
ENTITIES_STUB_NAMES = (
|
||||
"voluptuous",
|
||||
"homeassistant",
|
||||
"homeassistant.config_entries",
|
||||
"homeassistant.core",
|
||||
"homeassistant.const",
|
||||
"homeassistant.components",
|
||||
"homeassistant.components.media_player",
|
||||
"homeassistant.components.remote",
|
||||
"homeassistant.components.button",
|
||||
"homeassistant.helpers",
|
||||
"homeassistant.helpers.entity_registry",
|
||||
"homeassistant.helpers.selector",
|
||||
"homeassistant.helpers.event",
|
||||
"homeassistant.helpers.entity",
|
||||
"homeassistant.helpers.entity_platform",
|
||||
"nsh.helpers",
|
||||
"nsh.power",
|
||||
"nsh.jellyfin",
|
||||
"nsh.media_player",
|
||||
"nsh.remote",
|
||||
)
|
||||
|
||||
|
||||
class MediaPlayerState(enum.StrEnum):
|
||||
OFF = "off"
|
||||
ON = "on"
|
||||
IDLE = "idle"
|
||||
PLAYING = "playing"
|
||||
PAUSED = "paused"
|
||||
|
||||
|
||||
class MediaPlayerEntityFeature(enum.IntFlag):
|
||||
PLAY = 1
|
||||
PAUSE = 2
|
||||
STOP = 4
|
||||
VOLUME_MUTE = 8
|
||||
VOLUME_STEP = 16
|
||||
VOLUME_SET = 32
|
||||
TURN_ON = 64
|
||||
TURN_OFF = 128
|
||||
PLAY_MEDIA = 256
|
||||
NEXT_TRACK = 512
|
||||
PREVIOUS_TRACK = 1024
|
||||
|
||||
|
||||
class MediaEntityManager:
|
||||
"""trivial stand-in for the HA platform internals we don't exercise."""
|
||||
|
||||
|
||||
class MediaPlayerEntity:
|
||||
"""Base entity: attributes only; state writing is a no-op."""
|
||||
|
||||
def async_write_ha_state(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class RemoteEntity:
|
||||
def async_write_ha_state(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def install() -> None:
|
||||
"""Register the stub modules (idempotent)."""
|
||||
if "homeassistant.components.media_player" in sys.modules:
|
||||
return
|
||||
|
||||
vol = types.ModuleType("voluptuous")
|
||||
vol.Required = lambda key, default=None: key
|
||||
vol.Optional = lambda key, default=None: key
|
||||
vol.Schema = lambda schema: schema
|
||||
sys.modules["voluptuous"] = vol
|
||||
|
||||
ha = types.ModuleType("homeassistant")
|
||||
sys.modules["homeassistant"] = ha
|
||||
|
||||
config_entries = types.ModuleType("homeassistant.config_entries")
|
||||
config_entries.ConfigEntry = type("ConfigEntry", (), {})
|
||||
config_entries.ConfigFlow = type("ConfigFlow", (), {})
|
||||
config_entries.OptionsFlow = type("OptionsFlow", (), {})
|
||||
sys.modules["homeassistant.config_entries"] = config_entries
|
||||
|
||||
core = types.ModuleType("homeassistant.core")
|
||||
core.HomeAssistant = object
|
||||
core.callback = lambda fn: fn
|
||||
core.CALLBACK_TYPE = object
|
||||
core.Event = object
|
||||
sys.modules["homeassistant.core"] = core
|
||||
|
||||
ha_const = types.ModuleType("homeassistant.const")
|
||||
ha_const.EVENT_HOMEASSISTANT_STOP = "homeassistant_stop"
|
||||
ha_const.STATE_PLAYING = "playing"
|
||||
ha_const.STATE_PAUSED = "paused"
|
||||
ha_const.Platform = types.SimpleNamespace(
|
||||
MEDIA_PLAYER="media_player",
|
||||
SENSOR="sensor",
|
||||
REMOTE="remote",
|
||||
BUTTON="button",
|
||||
)
|
||||
sys.modules["homeassistant.const"] = ha_const
|
||||
|
||||
components = types.ModuleType("homeassistant.components")
|
||||
components.__path__ = []
|
||||
sys.modules["homeassistant.components"] = components
|
||||
|
||||
mp = types.ModuleType("homeassistant.components.media_player")
|
||||
mp.MediaPlayerEntity = MediaPlayerEntity
|
||||
mp.MediaPlayerDeviceClass = types.SimpleNamespace(TV="tv")
|
||||
mp.MediaPlayerEntityFeature = MediaPlayerEntityFeature
|
||||
mp.MediaPlayerState = MediaPlayerState
|
||||
sys.modules["homeassistant.components.media_player"] = mp
|
||||
|
||||
remote = types.ModuleType("homeassistant.components.remote")
|
||||
remote.RemoteEntity = RemoteEntity
|
||||
sys.modules["homeassistant.components.remote"] = remote
|
||||
|
||||
button = types.ModuleType("homeassistant.components.button")
|
||||
button.ButtonEntity = type("ButtonEntity", (), {})
|
||||
sys.modules["homeassistant.components.button"] = button
|
||||
|
||||
helpers = types.ModuleType("homeassistant.helpers")
|
||||
entity_registry = types.ModuleType("homeassistant.helpers.entity_registry")
|
||||
|
||||
class _Registry:
|
||||
entities: dict = {}
|
||||
|
||||
def async_get(self, entity_id):
|
||||
return None
|
||||
|
||||
entity_registry.async_get = lambda hass: _Registry()
|
||||
helpers.entity_registry = entity_registry
|
||||
sys.modules["homeassistant.helpers.entity_registry"] = entity_registry
|
||||
|
||||
event = types.ModuleType("homeassistant.helpers.event")
|
||||
event.async_call_later = lambda hass, delay, callback_fn: (lambda: None)
|
||||
event.async_track_state_change_event = lambda *args, **kwargs: (lambda: None)
|
||||
helpers.event = event
|
||||
helpers.selector = types.SimpleNamespace(
|
||||
EntitySelector=lambda *a, **k: None,
|
||||
EntitySelectorConfig=lambda *a, **k: "",
|
||||
SelectSelector=lambda *a, **k: None,
|
||||
SelectSelectorConfig=lambda **k: "",
|
||||
SelectSelectorMode=types.SimpleNamespace(DROPDOWN="dropdown"),
|
||||
)
|
||||
entity = types.ModuleType("homeassistant.helpers.entity")
|
||||
entity.DeviceInfo = dict
|
||||
helpers.entity = entity
|
||||
entity_platform = types.ModuleType("homeassistant.helpers.entity_platform")
|
||||
entity_platform.AddEntitiesCallback = object
|
||||
helpers.entity_platform = entity_platform
|
||||
sys.modules["homeassistant.helpers.event"] = event
|
||||
sys.modules["homeassistant.helpers.entity"] = entity
|
||||
sys.modules["homeassistant.helpers.entity_platform"] = entity_platform
|
||||
sys.modules["homeassistant.helpers"] = helpers
|
||||
|
||||
|
||||
def load_entity_modules() -> dict:
|
||||
"""Load the integration's HA-free-ish entity modules under nsh."""
|
||||
install()
|
||||
from conftest import _SHIELD_DIR, _load_module
|
||||
|
||||
for name in ("helpers", "power", "jellyfin", "media_player", "remote"):
|
||||
if f"nsh.{name}" not in sys.modules:
|
||||
_load_module(f"nsh.{name}", _SHIELD_DIR / f"{name}.py")
|
||||
return {
|
||||
name: sys.modules[f"nsh.{name}"]
|
||||
for name in ("power", "jellyfin", "media_player", "remote")
|
||||
}
|
||||
|
||||
|
||||
class FakeHass:
|
||||
"""Sized to entity unit tests: background tasks + no-op call_later."""
|
||||
|
||||
def async_create_task(self, coro):
|
||||
return asyncio.create_task(coro)
|
||||
|
||||
def async_call_later(self, delay, callback_fn):
|
||||
return lambda: None
|
||||
Reference in new issue
Block a user