mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
fix v1.44.2: external review CR-1..CR-3
CR-1: the lock invariant is restated precisely (never by an accidental
tap; the door card's labeled button is the ONE sanctioned surface),
unlocking now confirms, and smoke_lock_invariant exercises all five
actuation paths (icon tap, controls[], card entities, _cardToggle,
opening card).
CR-2: attachment migration is transactional — the server COPIES files,
the config is committed with its revision check, and only then the old
folder is removed via the new houseplan/files/cleanup. A rejected save
no longer leaves the stored urls pointing at an emptied folder.
CR-3: migrate returns an exact {source: written} mapping; only confirmed
copies are rewritten, destination name collisions get a unique name
instead of silently linking a pre-existing file, and a failed migration
raises a toast instead of being swallowed.
+1 unit test (119), +1 backend test, +1 smoke (51 total); docs
same-commit
This commit is contained in:
@@ -125,3 +125,42 @@ async def test_admin_check_fails_closed(hass, hass_ws_client):
|
||||
user = _Admin()
|
||||
|
||||
assert wsapi._check_write(hass, _AdminConn()) is True
|
||||
|
||||
|
||||
async def test_files_migrate_copies_and_reports_mapping(hass, hass_ws_client, tmp_path):
|
||||
"""review CR-2/CR-3: migrate COPIES, never overwrites, and reports the mapping."""
|
||||
import os
|
||||
from custom_components.houseplan.const import FILES_DIR
|
||||
|
||||
base = os.path.join(hass.config.path(FILES_DIR))
|
||||
src = os.path.join(base, "old1")
|
||||
dst = os.path.join(base, "new1")
|
||||
await hass.async_add_executor_job(lambda: os.makedirs(src, exist_ok=True))
|
||||
await hass.async_add_executor_job(lambda: os.makedirs(dst, exist_ok=True))
|
||||
await hass.async_add_executor_job(
|
||||
lambda: open(os.path.join(src, "m.pdf"), "wb").write(b"SOURCE")
|
||||
)
|
||||
# коллизия: в целевой папке уже есть ДРУГОЙ файл с тем же именем
|
||||
await hass.async_add_executor_job(
|
||||
lambda: open(os.path.join(dst, "m.pdf"), "wb").write(b"OTHER")
|
||||
)
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json({"id": 1, "type": "houseplan/files/migrate",
|
||||
"from_id": "old1", "to_id": "new1"})
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
mapping = resp["result"]["mapping"]
|
||||
assert mapping["m.pdf"] != "m.pdf" # переименован, а не перезаписан
|
||||
# источник ещё на месте (копия, не перенос) и чужой файл не тронут
|
||||
assert await hass.async_add_executor_job(lambda: os.path.isfile(os.path.join(src, "m.pdf")))
|
||||
assert await hass.async_add_executor_job(
|
||||
lambda: open(os.path.join(dst, "m.pdf"), "rb").read()) == b"OTHER"
|
||||
assert await hass.async_add_executor_job(
|
||||
lambda: open(os.path.join(dst, mapping["m.pdf"]), "rb").read()) == b"SOURCE"
|
||||
|
||||
# cleanup удаляет исходную папку — вызывается только после успешного сохранения
|
||||
await client.send_json({"id": 2, "type": "houseplan/files/cleanup", "marker_id": "old1"})
|
||||
resp2 = await client.receive_json()
|
||||
assert resp2["success"] and resp2["result"]["removed"] is True
|
||||
assert not await hass.async_add_executor_job(lambda: os.path.isdir(src))
|
||||
|
||||
Reference in New Issue
Block a user