ThePirateRay/tests/test_m3_hardware_protocol.py
2026-10-21 21:55:28 +02:00

153 lines
6.7 KiB
Python

import copy
import json
import tempfile
import unittest
from pathlib import Path
from scripts.validate_m3_hardware_protocol import validate
ROOT = Path(__file__).resolve().parents[1]
TEMPLATE = ROOT / "tests" / "fixtures" / "m3-hardware-protocol-template.json"
class M3HardwareProtocolTests(unittest.TestCase):
def setUp(self):
self.template = json.loads(TEMPLATE.read_text(encoding="utf-8"))
def _write(self, root: Path, plan: dict) -> Path:
path = root / "plan.json"
path.write_text(json.dumps(plan), encoding="utf-8")
return path
def _complete(self) -> dict:
plan = copy.deepcopy(self.template)
plan["roles"] = {
"operator": "Synthetic Operator",
"safety_observer": "Synthetic Observer",
"protocol_reviewer": "Synthetic Reviewer",
}
plan["physical_controls"] = {
key: True for key in plan["physical_controls"]
if key != "emission_inhibition_method"
}
plan["physical_controls"]["emission_inhibition_method"] = (
"Synthetic reviewed isolation method"
)
plan["software_controls"]["operation_allowlist_enforced"] = True
plan["software_controls"]["reversible_driver_plan_reviewed"] = True
plan["allowed_operations"] = [{
"operation_id": "synthetic-read-identity",
"purpose": "Synthetic validator fixture only",
"evidence_artifact_id": "sha256-0123456789abcdef0123",
"evidence_level": "independently-implemented",
"safety_class": "read-only-no-motion-no-emission",
"direction": "host-to-controller-request-response",
"request_hex": "0102",
"expected_response": "Synthetic fixed response contract",
"timeout_ms": 1000,
"maximum_attempts": 1,
"state_change_expected": False,
}]
plan["recovery"].update({
"power_down_procedure_reviewed": True,
"driver_rollback_procedure_reviewed": True,
"incident_record_destination_prepared": True,
})
plan["session"] = {
"machine_location": "Synthetic test location",
"planned_at": "2026-10-21T12:00:00+02:00",
"ends_at": "2026-10-21T12:30:00+02:00",
"notes": "No hardware is used by this unit test.",
}
return plan
def test_public_template_is_incomplete_and_never_authorizes_hardware(self):
result = validate(TEMPLATE)
self.assertEqual("incomplete", result["status"])
self.assertFalse(result["hardware_access_authorized"])
self.assertTrue(result["separate_human_approval_required"])
self.assertIn(
"no evidence-backed USB operation is allowlisted",
result["incomplete"],
)
def test_complete_plan_is_only_ready_for_review(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
result = validate(self._write(Path(directory), self._complete()))
self.assertEqual("ready_for_review", result["status"])
self.assertEqual(1, result["validated_operations"])
self.assertFalse(result["hardware_access_authorized"])
def test_roles_must_be_distinct(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
plan["roles"]["safety_observer"] = plan["roles"]["operator"]
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertIn(
"operator, safety observer and protocol reviewer must be distinct",
result["errors"],
)
self.assertFalse(result["hardware_access_authorized"])
def test_session_window_must_be_bounded_and_ordered(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
plan["session"]["ends_at"] = plan["session"]["planned_at"]
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertIn("session end must be later than session start", result["errors"])
self.assertFalse(result["hardware_access_authorized"])
def test_safety_boundary_cannot_be_narrowed(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
plan["scope"]["prohibited_capabilities"].remove("laser_emission")
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertIn(
"prohibited capability set does not match the safety boundary",
result["errors"],
)
def test_operation_requires_exact_bytes_provenance_and_no_retry(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
operation = plan["allowed_operations"][0]
operation["request_hex"] = "unknown"
operation["evidence_artifact_id"] = "private-path"
operation["maximum_attempts"] = 2
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertGreaterEqual(len(result["errors"]), 3)
self.assertFalse(result["hardware_access_authorized"])
def test_motion_or_state_change_operation_is_rejected(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
operation = plan["allowed_operations"][0]
operation["safety_class"] = "motion"
operation["state_change_expected"] = True
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertTrue(any("unsafe classification" in item for item in result["errors"]))
self.assertTrue(any("expects a state change" in item for item in result["errors"]))
def test_malformed_types_fail_closed_instead_of_crashing(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
plan = self._complete()
plan["scope"] = None
plan["roles"] = []
operation = plan["allowed_operations"][0]
operation["request_hex"] = 102
operation["evidence_artifact_id"] = False
operation["timeout_ms"] = True
result = validate(self._write(Path(directory), plan))
self.assertEqual("invalid", result["status"])
self.assertGreaterEqual(len(result["errors"]), 6)
self.assertFalse(result["hardware_access_authorized"])
if __name__ == "__main__":
unittest.main()