Add Islamic Prayer Times config flow (#31590)

* Add Islamic Prayer Times config_flow

* Add Islamic Prayer Times config_flow

* handle options update and fix tests

* fix sensor update handling

* fix pylint

* fix scheduled update and add test

* update test_init

* update flow options to show drop list

* clean up code

* async scheduling and revert state to timestamp

* fix update retry method

* update strings

* keep title as root key
pull/34457/head
Rami Mosleh 2020-04-20 15:22:48 +03:00 committed by GitHub
parent 1b4851f2e0
commit 2a6948e696
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 628 additions and 349 deletions

View File

@ -193,6 +193,7 @@ homeassistant/components/ipma/* @dgomes @abmantis
homeassistant/components/ipp/* @ctalkington
homeassistant/components/iqvia/* @bachya
homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/islamic_prayer_times/* @engrbm87
homeassistant/components/izone/* @Swamp-Ig
homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/juicenet/* @jesserockz

View File

@ -0,0 +1,23 @@
{
"title": "Islamic Prayer Times",
"config": {
"step": {
"user": {
"title": "Set up Islamic Prayer Times",
"description": "Do you want to set up Islamic Prayer Times?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary."
}
},
"options": {
"step": {
"init": {
"data": {
"calc_method": "Prayer calculation method"
}
}
}
}
}

View File

@ -1 +1,206 @@
"""The islamic_prayer_times component."""
from datetime import timedelta
import logging
from prayer_times_calculator import PrayerTimesCalculator, exceptions
from requests.exceptions import ConnectionError as ConnError
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later, async_track_point_in_time
import homeassistant.util.dt as dt_util
from .const import (
CALC_METHODS,
CONF_CALC_METHOD,
DATA_UPDATED,
DEFAULT_CALC_METHOD,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: {
vol.Optional(CONF_CALC_METHOD, default=DEFAULT_CALC_METHOD): vol.In(
CALC_METHODS
),
}
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass, config):
"""Import the Islamic Prayer component from config."""
if DOMAIN in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
)
return True
async def async_setup_entry(hass, config_entry):
"""Set up the Islamic Prayer Component."""
client = IslamicPrayerClient(hass, config_entry)
if not await client.async_setup():
return False
hass.data.setdefault(DOMAIN, client)
return True
async def async_unload_entry(hass, config_entry):
"""Unload Islamic Prayer entry from config_entry."""
if hass.data[DOMAIN].event_unsub:
hass.data[DOMAIN].event_unsub()
hass.data.pop(DOMAIN)
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
return True
class IslamicPrayerClient:
"""Islamic Prayer Client Object."""
def __init__(self, hass, config_entry):
"""Initialize the Islamic Prayer client."""
self.hass = hass
self.config_entry = config_entry
self.prayer_times_info = {}
self.available = True
self.event_unsub = None
@property
def calc_method(self):
"""Return the calculation method."""
return self.config_entry.options[CONF_CALC_METHOD]
def get_new_prayer_times(self):
"""Fetch prayer times for today."""
calc = PrayerTimesCalculator(
latitude=self.hass.config.latitude,
longitude=self.hass.config.longitude,
calculation_method=self.calc_method,
date=str(dt_util.now().date()),
)
return calc.fetch_prayer_times()
async def async_schedule_future_update(self):
"""Schedule future update for sensors.
Midnight is a calculated time. The specifics of the calculation
depends on the method of the prayer time calculation. This calculated
midnight is the time at which the time to pray the Isha prayers have
expired.
Calculated Midnight: The Islamic midnight.
Traditional Midnight: 12:00AM
Update logic for prayer times:
If the Calculated Midnight is before the traditional midnight then wait
until the traditional midnight to run the update. This way the day
will have changed over and we don't need to do any fancy calculations.
If the Calculated Midnight is after the traditional midnight, then wait
until after the calculated Midnight. We don't want to update the prayer
times too early or else the timings might be incorrect.
Example:
calculated midnight = 11:23PM (before traditional midnight)
Update time: 12:00AM
calculated midnight = 1:35AM (after traditional midnight)
update time: 1:36AM.
"""
_LOGGER.debug("Scheduling next update for Islamic prayer times")
now = dt_util.as_local(dt_util.now())
midnight_dt = self.prayer_times_info["Midnight"]
if now > dt_util.as_local(midnight_dt):
next_update_at = midnight_dt + timedelta(days=1, minutes=1)
_LOGGER.debug(
"Midnight is after day the changes so schedule update for after Midnight the next day"
)
else:
_LOGGER.debug(
"Midnight is before the day changes so schedule update for the next start of day"
)
next_update_at = dt_util.start_of_local_day(now + timedelta(days=1))
_LOGGER.info("Next update scheduled for: %s", next_update_at)
self.event_unsub = async_track_point_in_time(
self.hass, self.async_update, next_update_at
)
async def async_update(self, *_):
"""Update sensors with new prayer times."""
try:
prayer_times = await self.hass.async_add_executor_job(
self.get_new_prayer_times
)
self.available = True
except (exceptions.InvalidResponseError, ConnError):
self.available = False
_LOGGER.debug("Error retrieving prayer times.")
async_call_later(self.hass, 60, self.async_update)
return
for prayer, time in prayer_times.items():
self.prayer_times_info[prayer] = dt_util.parse_datetime(
f"{dt_util.now().date()} {time}"
)
await self.async_schedule_future_update()
_LOGGER.debug("New prayer times retrieved. Updating sensors.")
async_dispatcher_send(self.hass, DATA_UPDATED)
async def async_setup(self):
"""Set up the Islamic prayer client."""
await self.async_add_options()
try:
await self.hass.async_add_executor_job(self.get_new_prayer_times)
except (exceptions.InvalidResponseError, ConnError):
raise ConfigEntryNotReady
await self.async_update()
self.config_entry.add_update_listener(self.async_options_updated)
self.hass.async_create_task(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, "sensor"
)
)
return True
async def async_add_options(self):
"""Add options for entry."""
if not self.config_entry.options:
data = dict(self.config_entry.data)
calc_method = data.pop(CONF_CALC_METHOD, DEFAULT_CALC_METHOD)
self.hass.config_entries.async_update_entry(
self.config_entry, data=data, options={CONF_CALC_METHOD: calc_method}
)
@staticmethod
async def async_options_updated(hass, entry):
"""Triggered by config entry options updates."""
if hass.data[DOMAIN].event_unsub:
hass.data[DOMAIN].event_unsub()
await hass.data[DOMAIN].async_update()

View File

@ -0,0 +1,59 @@
"""Config flow for Islamic Prayer Times integration."""
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
# pylint: disable=unused-import
from .const import CALC_METHODS, CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME
class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the Islamic Prayer config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return IslamicPrayerOptionsFlowHandler(config_entry)
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="one_instance_allowed")
if user_input is None:
return self.async_show_form(step_id="user")
return self.async_create_entry(title=NAME, data=user_input)
async def async_step_import(self, import_config):
"""Import from config."""
return await self.async_step_user(user_input=import_config)
class IslamicPrayerOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Islamic Prayer client options."""
def __init__(self, config_entry):
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Manage options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
options = {
vol.Optional(
CONF_CALC_METHOD,
default=self.config_entry.options.get(
CONF_CALC_METHOD, DEFAULT_CALC_METHOD
),
): vol.In(CALC_METHODS)
}
return self.async_show_form(step_id="init", data_schema=vol.Schema(options))

View File

@ -0,0 +1,14 @@
"""Constants for the Islamic Prayer component."""
DOMAIN = "islamic_prayer_times"
NAME = "Islamic Prayer Times"
SENSOR_SUFFIX = "Prayer"
PRAYER_TIMES_ICON = "mdi:calendar-clock"
SENSOR_TYPES = ["Fajr", "Sunrise", "Dhuhr", "Asr", "Maghrib", "Isha", "Midnight"]
CONF_CALC_METHOD = "calc_method"
CALC_METHODS = ["isna", "karachi", "mwl", "makkah"]
DEFAULT_CALC_METHOD = "isna"
DATA_UPDATED = "Islamic_prayer_data_updated"

View File

@ -3,5 +3,6 @@
"name": "Islamic Prayer Times",
"documentation": "https://www.home-assistant.io/integrations/islamic_prayer_times",
"requirements": ["prayer_times_calculator==0.0.3"],
"codeowners": []
"codeowners": ["@engrbm87"],
"config_flow": true
}

View File

@ -1,186 +1,44 @@
"""Platform to retrieve Islamic prayer times information for Home Assistant."""
from datetime import datetime, timedelta
import logging
from prayer_times_calculator import PrayerTimesCalculator
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_time
import homeassistant.util.dt as dt_util
from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_SUFFIX, SENSOR_TYPES
_LOGGER = logging.getLogger(__name__)
PRAYER_TIMES_ICON = "mdi:calendar-clock"
SENSOR_TYPES = ["fajr", "sunrise", "dhuhr", "asr", "maghrib", "isha", "midnight"]
CONF_CALC_METHOD = "calculation_method"
CONF_SENSORS = "sensors"
CALC_METHODS = ["karachi", "isna", "mwl", "makkah"]
DEFAULT_CALC_METHOD = "isna"
DEFAULT_SENSORS = ["fajr", "dhuhr", "asr", "maghrib", "isha"]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_CALC_METHOD, default=DEFAULT_CALC_METHOD): vol.In(
CALC_METHODS
),
vol.Optional(CONF_SENSORS, default=DEFAULT_SENSORS): vol.All(
cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]
),
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Islamic prayer times sensor platform."""
latitude = hass.config.latitude
longitude = hass.config.longitude
calc_method = config.get(CONF_CALC_METHOD)
if None in (latitude, longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return
client = hass.data[DOMAIN]
prayer_times_data = IslamicPrayerTimesData(latitude, longitude, calc_method)
entities = []
for sensor_type in SENSOR_TYPES:
entities.append(IslamicPrayerTimeSensor(sensor_type, client))
prayer_times = prayer_times_data.get_new_prayer_times()
sensors = []
for sensor_type in config[CONF_SENSORS]:
sensors.append(IslamicPrayerTimeSensor(sensor_type, prayer_times_data))
async_add_entities(sensors, True)
# schedule the next update for the sensors
await schedule_future_update(
hass, sensors, prayer_times["Midnight"], prayer_times_data
)
async def schedule_future_update(hass, sensors, midnight_time, prayer_times_data):
"""Schedule future update for sensors.
Midnight is a calculated time. The specifics of the calculation
depends on the method of the prayer time calculation. This calculated
midnight is the time at which the time to pray the Isha prayers have
expired.
Calculated Midnight: The Islamic midnight.
Traditional Midnight: 12:00AM
Update logic for prayer times:
If the Calculated Midnight is before the traditional midnight then wait
until the traditional midnight to run the update. This way the day
will have changed over and we don't need to do any fancy calculations.
If the Calculated Midnight is after the traditional midnight, then wait
until after the calculated Midnight. We don't want to update the prayer
times too early or else the timings might be incorrect.
Example:
calculated midnight = 11:23PM (before traditional midnight)
Update time: 12:00AM
calculated midnight = 1:35AM (after traditional midnight)
update time: 1:36AM.
"""
_LOGGER.debug("Scheduling next update for Islamic prayer times")
now = dt_util.as_local(dt_util.now())
today = now.date()
midnight_dt_str = f"{today}::{midnight_time}"
midnight_dt = datetime.strptime(midnight_dt_str, "%Y-%m-%d::%H:%M")
if now > dt_util.as_local(midnight_dt):
_LOGGER.debug(
"Midnight is after day the changes so schedule update "
"for after Midnight the next day"
)
next_update_at = midnight_dt + timedelta(days=1, minutes=1)
else:
_LOGGER.debug(
"Midnight is before the day changes so schedule update for the "
"next start of day"
)
tomorrow = now + timedelta(days=1)
next_update_at = dt_util.start_of_local_day(tomorrow)
_LOGGER.debug("Next update scheduled for: %s", str(next_update_at))
async def update_sensors(_):
"""Update sensors with new prayer times."""
# Update prayer times
prayer_times = prayer_times_data.get_new_prayer_times()
_LOGGER.debug("New prayer times retrieved. Updating sensors.")
# Update all prayer times sensors
for sensor in sensors:
sensor.async_schedule_update_ha_state(True)
# Schedule next update
await schedule_future_update(
hass, sensors, prayer_times["Midnight"], prayer_times_data
)
async_track_point_in_time(hass, update_sensors, next_update_at)
class IslamicPrayerTimesData:
"""Data object for Islamic prayer times."""
def __init__(self, latitude, longitude, calc_method):
"""Create object to hold data."""
self.latitude = latitude
self.longitude = longitude
self.calc_method = calc_method
self.prayer_times_info = None
def get_new_prayer_times(self):
"""Fetch prayer times for today."""
today = datetime.today().strftime("%Y-%m-%d")
calc = PrayerTimesCalculator(
latitude=self.latitude,
longitude=self.longitude,
calculation_method=self.calc_method,
date=str(today),
)
self.prayer_times_info = calc.fetch_prayer_times()
return self.prayer_times_info
async_add_entities(entities, True)
class IslamicPrayerTimeSensor(Entity):
"""Representation of an Islamic prayer time sensor."""
def __init__(self, sensor_type, prayer_times_data):
def __init__(self, sensor_type, client):
"""Initialize the Islamic prayer time sensor."""
self.sensor_type = sensor_type
self.entity_id = f"sensor.islamic_prayer_time_{self.sensor_type}"
self.prayer_times_data = prayer_times_data
self._name = self.sensor_type.capitalize()
self._device_class = DEVICE_CLASS_TIMESTAMP
prayer_time = self.prayer_times_data.prayer_times_info[self._name]
pt_dt = self.get_prayer_time_as_dt(prayer_time)
self._state = pt_dt.isoformat()
self.client = client
@property
def name(self):
"""Return the name of the sensor."""
return self._name
return f"{self.sensor_type} {SENSOR_SUFFIX}"
@property
def unique_id(self):
"""Return the unique id of the entity."""
return self.sensor_type
@property
def icon(self):
@ -190,7 +48,7 @@ class IslamicPrayerTimeSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
return self._state
return self.client.prayer_times_info.get(self.sensor_type).isoformat()
@property
def should_poll(self):
@ -200,18 +58,10 @@ class IslamicPrayerTimeSensor(Entity):
@property
def device_class(self):
"""Return the device class."""
return self._device_class
return DEVICE_CLASS_TIMESTAMP
@staticmethod
def get_prayer_time_as_dt(prayer_time):
"""Create a datetime object for the respective prayer time."""
today = datetime.today().strftime("%Y-%m-%d")
date_time_str = f"{today} {prayer_time}"
pt_dt = dt_util.parse_datetime(date_time_str)
return pt_dt
async def async_update(self):
"""Update the sensor."""
prayer_time = self.prayer_times_data.prayer_times_info[self.name]
pt_dt = self.get_prayer_time_as_dt(prayer_time)
self._state = pt_dt.isoformat()
async def async_added_to_hass(self):
"""Handle entity which will be added."""
self.async_on_remove(
async_dispatcher_connect(self.hass, DATA_UPDATED, self.async_write_ha_state)
)

View File

@ -0,0 +1,23 @@
{
"title": "Islamic Prayer Times",
"config": {
"step": {
"user": {
"title": "Set up Islamic Prayer Times",
"description": "Do you want to set up Islamic Prayer Times?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary."
}
},
"options": {
"step": {
"init": {
"data": {
"calc_method": "Prayer calculation method"
}
}
}
}
}

View File

@ -59,6 +59,7 @@ FLOWS = [
"ipma",
"ipp",
"iqvia",
"islamic_prayer_times",
"izone",
"konnected",
"life360",

View File

@ -1 +1,45 @@
"""Tests for the islamic_prayer_times component."""
from datetime import datetime
PRAYER_TIMES = {
"Fajr": "06:10",
"Sunrise": "07:25",
"Dhuhr": "12:30",
"Asr": "15:32",
"Maghrib": "17:35",
"Isha": "18:53",
"Midnight": "00:45",
}
PRAYER_TIMES_TIMESTAMPS = {
"Fajr": datetime(2020, 1, 1, 6, 10, 0),
"Sunrise": datetime(2020, 1, 1, 7, 25, 0),
"Dhuhr": datetime(2020, 1, 1, 12, 30, 0),
"Asr": datetime(2020, 1, 1, 15, 32, 0),
"Maghrib": datetime(2020, 1, 1, 17, 35, 0),
"Isha": datetime(2020, 1, 1, 18, 53, 0),
"Midnight": datetime(2020, 1, 1, 00, 45, 0),
}
NEW_PRAYER_TIMES = {
"Fajr": "06:00",
"Sunrise": "07:25",
"Dhuhr": "12:30",
"Asr": "15:32",
"Maghrib": "17:45",
"Isha": "18:53",
"Midnight": "00:43",
}
NEW_PRAYER_TIMES_TIMESTAMPS = {
"Fajr": datetime(2020, 1, 1, 6, 00, 0),
"Sunrise": datetime(2020, 1, 1, 7, 25, 0),
"Dhuhr": datetime(2020, 1, 1, 12, 30, 0),
"Asr": datetime(2020, 1, 1, 15, 32, 0),
"Maghrib": datetime(2020, 1, 1, 17, 45, 0),
"Isha": datetime(2020, 1, 1, 18, 53, 0),
"Midnight": datetime(2020, 1, 1, 00, 43, 0),
}
NOW = datetime(2020, 1, 1, 00, 00, 0)

View File

@ -0,0 +1,83 @@
"""Tests for Islamic Prayer Times config flow."""
from unittest.mock import patch
import pytest
from homeassistant import data_entry_flow
from homeassistant.components import islamic_prayer_times
from homeassistant.components.islamic_prayer_times.const import CONF_CALC_METHOD, DOMAIN
from tests.common import MockConfigEntry
@pytest.fixture(name="mock_setup", autouse=True)
def mock_setup():
"""Mock entry setup."""
with patch(
"homeassistant.components.islamic_prayer_times.async_setup_entry",
return_value=True,
):
yield
async def test_flow_works(hass):
"""Test user config."""
result = await hass.config_entries.flow.async_init(
islamic_prayer_times.DOMAIN, context={"source": "user"}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Islamic Prayer Times"
async def test_options(hass):
"""Test updating options."""
entry = MockConfigEntry(
domain=DOMAIN,
title="Islamic Prayer Times",
data={},
options={CONF_CALC_METHOD: "isna"},
)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_CALC_METHOD: "makkah"}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"][CONF_CALC_METHOD] == "makkah"
async def test_import(hass):
"""Test import step."""
result = await hass.config_entries.flow.async_init(
islamic_prayer_times.DOMAIN,
context={"source": "import"},
data={CONF_CALC_METHOD: "makkah"},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Islamic Prayer Times"
assert result["data"][CONF_CALC_METHOD] == "makkah"
async def test_integration_already_configured(hass):
"""Test integration is already configured."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options={},)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
islamic_prayer_times.DOMAIN, context={"source": "user"}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "one_instance_allowed"

View File

@ -0,0 +1,133 @@
"""Tests for Islamic Prayer Times init."""
from datetime import timedelta
from unittest.mock import patch
from prayer_times_calculator.exceptions import InvalidResponseError
from homeassistant import config_entries
from homeassistant.components import islamic_prayer_times
from homeassistant.setup import async_setup_component
from . import (
NEW_PRAYER_TIMES,
NEW_PRAYER_TIMES_TIMESTAMPS,
NOW,
PRAYER_TIMES,
PRAYER_TIMES_TIMESTAMPS,
)
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_setup_with_config(hass):
"""Test that we import the config and setup the client."""
config = {
islamic_prayer_times.DOMAIN: {islamic_prayer_times.CONF_CALC_METHOD: "isna"}
}
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
):
assert (
await async_setup_component(hass, islamic_prayer_times.DOMAIN, config)
is True
)
async def test_successful_config_entry(hass):
"""Test that Islamic Prayer Times is configured successfully."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
entry.add_to_hass(hass)
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
):
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == config_entries.ENTRY_STATE_LOADED
assert entry.options == {
islamic_prayer_times.CONF_CALC_METHOD: islamic_prayer_times.DEFAULT_CALC_METHOD
}
async def test_setup_failed(hass):
"""Test Islamic Prayer Times failed due to an error."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
entry.add_to_hass(hass)
# test request error raising ConfigEntryNotReady
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
side_effect=InvalidResponseError(),
):
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
async def test_unload_entry(hass):
"""Test removing Islamic Prayer Times."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
entry.add_to_hass(hass)
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
):
await hass.config_entries.async_setup(entry.entry_id)
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
assert islamic_prayer_times.DOMAIN not in hass.data
async def test_islamic_prayer_times_timestamp_format(hass):
"""Test Islamic prayer times timestamp format."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
entry.add_to_hass(hass)
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
), patch("homeassistant.util.dt.now", return_value=NOW):
await hass.config_entries.async_setup(entry.entry_id)
assert (
hass.data[islamic_prayer_times.DOMAIN].prayer_times_info
== PRAYER_TIMES_TIMESTAMPS
)
async def test_update(hass):
"""Test sensors are updated with new prayer times."""
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
entry.add_to_hass(hass)
with patch(
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times"
) as FetchPrayerTimes, patch("homeassistant.util.dt.now", return_value=NOW):
FetchPrayerTimes.side_effect = [
PRAYER_TIMES,
PRAYER_TIMES,
NEW_PRAYER_TIMES,
]
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
pt_data = hass.data[islamic_prayer_times.DOMAIN]
assert pt_data.prayer_times_info == PRAYER_TIMES_TIMESTAMPS
future = pt_data.prayer_times_info["Midnight"] + timedelta(days=1, minutes=1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert (
hass.data[islamic_prayer_times.DOMAIN].prayer_times_info
== NEW_PRAYER_TIMES_TIMESTAMPS
)

View File

@ -1,186 +1,28 @@
"""The tests for the Islamic prayer times sensor platform."""
from datetime import datetime, timedelta
from unittest.mock import patch
from homeassistant.components.islamic_prayer_times.sensor import IslamicPrayerTimesData
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.components import islamic_prayer_times
from tests.common import async_fire_time_changed
from . import NOW, PRAYER_TIMES, PRAYER_TIMES_TIMESTAMPS
LATITUDE = 41
LONGITUDE = -87
CALC_METHOD = "isna"
PRAYER_TIMES = {
"Fajr": "06:10",
"Sunrise": "07:25",
"Dhuhr": "12:30",
"Asr": "15:32",
"Maghrib": "17:35",
"Isha": "18:53",
"Midnight": "00:45",
}
ENTITY_ID_FORMAT = "sensor.islamic_prayer_time_{}"
from tests.common import MockConfigEntry
def get_prayer_time_as_dt(prayer_time):
"""Create a datetime object for the respective prayer time."""
today = datetime.today().strftime("%Y-%m-%d")
date_time_str = "{} {}".format(str(today), prayer_time)
pt_dt = dt_util.parse_datetime(date_time_str)
return pt_dt
async def test_islamic_prayer_times_min_config(hass):
async def test_islamic_prayer_times_sensors(hass):
"""Test minimum Islamic prayer times configuration."""
min_config_sensors = ["fajr", "dhuhr", "asr", "maghrib", "isha"]
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
entry.add_to_hass(hass)
with patch(
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
) as PrayerTimesCalculator:
PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = (
PRAYER_TIMES
)
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
return_value=PRAYER_TIMES,
), patch("homeassistant.util.dt.now", return_value=NOW):
config = {"sensor": {"platform": "islamic_prayer_times"}}
assert await async_setup_component(hass, "sensor", config) is True
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
for sensor in min_config_sensors:
entity_id = ENTITY_ID_FORMAT.format(sensor)
entity_id_name = sensor.capitalize()
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
state = hass.states.get(entity_id)
assert state.state == pt_dt.isoformat()
assert state.name == entity_id_name
async def test_islamic_prayer_times_multiple_sensors(hass):
"""Test Islamic prayer times sensor with multiple sensors setup."""
multiple_sensors = [
"fajr",
"sunrise",
"dhuhr",
"asr",
"maghrib",
"isha",
"midnight",
]
with patch(
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
) as PrayerTimesCalculator:
PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = (
PRAYER_TIMES
)
config = {
"sensor": {"platform": "islamic_prayer_times", "sensors": multiple_sensors}
}
assert await async_setup_component(hass, "sensor", config) is True
for sensor in multiple_sensors:
entity_id = ENTITY_ID_FORMAT.format(sensor)
entity_id_name = sensor.capitalize()
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
state = hass.states.get(entity_id)
assert state.state == pt_dt.isoformat()
assert state.name == entity_id_name
async def test_islamic_prayer_times_with_calculation_method(hass):
"""Test Islamic prayer times configuration with calculation method."""
sensors = ["fajr", "maghrib"]
with patch(
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
) as PrayerTimesCalculator:
PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = (
PRAYER_TIMES
)
config = {
"sensor": {
"platform": "islamic_prayer_times",
"calculation_method": "mwl",
"sensors": sensors,
}
}
assert await async_setup_component(hass, "sensor", config) is True
for sensor in sensors:
entity_id = ENTITY_ID_FORMAT.format(sensor)
entity_id_name = sensor.capitalize()
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
state = hass.states.get(entity_id)
assert state.state == pt_dt.isoformat()
assert state.name == entity_id_name
async def test_islamic_prayer_times_data_get_prayer_times(hass):
"""Test Islamic prayer times data fetcher."""
with patch(
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
) as PrayerTimesCalculator:
PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = (
PRAYER_TIMES
)
pt_data = IslamicPrayerTimesData(
latitude=LATITUDE, longitude=LONGITUDE, calc_method=CALC_METHOD
)
assert pt_data.get_new_prayer_times() == PRAYER_TIMES
assert pt_data.prayer_times_info == PRAYER_TIMES
async def test_islamic_prayer_times_sensor_update(hass):
"""Test Islamic prayer times sensor update."""
new_prayer_times = {
"Fajr": "06:10",
"Sunrise": "07:25",
"Dhuhr": "12:30",
"Asr": "15:32",
"Maghrib": "17:45",
"Isha": "18:53",
"Midnight": "00:45",
}
with patch(
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
) as PrayerTimesCalculator:
PrayerTimesCalculator.return_value.fetch_prayer_times.side_effect = [
PRAYER_TIMES,
new_prayer_times,
]
config = {
"sensor": {"platform": "islamic_prayer_times", "sensors": ["maghrib"]}
}
assert await async_setup_component(hass, "sensor", config)
entity_id = "sensor.islamic_prayer_time_maghrib"
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES["Maghrib"])
state = hass.states.get(entity_id)
assert state.state == pt_dt.isoformat()
midnight = PRAYER_TIMES["Midnight"]
now = dt_util.as_local(dt_util.now())
today = now.date()
midnight_dt_str = "{}::{}".format(str(today), midnight)
midnight_dt = datetime.strptime(midnight_dt_str, "%Y-%m-%d::%H:%M")
future = midnight_dt + timedelta(days=1, minutes=1)
with patch(
"homeassistant.components.islamic_prayer_times.sensor.dt_util.utcnow",
return_value=future,
):
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
pt_dt = get_prayer_time_as_dt(new_prayer_times["Maghrib"])
assert state.state == pt_dt.isoformat()
for prayer in PRAYER_TIMES:
assert (
hass.states.get(f"sensor.{prayer}_prayer").state
== PRAYER_TIMES_TIMESTAMPS[prayer].isoformat()
)