Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Limych committed Mar 5, 2021
1 parent 3275f39 commit d4e2b1d
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 370 deletions.
9 changes: 8 additions & 1 deletion .devcontainer/configuration.yaml
Expand Up @@ -3,7 +3,14 @@ default_config:
logger:
default: info
logs:
custom_components.integration_blueprint: debug
custom_components.car_wash: debug

# If you need to debug uncomment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
# debugpy:

binary_sensor:
- platform: car_wash
weather: weather.home_assistant

weather:
- platform: demo
78 changes: 47 additions & 31 deletions custom_components/car_wash/binary_sensor.py
@@ -1,22 +1,22 @@
#
# Copyright (c) 2019, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
# Copyright (c) 2019-2021, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
# Creative Commons BY-NC-SA 4.0 International Public License
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
#
"""
The Car Wash binary sensor.
For more details about this platform, please refer to the documentation at
https://github.com/Limych/ha-car_wash/
"""

import logging
from datetime import datetime
from typing import Optional

import voluptuous as vol

try:
from homeassistant.components.binary_sensor import BinarySensorEntity
except ImportError:
except ImportError: # pragma: no cover
from homeassistant.components.binary_sensor import (
BinarySensorDevice as BinarySensorEntity,
)
Expand Down Expand Up @@ -44,21 +44,24 @@
CONF_WEATHER,
DEFAULT_DAYS,
DEFAULT_NAME,
DOMAIN,
ICON,
STARTUP_MESSAGE,
)

_LOGGER = logging.getLogger(__name__)


PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_WEATHER): cv.entity_id,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DAYS, default=DEFAULT_DAYS): vol.Coerce(int),
vol.Optional(CONF_DAYS, default=DEFAULT_DAYS): cv.positive_int,
}
)


# pylint: disable=w0613
# pylint: disable=unused-argument
async def async_setup_platform(
hass: HomeAssistant, config, async_add_entities, discovery_info=None
):
Expand All @@ -84,26 +87,10 @@ def __init__(self, hass: HomeAssistant, friendly_name: str, weather_entity, days
self._days = days
self._state = None

async def async_added_to_hass(self):
"""Register callbacks."""

@callback
def sensor_state_listener( # pylint: disable=w0613
entity, old_state, new_state
):
"""Handle device state changes."""
self.async_schedule_update_ha_state(True)

@callback
def sensor_startup(event): # pylint: disable=w0613
"""Update template on startup."""
async_track_state_change(
self._hass, [self._weather_entity], sensor_state_listener
)

self.async_schedule_update_ha_state(True)

self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, sensor_startup)
@property
def unique_id(self):
"""Return a unique ID to use for this entity."""
return DOMAIN + "-" + str(self._weather_entity).split(".")[1]

@property
def should_poll(self):
Expand All @@ -115,6 +102,11 @@ def name(self):
"""Return the name of the sensor."""
return self._name

@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._state is not None

@property
def is_on(self):
"""Return True if sensor is on."""
Expand All @@ -123,10 +115,31 @@ def is_on(self):
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:car-wash"
return ICON

async def async_added_to_hass(self):
"""Register callbacks."""

# pylint: disable=unused-argument
@callback
def sensor_state_listener(entity, old_state, new_state):
"""Handle device state changes."""
self.async_schedule_update_ha_state(True)

# pylint: disable=unused-argument
@callback
def sensor_startup(event):
"""Update template on startup."""
async_track_state_change(
self._hass, [self._weather_entity], sensor_state_listener
)

self.async_schedule_update_ha_state(True)

self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, sensor_startup)

@staticmethod
def _temp2c(temperature: float, temperature_unit: str) -> float:
def _temp2c(temperature: Optional[float], temperature_unit: str) -> Optional[float]:
"""Convert weather temperature to Celsius degree."""
if temperature is not None and temperature_unit != TEMP_CELSIUS:
temperature = convert_temperature(
Expand All @@ -135,7 +148,8 @@ def _temp2c(temperature: float, temperature_unit: str) -> float:

return temperature

async def async_update(self): # pylint: disable=r0912,r0915
# pylint: disable=r0912,r0915
async def async_update(self):
"""Update the sensor state."""
wdata = self._hass.states.get(self._weather_entity)

Expand All @@ -150,6 +164,7 @@ async def async_update(self): # pylint: disable=r0912,r0915
forecast = wdata.attributes.get(ATTR_FORECAST)

if forecast is None:
self._state = None
raise HomeAssistantError(
"Can't get forecast data! Are you sure it's the weather provider?"
)
Expand All @@ -163,9 +178,10 @@ async def async_update(self): # pylint: disable=r0912,r0915
self._state = False
return

cur_date = datetime.now().strftime("%F")
today = dt_util.start_of_local_day()
cur_date = today.strftime("%F")
stop_date = datetime.fromtimestamp(
datetime.now().timestamp() + 86400 * (self._days + 1)
today.timestamp() + 86400 * (self._days + 1)
).strftime("%F")

_LOGGER.debug("Inspect weather forecast from now till %s", stop_date)
Expand Down
33 changes: 20 additions & 13 deletions custom_components/car_wash/const.py
@@ -1,15 +1,24 @@
#
# Copyright (c) 2019-2021, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
# Creative Commons BY-NC-SA 4.0 International Public License
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
#
"""
The Car Wash binary sensor.
For more details about this platform, please refer to the documentation at
https://github.com/Limych/ha-car_wash/
"""

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.weather import (
ATTR_CONDITION_EXCEPTIONAL,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_LIGHTNING_RAINY,
ATTR_CONDITION_POURING,
ATTR_CONDITION_RAINY,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SNOWY_RAINY,
)

# Base component constants
NAME = "Car Wash"
DOMAIN = "car_wash"
Expand All @@ -27,14 +36,12 @@
"""

# Icons
ICON = "mdi:car-wash"

# Device classes

# Platforms
BINARY_SENSOR = "binary_sensor"
SENSOR = "sensor"
SWITCH = "switch"
PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH]
PLATFORMS = [BINARY_SENSOR]

# Configuration and options
CONF_WEATHER = "weather"
Expand All @@ -48,11 +55,11 @@


BAD_CONDITIONS = [
"lightning-rainy",
"rainy",
"pouring",
"snowy",
"snowy-rainy",
"hail",
"exceptional",
ATTR_CONDITION_LIGHTNING_RAINY,
ATTR_CONDITION_RAINY,
ATTR_CONDITION_POURING,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SNOWY_RAINY,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_EXCEPTIONAL,
]
22 changes: 0 additions & 22 deletions tests/conftest.py
Expand Up @@ -18,8 +18,6 @@

import pytest

from custom_components.car_wash import IntegrationBlueprintApiClient

pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name


Expand All @@ -33,23 +31,3 @@ def skip_notifications_fixture():
"homeassistant.components.persistent_notification.async_dismiss"
):
yield


# This fixture, when used, will result in calls to async_get_data to return None. To have the call
# return a value, we would add the `return_value=<VALUE_TO_RETURN>` parameter to the patch call.
@pytest.fixture(name="bypass_get_data")
def bypass_get_data_fixture():
"""Skip calls to get data from API."""
with patch.object(IntegrationBlueprintApiClient, "async_get_data"):
yield


# In this fixture, we are forcing calls to async_get_data to raise an Exception. This is useful
# for exception handling.
@pytest.fixture(name="error_on_get_data")
def error_get_data_fixture():
"""Simulate error when retrieving data from API."""
with patch.object(
IntegrationBlueprintApiClient, "async_get_data", side_effect=Exception
):
yield
4 changes: 0 additions & 4 deletions tests/const.py
@@ -1,5 +1 @@
"""Constants for tests."""
from custom_components.car_wash.const import CONF_PASSWORD, CONF_USERNAME

# Mock config data to be used across multiple tests
MOCK_CONFIG = {CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password"}
Empty file removed tests/fixtures/.gitkeep
Empty file.
89 changes: 0 additions & 89 deletions tests/test_api.py

This file was deleted.

0 comments on commit d4e2b1d

Please sign in to comment.