mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: load virtual_lights by path so offline collection survives
The file declared itself pure but imported the module through the package, and the package __init__ unconditionally imports homeassistant. Without homeassistant installed pytest did not skip the file — it stopped collecting the whole tests_backend directory, taking the previously working pure suite down with it. test_validation.py had already established the by-path pattern; virtual_lights.py imports nothing beyond the standard library, so it loads cleanly. The async tests also dropped their pytest-asyncio dependency in favour of asyncio.run: the offline environment does not carry the plugin, and without it the two tests failed as unsupported async defs. The offline gate has to be green, or nobody runs it. Verified in both environments: pytest+voluptuous only — 129 passed where collection previously stopped dead; with pytest-asyncio as in CI — 129 passed. Issue: #135 User-Visible: no
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user