4998ffddb6
- bin/muse-tui.py: * Parse raw SGR 1006 (\033[<btn;x;yM/m) and Xterm mouse escape sequences in _handle_escape_sequence fallback. * Multi-trigger context menus: Button 3, Button 2, Ctrl/Shift/Alt+Click, double-click, click on active item, or click [⚡] / [sid] target. * Render permanent [⚡] action target across all sidebar thread rows. * Separate focus partitions for FLEET AGENTS and SIDECHATS with partition-specific wheel scrolling and keyboard navigation (j/k, Enter, h/l). * Space key support in NORMAL mode to open context menus. * Active pane highlighting and updated footer hints. - bin/super-cli.py: * Add 'box tui' command dispatching directly to muse-tui.py --mode box. - tests: * Add unit tests in test_context_menus.py, test_focus_highlight.py, and test_main_nav.py (51/51 passing).
116 lines
4.9 KiB
Python
116 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
test_main_nav.py — Comprehensive unit and integration test suite for:
|
|
1. Strict Main Chat blocking in CLI (`super dm send`, `super dm wo`)
|
|
2. Explicit override `--allow-main-chat`
|
|
3. Sidechat target resolution & alias mapping
|
|
4. "Reset-at-Begin, Leave-in-Sidechat" navigation logic in dm.py
|
|
5. Harvester sidechat-only thread scope (no Main Chat polling)
|
|
6. Staged file transfer & metadata registry lifecycle
|
|
"""
|
|
import unittest
|
|
import subprocess
|
|
import json
|
|
import re
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path("/home/super/Projects/NetVM")
|
|
BIN_DIR = REPO_ROOT / "bin"
|
|
SUPER_CLI = BIN_DIR / "super-cli.py"
|
|
DM_PY = BIN_DIR / "dm.py"
|
|
HARVESTER_PY = BIN_DIR / "response-harvester.py"
|
|
|
|
sys.path.insert(0, str(BIN_DIR))
|
|
|
|
class TestMainChatPolicyEnforcement(unittest.TestCase):
|
|
"""Test CLI guards blocking unintended Main Chat access."""
|
|
|
|
def test_super_dm_send_blocks_main_chat_by_default(self):
|
|
cmd = [sys.executable, str(SUPER_CLI), "dm", "send", "--to", "646", "--target", "main", "Automated ping"]
|
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
self.assertNotEqual(res.returncode, 0, "CLI should reject --target main without override")
|
|
self.assertIn("Targeting Main Chat is blocked by CHAT_POLICY.md", res.stderr)
|
|
self.assertIn("--allow-main-chat", res.stderr)
|
|
|
|
def test_super_dm_wo_blocks_main_chat_by_default(self):
|
|
cmd = [sys.executable, str(SUPER_CLI), "dm", "wo", "--to", "646", "--target", "main", "--title", "Task", "Task body"]
|
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
self.assertNotEqual(res.returncode, 0, "CLI should reject work order in Main Chat without override")
|
|
self.assertIn("Work Orders must target sidechats per CHAT_POLICY.md", res.stderr)
|
|
self.assertIn("--allow-main-chat", res.stderr)
|
|
|
|
def test_super_dm_wo_defaults_to_agent_sidechat(self):
|
|
# Dry-run test parser defaults: when --target is omitted, should target agent task sidechat
|
|
cmd = [sys.executable, str(SUPER_CLI), "dm", "wo", "--help"]
|
|
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
self.assertEqual(res.returncode, 0)
|
|
self.assertIn("Target sidechat (default: agent primary task sidechat)", res.stdout)
|
|
|
|
|
|
class TestSidechatNavigationLogic(unittest.TestCase):
|
|
"""Test dm.py and sidechat target resolution."""
|
|
|
|
def test_resolve_sidechat_target(self):
|
|
import dm
|
|
# Well-known mappings
|
|
self.assertEqual(dm.resolve_sidechat_target("main"), "main")
|
|
hb_uuid = dm.resolve_sidechat_target("heartbeat")
|
|
self.assertTrue(bool(re.fullmatch(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", hb_uuid)))
|
|
|
|
def test_uuid_regex_detection(self):
|
|
import dm, re
|
|
valid_uuid = "1e75a740-d08f-443d-a0f9-793db196e24f"
|
|
invalid_uuid = "646 tasks"
|
|
pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
self.assertTrue(bool(re.match(pattern, valid_uuid, re.I)))
|
|
self.assertFalse(bool(re.match(pattern, invalid_uuid, re.I)))
|
|
|
|
|
|
class TestResponseHarvesterScope(unittest.TestCase):
|
|
"""Verify harvester monitors only sidechats and leaves Main Chat out of periodic cycles."""
|
|
|
|
def test_harvester_excludes_main_chat(self):
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("harvester", str(HARVESTER_PY))
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
|
|
monitored = mod.get_monitored_threads()
|
|
for agent, threads in monitored.items():
|
|
thread_ids = [t["id"] for t in threads]
|
|
self.assertNotIn("main", thread_ids, f"Agent {agent} should not have 'main' in automated harvester scope")
|
|
|
|
|
|
class TestPayloadTransfersLifecycle(unittest.TestCase):
|
|
"""Test staging and metadata registry for payload file transfers."""
|
|
|
|
def test_files_clean_and_registry_sync(self):
|
|
with tempfile.NamedTemporaryFile("w", delete=False) as f:
|
|
f.write("Unit test payload content")
|
|
tmp_path = f.name
|
|
|
|
try:
|
|
# 1. Clean aged payloads
|
|
clean_cmd = [sys.executable, str(SUPER_CLI), "dm", "files", "clean", "--older-than", "0"]
|
|
res = subprocess.run(clean_cmd, capture_output=True, text=True)
|
|
self.assertEqual(res.returncode, 0)
|
|
self.assertIn("Cleaned", res.stdout)
|
|
|
|
# 2. View files list (JSON output)
|
|
list_cmd = [sys.executable, str(SUPER_CLI), "dm", "files", "--json"]
|
|
res = subprocess.run(list_cmd, capture_output=True, text=True)
|
|
self.assertEqual(res.returncode, 0)
|
|
data = json.loads(res.stdout)
|
|
self.assertTrue(data.get("ok"))
|
|
self.assertEqual(len(data.get("files", [])), 0)
|
|
finally:
|
|
if os.path.exists(tmp_path):
|
|
os.unlink(tmp_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|