Add more tests for APC UPS Daemon integration (#85967)

* Add tests for init.

* Add more test init.

* Fix test init side_effect.

* Add test sensor.

* Fix sensor test file name.

* Fix sensor test.

* Add binary sensor test.

* Fix comments and styling.

* Remove apcupsd from omissions in coveragerc.

* Add a test case for binary sensor when STATFLAG is not available.

* Complete type annotations for test files.

* Revert "Remove apcupsd from omissions in coveragerc."

This reverts commit 66b05fcb8829619a771a650a3d70174089e15d91.
This commit is contained in:
Yuxin Wang 2023-02-20 03:51:01 -05:00 committed by GitHub
parent b78318a617
commit ba2e80f741
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 260 additions and 0 deletions

View file

@ -1,8 +1,14 @@
"""Tests for the APCUPSd component.""" """Tests for the APCUPSd component."""
from collections import OrderedDict from collections import OrderedDict
from typing import Final from typing import Final
from unittest.mock import patch
from homeassistant.components.apcupsd import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
CONF_DATA: Final = {CONF_HOST: "test", CONF_PORT: 1234} CONF_DATA: Final = {CONF_HOST: "test", CONF_PORT: 1234}
@ -64,3 +70,30 @@ MOCK_MINIMAL_STATUS: Final = OrderedDict(
("END APC", "1970-01-01 00:00:00 0000"), ("END APC", "1970-01-01 00:00:00 0000"),
] ]
) )
async def init_integration(
hass: HomeAssistant, host: str = "test", status=None
) -> MockConfigEntry:
"""Set up the APC UPS Daemon integration in HomeAssistant."""
if status is None:
status = MOCK_STATUS
entry = MockConfigEntry(
version=1,
domain=DOMAIN,
title="APCUPSd",
data=CONF_DATA | {CONF_HOST: host},
unique_id=status.get("SERIALNO", None),
source=SOURCE_USER,
)
entry.add_to_hass(hass)
with patch("apcaccess.status.parse", return_value=status), patch(
"apcaccess.status.get", return_value=b""
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry

View file

@ -0,0 +1,28 @@
"""Test binary sensors of APCUPSd integration."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MOCK_STATUS, init_integration
async def test_binary_sensor(hass: HomeAssistant) -> None:
"""Test states of binary sensor."""
await init_integration(hass, status=MOCK_STATUS)
registry = er.async_get(hass)
state = hass.states.get("binary_sensor.ups_online_status")
assert state
assert state.state == "on"
entry = registry.async_get("binary_sensor.ups_online_status")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_statflag"
async def test_no_binary_sensor(hass: HomeAssistant) -> None:
"""Test binary sensor when STATFLAG is not available."""
status = MOCK_STATUS.copy()
status.pop("STATFLAG")
await init_integration(hass, status=status)
state = hass.states.get("binary_sensor.ups_online_status")
assert state is None

View file

@ -0,0 +1,101 @@
"""Test init of APCUPSd integration."""
from collections import OrderedDict
from unittest.mock import patch
import pytest
from homeassistant.components.apcupsd import DOMAIN, APCUPSdData
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from . import CONF_DATA, MOCK_MINIMAL_STATUS, MOCK_STATUS, init_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize("status", (MOCK_STATUS, MOCK_MINIMAL_STATUS))
async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> None:
"""Test a successful setup entry."""
# Minimal status does not contain "SERIALNO" field, which is used to determine the
# unique ID of this integration. But, the integration should work fine without it.
await init_integration(hass, status=status)
# Verify successful setup by querying the status sensor.
state = hass.states.get("binary_sensor.ups_online_status")
assert state is not None
assert state.state != STATE_UNAVAILABLE
assert state.state == "on"
async def test_multiple_integrations(hass: HomeAssistant) -> None:
"""Test successful setup for multiple entries."""
# Load two integrations from two mock hosts.
entries = (
await init_integration(hass, host="test1", status=MOCK_STATUS),
await init_integration(hass, host="test2", status=MOCK_MINIMAL_STATUS),
)
# Data dict should contain different API objects.
assert len(hass.data[DOMAIN]) == len(entries)
for entry in entries:
assert entry.entry_id in hass.data[DOMAIN]
assert isinstance(hass.data[DOMAIN][entry.entry_id], APCUPSdData)
assert (
hass.data[DOMAIN][entries[0].entry_id] != hass.data[DOMAIN][entries[1].entry_id]
)
async def test_connection_error(hass: HomeAssistant) -> None:
"""Test connection error during integration setup."""
entry = MockConfigEntry(
version=1,
domain=DOMAIN,
title="APCUPSd",
data=CONF_DATA,
source=SOURCE_USER,
)
entry.add_to_hass(hass)
with patch("apcaccess.status.parse", side_effect=OSError()), patch(
"apcaccess.status.get"
):
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_ERROR
async def test_unload_remove(hass):
"""Test successful unload of entry."""
# Load two integrations from two mock hosts.
entries = (
await init_integration(hass, host="test1", status=MOCK_STATUS),
await init_integration(hass, host="test2", status=MOCK_MINIMAL_STATUS),
)
# Assert they are loaded.
assert len(hass.config_entries.async_entries(DOMAIN)) == 2
assert all(entry.state is ConfigEntryState.LOADED for entry in entries)
# Unload the first entry.
assert await hass.config_entries.async_unload(entries[0].entry_id)
await hass.async_block_till_done()
assert entries[0].state is ConfigEntryState.NOT_LOADED
assert entries[1].state is ConfigEntryState.LOADED
assert len(hass.data[DOMAIN]) == 1
# Unload the second entry.
assert await hass.config_entries.async_unload(entries[1].entry_id)
await hass.async_block_till_done()
assert all(entry.state is ConfigEntryState.NOT_LOADED for entry in entries)
# We should never leave any garbage in the data dict.
assert len(hass.data[DOMAIN]) == 0
# Remove both entries.
for entry in entries:
await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entry.entry_id)
assert state is None

View file

@ -0,0 +1,98 @@
"""Test sensors of APCUPSd integration."""
import pytest
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
PERCENTAGE,
UnitOfElectricPotential,
UnitOfPower,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MOCK_STATUS, init_integration
async def test_sensor(hass: HomeAssistant) -> None:
"""Test states of sensor."""
await init_integration(hass, status=MOCK_STATUS)
registry = er.async_get(hass)
# Test a representative string sensor.
state = hass.states.get("sensor.ups_mode")
assert state
assert state.state == "Stand Alone"
entry = registry.async_get("sensor.ups_mode")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_upsmode"
# Test two representative voltage sensors.
state = hass.states.get("sensor.ups_input_voltage")
assert state
assert pytest.approx(float(state.state)) == 124.0
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricPotential.VOLT
)
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE
entry = registry.async_get("sensor.ups_input_voltage")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_linev"
state = hass.states.get("sensor.ups_battery_voltage")
assert state
assert pytest.approx(float(state.state)) == 13.7
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricPotential.VOLT
)
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE
entry = registry.async_get("sensor.ups_battery_voltage")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_battv"
# Test a representative percentage sensor.
state = hass.states.get("sensor.ups_load")
assert state
assert pytest.approx(float(state.state)) == 14.0
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = registry.async_get("sensor.ups_load")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_loadpct"
# Test a representative wattage sensor.
state = hass.states.get("sensor.ups_nominal_output_power")
assert state
assert pytest.approx(float(state.state)) == 330.0
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER
entry = registry.async_get("sensor.ups_nominal_output_power")
assert entry
assert entry.unique_id == "XXXXXXXXXXXX_nompower"
async def test_sensor_disabled(hass: HomeAssistant) -> None:
"""Test sensor disabled by default."""
await init_integration(hass)
registry = er.async_get(hass)
# Test a representative integration-disabled sensor.
entry = registry.async_get("sensor.ups_model")
assert entry.disabled
assert entry.unique_id == "XXXXXXXXXXXX_model"
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
# Test enabling entity.
updated_entry = registry.async_update_entity(
entry.entity_id, **{"disabled_by": None}
)
assert updated_entry != entry
assert updated_entry.disabled is False