diff --git a/pyproject.toml b/pyproject.toml index a0ee6a8..77c6d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,3 +2,5 @@ asyncio_mode = "auto" testpaths = ["tests"] norecursedirs = [".git"] +log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" diff --git a/tests/const.py b/tests/const.py new file mode 100644 index 0000000..9ae9c94 --- /dev/null +++ b/tests/const.py @@ -0,0 +1,36 @@ +"""Test constants.""" + +HOME_CONFIG = {"unique_id": "home"} + +NY_LOC = { + "latitude": 40.68954412564642, + "longitude": -74.04486696480146, + "elevation": 0, + "time_zone": "America/New_York", +} +NY_CONFIG = { + "unique_id": "new_york", + "location": "Statue of Liberty", +} | NY_LOC + +TWINE_LOC = { + "latitude": 39.50924426436838, + "longitude": -98.43369506033378, + "elevation": 10, + "time_zone": "America/Chicago", +} +TWINE_CONFIG = { + "unique_id": "twine", + "location": "World's Largest Ball of Twine", +} | TWINE_LOC + +HW_LOC = { + "latitude": 34.134092337996336, + "longitude": -118.32154780135669, + "elevation": 391, + "time_zone": "America/Los_Angeles", +} +HW_CONFIG = { + "unique_id": "hollywood", + "location": "Hollywood Sign", +} | HW_LOC diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py new file mode 100644 index 0000000..75f641b --- /dev/null +++ b/tests/test_binary_sensor.py @@ -0,0 +1,87 @@ +"""Test binary_sensor module.""" +from __future__ import annotations + +from datetime import datetime, time, timedelta +from unittest.mock import patch +import pytest + +from pytest_homeassistant_custom_component.common import ( + async_fire_time_changed, + assert_setup_component, +) + +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_registry import EntityRegistry +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util, slugify + +from custom_components.sun2.const import DOMAIN + +from .const import NY_CONFIG + + +@pytest.mark.parametrize( + "elevation,name,slug", + ( + ("horizon", None, "above_horizon"), + (1, None, "above_1_0_deg"), + (-1, None, "above_minus_1_0_deg"), + (0, "Name Test", "name_test"), + ), +) +async def test_yaml_binary_sensor( + hass: HomeAssistant, + entity_registry: EntityRegistry, + elevation: str | float, + name: str | None, + slug: str, +) -> None: + """Test YAML configured elevation binary sensor.""" + config = NY_CONFIG | { + "binary_sensors": [ + { + "unique_id": "bs1", + "elevation": elevation, + } + ] + } + if name: + config["binary_sensors"][0]["name"] = name + + tz = dt_util.get_time_zone(NY_CONFIG["time_zone"]) + base_time = dt_util.now(tz) + + # Set time to 00:00:00 tommorow. + now = datetime.combine((base_time + timedelta(1)).date(), time()).replace(tzinfo=tz) + + with patch("homeassistant.util.dt.now", return_value=now): + with assert_setup_component(1, DOMAIN): + await async_setup_component(hass, DOMAIN, {DOMAIN: [config]}) + await hass.async_block_till_done() + + config_entry = hass.config_entries.async_entries(DOMAIN)[0] + + # Check that elevation binary_sensor was created with expected entity ID and it has + # the expected state. + entity_id = entity_registry.async_get_entity_id( + "binary_sensor", DOMAIN, f"{config_entry.entry_id}-bs1" + ) + expected_id = f"binary_sensor.{slugify(NY_CONFIG['location'])}_sun_{slug}" + assert entity_id == expected_id + state = hass.states.get(entity_id) + # Sun is always below the horizon at midnight in New York. + assert state.state == STATE_OFF + # And is always above the horizon at noon. + # Next change should be after midnight and before noon. + next_change = state.attributes.get("next_change") + noon = now.replace(hour=12) + assert isinstance(next_change, datetime) + assert now < next_change < noon + + # Move time to next_change and make sure state has changed. + with patch("homeassistant.util.dt.now", return_value=next_change): + async_fire_time_changed(hass, next_change) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_ON diff --git a/tests/test_init.py b/tests/test_init.py index 7305b45..6877d0b 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,3 +1,4 @@ +"""Test init module.""" from __future__ import annotations from unittest.mock import patch @@ -11,40 +12,7 @@ from custom_components.sun2.const import DOMAIN -CONFIG_1 = {"unique_id": "1"} - -LOC_2A = { - "latitude": 40.68954412564642, - "longitude": -74.04486696480146, - "elevation": 0, - "time_zone": "America/New_York", -} -CONFIG_2A = { - "unique_id": "Test 2", - "location": "Statue of Liberty", -} | LOC_2A - -LOC_2B = { - "latitude": 39.50924426436838, - "longitude": -98.43369506033378, - "elevation": 10, - "time_zone": "America/Chicago", -} -CONFIG_2B = { - "unique_id": "Test 2", - "location": "World's Largest Ball of Twine", -} | LOC_2B - -LOC_3 = { - "latitude": 34.134092337996336, - "longitude": -118.32154780135669, - "elevation": 391, - "time_zone": "America/Los_Angeles", -} -CONFIG_3 = { - "unique_id": "3", - "location": "Hollywood Sign", -} | LOC_3 +from .const import HOME_CONFIG, HW_CONFIG, HW_LOC, NY_CONFIG, NY_LOC, TWINE_CONFIG, TWINE_LOC async def test_basic_yaml_config( @@ -52,7 +20,7 @@ async def test_basic_yaml_config( ) -> None: """Test basic YAML configuration.""" with assert_setup_component(1, DOMAIN): - await async_setup_component(hass, DOMAIN, {DOMAIN: [CONFIG_1]}) + await async_setup_component(hass, DOMAIN, {DOMAIN: [HOME_CONFIG]}) await hass.async_block_till_done() expected_entities = ( @@ -104,11 +72,9 @@ async def test_basic_yaml_config( assert entry.disabled != enabled -async def test_reload_service( - hass: HomeAssistant, entity_registry: EntityRegistry -) -> None: +async def test_reload_service(hass: HomeAssistant) -> None: """Test basic YAML configuration.""" - init_config = [CONFIG_1, CONFIG_2A] + init_config = [HOME_CONFIG, NY_CONFIG] with assert_setup_component(len(init_config), DOMAIN): await async_setup_component(hass, DOMAIN, {DOMAIN: init_config}) await hass.async_block_till_done() @@ -117,19 +83,19 @@ async def test_reload_service( config_entries = hass.config_entries.async_entries(DOMAIN) assert len(config_entries) == 2 config_entry_1 = config_entries[0] - assert config_entry_1.unique_id == CONFIG_1["unique_id"] + assert config_entry_1.unique_id == HOME_CONFIG["unique_id"] assert config_entry_1.title == hass.config.location_name assert config_entry_1.options == {} config_entry_2 = config_entries[1] - assert config_entry_2.unique_id == CONFIG_2A["unique_id"] - assert config_entry_2.title == CONFIG_2A["location"] - assert config_entry_2.options == LOC_2A + assert config_entry_2.unique_id == NY_CONFIG["unique_id"] + assert config_entry_2.title == NY_CONFIG["location"] + assert config_entry_2.options == NY_LOC # Check reload service exists. assert hass.services.has_service(DOMAIN, "reload") # Reload new config. - reload_config = [CONFIG_2B, CONFIG_3] + reload_config = [TWINE_CONFIG, HW_CONFIG] with patch( "custom_components.sun2.async_integration_yaml_config", autospec=True, @@ -142,16 +108,16 @@ async def test_reload_service( config_entries = hass.config_entries.async_entries(DOMAIN) assert len(config_entries) == 2 config_entry_1 = config_entries[0] - assert config_entry_1.unique_id == CONFIG_2B["unique_id"] - assert config_entry_1.title == CONFIG_2B["location"] - assert config_entry_1.options == LOC_2B + assert config_entry_1.unique_id == TWINE_CONFIG["unique_id"] + assert config_entry_1.title == TWINE_CONFIG["location"] + assert config_entry_1.options == TWINE_LOC config_entry_2 = config_entries[1] - assert config_entry_2.unique_id == CONFIG_3["unique_id"] - assert config_entry_2.title == CONFIG_3["location"] - assert config_entry_2.options == LOC_3 + assert config_entry_2.unique_id == HW_CONFIG["unique_id"] + assert config_entry_2.title == HW_CONFIG["location"] + assert config_entry_2.options == HW_LOC # Reload with same config. - reload_config = [CONFIG_2B, CONFIG_3] + reload_config = [TWINE_CONFIG, HW_CONFIG] with patch( "custom_components.sun2.async_integration_yaml_config", autospec=True, @@ -192,9 +158,7 @@ async def test_reload_service( assert not hass.config_entries.async_entries(DOMAIN) -async def test_ha_config_update( - hass: HomeAssistant, entity_registry: EntityRegistry -) -> None: +async def test_ha_config_update(hass: HomeAssistant) -> None: """Test when HA config is updated.""" new_time_zone = "America/New_York" new_location_name = "New York, NY" @@ -203,7 +167,7 @@ async def test_ha_config_update( assert hass.config.time_zone != new_time_zone assert hass.config.location_name != new_location_name - await async_setup_component(hass, DOMAIN, {DOMAIN: [CONFIG_1]}) + await async_setup_component(hass, DOMAIN, {DOMAIN: [HOME_CONFIG]}) await hass.async_block_till_done() # ConfigEntry object may change values, but it should not be replaced by a new @@ -237,7 +201,7 @@ async def test_ha_config_update( with patch( "custom_components.sun2.async_integration_yaml_config", autospec=True, - return_value={DOMAIN: [CONFIG_1]}, + return_value={DOMAIN: [HOME_CONFIG]}, ): await hass.config.async_update(location_name=new_location_name) await hass.async_block_till_done()