feat(vesc): integrate native calibration diagnostics and configuration archives
This commit is contained in:
@@ -84,6 +84,25 @@ def cert(row):
|
||||
serialization.Encoding.DER
|
||||
)
|
||||
|
||||
def test_rover_stream_requires_current_pairing_and_binding(setup):
|
||||
fleet, _, _, _ = setup
|
||||
public, _ = create(setup)
|
||||
fleet.advance(public["id"])
|
||||
row = fleet.find(public["id"])
|
||||
body = {"schema": SCHEMA, "node_id": row["node_id"],
|
||||
"binding_id": row["binding"]["binding_id"], "relay_id": "a"*32}
|
||||
certificate = cert(row)
|
||||
status, result = fleet.receive(certificate, "/v1/node/rover-stream", body)
|
||||
assert status == 200
|
||||
assert result['command'] is None
|
||||
assert result['control_clock']['instance'] == fleet.rover_control.clock_id
|
||||
status, _ = fleet.receive(certificate, "/v1/node/rover-stream", {**body, "binding_id": "other"})
|
||||
assert status == 410
|
||||
row['enrollment'] = 'revoked'
|
||||
fleet.save(row)
|
||||
status, _ = fleet.receive(certificate, "/v1/node/rover-stream", body)
|
||||
assert status == 410
|
||||
|
||||
|
||||
def test_migrated_heartbeat_preserves_identity_and_old_reply_cannot_reverse(setup):
|
||||
fleet, _, _, _ = setup
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import base64
|
||||
import copy
|
||||
import hashlib
|
||||
from datetime import UTC, datetime
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.device_plugins.vesc.archive import Archive
|
||||
|
||||
|
||||
def backup():
|
||||
uid = "01" * 12
|
||||
configs = {}
|
||||
for name, command in (("motor", 14), ("application", 17)):
|
||||
raw = bytes([command]) + b"test-synthetic-config"
|
||||
configs[name] = {"encoding": "base64", "payload": base64.b64encode(raw).decode(),
|
||||
"bytes": len(raw), "sha256": hashlib.sha256(raw).hexdigest(),
|
||||
"signature_hex": raw[1:5].hex()}
|
||||
return {"schema": "missioncore.vesc.config-backup/v1", "operation_id": "op_" + uuid4().hex,
|
||||
"device_id": "vesc_" + hashlib.sha256(("uuid:" + uid).encode()).hexdigest()[:32],
|
||||
"identity": {"uuid": uid, "version": "6.06"}, "configs": configs,
|
||||
"observed_at": datetime.now(UTC).isoformat()}
|
||||
|
||||
|
||||
def test_immutable_versions_survive_restart_and_do_not_cross_node_scope(tmp_path):
|
||||
archive = Archive(tmp_path / "archive")
|
||||
value = backup()
|
||||
archive.add("node-a", value)
|
||||
archive.add("node-a", copy.deepcopy(value))
|
||||
reopened = Archive(tmp_path / "archive")
|
||||
assert len(reopened.listing("node-a", value["device_id"])["items"]) == 1
|
||||
assert reopened.read("node-a", value["device_id"], value["operation_id"]) == value
|
||||
assert reopened.listing("node-b", value["device_id"])["items"] == []
|
||||
with pytest.raises(ValueError):
|
||||
reopened.read("node-b", value["device_id"], value["operation_id"])
|
||||
changed = copy.deepcopy(value)
|
||||
changed["observed_at"] = datetime.now(UTC).isoformat()
|
||||
with pytest.raises(ValueError, match="immutable"):
|
||||
reopened.add("node-a", changed)
|
||||
|
||||
|
||||
def test_corrupt_or_misattributed_payload_is_never_archived(tmp_path):
|
||||
archive = Archive(tmp_path / "archive")
|
||||
value = backup()
|
||||
value["configs"]["motor"]["sha256"] = "0" * 64
|
||||
with pytest.raises(ValueError, match="integrity"):
|
||||
archive.add("a", value)
|
||||
value = backup()
|
||||
value["device_id"] = "vesc_" + "0" * 32
|
||||
with pytest.raises(ValueError, match="mismatch"):
|
||||
archive.add("a", value)
|
||||
assert archive.export("a", 0)["items"] == []
|
||||
|
||||
|
||||
def test_bounded_replication_replays_without_losing_versions_and_paginates(tmp_path):
|
||||
board, core = Archive(tmp_path / "board"), Archive(tmp_path / "core")
|
||||
values = [backup() for _ in range(53)]
|
||||
for value in values:
|
||||
board.add("local", value)
|
||||
after = 0
|
||||
while True:
|
||||
batch = board.export("local", after)
|
||||
assert len(batch["items"]) <= 4
|
||||
assert core.receive("paired-node", batch) == batch["next"]
|
||||
assert core.receive("paired-node", batch) == batch["next"]
|
||||
if batch["next"] == after:
|
||||
break
|
||||
after = batch["next"]
|
||||
page = core.listing("paired-node", values[0]["device_id"])
|
||||
assert len(page["items"]) == 50 and page["next"]
|
||||
older = core.listing("paired-node", values[0]["device_id"], int(page["next"]))
|
||||
assert len(older["items"]) == 3 and older["next"] is None
|
||||
assert len({item["id"] for item in page["items"] + older["items"]}) == 53
|
||||
Reference in New Issue
Block a user