88 lines
3.4 KiB
Python
88 lines
3.4 KiB
Python
|
|
import unittest
|
||
|
|
from unittest.mock import MagicMock, patch
|
||
|
|
import importlib.util
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
REPO_ROOT = Path("/home/super/Projects/NetVM")
|
||
|
|
sys.path.insert(0, str(REPO_ROOT / "bin"))
|
||
|
|
|
||
|
|
|
||
|
|
class TestBlockedApprovals(unittest.TestCase):
|
||
|
|
@classmethod
|
||
|
|
def setUpClass(cls):
|
||
|
|
spec = importlib.util.spec_from_file_location("muse_tui", str(REPO_ROOT / "bin" / "muse-tui.py"))
|
||
|
|
cls.muse_tui = importlib.util.module_from_spec(spec)
|
||
|
|
spec.loader.exec_module(cls.muse_tui)
|
||
|
|
|
||
|
|
def setUp(self):
|
||
|
|
self.mock_stdscr = MagicMock()
|
||
|
|
self.mock_stdscr.getmaxyx.return_value = (40, 120)
|
||
|
|
self.app = self.muse_tui.MuseTUI(self.mock_stdscr, initial_mode="muse", initial_node="muse")
|
||
|
|
self.app.data = MagicMock()
|
||
|
|
self.app.data.nodes = ["muse", "pip", "646", "opm", "def", "dev"]
|
||
|
|
self.app.data.active_node = "muse"
|
||
|
|
self.app.data.active_thread_id = "thread-1"
|
||
|
|
self.app.data.active_thread_title = "Main Chat"
|
||
|
|
self.app.data.lock = MagicMock()
|
||
|
|
self.app.data.lock.__enter__.return_value = None
|
||
|
|
self.app.data.lock.__exit__.return_value = None
|
||
|
|
|
||
|
|
def test_slash_blocked_opens_approvals_modal(self):
|
||
|
|
self.app.data.approvals_cache = [
|
||
|
|
{"node": "opm", "status": "PENDING", "title": "Review task", "is_trusted": True},
|
||
|
|
{"node": "646", "status": "PENDING", "title": "SSH connect", "is_trusted": False},
|
||
|
|
]
|
||
|
|
self.app._execute_input_line("/blocked")
|
||
|
|
self.assertEqual(self.app.modal, "approvals")
|
||
|
|
self.assertEqual(self.app.approvals_sel_idx, 0)
|
||
|
|
|
||
|
|
self.app._execute_input_line("/Blocked")
|
||
|
|
self.assertEqual(self.app.modal, "approvals")
|
||
|
|
|
||
|
|
self.app._execute_input_line("/approvals")
|
||
|
|
self.assertEqual(self.app.modal, "approvals")
|
||
|
|
|
||
|
|
def test_approvals_modal_navigation_and_targeting(self):
|
||
|
|
self.app.modal = "approvals"
|
||
|
|
self.app.approvals_sel_idx = 0
|
||
|
|
self.app.data.approvals_cache = [
|
||
|
|
{"node": "muse", "status": "PENDING", "title": "Task 1"},
|
||
|
|
{"node": "opm", "status": "PENDING", "title": "Task 2"},
|
||
|
|
]
|
||
|
|
|
||
|
|
# Press 'j' (down)
|
||
|
|
handled = self.app._handle_modal_key(ord('j'))
|
||
|
|
self.assertTrue(handled)
|
||
|
|
self.assertEqual(self.app.approvals_sel_idx, 1)
|
||
|
|
|
||
|
|
# Press '1' (allow) -> should target 'opm' (selected row), not 'muse' (active_node)
|
||
|
|
with patch.object(self.app, "_async_resolve_approval") as mock_resolve:
|
||
|
|
handled = self.app._handle_modal_key(ord('1'))
|
||
|
|
self.assertTrue(handled)
|
||
|
|
self.assertIsNone(self.app.modal)
|
||
|
|
mock_resolve.assert_called_once_with("allow", "opm")
|
||
|
|
|
||
|
|
def test_approvals_modal_navigation_k(self):
|
||
|
|
self.app.modal = "approvals"
|
||
|
|
self.app.approvals_sel_idx = 1
|
||
|
|
self.app.data.approvals_cache = [
|
||
|
|
{"node": "muse", "status": "PENDING", "title": "Task 1"},
|
||
|
|
{"node": "opm", "status": "PENDING", "title": "Task 2"},
|
||
|
|
]
|
||
|
|
|
||
|
|
# Press 'k' (up)
|
||
|
|
handled = self.app._handle_modal_key(ord('k'))
|
||
|
|
self.assertTrue(handled)
|
||
|
|
self.assertEqual(self.app.approvals_sel_idx, 0)
|
||
|
|
|
||
|
|
# Press '2' (always) -> should target 'muse'
|
||
|
|
with patch.object(self.app, "_async_resolve_approval") as mock_resolve:
|
||
|
|
handled = self.app._handle_modal_key(ord('2'))
|
||
|
|
self.assertTrue(handled)
|
||
|
|
mock_resolve.assert_called_once_with("always", "muse")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|