Initial commit: Main Gate Controller HACS integration
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""Tests covering error paths in the controller."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
from custom_components.main_gate_controller.const import (
|
||||
DOMAIN,
|
||||
GATE_STATE_CLOSED,
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_SEND_MESSAGE,
|
||||
)
|
||||
from tests.conftest import ServiceCalls, make_entry_data
|
||||
|
||||
|
||||
def _make_failing_handler(fail_on_domain: str, fail_on_service: str, message: str):
|
||||
async def _handler(call) -> None: # type: ignore[no-untyped-def]
|
||||
if call.domain == fail_on_domain and call.service == fail_on_service:
|
||||
raise HomeAssistantError(message)
|
||||
# Recording handled elsewhere – this helper is only for failure simulation.
|
||||
|
||||
return _handler
|
||||
|
||||
|
||||
async def test_notification_failure_continues_cycle(
|
||||
hass: HomeAssistant, service_calls: ServiceCalls
|
||||
):
|
||||
"""If a notify.send_message call fails, the cycle still finishes normally."""
|
||||
switch = "switch.main_gate_relay"
|
||||
hass.states.async_set(switch, "off")
|
||||
hass.states.async_set("notify.mobile_app_phone", "unknown")
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Main Gate",
|
||||
data=make_entry_data(
|
||||
switch_entity_id=switch,
|
||||
name="Main Gate",
|
||||
open_duration=1,
|
||||
pulse_duration=0.1,
|
||||
notify_enabled=True,
|
||||
notify_entities=["notify.mobile_app_phone"],
|
||||
),
|
||||
unique_id=switch,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
# Replace the recording notify handler with one that fails.
|
||||
hass.services.async_remove(NOTIFY_DOMAIN, SERVICE_SEND_MESSAGE)
|
||||
hass.services.async_register(
|
||||
NOTIFY_DOMAIN,
|
||||
SERVICE_SEND_MESSAGE,
|
||||
_make_failing_handler(
|
||||
NOTIFY_DOMAIN, SERVICE_SEND_MESSAGE, "deliberate failure"
|
||||
),
|
||||
)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
controller = entry.runtime_data
|
||||
assert controller.async_trigger() is True
|
||||
await controller.async_wait_until_idle()
|
||||
|
||||
assert controller.status == GATE_STATE_CLOSED
|
||||
assert controller.is_running is False
|
||||
|
||||
|
||||
async def test_notification_log_warning_emitted(
|
||||
hass: HomeAssistant, service_calls: ServiceCalls, caplog
|
||||
):
|
||||
"""The notify failure path emits a warning via the module logger."""
|
||||
switch = "switch.main_gate_relay"
|
||||
hass.states.async_set(switch, "off")
|
||||
hass.states.async_set("notify.mobile_app_phone", "unknown")
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Main Gate",
|
||||
data=make_entry_data(
|
||||
switch_entity_id=switch,
|
||||
name="Main Gate",
|
||||
open_duration=0.5,
|
||||
pulse_duration=0.1,
|
||||
notify_enabled=True,
|
||||
notify_entities=["notify.mobile_app_phone"],
|
||||
),
|
||||
unique_id=switch,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
hass.services.async_remove(NOTIFY_DOMAIN, SERVICE_SEND_MESSAGE)
|
||||
|
||||
async def _failing_notify(call) -> None:
|
||||
raise HomeAssistantError("boom")
|
||||
|
||||
hass.services.async_register(NOTIFY_DOMAIN, SERVICE_SEND_MESSAGE, _failing_notify)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
controller = entry.runtime_data
|
||||
with caplog.at_level(logging.WARNING):
|
||||
controller.async_trigger()
|
||||
await controller.async_wait_until_idle()
|
||||
|
||||
assert any(
|
||||
"notification" in r.message.lower() and "boom" in r.message
|
||||
for r in caplog.records
|
||||
)
|
||||
|
||||
|
||||
async def test_switch_failure_aborts_cycle(
|
||||
hass: HomeAssistant, service_calls: ServiceCalls, caplog
|
||||
):
|
||||
"""A switch.turn_on failure aborts the cycle and resets state to closed."""
|
||||
switch = "switch.main_gate_relay"
|
||||
hass.states.async_set(switch, "off")
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Main Gate",
|
||||
data=make_entry_data(
|
||||
switch_entity_id=switch,
|
||||
name="Main Gate",
|
||||
open_duration=20,
|
||||
pulse_duration=0.1,
|
||||
notify_enabled=False,
|
||||
notify_entities=[],
|
||||
),
|
||||
unique_id=switch,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
# Replace the recording handler for switch.turn_off with a failing one.
|
||||
hass.services.async_remove("switch", "turn_off")
|
||||
|
||||
async def _failing_turn_off(call) -> None:
|
||||
raise HomeAssistantError("relay offline")
|
||||
|
||||
hass.services.async_register("switch", "turn_off", _failing_turn_off)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
controller = entry.runtime_data
|
||||
with caplog.at_level(logging.ERROR):
|
||||
controller.async_trigger()
|
||||
await controller.async_wait_until_idle()
|
||||
|
||||
assert controller.status == GATE_STATE_CLOSED
|
||||
assert controller.is_running is False
|
||||
assert any(
|
||||
"aborted" in r.message.lower() or "cycle aborted" in r.message.lower()
|
||||
for r in caplog.records
|
||||
)
|
||||
|
||||
|
||||
async def test_unload_cancels_running_cycle(
|
||||
hass: HomeAssistant, service_calls: ServiceCalls
|
||||
):
|
||||
"""Unloading an entry while a cycle runs cancels the background task."""
|
||||
switch = "switch.main_gate_relay"
|
||||
hass.states.async_set(switch, "off")
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Main Gate",
|
||||
data=make_entry_data(
|
||||
switch_entity_id=switch,
|
||||
name="Main Gate",
|
||||
open_duration=3,
|
||||
pulse_duration=0.2,
|
||||
notify_enabled=False,
|
||||
notify_entities=[],
|
||||
),
|
||||
unique_id=switch,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
controller = entry.runtime_data
|
||||
controller.async_trigger()
|
||||
# Immediately start unloading.
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert controller.is_running is False
|
||||
assert controller.status == GATE_STATE_CLOSED
|
||||
Reference in New Issue
Block a user