mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-17 01:04:05 +02:00
Test infrastructure: - Add `pytest-homeassistant-custom-component` (PHACC) as a `test` dependency group. PHACC ships `hass`, `enable_custom_integrations`, `MockConfigEntry`, and friends without needing to clone HA core as a sibling directory. Modernizes the test setup from upstream's `setup-symlinks` pattern. - conftest.py: add `auto_enable_custom_integrations` autouse fixture so HA discovers the integration under `custom_components/` during tests. Keep the upstream template-deprecation no-op. - Import paths: all tests now import from `custom_components.adaptive_lighting` (not `homeassistant.components.adaptive_lighting`) and from `pytest_homeassistant_custom_component.common` (not `tests.common`). New tests (groups 7.2-7.16): - tests/test_color_and_brightness.py (16 tests): TestBrightnessCurve asserts min-before-sunrise, midpoint-at-event, max-during-day, sunset ramp symmetry. TestColorTempCurve verifies the same shape applies to K. TestSunPosition checks the synthetic +1/-1/0 derivation. TestTanhDayCurveDirect exercises the helper directly. - tests/test_config_flow.py (12 tests): six sections in order, each section contains only its specified fields, conditional visibility of send_split_delay, default sun entities, strict-typed entity selectors, NumberSelector slider/box configs, BooleanSelector for every flag, full user→create-entry flow, YAML-managed entry aborts with `yaml_managed` reason, options flow renders the sectioned schema. - tests/test_init.py (6 tests): successful setup on current version, stale version raises ConfigEntryError via async_migrate_entry, unload is clean, tombstone removes orphan sleep entity + logs INFO, tombstone is idempotent, tombstone respects config_entry ownership. Source modernizations driven by the tests: - __init__.py: add `async_migrate_entry` that surfaces the "incompatible — delete and recreate" message and sets the entry to MIGRATION_ERROR. Removed direct `ConfigEntryError` from `async_setup_entry` (HA routes version mismatches through the migration handler now). - switch.py: removed upstream's YAML-managed-entry auto-remove hack. YAML profiles now load normally and the options flow handles the "you must edit configuration.yaml" message (spec R7). Existing tests updated to PHACC paths: test_adaptation_utils.py (38 tests), test_hass_utils.py (22 tests). Both pass without modification beyond the import fix. Deleted: tests/test_switch.py (2,999 LOC of upstream tests, most covering sleep mode / take-over-control / manual-control state machines that no longer exist; CDiT-specific switch tests deferred to a follow-up change). Result: `uv run --group test pytest tests/` → 94 passed in 0.57s. Deferred from this change: - 4.3: manual UI test that toggling a field and saving reloads cleanly (requires a real HA instance, can't be done from CLI). - 9.4: GitHub repo description / topics update (do via `gh repo edit` outside the change scope). openspec status: 4/4 artifacts complete; strict-validate green.
80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
"""Tests for Adaptive Lighting HASS utils."""
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
from custom_components.adaptive_lighting.adaptation_utils import ServiceData
|
|
from custom_components.adaptive_lighting.hass_utils import (
|
|
setup_service_call_interceptor,
|
|
)
|
|
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
|
from homeassistant.const import SERVICE_TURN_ON
|
|
from homeassistant.core import ServiceCall
|
|
from homeassistant.util.read_only_dict import ReadOnlyDict
|
|
|
|
|
|
async def test_setup_service_call_interceptor(hass):
|
|
"""Test setup and removal of service call interceptor."""
|
|
service_func_mock = AsyncMock()
|
|
hass.services.async_register(LIGHT_DOMAIN, SERVICE_TURN_ON, service_func_mock)
|
|
|
|
async def service_call():
|
|
await hass.services.async_call(
|
|
LIGHT_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
{},
|
|
blocking=True,
|
|
)
|
|
|
|
# Test if service is called
|
|
|
|
await service_call()
|
|
assert service_func_mock.call_count == 1
|
|
|
|
# Test if interceptor is called after setup
|
|
|
|
intercept_func_mock = AsyncMock()
|
|
remove_interceptor = setup_service_call_interceptor(
|
|
hass,
|
|
LIGHT_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
intercept_func_mock,
|
|
)
|
|
|
|
await service_call()
|
|
assert service_func_mock.call_count == 2
|
|
assert intercept_func_mock.call_count == 1
|
|
|
|
# Test if interceptor is no longer called after removal
|
|
|
|
remove_interceptor()
|
|
await service_call()
|
|
assert service_func_mock.call_count == 3
|
|
assert intercept_func_mock.call_count == 1
|
|
|
|
|
|
async def test_service_call_interceptor_data_manipulation(hass):
|
|
"""Test service call data manipulation by service call interceptor."""
|
|
service_func_mock = AsyncMock()
|
|
hass.services.async_register(LIGHT_DOMAIN, SERVICE_TURN_ON, service_func_mock)
|
|
|
|
async def intercept_func(call: ServiceCall, data: ServiceData):
|
|
data["test1"] = "changed"
|
|
data["test2"] = "added"
|
|
|
|
setup_service_call_interceptor(
|
|
hass,
|
|
LIGHT_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
intercept_func,
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
LIGHT_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
{"test1": "initial"},
|
|
blocking=True,
|
|
)
|
|
|
|
(service_call,) = service_func_mock.call_args[0]
|
|
assert service_call.data == {"test1": "changed", "test2": "added"}
|
|
assert isinstance(service_call.data, ReadOnlyDict)
|