Files
ha-nvidia-shield/tests/entity_stublib.py
T

226 lines
7.0 KiB
Python

"""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
BROWSE_MEDIA = 4096
TURN_ON = 64
TURN_OFF = 128
PLAY_MEDIA = 256
NEXT_TRACK = 512
PREVIOUS_TRACK = 1024
class MediaClass:
APP = "app"
DIRECTORY = "directory"
class BrowseMediaSource:
"""HA BrowseMedia shape used by the app directory tests."""
def __init__(
self,
*,
title,
media_class,
media_content_type,
media_content_id,
can_play,
can_expand,
children=None,
thumbnail=None,
):
self.title = title
self.media_class = media_class
self.media_content_type = media_content_type
self.media_content_id = media_content_id
self.can_play = can_play
self.can_expand = can_expand
self.children = children or []
self.thumbnail = thumbnail
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.BrowseMedia = BrowseMediaSource # factory used as a constructor
mp.BrowseMediaSource = BrowseMediaSource
mp.MediaClass = MediaClass
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