Files
NODEDC_MISSION_CORE/plugins/insta360-x4/tests/check_recovery.py
T

347 lines
15 KiB
Python

"""Synthetic recovery/privilege-boundary tests. No USB, Bluetooth or service changes."""
import io
import json
import sys
import tempfile
import unittest
from datetime import UTC, datetime, timedelta
from pathlib import Path
from unittest.mock import Mock, patch
ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(ROOT), str(ROOT / "packaging")]
import layout # noqa: E402
import wake_service # noqa: E402
from check_runtime import command # noqa: E402
from runtime import broker, identity, recovery # noqa: E402
from runtime.native import parameters # noqa: E402
A = identity.device_id("SYNTHETICAAAAAA")
B = identity.device_id("SYNTHETICBBBBBB")
def snapshot(ident=A, session="x4_one", preview=0, recording=0):
now = datetime.now(UTC).isoformat()
return {
"id": ident,
"session_id": session,
"opened_at": now,
"observed_at": now,
"revision": 2,
"prepared": True,
"verified": True,
"preparation_safe": True,
"message": None,
"online": True,
"status": {"connected": True, "preview": preview, "recording": recording},
}
class RecoveryTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(dir=ROOT / "build")
self.root = Path(self.temporary.name)
self.broker = broker.Broker()
self.value = recovery.Recovery(self.broker, self.root / "recovery")
self.broker.recovery = self.value
self.addCleanup(self.value.pool.shutdown, wait=True)
self.addCleanup(self.temporary.cleanup)
def enable(self, ident=A):
self.value.observe([snapshot(ident)], 0)
with patch.object(recovery, "request", return_value={"enabled": True}):
self.value.configure(ident, True)
def test_default_has_no_wake_and_independent_instances_keep_distinct_intents(self):
self.assertEqual(self.value.observe([], 0), [])
self.enable()
self.value.states[B] = self.value.default()
self.assertEqual(self.value.observe([], 0), [])
self.assertEqual(self.value.observe([], 8), [(A, "wake")])
self.assertFalse(self.value.view(B)["enabled"])
def test_grace_three_attempts_and_restart_do_not_reset_budget(self):
self.enable()
self.value.observe([], 0)
with patch.object(recovery, "request", return_value={"state": "unavailable"}):
for number, threshold in enumerate((8, 40, 100)):
self.assertEqual(self.value.observe([], threshold - 1), [])
self.assertEqual(self.value.observe([], threshold), [(A, "wake")])
self.value.work(A, "wake")
self.assertEqual(self.value.states[A]["attempts"], number + 1)
self.assertEqual(self.value.observe([], 900), [])
restored = recovery.Recovery(self.broker, self.root / "recovery")
self.addCleanup(restored.pool.shutdown, wait=True)
self.assertEqual(restored.observe([], 1000), [])
def test_stable_return_resets_budget_only_after_one_minute(self):
self.enable()
self.value.states[A]["attempts"] = 3
self.value.observe([], 0)
self.value.observe([snapshot()], 10)
self.assertEqual(self.value.states[A]["attempts"], 3)
self.value.observe([snapshot()], 70)
self.assertEqual(self.value.states[A]["attempts"], 0)
def test_disable_cancels_helper_even_while_offline_and_clears_preview(self):
self.enable()
self.value.states[A]["preview_wanted"] = True
self.value.observe([], 1)
with patch.object(recovery, "request", return_value={"enabled": False}) as call:
self.value.configure(A, False)
call.assert_called_once()
self.assertFalse(self.value.view(A)["preview_wanted"])
self.assertEqual(self.value.observe([], 500), [])
def test_no_preview_restore_without_confirmed_intent_or_when_recording_unknown(self):
self.enable()
self.assertEqual(self.value.observe([snapshot(session="x4_two")], 1), [])
self.value.states[A]["preview_wanted"] = True
for recording in (1, -1, None):
self.assertEqual(self.value.observe([snapshot(recording=recording)], 2), [])
def test_restore_once_for_new_session_and_never_replay_sd_commands(self):
self.enable()
self.value.states[A]["preview_wanted"] = True
self.value.observe([snapshot(session="x4_two")], 2)
with patch.object(
self.broker, "capture_operation", return_value={"state": "unknown"}
) as call:
self.value.work(A, "preview")
self.value.work(A, "preview")
self.assertEqual(call.call_count, 1)
self.assertEqual(call.call_args.args[1]["action_id"], "preview.start")
self.assertEqual(self.value.observe([snapshot(session="x4_two")], 3), [])
def test_cancelled_intent_blocks_previously_scheduled_preview_job(self):
self.enable()
self.value.states[A]["preview_wanted"] = True
self.assertEqual(self.value.observe([snapshot(session="x4_two")], 1), [(A, "preview")])
self.value.states[A]["preview_wanted"] = False
with patch.object(self.broker, "capture_operation") as call:
self.value.work(A, "preview")
call.assert_not_called()
def test_old_start_receipt_cannot_reenable_preview_after_stop(self):
self.enable()
start = command(A, "x4_one", "preview.start")
stop = command(A, "x4_one", "preview.stop")
stop["requested_at"] = (datetime.now(UTC) + timedelta(seconds=1)).isoformat()
for value in (start, stop, start):
self.value.preview_intent(A, value, {"state": "complete"})
self.assertFalse(self.value.view(A)["preview_wanted"])
def test_offline_inventory_preserves_control_but_never_claims_online(self):
self.enable()
offline = self.value.augment([])[0]
self.assertFalse(offline["online"])
self.assertFalse(offline["preparation_safe"])
self.assertEqual(offline["status"], {"recovery": self.value.view(A)})
self.assertNotIn("serial", json.dumps(offline))
def test_recovery_parameters_require_explicit_camera_setting_confirmation(self):
parameters("recovery.configure", {"enabled": False, "wakeup_confirmed": False})
for value in (
{"enabled": True, "wakeup_confirmed": False},
{"enabled": 1, "wakeup_confirmed": True},
{"enabled": True},
):
with self.assertRaises(ValueError):
parameters("recovery.configure", value)
def test_registry_retains_offline_wake_identity_without_automatic_intent(self):
with patch.object(recovery, "request", return_value={"items": [A, B]}):
self.value.sync_registry()
self.assertTrue(self.value.view(A)["wake_available"])
self.assertFalse(self.value.view(A)["enabled"])
self.assertEqual(self.value.observe([], 900), [])
self.assertEqual(len(self.value.augment([])), 2)
def test_manual_wake_is_durable_once_and_does_not_enable_policy_or_preview(self):
self.value.states[A] = self.value.default()
session = self.value.states[A]["session_id"]
value = command(A, session, "power.wake")
with patch.object(recovery, "request", return_value={"state": "connected"}) as call:
self.assertEqual(self.value.operation(A, value)["state"], "complete")
self.assertEqual(self.value.operation(A, value)["state"], "complete")
call.assert_called_once_with(
recovery.HELPER,
"/wake-once",
{
"device_id": A,
"attempt": value["operation_id"][3:],
},
timeout=52,
)
self.assertFalse(self.value.view(A)["enabled"])
self.assertFalse(self.value.view(A)["preview_wanted"])
with self.assertRaises(ValueError):
parameters("power.wake", {"serial": "ARBITRARY"})
def test_manual_wake_failure_does_not_schedule_automatic_retry(self):
self.value.states[A] = self.value.default()
value = command(A, self.value.states[A]["session_id"], "power.wake")
with (
patch.object(recovery, "request", return_value={"state": "unavailable"}) as call,
patch.object(recovery.time, "monotonic", side_effect=[0, 53]),
):
result = self.value.operation(A, value)
self.assertEqual(self.value.operation(A, value), result)
self.assertEqual(result["state"], "error")
call.assert_called_once()
self.assertEqual(self.value.observe([], 1000), [])
def test_manual_wake_waits_for_late_sdk_return_without_another_radio_command(self):
with (
patch.object(recovery, "request", return_value={"state": "unavailable"}) as call,
patch.object(recovery.time, "monotonic", side_effect=[0, 22, 37]),
patch.object(self.value.stopping, "wait") as wait,
patch.object(self.broker, "snapshots", side_effect=[[snapshot(B)], [snapshot(A)]]),
):
result = self.value.wake_once(A, command(A, "x4_one", "power.wake"))
self.assertEqual(result["state"], "complete")
call.assert_called_once()
wait.assert_called_once_with(1)
self.assertFalse(self.value.view(A)["enabled"])
def test_manual_wake_does_not_wait_for_other_camera_or_retry_after_deadline(self):
with (
patch.object(recovery, "request", return_value={"state": "unavailable"}) as call,
patch.object(recovery.time, "monotonic", side_effect=[0, 22, 53]),
patch.object(self.value.stopping, "wait"),
patch.object(self.broker, "snapshots", return_value=[snapshot(B)]),
):
result = self.value.wake_once(A, command(A, "x4_one", "power.wake"))
self.assertEqual(result["state"], "error")
call.assert_called_once()
class WakeServiceTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(dir=ROOT / "build")
self.root = Path(self.temporary.name)
self.addCleanup(self.temporary.cleanup)
self.service = wake_service.WakeService(self.root)
self.trust = patch.object(wake_service, "trusted", side_effect=lambda path, *args: path)
self.trust.start()
self.addCleanup(self.trust.stop)
layout_trust = patch.object(layout, "trusted", side_effect=lambda path, *args: path)
layout_trust.start()
self.addCleanup(layout_trust.stop)
self.binding = identity.Binding(A, "1-2", 1, 2, "SYNTHETICAAAAAA")
def enable(self):
with patch.object(wake_service, "connected", return_value=[self.binding]):
self.service.control(A, True)
def test_enrollment_uses_os_serial_and_refuses_arbitrary_transport_input(self):
self.enable()
self.assertEqual(self.service.load(A)["serial"], self.binding.serial)
with self.assertRaises(ValueError):
self.service.dispatch(
"POST", "/control", {"device_id": A, "enabled": True, "serial": "ATTACKER"}, {}
)
with (
patch.object(wake_service, "connected", return_value=[]),
self.assertRaises(ValueError),
):
self.service.control(B, True)
def test_present_sdk_and_duplicate_serials_never_launch_advertiser(self):
self.enable()
with (
patch.object(wake_service, "connected", return_value=[self.binding]),
patch.object(wake_service.subprocess, "Popen") as spawn,
):
self.assertEqual(self.service.wake_locked(A, "1" * 32)["state"], "connected")
spawn.assert_not_called()
with (
patch.object(wake_service, "connected", return_value=[self.binding, self.binding]),
self.assertRaises(ValueError),
):
self.service.control(A, True)
def test_persisted_rate_limit_and_duplicate_attempt_prevent_commands(self):
self.enable()
value = self.service.load(A)
value["attempts"] = [{"id": str(i) * 32, "at": wake_service.time.time()} for i in range(3)]
self.service.save(A, value)
with (
patch.object(wake_service, "connected", return_value=[]),
patch.object(wake_service.subprocess, "Popen") as spawn,
):
self.assertEqual(self.service.wake_locked(A, "0" * 32)["state"], "already_attempted")
self.assertEqual(self.service.wake_locked(A, "4" * 32)["state"], "exhausted")
spawn.assert_not_called()
def test_known_radio_suffix_collision_blocks_wake(self):
self.enable()
self.service.save(B, {"serial": "DIFFERENTAAAAAA", "enabled": True, "attempts": []})
with patch.object(wake_service, "connected", return_value=[]):
self.assertEqual(self.service.wake_locked(A, "1" * 32)["state"], "identity_conflict")
def test_disable_persists_before_cancelling_only_selected_child(self):
self.enable()
left, right = Mock(), Mock()
self.service.running = {A: left, B: right}
self.service.control(A, False)
left.terminate.assert_called_once()
right.terminate.assert_not_called()
self.assertFalse(self.service.load(A)["enabled"])
def test_one_adapter_busy_does_not_consume_another_camera_attempt(self):
self.service.advertiser.acquire()
try:
self.assertEqual(self.service.wake(A, "1" * 32), {"state": "busy"})
finally:
self.service.advertiser.release()
self.assertEqual(list(self.root.iterdir()), [])
def test_registry_enrolls_usb_without_enabling_recovery_and_keeps_it_when_unplugged(self):
with patch.object(wake_service, "connected", return_value=[self.binding]):
self.assertEqual(self.service.registry(), {"items": [A]})
self.assertFalse(self.service.load(A)["enabled"])
self.assertEqual(self.service.load(A)["attempts"], [])
with patch.object(wake_service, "connected", return_value=[]):
self.assertEqual(self.service.registry(), {"items": [A]})
def test_registry_never_enrolls_ambiguous_usb_identity(self):
with patch.object(wake_service, "connected", return_value=[self.binding, self.binding]):
self.assertEqual(self.service.registry(), {"items": []})
def test_manual_wake_works_while_disabled_and_shares_the_persisted_attempt_budget(self):
self.enable()
self.service.control(A, False)
def start(*args, **kwargs):
kwargs["stdout"].write(b'{"sdk_usb_returned": true}')
kwargs["stdout"].flush()
return Mock(stdin=io.BytesIO(), returncode=0)
with (
patch.object(wake_service, "connected", return_value=[]),
patch.object(
wake_service.subprocess,
"Popen",
side_effect=start,
) as spawn,
):
self.assertEqual(self.service.wake_locked(A, "1" * 32), {"state": "disabled"})
self.assertEqual(
self.service.wake_locked(A, "1" * 32, manual=True), {"state": "connected"}
)
self.assertEqual(
self.service.wake_locked(A, "1" * 32, manual=True), {"state": "already_attempted"}
)
self.assertEqual(
self.service.wake_locked(B, "2" * 32, manual=True), {"state": "disabled"}
)
spawn.assert_called_once()
self.assertFalse(self.service.load(A)["enabled"])
self.assertEqual(len(self.service.load(A)["attempts"]), 1)
if __name__ == "__main__":
unittest.main()