#!/usr/bin/env python3 """ test_prompts.py — Comprehensive unit and integration test suite for: 1. PromptManager persistence, defaults, formulation, deletion, and pin sorting. 2. Compact persistent prompt shelf in View 1 (Agent Chat) and 1-click auto-fill. 3. Expanded Prompt Library modal ('P') with Vim hjkl navigation, preview scroll, and Enter/click fill. 4. Searchable chat history sends ('s' / Ctrl-R) aggregating session and history cache. 5. 'p' pinning chat sent messages to the prompt fill / list in history search and transcript view. 6. Horizontal scrolling of prompt shelf via chips bounds, navigation buttons, wheel, and '[' / ']'. """ import unittest from unittest.mock import MagicMock, patch import importlib.util from pathlib import Path import tempfile import time import json import curses REPO_ROOT = Path("/home/super/Projects/NetVM") TUI_PATH = REPO_ROOT / "bin" / "muse-tui.py" spec = importlib.util.spec_from_file_location("muse_tui", str(TUI_PATH)) muse_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(muse_mod) class TestPromptManager(unittest.TestCase): """Test suite covering PromptManager storage, formulation, and pin ranking.""" def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.storage_file = Path(self.temp_dir.name) / "test_prompts.json" self.pm = muse_mod.PromptManager(storage_path=self.storage_file) def tearDown(self): self.temp_dir.cleanup() def test_default_prompts_seeded_and_persisted(self): """Verify PromptManager initializes with default prompts and writes to disk.""" self.assertTrue(self.storage_file.exists()) prompts = self.pm.get_sorted_prompts() self.assertGreaterEqual(len(prompts), 5) titles = [p["title"] for p in prompts] self.assertIn("Fleet Audit", titles) self.assertIn("Work Order", titles) self.assertIn("CDP Inspect", titles) def test_add_and_delete_prompt(self): """Verify formulation of custom prompt skills and subsequent deletion.""" new_p = self.pm.add_prompt( title="Custom Health Probe", prompt_text="Run detailed tcpdump and box health probes on all nodes.", tags=["probe", "custom"], pinned=True ) self.assertEqual(new_p["title"], "Custom Health Probe") self.assertTrue(new_p["pinned"]) # Check persistence reloaded = muse_mod.PromptManager(storage_path=self.storage_file) prompts = reloaded.get_sorted_prompts() self.assertTrue(any(p["id"] == new_p["id"] for p in prompts)) # Delete self.assertTrue(self.pm.delete_prompt(new_p["id"])) prompts_after = self.pm.get_sorted_prompts() self.assertFalse(any(p["id"] == new_p["id"] for p in prompts_after)) def test_toggle_pin_prompt(self): """Verify toggle_pin_prompt alters pinned flag and affects sorted order.""" prompts = self.pm.get_sorted_prompts() target = prompts[-1] # Get unpinned prompt target_id = target["id"] new_state = self.pm.toggle_pin_prompt(target_id) self.assertTrue(new_state) # In sorted prompts, newly pinned should be among first sorted_p = self.pm.get_sorted_prompts() first_few_ids = [p["id"] for p in sorted_p[:4]] self.assertIn(target_id, first_few_ids) def test_pin_chat_send(self): """Verify pinning a chat sent message formats title, pins to top, and persists.""" sent_text = "Investigate high CDP drop rates and verify routing table on node 646." prompt, is_new = self.pm.pin_chat_send(sent_text, node="646") self.assertTrue(is_new) self.assertTrue(prompt["pinned"]) self.assertEqual(prompt["prompt"], sent_text) self.assertIn("Investigate high CDP", prompt["title"]) # Pinned chat send should be first in sorted prompts sorted_p = self.pm.get_sorted_prompts() self.assertEqual(sorted_p[0]["id"], prompt["id"]) # Pinning same text again should mark pinned without creating duplicate p2, is_new2 = self.pm.pin_chat_send(sent_text, node="646") self.assertFalse(is_new2) self.assertEqual(p2["id"], prompt["id"]) def test_toggle_pin_chat_send(self): """Verify toggle_pin_chat_send flips pinned state between True and False.""" sent_text = "Investigate high CDP drop rates and verify routing table." p1, is_new = self.pm.toggle_pin_chat_send(sent_text, node="646") self.assertTrue(is_new) self.assertTrue(p1["pinned"]) # Toggle again: should unpin p2, is_new2 = self.pm.toggle_pin_chat_send(sent_text, node="646") self.assertFalse(is_new2) self.assertFalse(p2["pinned"]) # Toggle again: should re-pin p3, is_new3 = self.pm.toggle_pin_chat_send(sent_text, node="646") self.assertFalse(is_new3) self.assertTrue(p3["pinned"]) class TestPromptTUIIntegration(unittest.TestCase): """Test suite covering TUI compact shelf, click auto-fill, modals, and sends search.""" def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.storage_file = Path(self.temp_dir.name) / "test_prompts.json" self.mock_stdscr = MagicMock() self.mock_stdscr.getmaxyx.return_value = (30, 100) self.tui = muse_mod.MuseTUI(self.mock_stdscr, initial_mode="muse", initial_node="muse") self.tui.data.stop() self.tui.safe_addstr = MagicMock() # Replace prompt manager with isolated instance self.tui.prompt_manager = muse_mod.PromptManager(storage_path=self.storage_file) self.tui.prompts = self.tui.prompt_manager.get_sorted_prompts() # Clear history cache to isolate unit test data self.tui.data.history_cache.clear() # Seed sample chat history self.tui.data.history_cache[("muse", "thread_1")] = [ {"role": "user", "text": "Check all docker network bridges.", "message_id": "m1"}, {"role": "assistant", "text": "All docker network bridges are operational.", "message_id": "m2"}, {"role": "user", "text": "Inspect CDP latency on pip and opm.", "message_id": "m3"}, ] self.tui.data.active_node = "muse" self.tui.data.active_thread_id = "thread_1" self.tui.data.active_thread_title = "thread_1" self.tui.input_history = ["Recent typed command from session"] def tearDown(self): self.temp_dir.cleanup() def test_compact_prompt_shelf_render_and_chip_bounds(self): """Verify _render_prompt_shelf populates chip bounds and button boundaries.""" self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100) bounds = self.tui._prompt_chip_bounds self.assertGreater(len(bounds), 0, "Prompt chips should be registered with bounding boxes") first_start, first_end, first_prompt = bounds[0] self.assertGreater(first_end, first_start) self.assertIn("title", first_prompt) self.assertTrue(hasattr(self.tui, "_btn_expand_bounds")) self.assertTrue(hasattr(self.tui, "_btn_sends_bounds")) def test_one_click_auto_fill_from_shelf(self): """Verify clicking a prompt chip immediately auto-fills input_buf and sets INSERT mode.""" self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100) first_start, first_end, first_prompt = self.tui._prompt_chip_bounds[0] # Simulate click on chip click_x = (first_start + first_end) // 2 click_y = 25 # Row 1 of prompt shelf (chip row) res = self.tui._handle_mouse(mx=click_x, my=click_y, bstate=curses.BUTTON1_CLICKED) self.assertTrue(res) # Input buffer should contain prompt text self.assertEqual(self.tui.input_buf, first_prompt["prompt"]) self.assertEqual(self.tui.input_cursor, len(first_prompt["prompt"])) self.assertEqual(self.tui.editor_mode, "INSERT") self.assertIn("Filled prompt", self.tui.toast_msg) def test_expand_prompts_modal_and_vim_navigation(self): """Verify pressing 'P' expands modal and hjkl keys navigate and Enter fills.""" # Press capital 'P' in NORMAL mode self.tui.editor_mode = "NORMAL" self.tui.focus_pane = "transcript" res = self.tui._handle_key(ord('P')) self.assertTrue(res) self.assertEqual(self.tui.modal, "prompts") self.assertEqual(self.tui.prompt_sel_idx, 0) # 'j' moves down self.tui._handle_key(ord('j')) self.assertEqual(self.tui.prompt_sel_idx, 1) # 'k' moves up self.tui._handle_key(ord('k')) self.assertEqual(self.tui.prompt_sel_idx, 0) # 'G' jumps to last self.tui._handle_key(ord('G')) self.assertEqual(self.tui.prompt_sel_idx, len(self.tui.prompts) - 1) # 'g' jumps to first self.tui._handle_key(ord('g')) self.assertEqual(self.tui.prompt_sel_idx, 0) # Enter fills selected prompt and switches to INSERT expected_text = self.tui.prompts[0]["prompt"] self.tui._handle_key(10) # Enter self.assertIsNone(self.tui.modal) self.assertEqual(self.tui.input_buf, expected_text) self.assertEqual(self.tui.editor_mode, "INSERT") def test_searchable_chat_history_sends(self): """Verify get_chat_sends aggregates past sends and filters with query.""" sends = self.tui.get_chat_sends() self.assertGreaterEqual(len(sends), 3) texts = [s["text"] for s in sends] self.assertIn("Recent typed command from session", texts) self.assertIn("Check all docker network bridges.", texts) self.assertIn("Inspect CDP latency on pip and opm.", texts) # Filter query filtered = self.tui.get_chat_sends("docker") self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0]["text"], "Check all docker network bridges.") def test_pin_chat_send_to_prompt_fill_in_history_search(self): """Verify 'p' in history_search toggles pin and unpin for highlighted send.""" self.tui.modal = "history_search" self.tui.modal_input_buf = "" self.tui.history_search_sel = 0 initial_prompts_count = len(self.tui.prompts) sends = self.tui.get_chat_sends() target_send_text = sends[0]["text"] # Press 'p': 1st time pins res = self.tui._handle_key(ord('p')) self.assertTrue(res) self.assertEqual(len(self.tui.prompts), initial_prompts_count + 1) top_prompt = self.tui.prompts[0] self.assertTrue(top_prompt["pinned"]) self.assertEqual(top_prompt["prompt"], target_send_text) self.assertIn("Pinned", self.tui.toast_msg) # Press 'p': 2nd time UNPINS res2 = self.tui._handle_key(ord('p')) self.assertTrue(res2) target_prompt_obj = [p for p in self.tui.prompt_manager.prompts if p["prompt"] == target_send_text][0] self.assertFalse(target_prompt_obj["pinned"]) self.assertIn("Unpinned", self.tui.toast_msg) # Press 'p': 3rd time RE-PINS res3 = self.tui._handle_key(ord('p')) self.assertTrue(res3) self.assertTrue(target_prompt_obj["pinned"]) self.assertIn("Pinned", self.tui.toast_msg) def test_history_search_mouse_toggle_pin(self): """Verify clicking the pin column in history_search toggles pin/unpin.""" self.tui.modal = "history_search" self.tui.modal_input_buf = "" self.tui.history_search_sel = 0 sends = self.tui.get_chat_sends() target_text = sends[0]["text"] h, w = self.tui.stdscr.getmaxyx() modal_w = min(84 if self.tui.modal in ("prompts", "history_search") else 74, w - 6) modal_h = min(22 if self.tui.modal in ("prompts", "history_search") else 20, h - 4) top_y = (h - modal_h) // 2 left_x = (w - modal_w) // 2 # 1st click pins click_y = top_y + 4 click_x = left_x + 4 handled = self.tui._handle_mouse(click_x, click_y, curses.BUTTON1_CLICKED) self.assertTrue(handled) target_prompt = [p for p in self.tui.prompt_manager.prompts if p["prompt"] == target_text][0] self.assertTrue(target_prompt["pinned"]) self.assertIn("Pinned", self.tui.toast_msg) # 2nd click unpins handled2 = self.tui._handle_mouse(click_x, click_y, curses.BUTTON1_CLICKED) self.assertTrue(handled2) self.assertFalse(target_prompt["pinned"]) self.assertIn("Unpinned", self.tui.toast_msg) def test_transcript_view_p_pins_latest_send(self): """Verify pressing 'p' on transcript pins the current thread's latest sent message.""" self.tui.modal = None self.tui.editor_mode = "NORMAL" self.tui.focus_pane = "transcript" initial_count = len(self.tui.prompts) # In current thread, latest user message is "Inspect CDP latency on pip and opm." res = self.tui._handle_key(ord('p')) self.assertTrue(res) self.assertEqual(len(self.tui.prompts), initial_count + 1) self.assertEqual(self.tui.prompts[0]["prompt"], "Inspect CDP latency on pip and opm.") self.assertTrue(self.tui.prompts[0]["pinned"]) self.assertIn("Pinned", self.tui.toast_msg) def test_sidebar_p_still_toggles_thread_pin(self): """Verify pressing 'p' when focus is on sidebar preserves thread pinning.""" self.tui.modal = None self.tui.editor_mode = "NORMAL" self.tui.focus_pane = "sidebar" # Ensure thread_1 is unpinned initially self.tui.data.pinned_threads.setdefault("muse", set()).discard("thread_1") # Mock threads cache self.tui.data.threads_cache["muse"] = [ {"session_id": "thread_1", "title": "Test Chat", "is_main": False} ] self.tui.thread_sel_idx = 0 res = self.tui._handle_key(ord('p')) self.assertTrue(res) self.assertIn("thread_1", self.tui.data.pinned_threads.get("muse", set())) self.assertIn("Pinned chat", self.tui.toast_msg) # Clean up disk state self.tui.data.toggle_pin_thread("muse", "thread_1") def test_bracket_keys_scroll_prompt_shelf(self): """Verify '[' and ']' scroll the compact prompt shelf horizontally.""" self.tui.editor_mode = "NORMAL" self.tui.focus_pane = "transcript" self.tui.prompt_scroll_idx = 0 self.tui._handle_key(ord(']')) self.assertEqual(self.tui.prompt_scroll_idx, 1) self.tui._handle_key(ord('[')) self.assertEqual(self.tui.prompt_scroll_idx, 0) def test_slash_prompt_and_sends_commands(self): """Verify /prompt save and /sends slash commands formulate and open modals.""" self.tui.input_buf = "Check loop telemetry on all nodes" self.tui._execute_input_line("/prompt save Telemetry Check") prompts = self.tui.prompt_manager.get_sorted_prompts() saved = [p for p in prompts if p["title"] == "Telemetry Check"] self.assertEqual(len(saved), 1) self.assertEqual(saved[0]["prompt"], "Check loop telemetry on all nodes") # /prompt add self.tui._execute_input_line("/prompt add Quick Probe | ping all nodes") saved_add = [p for p in self.tui.prompt_manager.get_sorted_prompts() if p["title"] == "Quick Probe"] self.assertEqual(len(saved_add), 1) self.assertEqual(saved_add[0]["prompt"], "ping all nodes") # /prompt del self.tui._execute_input_line(f"/prompt del {saved_add[0]['id']}") saved_del = [p for p in self.tui.prompt_manager.get_sorted_prompts() if p["title"] == "Quick Probe"] self.assertEqual(len(saved_del), 0) # /sends opens history search self.tui._execute_input_line("/sends docker") self.assertEqual(self.tui.modal, "history_search") self.assertEqual(self.tui.modal_input_buf, "docker") def test_shelf_buttons_mouse_clicks(self): """Verify clicking buttons on row 0 of prompt shelf opens modals and scrolls.""" self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100) # Click [P:Expand] exp_x = (self.tui._btn_expand_bounds[0] + self.tui._btn_expand_bounds[1]) // 2 self.tui._handle_mouse(mx=exp_x, my=24, bstate=curses.BUTTON1_CLICKED) self.assertEqual(self.tui.modal, "prompts") self.tui.modal = None # Click [🔍 Sends] sends_x = (self.tui._btn_sends_bounds[0] + self.tui._btn_sends_bounds[1]) // 2 self.tui._handle_mouse(mx=sends_x, my=24, bstate=curses.BUTTON1_CLICKED) self.assertEqual(self.tui.modal, "history_search") self.tui.modal = None # Click [►] next_x = (self.tui._btn_next_bounds[0] + self.tui._btn_next_bounds[1]) // 2 self.tui.prompt_scroll_idx = 0 self.tui._handle_mouse(mx=next_x, my=24, bstate=curses.BUTTON1_CLICKED) self.assertEqual(self.tui.prompt_scroll_idx, 1) # Click [◄] prev_x = (self.tui._btn_prev_bounds[0] + self.tui._btn_prev_bounds[1]) // 2 self.tui._handle_mouse(mx=prev_x, my=24, bstate=curses.BUTTON1_CLICKED) self.assertEqual(self.tui.prompt_scroll_idx, 0) def test_mouse_wheel_scrolling_on_shelf(self): """Verify mouse wheel up/down over prompt shelf scrolls chips.""" self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100) self.tui.prompt_scroll_idx = 0 # Wheel down over prompt shelf (my=25) self.tui._handle_mouse(mx=50, my=25, bstate=0x200000) self.assertEqual(self.tui.prompt_scroll_idx, 1) # Wheel up over prompt shelf (my=25) self.tui._handle_mouse(mx=50, my=25, bstate=0x10000) self.assertEqual(self.tui.prompt_scroll_idx, 0) def test_prompt_modal_pin_toggle_and_delete(self): """Verify 'p' toggles pin and 'd' deletes in prompt modal.""" self.tui.modal = "prompts" self.tui.prompt_sel_idx = 0 target = self.tui.prompts[0] initial_pin = target.get("pinned", False) # Press 'p' to toggle pin self.tui._handle_key(ord('p')) toggled = [p for p in self.tui.prompts if p["id"] == target["id"]][0] self.assertEqual(toggled.get("pinned", False), not initial_pin) # Now select the prompt and press 'd' to delete target_idx = [i for i, p in enumerate(self.tui.prompts) if p["id"] == target["id"]][0] self.tui.prompt_sel_idx = target_idx self.tui._handle_key(ord('d')) self.assertFalse(any(p["id"] == target["id"] for p in self.tui.prompts)) def test_formulate_prompt_modal(self): """Verify prompt_formulate modal creates and saves new prompt.""" self.tui.modal = "prompt_formulate" self.tui.input_buf = "Draft message from input composer" self.tui.modal_input_buf = "New Diagnostic" self.tui.modal_input_cursor = len(self.tui.modal_input_buf) # Press Enter self.tui._handle_key(10) self.assertIsNone(self.tui.modal) # Check prompt exists in manager matching = [p for p in self.tui.prompts if p["title"] == "New Diagnostic"] self.assertEqual(len(matching), 1) self.assertEqual(matching[0]["prompt"], "Draft message from input composer") self.assertTrue(matching[0]["pinned"]) if __name__ == "__main__": unittest.main()