- flow/devices.py: DeviceDef in FLOWS_DIR/_devices/<id>.json, committed under
the flow store's write lock, 409 on a stale version; nonces in DEVICES_FILE
outside git; in-memory presence announced as device_status
- api/routes/devices.py: CRUD, pairing with an optional {name, board,
firmware}, a pending list adopted without a code from the LAN only, unpair;
/panels/pair, /panels/pair/{code} and GET /panels/{id} kept as aliases,
PUT /panels/ gone
- _device_may replaces _panel_may, JWT claims unchanged
- panels.json moves into devices once at startup, then .migrated
- MCP list_devices, get_device; search category device
- Devices tab (list, detail with presence, pairing, ordered dashboards,
touch, I/O), PanelsDialog removed, serialised optimistic saves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
"""The one-time move of an older instance's panels file into devices."""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from fluksio.flow.devices import DeviceStore
|
|
from fluksio.flow.store import FlowStore
|
|
|
|
|
|
def test_panels_move_into_devices_once(tmp_path: Path) -> None:
|
|
flows = FlowStore(tmp_path / "flows")
|
|
panels = tmp_path / "panels.json"
|
|
panels.write_text(
|
|
json.dumps(
|
|
{
|
|
"panels": [
|
|
{"id": "hall", "title": "Hall", "dashboards": ["home"], "nonce": 2},
|
|
{"id": "knob", "touch": True, "io": [], "nonce": 0},
|
|
],
|
|
"version": 7,
|
|
}
|
|
)
|
|
)
|
|
store = DeviceStore(flows, tmp_path / "devices.json")
|
|
store.migrate(panels)
|
|
|
|
assert not panels.exists()
|
|
assert (tmp_path / "panels.json.migrated").exists()
|
|
hall = store.find("hall")
|
|
assert hall is not None and hall.dashboards == ["home"] and hall.version == 1
|
|
assert store.find("knob").touch is True # type: ignore[union-attr]
|
|
# The nonce stays outside git, so a credential minted at it still stands.
|
|
assert store.nonce("hall") == 2
|
|
assert "nonce" not in (flows.root / "_devices" / "hall.json").read_text()
|
|
log = subprocess.run(
|
|
["git", "-C", str(flows.root), "log", "--format=%s"],
|
|
capture_output=True,
|
|
text=True,
|
|
).stdout
|
|
assert log.splitlines()[0] == "Migrate panels to devices"
|
|
# Nothing in `_devices` is taken for a flow.
|
|
assert flows.list_flows() == []
|
|
|
|
# A second start finds nothing to do, and a panels file restored from a
|
|
# backup does not lower a nonce an unpair has raised since.
|
|
store.bump("hall")
|
|
panels.write_text((tmp_path / "panels.json.migrated").read_text())
|
|
store.migrate(panels)
|
|
assert store.nonce("hall") == 3
|
|
assert (
|
|
log
|
|
== subprocess.run(
|
|
["git", "-C", str(flows.root), "log", "--format=%s"],
|
|
capture_output=True,
|
|
text=True,
|
|
).stdout
|
|
)
|