Files
box/tests/test_prompts.py
T

450 lines
19 KiB
Python
Raw Normal View History

#!/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(74, w - 6)
modal_h = min(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()