Skip to content

Commit

Permalink
Test coverage 💯
Browse files Browse the repository at this point in the history
  • Loading branch information
autoSteve committed Dec 28, 2024
1 parent d43e77f commit 04a9cf6
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 21 deletions.
16 changes: 12 additions & 4 deletions custom_components/solcast_solar/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,11 @@ def __init__(
self._attributes = {}
self._attr_extra_state_attributes = {}

self._sensor_data = self._coordinator.get_sensor_value(entity_description.key)
try:
self._sensor_data = self._coordinator.get_sensor_value(self.entity_description.key)
except Exception as e: # noqa: BLE001 # pragma: no cover, handle uncaught exceptions
_LOGGER.error("Unable to get sensor value: %s", e)
self._sensor_data = None

if self._sensor_data is None:
self._attr_available = False
Expand Down Expand Up @@ -409,7 +413,11 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
dict[str, Any] | None: The current attributes of a sensor.
"""
return self._coordinator.get_sensor_extra_attributes(self.entity_description.key)
try:
return self._coordinator.get_sensor_extra_attributes(self.entity_description.key)
except Exception as e: # noqa: BLE001 # pragma: no cover, handle uncaught exceptions
_LOGGER.error("Unable to get sensor attributes: %s", e)
return None

@property
def native_value(self) -> int | dt | float | str | bool | None:
Expand Down Expand Up @@ -509,7 +517,7 @@ def __init__(
try:
self._sensor_data = coordinator.get_site_sensor_value(self._rooftop_id, key)
except Exception as e: # noqa: BLE001 # pragma: no cover, handle uncaught exceptions
_LOGGER.error("Unable to get sensor value: %s: %s", e, traceback.format_exc())
_LOGGER.error("Unable to get sensor value: %s", e)
self._sensor_data = None

self._attr_device_info = {
Expand Down Expand Up @@ -545,7 +553,7 @@ def unique_id(self) -> str | None:
return f"solcast_{self._unique_id}"

@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state extra attributes of the sensor."""
try:
return self._coordinator.get_site_sensor_extra_attributes(self._rooftop_id, self._key)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/solcast_solar/solcastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ async def load_data(filename, set_loaded=True) -> dict | None:
data = json_data
if set_loaded:
self._loaded_data = True
if not self.previously_loaded:
if not self.previously_loaded: # pragma: no cover, not reached in tests
_LOGGER.info(
"%s data loaded",
"Dampened" if filename == self._filename else "Un-dampened",
Expand Down
8 changes: 7 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ async def async_init_integration(
return entry


async def async_cleanup_integration_tests(hass: HomeAssistant, config_dir: str) -> None:
async def async_cleanup_integration_tests(hass: HomeAssistant, config_dir: str, **kwargs) -> None:
"""Clean up the Solcast Solar integration caches."""

def list_files() -> list[str]:
Expand All @@ -314,6 +314,12 @@ def list_files() -> list[str]:
try:
caches = await hass.async_add_executor_job(list_files)
for cache in caches:
if not kwargs.get("solcast_dampening", True) and "solcast-dampening" in cache:
continue
if not kwargs.get("solcast_sites", True) and "solcast-sites" in cache:
continue
if not kwargs.get("solcast_usage", True) and "solcast-usage" in cache:
continue
_LOGGER.debug("Removing cache file: %s", cache)
Path(cache).unlink()
except Exception as e: # noqa: BLE001
Expand Down
24 changes: 16 additions & 8 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,6 @@ async def test_init(
assert Path(f"{config_dir}/solcast-usage-1.json").is_file()
assert Path(f"{config_dir}/solcast-usage-2.json").is_file()
assert not Path(f"{config_dir}/solcast-usage.json").is_file()
# Will exist from the prior test
assert f"Data cache {config_dir}/solcast.json exists" in caplog.text
assert f"Data cache {config_dir}/solcast-undampened.json exists" in caplog.text

# Test coordinator tasks are created
assert len(coordinator.tasks.keys()) == 3
Expand Down Expand Up @@ -220,11 +217,13 @@ async def test_init(
await hass.async_block_till_done()

finally:
# Do not clean up caches, as cahed data is loaded in the next test
# assert await async_cleanup_integration_tests(hass, config_dir)

if options == DEFAULT_INPUT2:
granular_dampening_file.unlink()
assert await async_cleanup_integration_tests(
hass,
config_dir,
solcast_dampening=options != DEFAULT_INPUT1,
solcast_sites=options != DEFAULT_INPUT1,
solcast_usage=options != DEFAULT_INPUT1,
)


async def test_remaining_actions(
Expand All @@ -234,6 +233,13 @@ async def test_remaining_actions(
) -> None:
"""Test remaining actions."""

# Start with two API keys and three sites
entry = await async_init_integration(hass, DEFAULT_INPUT2)
assert hass.data[DOMAIN].get("has_loaded") is True
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()

# Switch to one API key and two sites to assert the initial clean-up
entry = await async_init_integration(hass, DEFAULT_INPUT1)
coordinator = hass.data[DOMAIN][entry.entry_id]
solcast: SolcastApi = coordinator.solcast
Expand All @@ -251,6 +257,8 @@ def occurs_in_log(text: str, occurrences: int) -> int:
# Test logs for cache load
assert "Sites cache exists" in caplog.text
assert "Usage cache exists" in caplog.text
assert f"Data cache {config_dir}/solcast.json exists, file type is <class 'dict'>" in caplog.text
assert f"Data cache {config_dir}/solcast-undampened.json exists, file type is <class 'dict'>" in caplog.text
occurs_in_log("Renaming", 2)
occurs_in_log("Removing orphaned", 2)

Expand Down
20 changes: 13 additions & 7 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from homeassistant.components.sensor import SensorStateClass
from homeassistant.components.solcast_solar.const import DOMAIN
from homeassistant.components.solcast_solar.coordinator import SolcastUpdateCoordinator
from homeassistant.const import STATE_UNAVAILABLE, UnitOfEnergy, UnitOfPower
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfEnergy, UnitOfPower
from homeassistant.core import HomeAssistant

from . import (
Expand Down Expand Up @@ -393,13 +393,12 @@ async def test_sensor_states(
assert await async_cleanup_integration_tests(hass, hass.data[DOMAIN][entry.entry_id].solcast._config_dir)


'''
def get_sensor_value(self, key: str): # TODO: Presently never returns None
def get_sensor_value(self, key: str): # TODO: Presently never returns None
"""Raise an exception getting the value of a sensor."""
return 0 / 1
return 1 / 0


async def test_sensor_unavailable(
async def test_sensor_unknown(
recorder_mock: Recorder,
hass: HomeAssistant,
) -> None:
Expand All @@ -408,13 +407,20 @@ async def test_sensor_unavailable(
SolcastUpdateCoordinator.get_sensor_value = get_sensor_value
SolcastUpdateCoordinator.get_sensor_extra_attributes = get_sensor_value
entry = await async_init_integration(hass, DEFAULT_INPUT1)
coordinator: SolcastUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

try:
for sensor in SENSORS:
state = hass.states.get(f"sensor.solcast_pv_forecast_{sensor}")
assert state
assert state.state == STATE_UNAVAILABLE
assert state.state == STATE_UNKNOWN

coordinator._data_updated = True
coordinator.async_update_listeners()

for sensor in SENSORS:
state = hass.states.get(f"sensor.solcast_pv_forecast_{sensor}")
assert state.state == STATE_UNKNOWN

finally:
assert await async_cleanup_integration_tests(hass, hass.data[DOMAIN][entry.entry_id].solcast._config_dir)
'''

0 comments on commit 04a9cf6

Please sign in to comment.