Add packaged Insta360 X4 integration and recover paired Node channels
Discover independent camera instances and prepare their versioned runtime from Node or remote Core. Add isolated SDK workers, camera controls, raw dual-fisheye WebRTC preview, and shared action/region loading states. Recover existing Node bindings over known Tailscale addresses after a Core LAN address change. Preserve identities and trust, pin both peers, migrate endpoints with revision checks, and require real heartbeats for online status. Fix the Python client certificate profile for Go X509 verification. Pin Design Guideline 8c53f73 and retain installer/build/acceptance history. Node 0.8.19 is installed; X4 0.1.3-3 is bundled but hardware activation is pending. Validation: qualified DG/Node builds and Go race tests; 31 fleet tests; Python-to-Go certificate interoperability and live tailnet recovery with five fresh heartbeats; prior 38 X4 tests and bounded remote WebRTC acceptance. Clean-OS, replug/power autonomy, local X4 video and long-run stability remain open.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"""Synthetic cold-host bootstrap transactions; no actual APT/systemd/USB."""
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"x4_node_profile", REPO / "apps/node-agent/packaging/insta360_profile.py"
|
||||
)
|
||||
profile = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(profile)
|
||||
|
||||
|
||||
class ProfileTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory(dir=REPO / "plugins/insta360-x4/build")
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
root = Path(self.temp.name)
|
||||
self.share, self.state, self.plugin = (root / name for name in ("share", "state", "plugin"))
|
||||
for directory in (self.share, self.state, self.plugin):
|
||||
directory.mkdir()
|
||||
self.bundle = {
|
||||
"schema": "missioncore.node.bundled-model/v1",
|
||||
"version": "0.1.1",
|
||||
"revision": "a" * 24,
|
||||
"bytes": 3,
|
||||
"sha256": hashlib.sha256(b"deb").hexdigest(),
|
||||
}
|
||||
(self.share / "profile.json").write_text(json.dumps(self.bundle))
|
||||
(self.share / "mission-core-insta360-x4_0.1.1_amd64.deb").write_bytes(b"deb")
|
||||
self.calls = []
|
||||
self.previous = None
|
||||
self.failure = False
|
||||
for name, value in (
|
||||
("SHARE", self.share),
|
||||
("STATE", self.state),
|
||||
("PLUGIN_STATE", self.plugin),
|
||||
):
|
||||
context = patch.object(profile, name, value)
|
||||
context.start()
|
||||
self.addCleanup(context.stop)
|
||||
|
||||
# Production checks uid 0. Tests remain unprivileged and only admit the
|
||||
# three private fixture directories, never paths on the installed OS.
|
||||
def trusted(path, directory=False):
|
||||
self.assertTrue(path == root or root in path.parents)
|
||||
self.assertFalse(path.is_symlink())
|
||||
self.assertEqual(path.is_dir(), directory)
|
||||
return path
|
||||
|
||||
for context in (
|
||||
patch.object(profile, "trusted", trusted),
|
||||
patch.object(profile.platform, "machine", return_value="x86_64"),
|
||||
patch.object(
|
||||
profile.platform,
|
||||
"freedesktop_os_release",
|
||||
return_value={"ID": "ubuntu", "VERSION_ID": "24.04"},
|
||||
),
|
||||
patch.object(profile.subprocess, "run", side_effect=self.run_command),
|
||||
):
|
||||
context.start()
|
||||
self.addCleanup(context.stop)
|
||||
|
||||
def prepared(self):
|
||||
(self.plugin / "preparation.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"state": "complete",
|
||||
"revision": self.bundle["revision"],
|
||||
"started_at": time.time(),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def run_command(self, command, **kwargs):
|
||||
self.calls.append(command)
|
||||
name = Path(command[0]).name
|
||||
if name == "dpkg-query":
|
||||
return subprocess.CompletedProcess(
|
||||
command,
|
||||
1 if self.previous is None else 0,
|
||||
"" if self.previous is None else self.previous + "\tinstall ok installed",
|
||||
"",
|
||||
)
|
||||
if name == "dpkg" and command[1] == "--compare-versions":
|
||||
return subprocess.CompletedProcess(command, 0 if self.previous == "0.2.0" else 1)
|
||||
if name == "dpkg" and command[1] == "--install":
|
||||
if self.failure:
|
||||
return subprocess.CompletedProcess(command, 100, b"", b"synthetic package refusal")
|
||||
if self.previous:
|
||||
self.prepared() # Upgrade postinst owns preparation.
|
||||
elif name == "systemctl":
|
||||
self.assertEqual(command, ["/usr/bin/systemctl", "start", profile.UNIT])
|
||||
self.prepared()
|
||||
else:
|
||||
self.fail("Unexpected OS command")
|
||||
return subprocess.CompletedProcess(command, 0, b"", b"")
|
||||
|
||||
def names(self):
|
||||
return [Path(command[0]).name for command in self.calls]
|
||||
|
||||
def test_cold_host_installs_only_bundled_package_then_fixed_prepare(self):
|
||||
self.assertTrue(profile.prepare())
|
||||
self.assertEqual(self.names(), ["dpkg-query", "dpkg", "systemctl"])
|
||||
self.assertEqual(
|
||||
self.calls[1],
|
||||
[
|
||||
"/usr/bin/dpkg",
|
||||
"--install",
|
||||
str(self.share / "mission-core-insta360-x4_0.1.1_amd64.deb"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_existing_version_reuses_package_and_revalidates_runtime(self):
|
||||
self.previous = "0.1.1"
|
||||
self.assertTrue(profile.prepare())
|
||||
self.assertFalse(any("--install" in command for command in self.calls))
|
||||
self.assertIn("systemctl", self.names())
|
||||
|
||||
def test_debian_revision_remains_a_fixed_local_package(self):
|
||||
old = self.share / "mission-core-insta360-x4_0.1.1_amd64.deb"
|
||||
old.rename(self.share / "mission-core-insta360-x4_0.1.1-1_amd64.deb")
|
||||
self.bundle["version"] = "0.1.1-1"
|
||||
(self.share / "profile.json").write_text(json.dumps(self.bundle))
|
||||
self.assertTrue(profile.prepare())
|
||||
self.assertEqual(Path(self.calls[1][-1]).name, "mission-core-insta360-x4_0.1.1-1_amd64.deb")
|
||||
|
||||
def test_upgrade_does_not_race_postinst_with_second_prepare(self):
|
||||
self.previous = "0.1.0"
|
||||
self.assertTrue(profile.prepare())
|
||||
self.assertTrue(any("--install" in command for command in self.calls))
|
||||
self.assertNotIn("systemctl", self.names())
|
||||
|
||||
def test_corruption_never_reaches_package_manager_or_driver(self):
|
||||
(self.share / "mission-core-insta360-x4_0.1.1_amd64.deb").write_bytes(b"bad")
|
||||
self.assertFalse(profile.prepare())
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
def test_newer_driver_is_preserved(self):
|
||||
self.previous = "0.2.0"
|
||||
self.assertFalse(profile.prepare())
|
||||
self.assertFalse(any("--install" in command for command in self.calls))
|
||||
self.assertNotIn("systemctl", self.names())
|
||||
|
||||
def test_failed_package_never_opens_camera_and_keeps_error_evidence(self):
|
||||
self.failure = True
|
||||
self.assertFalse(profile.prepare())
|
||||
self.assertNotIn("systemctl", self.names())
|
||||
report = json.loads((self.state / "preparation.json").read_text())
|
||||
self.assertEqual(report["steps"][-1]["state"], "blocked")
|
||||
self.assertEqual(
|
||||
(self.state / report["run_id"] / "1.stderr").read_bytes(), b"synthetic package refusal"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user