Files
houseplan-card/tests_backend/test_summary_panel.py
T
2026-09-08 18:49:36 +03:00

94 lines
3.5 KiB
Python

"""Backend wire and compatibility contract for the #437 summary panel."""
from pathlib import Path
import pytest
import voluptuous as vol
from pure_imports import load_pure
ROOT = Path(__file__).resolve().parents[1]
v = load_pure("hp_validation_summary", ROOT / "custom_components" / "houseplan" / "validation.py")
def panel(entity_id="sensor.live", space_id="f1"):
return {
"version": 1,
"title": "Summary",
"show_on_mobile": True,
"blocks": [{
"id": "b1", "title": "General", "visible": True,
"scope": {"type": "space", "space_id": space_id},
"values": [{
"id": "v1", "label": "Value",
"source": {"type": "entity", "entity_id": entity_id},
}],
}],
}
def config(summary=None):
value = {"spaces": [], "markers": [], "settings": {}}
if summary is not None:
value["settings"]["summary_panel"] = summary
return value
def test_summary_v1_schema_accepts_bounded_content_and_rejects_duplicate_ids():
valid = panel()
assert v.CONFIG_SCHEMA(config(valid))["settings"]["summary_panel"] == valid
duplicate = panel()
duplicate["blocks"][0]["values"].append(dict(duplicate["blocks"][0]["values"][0]))
with pytest.raises(vol.Invalid, match="value ids"):
v.CONFIG_SCHEMA(config(duplicate))
@pytest.mark.parametrize("mutation", [
lambda p: p.update(title=""),
lambda p: p.update(title=" "),
lambda p: p.update(blocks=p["blocks"] * 11),
lambda p: p["blocks"][0].update(scope={"type": "other"}),
lambda p: p["blocks"][0]["values"][0].update(source={"type": "system", "key": "other"}),
])
def test_summary_v1_schema_rejects_malformed_wire(mutation):
value = panel()
mutation(value)
with pytest.raises(vol.Invalid):
v.CONFIG_SCHEMA(config(value))
def test_old_client_omission_preserves_namespace_and_future_version_losslessly():
future = {"version": 2, "opaque": {"anything": [1, 2, 3]}}
previous = config(future)
candidate = config()
v.preserve_summary_panel_namespace(candidate, previous)
checked = v.CONFIG_SCHEMA(candidate)
assert checked["settings"]["summary_panel"] == future
def test_summary_user_text_is_trimmed_at_the_server_boundary():
value = panel(" sensor.live ")
value["title"] = " Summary "
value["blocks"][0]["title"] = " General "
value["blocks"][0]["values"][0]["label"] = " Value "
checked = v.CONFIG_SCHEMA(config(value))["settings"]["summary_panel"]
assert checked["title"] == "Summary"
assert checked["blocks"][0]["title"] == "General"
assert checked["blocks"][0]["values"][0]["label"] == "Value"
assert checked["blocks"][0]["values"][0]["source"]["entity_id"] == "sensor.live"
def test_reference_validation_allows_old_broken_ids_but_rejects_new_ones():
previous = config(panel("sensor.gone", "gone"))
unchanged = config(panel("sensor.gone", "gone"))
v.validate_summary_panel_references(unchanged, previous, set())
renamed = config(panel("sensor.gone", "gone"))
renamed["settings"]["summary_panel"]["title"] = "Renamed"
v.validate_summary_panel_references(renamed, previous, set())
replaced = config(panel("sensor.other", "gone"))
with pytest.raises(vol.Invalid, match="entity reference"):
v.validate_summary_panel_references(replaced, previous, set())
new_scope = config(panel("sensor.live", "other"))
with pytest.raises(vol.Invalid, match="space reference"):
v.validate_summary_panel_references(new_scope, previous, {"sensor.live"})