feat: wire delayed prompt TUI actions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1718,3 +1718,152 @@ async def test_sandbox_switch_enabled_when_available():
|
||||
)
|
||||
await pilot.pause()
|
||||
assert app.screen.query_one("#sandbox-switch", Switch).disabled is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# delayed prompt: bindings, actions, poll dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prompt_later_binding_exists():
|
||||
bindings = {b.key: b for b in HqtApp.BINDINGS}
|
||||
assert "p" in bindings
|
||||
assert bindings["p"].action == "schedule_prompt"
|
||||
assert "P" in bindings
|
||||
assert bindings["P"].action == "cancel_scheduled_prompt"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_schedule_prompt_action_calls_service_and_refreshes():
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
from hqt.db.models import Harness, Project, Session
|
||||
from hqt.tui.widgets.session_list import SessionList
|
||||
|
||||
app = HqtApp()
|
||||
async with app.run_test(size=(120, 40)) as pilot:
|
||||
seed_db = app._db_factory()
|
||||
proj = Project(name="prompt-proj", path="/tmp/prompt-proj")
|
||||
seed_db.add(proj)
|
||||
seed_db.flush()
|
||||
harness = seed_db.query(Harness).first()
|
||||
sess = Session(
|
||||
project_id=proj.id,
|
||||
harness_id=harness.id,
|
||||
tmux_session_name="hqt-prompt-test",
|
||||
archived=False,
|
||||
)
|
||||
seed_db.add(sess)
|
||||
seed_db.commit()
|
||||
proj_id = proj.id
|
||||
sess_id = sess.id
|
||||
seed_db.close()
|
||||
|
||||
app._selected_project_id = proj_id
|
||||
await app._refresh_sessions()
|
||||
await pilot.pause()
|
||||
app.query_one(SessionList).query_one("#session-list").focus()
|
||||
await pilot.press("down")
|
||||
await pilot.pause()
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service.schedule_prompt = MagicMock()
|
||||
mock_service.list_sessions = AsyncMock(return_value=[])
|
||||
mock_service.sync_window_labels = AsyncMock(return_value=[])
|
||||
mock_service.dispatch_due_prompts = AsyncMock(return_value=[])
|
||||
app._session_service = mock_service
|
||||
|
||||
notify_calls = []
|
||||
|
||||
def capture_notify(message, **kwargs):
|
||||
notify_calls.append((message, kwargs))
|
||||
|
||||
with patch.object(app, "notify", side_effect=capture_notify):
|
||||
app.action_schedule_prompt()
|
||||
await pilot.pause()
|
||||
app.screen.dismiss(("continue", __import__("datetime").timedelta(minutes=30)))
|
||||
await pilot.pause()
|
||||
await app.workers.wait_for_complete()
|
||||
|
||||
assert mock_service.schedule_prompt.call_count == 1
|
||||
args = mock_service.schedule_prompt.call_args.args
|
||||
assert args[0] == sess_id
|
||||
assert args[1] == "continue"
|
||||
assert isinstance(args[2], datetime)
|
||||
mock_service.list_sessions.assert_awaited()
|
||||
assert any("Prompt scheduled" in call[0] for call in notify_calls)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_scheduled_prompt_action_calls_service_and_refreshes():
|
||||
from hqt.db.models import Harness, Project, Session
|
||||
from hqt.tui.widgets.session_list import SessionList
|
||||
|
||||
app = HqtApp()
|
||||
async with app.run_test(size=(120, 40)) as pilot:
|
||||
seed_db = app._db_factory()
|
||||
proj = Project(name="cancel-proj", path="/tmp/cancel-proj")
|
||||
seed_db.add(proj)
|
||||
seed_db.flush()
|
||||
harness = seed_db.query(Harness).first()
|
||||
sess = Session(
|
||||
project_id=proj.id,
|
||||
harness_id=harness.id,
|
||||
tmux_session_name="hqt-cancel-test",
|
||||
archived=False,
|
||||
)
|
||||
seed_db.add(sess)
|
||||
seed_db.commit()
|
||||
proj_id = proj.id
|
||||
sess_id = sess.id
|
||||
seed_db.close()
|
||||
|
||||
app._selected_project_id = proj_id
|
||||
await app._refresh_sessions()
|
||||
await pilot.pause()
|
||||
app.query_one(SessionList).query_one("#session-list").focus()
|
||||
await pilot.press("down")
|
||||
await pilot.pause()
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service.cancel_scheduled_prompt = MagicMock()
|
||||
mock_service.list_sessions = AsyncMock(return_value=[])
|
||||
mock_service.sync_window_labels = AsyncMock(return_value=[])
|
||||
mock_service.dispatch_due_prompts = AsyncMock(return_value=[])
|
||||
app._session_service = mock_service
|
||||
|
||||
await app.run_action("cancel_scheduled_prompt")
|
||||
await pilot.pause()
|
||||
await app.workers.wait_for_complete()
|
||||
|
||||
mock_service.cancel_scheduled_prompt.assert_called_once_with(sess_id)
|
||||
mock_service.list_sessions.assert_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_poll_sessions_dispatches_due_prompts_and_notifies():
|
||||
app = HqtApp()
|
||||
async with app.run_test(size=(120, 40)):
|
||||
result = MagicMock()
|
||||
result.ok = True
|
||||
result.window_name = "hqt-1"
|
||||
result.error = ""
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service.dispatch_due_prompts = AsyncMock(return_value=[result])
|
||||
mock_service.sync_window_labels = AsyncMock(return_value=[])
|
||||
app._session_service = mock_service
|
||||
|
||||
notify_calls = []
|
||||
|
||||
def capture_notify(message, **kwargs):
|
||||
notify_calls.append((message, kwargs))
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
with patch.object(app, "notify", side_effect=capture_notify):
|
||||
await app._poll_sessions()
|
||||
|
||||
mock_service.dispatch_due_prompts.assert_awaited_once()
|
||||
assert any("Prompt sent" in call[0] for call in notify_calls)
|
||||
|
||||
Reference in New Issue
Block a user