diff --git a/tests_backend/test_virtual_lights.py b/tests_backend/test_virtual_lights.py index 567d78d4..e2bd2091 100644 --- a/tests_backend/test_virtual_lights.py +++ b/tests_backend/test_virtual_lights.py @@ -1,12 +1,28 @@ -"""Pure operational-store rules independent of the HA WebSocket harness.""" -import pytest +"""Pure operational-store rules independent of the HA WebSocket harness. -from custom_components.houseplan.virtual_lights import ( - async_reconcile_virtual_lights, - async_toggle_virtual_light, - async_virtual_light_snapshot, - eligible_virtual_light_ids, +virtual_lights.py is loaded by path, without importing the HA integration +package: the ordinary `from custom_components.houseplan...` import executes the +package __init__, which unconditionally imports homeassistant — and a missing +homeassistant then breaks pytest collection for the whole tests_backend/ +directory, not just this file (#135). test_validation.py established the +pattern; the module itself imports nothing beyond the standard library. +""" +import asyncio +import importlib.util +import os + +_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "custom_components", "houseplan", "virtual_lights.py", ) +_spec = importlib.util.spec_from_file_location("hp_virtual_lights", _PATH) +_vl = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_vl) + +async_reconcile_virtual_lights = _vl.async_reconcile_virtual_lights +async_toggle_virtual_light = _vl.async_toggle_virtual_light +async_virtual_light_snapshot = _vl.async_virtual_light_snapshot +eligible_virtual_light_ids = _vl.eligible_virtual_light_ids class FakeStore: @@ -44,28 +60,29 @@ def test_eligibility_is_the_exact_triple_and_hidden_is_not_lifecycle(): assert eligible_virtual_light_ids(config) == {"eligible"} -@pytest.mark.asyncio -async def test_revision_gap_fails_safe_on_and_known_transition_preserves_only_eligible(): +def test_revision_gap_fails_safe_on_and_known_transition_preserves_only_eligible(): + # asyncio.run вместо pytest.mark.asyncio: маркер требует плагина + # pytest-asyncio, которого в офлайн-окружении без HA нет, и тесты падали бы + # как «async def not natively supported» — офлайн-гейт обязан быть зелёным. store = FakeStore({"rev": 5, "config_rev": 2, "off": ["keep", "drop"]}) - gap = await async_virtual_light_snapshot(store, _config(_manual("keep")), 4) + gap = asyncio.run(async_virtual_light_snapshot(store, _config(_manual("keep")), 4)) assert gap == {"rev": 6, "config_rev": 4, "off": []} store = FakeStore({"rev": 8, "config_rev": 4, "off": ["keep", "drop"]}) - carried = await async_reconcile_virtual_lights( + carried = asyncio.run(async_reconcile_virtual_lights( store, _config(_manual("keep", hidden=True), _manual("drop", is_light=False)), 5, previous_config_rev=4, - ) + )) assert carried == {"rev": 9, "config_rev": 5, "off": ["keep"]} -@pytest.mark.asyncio -async def test_toggle_accepts_only_an_id_and_inverts_server_current_state(): +def test_toggle_accepts_only_an_id_and_inverts_server_current_state(): store = FakeStore() config = _config(_manual("lamp")) - first = await async_toggle_virtual_light(store, config, 1, "lamp") - second = await async_toggle_virtual_light(store, config, 1, "lamp") + first = asyncio.run(async_toggle_virtual_light(store, config, 1, "lamp")) + second = asyncio.run(async_toggle_virtual_light(store, config, 1, "lamp")) assert first == {"marker_id": "lamp", "on": False, "rev": 1} assert second == {"marker_id": "lamp", "on": True, "rev": 2} - assert await async_toggle_virtual_light(store, config, 1, "missing") is None + assert asyncio.run(async_toggle_virtual_light(store, config, 1, "missing")) is None