fix(work): import hashlib and wire heal subparser into main CLI
This commit is contained in:
+82
-25
@@ -25,6 +25,58 @@ import approvals
|
||||
import gravity
|
||||
|
||||
|
||||
def _load(name, relpath):
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(name, REPO_ROOT / relpath)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
box_ctl = _load("box_ctl_approvaltest", "bin/box-ctl.py")
|
||||
super_cli = _load("super_cli_approvaltest", "bin/super-cli.py")
|
||||
|
||||
|
||||
class _InProcResult:
|
||||
"""Minimal CompletedProcess stand-in (returncode/stdout only)."""
|
||||
|
||||
def __init__(self, returncode, stdout):
|
||||
self.returncode = returncode
|
||||
self.stdout = stdout
|
||||
|
||||
|
||||
def _box_ctl_inproc(*args):
|
||||
"""In-process box-ctl call (proven pattern from test_box_loop_https).
|
||||
|
||||
Real main(argv): identical parsing, dispatch, audit, stdout JSON.
|
||||
"""
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
buf = io.StringIO()
|
||||
returncode = 0
|
||||
with redirect_stdout(buf):
|
||||
try:
|
||||
box_ctl.main(["box-ctl.py", *args])
|
||||
except SystemExit as e:
|
||||
returncode = e.code if isinstance(e.code, int) else 1
|
||||
return _InProcResult(returncode, buf.getvalue())
|
||||
|
||||
|
||||
def _super_cli_inproc(*args):
|
||||
"""In-process super-cli call (main() reads sys.argv; patch it)."""
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
buf = io.StringIO()
|
||||
returncode = 0
|
||||
with mock.patch.object(sys, "argv", ["super-cli.py", *args]):
|
||||
with redirect_stdout(buf):
|
||||
try:
|
||||
super_cli.main()
|
||||
except SystemExit as e:
|
||||
returncode = e.code if isinstance(e.code, int) else 1
|
||||
return _InProcResult(returncode, buf.getvalue())
|
||||
|
||||
|
||||
class TestApprovalsModule(unittest.TestCase):
|
||||
"""Test approvals.py core module functionality."""
|
||||
|
||||
@@ -79,8 +131,7 @@ class TestBoxApprovalsCli(unittest.TestCase):
|
||||
"""Test 'box approvals' and 'box approval' CLI commands."""
|
||||
|
||||
def test_box_approvals_check_json(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "approvals", "check", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("approvals", "check", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
@@ -88,15 +139,13 @@ class TestBoxApprovalsCli(unittest.TestCase):
|
||||
self.assertIsInstance(data["approvals"], list)
|
||||
|
||||
def test_box_approval_alias(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "approval", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("approval", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
|
||||
def test_box_approvals_auto_json(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "approvals", "auto", "--node", "pip", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("approvals", "auto", "--node", "pip", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
@@ -106,16 +155,14 @@ class TestBoxCtlApprovals(unittest.TestCase):
|
||||
"""Test box-ctl.py allowlisted RPC actions."""
|
||||
|
||||
def test_box_ctl_approval_check(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "box-ctl.py"), "approval-check"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _box_ctl_inproc("approval-check")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
self.assertIn("approvals", data)
|
||||
|
||||
def test_box_ctl_approval_auto(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "box-ctl.py"), "approval-auto", "pip"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _box_ctl_inproc("approval-auto", "pip")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
@@ -162,6 +209,22 @@ class TestApprovalsReplySafety(unittest.TestCase):
|
||||
class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||
"""Test key approval workflow and passkey retrieval architecture."""
|
||||
|
||||
def test_key_scan_tail_fallback_finds_old_request(self):
|
||||
# A live request older than the tail cap must still resolve via the
|
||||
# full-scan fallback (synthetic log; never touches real audit state).
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
p = Path(td) / "box-ctl.jsonl"
|
||||
old = {"ts": "2026-01-01T00:00:00Z", "action": "key-approval-request",
|
||||
"type": "key", "name": "dev", "reason": "buried-old-request",
|
||||
"caller": "t", "expires_at": "2030-01-01T00:00:00Z"}
|
||||
filler = {"ts": "2026-06-01T00:00:00Z", "action": "noop", "name": "x"}
|
||||
lines = [json.dumps(old)] + [json.dumps(filler)] * (approvals.KEY_SCAN_TAIL_LINES + 10)
|
||||
p.write_text("\n".join(lines) + "\n")
|
||||
with mock.patch.object(approvals, "CTL_LOG", p):
|
||||
res = approvals.check_node_key_request("dev")
|
||||
self.assertIsNotNone(res)
|
||||
self.assertEqual(res.get("reason"), "buried-old-request")
|
||||
|
||||
def test_request_and_resolve_key_approval(self):
|
||||
req = approvals.request_key_approval("dev", reason="UnitTest passkey verification", caller="unit-test")
|
||||
self.assertTrue(req.get("ok"))
|
||||
@@ -188,8 +251,7 @@ class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||
self.assertIsNone(cleared)
|
||||
|
||||
def test_box_passkey_info_json(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "passkey", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("passkey", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
@@ -199,8 +261,7 @@ class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||
self.assertEqual(data["key_location"]["canonical_path"], "/srv/box/passkey.txt")
|
||||
|
||||
def test_box_passkey_fetch_json(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "passkey", "fetch", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("passkey", "fetch", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertIn("operator_pin", data)
|
||||
@@ -210,8 +271,7 @@ class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||
self.assertIn("operator_command", data)
|
||||
|
||||
def test_box_lookup_key(self):
|
||||
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "lookup", "key", "--json"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
r = _super_cli_inproc("lookup", "key", "--json")
|
||||
self.assertEqual(r.returncode, 0)
|
||||
data = json.loads(r.stdout)
|
||||
self.assertTrue(data.get("ok"))
|
||||
@@ -219,29 +279,26 @@ class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||
|
||||
def test_cli_request_key_lifecycle(self):
|
||||
# 1. Request key
|
||||
r_req = subprocess.run([
|
||||
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||
r_req = _super_cli_inproc(
|
||||
"approvals", "request-key", "dev", "--reason", "CLI lifecycle test", "--json"
|
||||
], capture_output=True, text=True)
|
||||
)
|
||||
self.assertEqual(r_req.returncode, 0)
|
||||
req_data = json.loads(r_req.stdout)
|
||||
self.assertTrue(req_data.get("ok"))
|
||||
|
||||
# 2. Check shows KEY_APPROVAL
|
||||
r_check = subprocess.run([
|
||||
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||
r_check = _super_cli_inproc(
|
||||
"approvals", "check", "--node", "dev", "--json"
|
||||
], capture_output=True, text=True)
|
||||
)
|
||||
self.assertEqual(r_check.returncode, 0)
|
||||
check_data = json.loads(r_check.stdout)
|
||||
dev_app = next(a for a in check_data["approvals"] if a["node"] == "dev")
|
||||
self.assertEqual(dev_app["status"], "KEY_APPROVAL")
|
||||
|
||||
# 3. Deny key
|
||||
r_deny = subprocess.run([
|
||||
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||
r_deny = _super_cli_inproc(
|
||||
"approvals", "deny", "dev", "--json"
|
||||
], capture_output=True, text=True)
|
||||
)
|
||||
self.assertEqual(r_deny.returncode, 0)
|
||||
deny_data = json.loads(r_deny.stdout)
|
||||
self.assertTrue(deny_data.get("ok"))
|
||||
|
||||
Reference in New Issue
Block a user