Files
houseplan-card/tests_backend/test_radar_validation.py
T
2026-09-09 03:51:25 +03:00

108 lines
4.1 KiB
Python

"""Radar config compatibility and safety witnesses (#485)."""
from __future__ import annotations
import copy
import pytest
import voluptuous as vol
from custom_components.houseplan.radar_validation import validate_marker_radars
def _config():
return {
"spaces": [{"id": "floor", "rooms": [{"id": "living", "poly": [[0, 0], [1, 0], [1, 1]]}]}],
"markers": [{
"id": "radar", "binding": "device:abc", "space": "floor",
"radar": {
"version": 1, "enabled": True, "show_live": True,
"profile": "cartesian_v1", "room_id": "living",
"sources": {"slots": [{"id": "one", "x_entity": "sensor.x",
"y_entity": "sensor.y", "unit": "mm"}]},
"mount": {"installation_id": "install-1", "x": .5, "y": .5,
"heading_deg": 0},
"calibration": {"method": "manual", "mirror": False, "cell_cm": 5},
},
}],
"settings": {"radar": {"show_live": True}},
}
def test_valid_stage1_radar_and_absent_namespace():
validate_marker_radars(_config(), validate_all=True)
validate_marker_radars({"spaces": [], "markers": [], "settings": {}}, validate_all=True)
def test_explicit_null_removes_radar_configuration():
config = _config()
config["markers"][0]["radar"] = None
validate_marker_radars(config, validate_all=True)
def test_ld2450_requires_mm_and_distinct_axis_entities():
config = _config()
radar = config["markers"][0]["radar"]
radar["profile"] = "esphome_ld2450_v1"
radar["sources"]["slots"][0]["unit"] = "cm"
with pytest.raises(vol.Invalid, match="must be mm"):
validate_marker_radars(config, validate_all=True)
def test_two_point_fit_and_owner_scale_are_revalidated_on_write():
config = _config()
radar = config["markers"][0]["radar"]
radar["calibration"] = {
"method": "two_point", "mirror": False, "cell_cm": 5, "rms_cm": 0,
"refs": [
{"plan": {"x": .5 + 100 / 1200, "y": .5},
"local_cm": {"x": 100, "y": 0}},
{"plan": {"x": .5, "y": .5 - 100 / 1200},
"local_cm": {"x": 0, "y": 100}},
],
}
validate_marker_radars(config, validate_all=True)
radar["mount"]["heading_deg"] = 45
with pytest.raises(vol.Invalid, match="do not match"):
validate_marker_radars(config, validate_all=True)
radar["mount"]["heading_deg"] = 0
config["spaces"][0]["cell_cm"] = 10
with pytest.raises(vol.Invalid, match="scale"):
validate_marker_radars(config, validate_all=True)
radar["sources"]["slots"][0].update(unit="mm", y_entity="sensor.x")
with pytest.raises(vol.Invalid, match="distinct"):
validate_marker_radars(config, validate_all=True)
@pytest.mark.parametrize("mutation", [
lambda r: r["sources"]["slots"][0].update(x_entity="switch.bad"),
lambda r: r["sources"]["slots"][0].update(unit="guessed"),
lambda r: r["mount"].update(x=float("nan")),
lambda r: r.update(room_id="nearest-room"),
])
def test_changed_invalid_radar_is_rejected(mutation):
config = _config()
mutation(config["markers"][0]["radar"])
with pytest.raises(vol.Invalid):
validate_marker_radars(config, validate_all=True)
def test_untouched_future_radar_round_trips_but_changed_one_fails():
config = _config()
config["markers"][0]["radar"] = {"version": 99, "future": {"sentinel": 7}}
previous = copy.deepcopy(config)
validate_marker_radars(config, previous)
changed = copy.deepcopy(config)
changed["markers"][0]["radar"]["future"]["sentinel"] = 8
with pytest.raises(vol.Invalid, match="unsupported radar version"):
validate_marker_radars(changed, previous)
def test_stage2_zone_polygon_and_stage3_cross_space_group_fail_closed():
config = _config()
config["markers"][0]["radar"]["zones"] = {"local": [{
"id": "sofa", "name": "Sofa", "poly": [{"x": 0, "y": 0},
{"x": 1, "y": 0}, {"x": 2, "y": 0}], "state": {"kind": "targets"},
}]}
with pytest.raises(vol.Invalid, match="zero area"):
validate_marker_radars(config, validate_all=True)