Bump pyjvcprojector to 2.0.0 (#160739)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Steve Easley
2026-01-17 15:44:29 +01:00
committed by GitHub
co-authored by Joost Lekkerkerker
parent 862a2bc95c
commit 907861effd
22 changed files with 292 additions and 137 deletions
@@ -2,7 +2,7 @@
from __future__ import annotations
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorTimeoutError
from homeassistant.const import (
CONF_HOST,
@@ -11,8 +11,9 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
@@ -28,8 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
)
try:
await device.connect(True)
except JvcProjectorConnectError as err:
await device.connect()
except JvcProjectorTimeoutError as err:
await device.disconnect()
raise ConfigEntryNotReady(
f"Unable to connect to {entry.data[CONF_HOST]}"
@@ -50,6 +51,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect)
)
await async_migrate_entities(hass, entry, coordinator)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -60,3 +63,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await entry.runtime_data.device.disconnect()
return unload_ok
async def async_migrate_entities(
hass: HomeAssistant,
config_entry: JVCConfigEntry,
coordinator: JvcProjectorDataUpdateCoordinator,
) -> None:
"""Migrate old entities as needed."""
@callback
def _update_entry(entry: RegistryEntry) -> dict[str, str] | None:
"""Fix unique_id of power binary_sensor entry."""
if entry.domain == Platform.BINARY_SENSOR and ":" not in entry.unique_id:
if "_power" in entry.unique_id:
return {"new_unique_id": f"{coordinator.unique_id}_power"}
return None
await async_migrate_entries(hass, config_entry.entry_id, _update_entry)
@@ -2,16 +2,17 @@
from __future__ import annotations
from jvcprojector import const
from jvcprojector import command as cmd
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import POWER
from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
from .entity import JvcProjectorEntity
ON_STATUS = (const.ON, const.WARMING)
ON_STATUS = (cmd.Power.ON, cmd.Power.WARMING)
async def async_setup_entry(
@@ -21,14 +22,13 @@ async def async_setup_entry(
) -> None:
"""Set up the JVC Projector platform from a config entry."""
coordinator = entry.runtime_data
async_add_entities([JvcBinarySensor(coordinator)])
class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
"""The entity class for JVC Projector Binary Sensor."""
_attr_translation_key = "jvc_power"
_attr_translation_key = "power"
def __init__(
self,
@@ -36,9 +36,9 @@ class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
) -> None:
"""Initialize the JVC Projector sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.device.mac}_power"
self._attr_unique_id = f"{coordinator.unique_id}_power"
@property
def is_on(self) -> bool:
"""Return true if the JVC is on."""
return self.coordinator.data["power"] in ON_STATUS
"""Return true if the JVC Projector is on."""
return self.coordinator.data[POWER] in ON_STATUS
@@ -5,7 +5,12 @@ from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import (
JvcProjector,
JvcProjectorAuthError,
JvcProjectorTimeoutError,
command as cmd,
)
from jvcprojector.projector import DEFAULT_PORT
import voluptuous as vol
@@ -40,7 +45,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN):
mac = await get_mac_address(host, port, password)
except InvalidHost:
errors["base"] = "invalid_host"
except JvcProjectorConnectError:
except JvcProjectorTimeoutError:
errors["base"] = "cannot_connect"
except JvcProjectorAuthError:
errors["base"] = "invalid_auth"
@@ -91,7 +96,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN):
try:
await get_mac_address(host, port, password)
except JvcProjectorConnectError:
except JvcProjectorTimeoutError:
errors["base"] = "cannot_connect"
except JvcProjectorAuthError:
errors["base"] = "invalid_auth"
@@ -115,7 +120,7 @@ async def get_mac_address(host: str, port: int, password: str | None) -> str:
"""Get device mac address for config flow."""
device = JvcProjector(host, port=port, password=password)
try:
await device.connect(True)
await device.connect()
return await device.get(cmd.MacAddress)
finally:
await device.disconnect()
return device.mac
@@ -3,3 +3,7 @@
NAME = "JVC Projector"
DOMAIN = "jvc_projector"
MANUFACTURER = "JVC"
POWER = "power"
INPUT = "input"
SOURCE = "source"
@@ -4,22 +4,21 @@ from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from typing import TYPE_CHECKING, Any
from jvcprojector import (
JvcProjector,
JvcProjectorAuthError,
JvcProjectorConnectError,
const,
JvcProjectorTimeoutError,
command as cmd,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import NAME
from .const import INPUT, NAME, POWER
_LOGGER = logging.getLogger(__name__)
@@ -46,26 +45,33 @@ class JvcProjectorDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str]]):
update_interval=INTERVAL_SLOW,
)
self.device = device
self.unique_id = format_mac(device.mac)
self.device: JvcProjector = device
if TYPE_CHECKING:
assert config_entry.unique_id is not None
self.unique_id = config_entry.unique_id
async def _async_update_data(self) -> dict[str, Any]:
"""Get the latest state data."""
state: dict[str, str | None] = {
POWER: None,
INPUT: None,
}
try:
state = await self.device.get_state()
except JvcProjectorConnectError as err:
state[POWER] = await self.device.get(cmd.Power)
if state[POWER] == cmd.Power.ON:
state[INPUT] = await self.device.get(cmd.Input)
except JvcProjectorTimeoutError as err:
raise UpdateFailed(f"Unable to connect to {self.device.host}") from err
except JvcProjectorAuthError as err:
raise ConfigEntryAuthFailed("Password authentication failed") from err
old_interval = self.update_interval
if state[const.POWER] != const.STANDBY:
if state[POWER] != cmd.Power.STANDBY:
self.update_interval = INTERVAL_FAST
else:
self.update_interval = INTERVAL_SLOW
if self.update_interval != old_interval:
_LOGGER.debug("Changed update interval to %s", self.update_interval)
return state
@@ -26,7 +26,7 @@ class JvcProjectorEntity(CoordinatorEntity[JvcProjectorDataUpdateCoordinator]):
self._attr_unique_id = coordinator.unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.unique_id)},
identifiers={(DOMAIN, self._attr_unique_id)},
name=NAME,
model=self.device.model,
manufacturer=MANUFACTURER,
@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["jvcprojector"],
"requirements": ["pyjvcprojector==1.1.3"]
"requirements": ["pyjvcprojector==2.0.0"]
}
@@ -7,54 +7,62 @@ from collections.abc import Iterable
import logging
from typing import Any
from jvcprojector import const
from jvcprojector import command as cmd
from homeassistant.components.remote import RemoteEntity
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import POWER
from .coordinator import JVCConfigEntry
from .entity import JvcProjectorEntity
COMMANDS = {
"menu": const.REMOTE_MENU,
"up": const.REMOTE_UP,
"down": const.REMOTE_DOWN,
"left": const.REMOTE_LEFT,
"right": const.REMOTE_RIGHT,
"ok": const.REMOTE_OK,
"back": const.REMOTE_BACK,
"mpc": const.REMOTE_MPC,
"hide": const.REMOTE_HIDE,
"info": const.REMOTE_INFO,
"input": const.REMOTE_INPUT,
"cmd": const.REMOTE_CMD,
"advanced_menu": const.REMOTE_ADVANCED_MENU,
"picture_mode": const.REMOTE_PICTURE_MODE,
"color_profile": const.REMOTE_COLOR_PROFILE,
"lens_control": const.REMOTE_LENS_CONTROL,
"setting_memory": const.REMOTE_SETTING_MEMORY,
"gamma_settings": const.REMOTE_GAMMA_SETTINGS,
"hdmi_1": const.REMOTE_HDMI_1,
"hdmi_2": const.REMOTE_HDMI_2,
"mode_1": const.REMOTE_MODE_1,
"mode_2": const.REMOTE_MODE_2,
"mode_3": const.REMOTE_MODE_3,
"mode_4": const.REMOTE_MODE_4,
"mode_5": const.REMOTE_MODE_5,
"mode_6": const.REMOTE_MODE_6,
"mode_7": const.REMOTE_MODE_7,
"mode_8": const.REMOTE_MODE_8,
"mode_9": const.REMOTE_MODE_9,
"mode_10": const.REMOTE_MODE_10,
"lens_ap": const.REMOTE_LENS_AP,
"gamma": const.REMOTE_GAMMA,
"color_temp": const.REMOTE_COLOR_TEMP,
"natural": const.REMOTE_NATURAL,
"cinema": const.REMOTE_CINEMA,
"anamo": const.REMOTE_ANAMO,
"3d_format": const.REMOTE_3D_FORMAT,
COMMANDS: list[str] = [
cmd.Remote.MENU,
cmd.Remote.UP,
cmd.Remote.DOWN,
cmd.Remote.LEFT,
cmd.Remote.RIGHT,
cmd.Remote.OK,
cmd.Remote.BACK,
cmd.Remote.MPC,
cmd.Remote.HIDE,
cmd.Remote.INFO,
cmd.Remote.INPUT,
cmd.Remote.CMD,
cmd.Remote.ADVANCED_MENU,
cmd.Remote.PICTURE_MODE,
cmd.Remote.COLOR_PROFILE,
cmd.Remote.LENS_CONTROL,
cmd.Remote.SETTING_MEMORY,
cmd.Remote.GAMMA_SETTINGS,
cmd.Remote.HDMI1,
cmd.Remote.HDMI2,
cmd.Remote.MODE_1,
cmd.Remote.MODE_2,
cmd.Remote.MODE_3,
cmd.Remote.MODE_4,
cmd.Remote.MODE_5,
cmd.Remote.MODE_6,
cmd.Remote.MODE_7,
cmd.Remote.MODE_8,
cmd.Remote.MODE_9,
cmd.Remote.MODE_10,
cmd.Remote.GAMMA,
cmd.Remote.NATURAL,
cmd.Remote.CINEMA,
cmd.Remote.COLOR_TEMP,
cmd.Remote.ANAMORPHIC,
cmd.Remote.LENS_APERTURE,
cmd.Remote.V3D_FORMAT,
]
RENAMED_COMMANDS: dict[str, str] = {
"anamo": cmd.Remote.ANAMORPHIC,
"lens_ap": cmd.Remote.LENS_APERTURE,
"hdmi1": cmd.Remote.HDMI1,
"hdmi2": cmd.Remote.HDMI2,
}
_LOGGER = logging.getLogger(__name__)
@@ -77,25 +85,34 @@ class JvcProjectorRemote(JvcProjectorEntity, RemoteEntity):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self.coordinator.data["power"] in [const.ON, const.WARMING]
"""Return True if the entity is on."""
return self.coordinator.data[POWER] in (cmd.Power.ON, cmd.Power.WARMING)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
await self.device.power_on()
await self.device.set(cmd.Power, cmd.Power.ON)
await asyncio.sleep(1)
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self.device.power_off()
await self.device.set(cmd.Power, cmd.Power.OFF)
await asyncio.sleep(1)
await self.coordinator.async_refresh()
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send a remote command to the device."""
for cmd in command:
if cmd not in COMMANDS:
raise HomeAssistantError(f"{cmd} is not a known command")
_LOGGER.debug("Sending command '%s'", cmd)
await self.device.remote(COMMANDS[cmd])
for send_command in command:
# Legacy name replace
if send_command in RENAMED_COMMANDS:
send_command = RENAMED_COMMANDS[send_command]
# Legacy name fixup
if "_" in send_command:
send_command = send_command.replace("_", "-")
if send_command not in COMMANDS:
raise HomeAssistantError(f"{send_command} is not a known command")
_LOGGER.debug("Sending command '%s'", send_command)
await self.device.remote(send_command)
@@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Final
from jvcprojector import JvcProjector, const
from jvcprojector import JvcProjector, command as cmd
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
@@ -23,16 +23,12 @@ class JvcProjectorSelectDescription(SelectEntityDescription):
command: Callable[[JvcProjector, str], Awaitable[None]]
OPTIONS: Final[dict[str, dict[str, str]]] = {
"input": {const.HDMI1: const.REMOTE_HDMI_1, const.HDMI2: const.REMOTE_HDMI_2}
}
SELECTS: Final[list[JvcProjectorSelectDescription]] = [
JvcProjectorSelectDescription(
key="input",
translation_key="input",
options=list(OPTIONS["input"]),
command=lambda device, option: device.remote(OPTIONS["input"][option]),
options=[cmd.Input.HDMI1, cmd.Input.HDMI2],
command=lambda device, option: device.set(cmd.Input, option),
)
]
@@ -2,7 +2,7 @@
from __future__ import annotations
from jvcprojector import const
from jvcprojector import command as cmd
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -23,11 +23,11 @@ JVC_SENSORS = (
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
options=[
const.STANDBY,
const.ON,
const.WARMING,
const.COOLING,
const.ERROR,
cmd.Power.STANDBY,
cmd.Power.ON,
cmd.Power.WARMING,
cmd.Power.COOLING,
cmd.Power.ERROR,
],
),
)
@@ -35,7 +35,7 @@
},
"entity": {
"binary_sensor": {
"jvc_power": {
"power": {
"name": "[%key:component::binary_sensor::entity_component::power::name%]"
}
},
@@ -50,7 +50,7 @@
},
"sensor": {
"jvc_power_status": {
"name": "Power status",
"name": "Status",
"state": {
"cooling": "Cooling",
"error": "[%key:common::state::error%]",
+1 -1
View File
@@ -2132,7 +2132,7 @@ pyitachip2ir==0.0.7
pyituran==0.1.5
# homeassistant.components.jvc_projector
pyjvcprojector==1.1.3
pyjvcprojector==2.0.0
# homeassistant.components.kaleidescape
pykaleidescape==1.0.2
+1 -1
View File
@@ -1806,7 +1806,7 @@ pyisy==3.4.1
pyituran==0.1.5
# homeassistant.components.jvc_projector
pyjvcprojector==1.1.3
pyjvcprojector==2.0.0
# homeassistant.components.kaleidescape
pykaleidescape==1.0.2
+3 -2
View File
@@ -3,5 +3,6 @@
MOCK_HOST = "127.0.0.1"
MOCK_PORT = 20554
MOCK_PASSWORD = "jvcpasswd"
MOCK_MAC = "jvcmac"
MOCK_MODEL = "jvcmodel"
MOCK_MAC = "E0DADC0A1234"
MOCK_MAC_FORMATED = "e0:da:dc:0a:12:34"
MOCK_MODEL = "B2A2"
+21 -4
View File
@@ -3,11 +3,13 @@
from collections.abc import Generator
from unittest.mock import MagicMock, patch
from jvcprojector import command as cmd
import pytest
from homeassistant.components.jvc_projector.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
from . import MOCK_HOST, MOCK_MAC, MOCK_MODEL, MOCK_PASSWORD, MOCK_PORT
@@ -20,16 +22,31 @@ def fixture_mock_device(
) -> Generator[MagicMock]:
"""Return a mocked JVC Projector device."""
target = "homeassistant.components.jvc_projector.JvcProjector"
fixture: dict[str, str] = {
"mac": MOCK_MAC,
"power": "standby",
"input": "hdmi-1",
}
if hasattr(request, "param"):
target = request.param
target = request.param.get("target", target)
fixture = request.param.get("get", fixture)
async def device_get(command) -> str:
if command is cmd.MacAddress:
return fixture["mac"]
if command is cmd.Power:
return fixture["power"]
if command is cmd.Input:
return fixture["input"]
raise ValueError(f"Fixture failure; unexpected command {command}")
with patch(target, autospec=True) as mock:
device = mock.return_value
device.host = MOCK_HOST
device.port = MOCK_PORT
device.mac = MOCK_MAC
device.model = MOCK_MODEL
device.get_state.return_value = {"power": "standby", "input": "hdmi1"}
device.get.side_effect = device_get
yield device
@@ -38,7 +55,7 @@ def fixture_mock_config_entry() -> MockConfigEntry:
"""Return a mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_MAC,
unique_id=format_mac(MOCK_MAC),
version=1,
data={
CONF_HOST: MOCK_HOST,
@@ -2,9 +2,13 @@
from unittest.mock import MagicMock
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.jvc_projector.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MOCK_MAC, MOCK_MAC_FORMATED
from tests.common import MockConfigEntry
ENTITY_ID = "binary_sensor.jvc_projector_power"
@@ -20,3 +24,40 @@ async def test_entity_state(
entity = hass.states.get(ENTITY_ID)
assert entity
assert entity_registry.async_get(entity.entity_id)
async def test_migrate_old_unique_id(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_device: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Tests power binary sensor unique id is reformatted."""
mock_config_entry.add_to_hass(hass)
# Entity to be migrated
entity_registry.async_get_or_create(
BINARY_SENSOR_DOMAIN,
DOMAIN,
f"{MOCK_MAC}_power",
config_entry=mock_config_entry,
suggested_object_id="jvc_projector_power",
)
# Ignored entity to get to 100% coverage
entity_registry.async_get_or_create(
BINARY_SENSOR_DOMAIN,
DOMAIN,
f"{MOCK_MAC}_ignore",
config_entry=mock_config_entry,
suggested_object_id="jvc_projector_ignore",
)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
entity = hass.states.get(ENTITY_ID)
assert entity
entry = entity_registry.async_get(entity.entity_id)
assert entry
assert entry.unique_id == f"{MOCK_MAC_FORMATED}_power"
@@ -2,7 +2,7 @@
from unittest.mock import AsyncMock
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
import pytest
from homeassistant.components.jvc_projector.const import DOMAIN
@@ -18,7 +18,7 @@ from tests.common import MockConfigEntry
TARGET = "homeassistant.components.jvc_projector.config_flow.JvcProjector"
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_user_config_flow_success(
hass: HomeAssistant, mock_device: AsyncMock
) -> None:
@@ -46,12 +46,12 @@ async def test_user_config_flow_success(
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_user_config_flow_bad_connect_errors(
hass: HomeAssistant, mock_device: AsyncMock
) -> None:
"""Test errors when connection error occurs."""
mock_device.connect.side_effect = JvcProjectorConnectError
mock_device.connect.side_effect = JvcProjectorTimeoutError
result = await hass.config_entries.flow.async_init(
DOMAIN,
@@ -80,7 +80,7 @@ async def test_user_config_flow_bad_connect_errors(
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_user_config_flow_device_exists_abort(
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
) -> None:
@@ -94,7 +94,7 @@ async def test_user_config_flow_device_exists_abort(
assert result["reason"] == "already_configured"
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_user_config_flow_bad_host_errors(
hass: HomeAssistant, mock_device: AsyncMock
) -> None:
@@ -124,7 +124,7 @@ async def test_user_config_flow_bad_host_errors(
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_user_config_flow_bad_auth_errors(
hass: HomeAssistant, mock_device: AsyncMock
) -> None:
@@ -158,7 +158,7 @@ async def test_user_config_flow_bad_auth_errors(
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_reauth_config_flow_success(
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
) -> None:
@@ -180,7 +180,7 @@ async def test_reauth_config_flow_success(
assert mock_integration.data[CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_reauth_config_flow_auth_error(
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
) -> None:
@@ -221,12 +221,12 @@ async def test_reauth_config_flow_auth_error(
assert mock_integration.data[CONF_PASSWORD] == MOCK_PASSWORD
@pytest.mark.parametrize("mock_device", [TARGET], indirect=True)
@pytest.mark.parametrize("mock_device", [{"target": TARGET}], indirect=True)
async def test_reauth_config_flow_connect_error(
hass: HomeAssistant, mock_device: AsyncMock, mock_integration: MockConfigEntry
) -> None:
"""Test reauth config flow when connect fails."""
mock_device.connect.side_effect = JvcProjectorConnectError
mock_device.connect.side_effect = JvcProjectorTimeoutError
result = await mock_integration.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
@@ -3,7 +3,8 @@
from datetime import timedelta
from unittest.mock import AsyncMock
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
import pytest
from homeassistant.components.jvc_projector.coordinator import (
INTERVAL_FAST,
@@ -13,6 +14,8 @@ from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow
from . import MOCK_MAC
from tests.common import MockConfigEntry, async_fire_time_changed
@@ -22,49 +25,51 @@ async def test_coordinator_update(
mock_integration: MockConfigEntry,
) -> None:
"""Test coordinator update runs."""
mock_device.get_state.return_value = {"power": "standby", "input": "hdmi1"}
async_fire_time_changed(
hass, utcnow() + timedelta(seconds=INTERVAL_SLOW.seconds + 1)
)
await hass.async_block_till_done()
assert mock_device.get_state.call_count == 3
coordinator = mock_integration.runtime_data
assert coordinator.update_interval == INTERVAL_SLOW
async def test_coordinator_connect_error(
async def test_coordinator_setup_connect_error(
hass: HomeAssistant,
mock_device: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test coordinator connect error."""
mock_device.get_state.side_effect = JvcProjectorConnectError
mock_device.get.side_effect = JvcProjectorTimeoutError
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_coordinator_auth_error(
async def test_coordinator_setup_auth_error(
hass: HomeAssistant,
mock_device: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test coordinator auth error."""
mock_device.get_state.side_effect = JvcProjectorAuthError
mock_device.get.side_effect = JvcProjectorAuthError
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
@pytest.mark.parametrize(
"mock_device",
[{"get": {"mac": MOCK_MAC, "power": "on", "input": "hdmi-1"}}],
indirect=True,
)
async def test_coordinator_device_on(
hass: HomeAssistant,
mock_device: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test coordinator changes update interval when device is on."""
mock_device.get_state.return_value = {"power": "on", "input": "hdmi1"}
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
+24 -4
View File
@@ -2,12 +2,14 @@
from unittest.mock import AsyncMock
from jvcprojector import JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import JvcProjectorAuthError, JvcProjectorTimeoutError
from homeassistant.components.jvc_projector.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import format_mac
from . import MOCK_MAC
@@ -21,9 +23,10 @@ async def test_init(
mock_integration: MockConfigEntry,
) -> None:
"""Test initialization."""
device = device_registry.async_get_device(identifiers={(DOMAIN, MOCK_MAC)})
mac = format_mac(MOCK_MAC)
device = device_registry.async_get_device(identifiers={(DOMAIN, mac)})
assert device is not None
assert device.identifiers == {(DOMAIN, MOCK_MAC)}
assert device.identifiers == {(DOMAIN, mac)}
async def test_unload_config_entry(
@@ -38,6 +41,23 @@ async def test_unload_config_entry(
await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_device.disconnect.call_count == 1
async def test_disconnect_on_hass_stop(
hass: HomeAssistant,
mock_device: AsyncMock,
mock_integration: MockConfigEntry,
) -> None:
"""Test device disconnects when Home Assistant stops."""
assert mock_integration.state is ConfigEntryState.LOADED
assert mock_device.disconnect.call_count == 0
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert mock_device.disconnect.call_count == 1
async def test_config_entry_connect_error(
hass: HomeAssistant,
@@ -45,7 +65,7 @@ async def test_config_entry_connect_error(
mock_config_entry: MockConfigEntry,
) -> None:
"""Test config entry with connect error."""
mock_device.connect.side_effect = JvcProjectorConnectError
mock_device.connect.side_effect = JvcProjectorTimeoutError
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
+19 -3
View File
@@ -43,7 +43,7 @@ async def test_commands(
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
assert mock_device.power_on.call_count == 1
assert mock_device.get.call_count == 3
await hass.services.async_call(
REMOTE_DOMAIN,
@@ -51,7 +51,7 @@ async def test_commands(
{ATTR_ENTITY_ID: ENTITY_ID},
blocking=True,
)
assert mock_device.power_off.call_count == 1
assert mock_device.get.call_count == 4
await hass.services.async_call(
REMOTE_DOMAIN,
@@ -64,11 +64,27 @@ async def test_commands(
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["hdmi_1"]},
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["hdmi1"]},
blocking=True,
)
assert mock_device.remote.call_count == 2
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["anamo"]},
blocking=True,
)
assert mock_device.remote.call_count == 3
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["picture_mode"]},
blocking=True,
)
assert mock_device.remote.call_count == 4
async def test_unknown_command(
hass: HomeAssistant,
+12 -6
View File
@@ -2,7 +2,8 @@
from unittest.mock import MagicMock
from jvcprojector import const
from jvcprojector import command as cmd
import pytest
from homeassistant.components.select import (
ATTR_OPTIONS,
@@ -13,11 +14,18 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_OPTION
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MOCK_MAC
from tests.common import MockConfigEntry
INPUT_ENTITY_ID = "select.jvc_projector_input"
@pytest.mark.parametrize(
"mock_device",
[{"get": {"mac": MOCK_MAC, "power": "on", "input": "hdmi-1"}}],
indirect=True,
)
async def test_input_select(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
@@ -28,17 +36,15 @@ async def test_input_select(
entity = hass.states.get(INPUT_ENTITY_ID)
assert entity
assert entity.attributes.get(ATTR_FRIENDLY_NAME) == "JVC Projector Input"
assert entity.attributes.get(ATTR_OPTIONS) == [const.HDMI1, const.HDMI2]
assert entity.state == const.HDMI1
assert entity.attributes.get(ATTR_OPTIONS) == [cmd.Input.HDMI1, cmd.Input.HDMI2]
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: INPUT_ENTITY_ID,
ATTR_OPTION: const.HDMI2,
ATTR_OPTION: cmd.Input.HDMI2,
},
blocking=True,
)
mock_device.remote.assert_called_once_with(const.REMOTE_HDMI_2)
mock_device.set.assert_called_once_with(cmd.Input, cmd.Input.HDMI2)
@@ -7,7 +7,7 @@ from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry
POWER_ID = "sensor.jvc_projector_power_status"
POWER_ID = "sensor.jvc_projector_status"
async def test_entity_state(