v1.50.2: the v1.50.1 review (HP-1501-01, HP-1501-02)

- HP-1501-01: v1.50.1 bounded layout positions and left room rectangles,
  polygon vertices, view_box and opening coordinates on bare _finite — the
  same absurd-magnitude failure, one schema over. _GEOM (±4) covers them all
  now, opening angles get ±360. And because a store may already hold such a
  vertex from before the door existed, contentBounds applies its canvas
  envelope to room geometry exactly as it does to device positions: the
  point renders where it is, the frame ignores it, a space of nothing but
  absurd points falls back to the whole canvas.
- HP-1501-02: a repair matching zero positions answered ok/moved:0 and
  replaced the one-deep backup with an empty one — a typo right after
  repairing the wrong space destroyed the promised way back. Empty match is
  nothing_to_repair now: no write, no revision bump, backup intact.

Old test fixtures carried view_box [0,0,100,100] from the render-unit days;
they now use the normalised box the product actually stores.
This commit is contained in:
Matysh
2026-07-29 07:30:22 +03:00
parent aa53b33dd6
commit 5392dadeaa
18 changed files with 193 additions and 36 deletions
+43
View File
@@ -1374,3 +1374,46 @@ async def test_geometry_repair_is_explicit_previewable_and_undoable(
})
bad = await client.receive_json()
assert not bad["success"] and bad["error"]["code"] == "no_backup"
async def test_a_noop_repair_does_not_eat_the_backup(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""HP-1501-02: a repair that matches nothing used to answer moved: 0 and
replace the one-deep backup with an empty one — a typo after a wrong
repair destroyed the only way back. Now it is an error, the revision does
not move, and the previous repair is still undoable."""
await _setup(hass)
client = await hass_ws_client(hass)
await client.send_json_auto_id({
"type": "houseplan/layout/set",
"layout": {"lamp": {"s": "wide", "x": 0.2, "y": 0.1}},
})
assert (await client.receive_json())["success"]
await client.send_json_auto_id({
"type": "houseplan/geometry/repair", "space_id": "wide", "aspect": 2.0,
})
rev = (await client.receive_json())["result"]["rev"]
# the typo: syntactically valid, matches nothing
await client.send_json_auto_id({
"type": "houseplan/geometry/repair", "space_id": "wid", "aspect": 2.0,
})
bad = await client.receive_json()
assert not bad["success"] and bad["error"]["code"] == "nothing_to_repair"
await client.send_json_auto_id({"type": "houseplan/layout/get"})
got = (await client.receive_json())["result"]
assert got["rev"] == rev, "a refused repair moves nothing, including the revision"
# the wrong-space repair from before the typo is STILL undoable
await client.send_json_auto_id({
"type": "houseplan/geometry/repair", "space_id": "wide", "aspect": 2.0, "undo": True,
})
res = (await client.receive_json())["result"]
assert res["restored"] == 1
await client.send_json_auto_id({"type": "houseplan/layout/get"})
lay = (await client.receive_json())["result"]["layout"]
assert lay["lamp"] == {"s": "wide", "x": 0.2, "y": 0.1}
+27 -2
View File
@@ -190,7 +190,7 @@ def test_collection_caps():
def test_finite_on_every_coordinate():
"""audit follow-up B5: NaN/Infinity must be refused everywhere, not only in layout."""
base = {"id": "s1", "title": "S", "aspect": 1.0, "view_box": [0, 0, 100, 100], "rooms": []}
base = {"id": "s1", "title": "S", "aspect": 1.0, "view_box": [0, 0, 1, 1], "rooms": []}
# view_box
with pytest.raises(vol.Invalid):
v.CONFIG_SCHEMA({"spaces": [{**base, "view_box": [0, 0, "NaN", 100]}]})
@@ -211,13 +211,38 @@ def test_finite_on_every_coordinate():
{"id": "r", "name": "R", "poly": [[0, 0], [1, 0], [1, 1]]}]}]})
def test_geometry_magnitudes_are_bounded():
"""HP-1501-01: any finite float used to pass, and one schema-valid 1e100
room vertex made the space unviewable for every client the exact failure
HP-1500-03 closed for layout positions, one schema over. ±4 is slack for a
vertex nudged past an edge, not an envelope for absurdity."""
base = {"id": "s1", "title": "S", "view_box": [0, 0, 1, 1], "rooms": []}
huge = 1e100
for cfg in (
{**base, "rooms": [{"id": "r", "name": "R", "poly": [[0, 0], [huge, 0], [1, 1]]}]},
{**base, "rooms": [{"id": "r", "name": "R", "poly": [[0, 0], [-huge, 0], [1, 1]]}]},
{**base, "rooms": [{"id": "r", "name": "R", "x": huge, "y": 0, "w": 1, "h": 1}]},
{**base, "rooms": [{"id": "r", "name": "R", "x": 0, "y": 0, "w": huge, "h": 1}]},
{**base, "view_box": [0, 0, huge, 1]},
{**base, "openings": [{"id": "o", "type": "door", "x": huge, "y": 0.5,
"angle": 0, "length": 0.1}]},
{**base, "openings": [{"id": "o", "type": "door", "x": 0.5, "y": 0.5,
"angle": 1e6, "length": 0.1}]},
):
with pytest.raises(vol.Invalid):
v.CONFIG_SCHEMA({"spaces": [cfg]})
# a vertex a bit past the canvas edge is a drawing, not an attack
assert v.CONFIG_SCHEMA({"spaces": [{**base, "rooms": [
{"id": "r", "name": "R", "poly": [[-0.2, 0], [1.3, 0], [1, 1]]}]}]})
def test_openings_cap_enforced():
"""audit follow-up B5: MAX_OPENINGS was defined but never wired in."""
many = [{"id": f"o{i}", "type": "door", "x": 0.1, "y": 0.1, "angle": 0, "length": 0.1}
for i in range(v.MAX_OPENINGS + 1)]
with pytest.raises(vol.Invalid):
v.CONFIG_SCHEMA({"spaces": [{"id": "s1", "title": "S", "aspect": 1.0,
"view_box": [0, 0, 100, 100], "rooms": [],
"view_box": [0, 0, 1, 1], "rooms": [],
"openings": many}]})