Commit Graph
7 Commits
Author SHA1 Message Date
Matysh fad2e87ab5 v1.54.2: HP-1541-01 — vacuum selected_map fallback obeys the not-nullish map-id contract
The v1.54.1 contract (first not-None value wins, zero is a value) covered
the source entity but not the card's fallback on the vacuum's own
selected_map: _vacMapId still used truthiness, so selected_map: 0 became
'default' on the frontend while trails.py resolve_map_id stored the run
under '0'. Calibration and server trails split across two keys and the
recorded run never rendered after reload.

The fallback is now the shared pure helper vacMapIdWithFallback (nullish
check), mirroring resolve_map_id. Cross-runtime regressions added for
selected_map = 0, '0' and '' on both sides; the frontend cases fail on the
old truthiness code.
2026-07-31 13:44:10 +03:00
Matysh 4e3d8f1d53 Test harness: run async recorder regressions on a private event loop
asyncio.run() clears the thread's current-loop slot when it finishes; the
CI HA harness keeps a session event loop, so every test that followed the
new HP-1540-05 regression failed at SETUP with 'There is no current event
loop'. The pure-only local run never sees the harness and stayed green —
which is exactly how it slipped through. The regressions now spin up an
isolated loop and leave the ambient one untouched.
2026-07-31 13:12:14 +03:00
Matysh 257a71123a Trail recorder: zero map ids, one source per N markers, serialized refresh
Audit HP-1540-02: the recorder chose the map id with an or-chain, so a
valid numeric map_index=0 fell through to selected_map (or 'default') and
the server stored the run under a key the renderer never looks up. The
choice is now resolve_map_id() — the explicit backend half of the contract
shared with vacMapIdFromAttrs in src/vacuum.ts: the first value that is
not None wins, zero and empty string included.

HP-1540-03: pairs was a plain source -> (marker, vacuum) dict, so the
second placement of the same robot (the documented two-floor case) evicted
the first and its server history silently stopped. A source now maps to a
list of pairs, every marker gets its own copy of the run, and the state
subscription set is deduplicated.

HP-1540-05: every config/set spawns async_refresh as a detached task; two
of them interleaving across the awaited config load could both subscribe,
overwriting one unsub handle — a callback leak until HA restart. Refresh
is serialized with an asyncio.Lock and teardown flags the recorder closed
first, so a refresh parked on its await can never resubscribe afterwards.

Regressions cover map_index 0/'0'/''/selected_map cross-checks, one
source feeding two floor markers across a map switch, pair-list refresh
with a deduplicated entity set, and two overlapping refreshes leaving
exactly one live subscription (zero after teardown). On the v1.54.0
recorder 11 of these tests fail.
2026-07-31 13:07:27 +03:00
Matysh 9870b2fb98 A booting HA must not split the cleanup run
At startup the vacuum entity reads unavailable; the recorder took that
for 'stopped' and ended the open run, so every HA restart mid-cleanup
rotated the trail into previous and began a fresh one (observed live:
a 21-point run became previous and restarted at 5). Unavailable and
unknown now mean 'no verdict'.
2026-07-31 12:09:31 +03:00
Matysh 18ac9b459f Trail recorder reads object-style positions — the real-world fix
Caught live on the owner's X50 mid-cleanup with temporary logging: the
recorder saw every camera state change and rejected every one, because
server-side Tasshack keeps vacuum_position as a Point OBJECT — it only
becomes a dict when serialised to the frontend, which is why the card
adapter (and MCP inspection) always saw a dict and the stub test
faithfully reproduced the same wrong assumption. getattr fallback added,
regression test pinned, diagnostic logging removed (setup line kept at
INFO).
2026-07-31 12:04:33 +03:00
Matysh 4e355dcbd2 The stub loader no longer poisons the HA harness
test_trail_recorder injected fake homeassistant modules into sys.modules
at import time; every pytest_homeassistant_custom_component fixture in
the same session then failed with 'module homeassistant has no attribute
util'. The stubs now live inside a snapshot that is restored in a finally
block.
2026-07-31 11:54:01 +03:00
Matysh f3bc3278e5 Trail recorder: seed a run already in progress, and test the wiring
Only TrailBook was covered; the HA-facing half — subscription callback,
attribute dialects, map-id resolution, run end on docking — had no test
at all. It does now, against a stubbed hass, which is also where the
missing behaviour showed up: recording started at the NEXT state change,
so an HA restart (or finishing calibration) mid-cleanup dropped the
opening seconds of the path. Sampling is factored out and runs once per
source on setup and on every refresh.
2026-07-31 11:47:31 +03:00