main
py 109 lines 3.27 KB
Raw
1 import threading
2 from pathlib import Path
3
4 import pytest
5 from flask import Response
6
7 from agent import AgentContext
8 from api.stop import Stop
9
10
11 PROJECT_ROOT = Path(__file__).resolve().parents[1]
12
13
14 class _Log:
15 def __init__(self) -> None:
16 self.progress_calls = []
17 self.entries = []
18
19 def set_progress(self, progress: str, *, active: bool) -> None:
20 self.progress_calls.append((progress, active))
21
22 def log(self, **kwargs) -> None:
23 self.entries.append(kwargs)
24
25
26 class _Context:
27 id = "chat-1"
28
29 def __init__(self) -> None:
30 self.paused = True
31 self.killed = False
32 self.log = _Log()
33
34 def is_running(self) -> bool:
35 return True
36
37 def kill_process(self) -> None:
38 self.killed = True
39
40
41 @pytest.mark.asyncio
42 async def test_stop_endpoint_cancels_without_replacing_the_run(monkeypatch) -> None:
43 context = _Context()
44 handler = Stop(app=None, thread_lock=threading.RLock()) # type: ignore[arg-type]
45
46 def use_context(ctxid: str):
47 assert ctxid == context.id
48 return context
49
50 monkeypatch.setattr(AgentContext, "use", use_context)
51
52 result = await handler.process(
53 {"context": context.id}, request=None # type: ignore[arg-type]
54 )
55
56 assert context.killed is True
57 assert context.paused is False
58 assert context.log.progress_calls == [("", False)]
59 assert context.log.entries == [
60 {
61 "type": "info",
62 "content": "Agent process stopped.",
63 "finished": True,
64 }
65 ]
66 assert result == {
67 "message": "Agent process stopped.",
68 "context": context.id,
69 "stopped": True,
70 }
71
72
73 @pytest.mark.asyncio
74 async def test_stop_endpoint_requires_an_explicit_context() -> None:
75 handler = Stop(app=None, thread_lock=threading.RLock()) # type: ignore[arg-type]
76
77 result = await handler.process({}, request=None) # type: ignore[arg-type]
78
79 assert isinstance(result, Response)
80 assert result.status_code == 400
81 assert Stop.requires_auth() is True
82 assert Stop.requires_csrf() is True
83
84
85 def test_composer_stop_button_preserves_queue_keyboard_behavior() -> None:
86 input_store = (
87 PROJECT_ROOT / "webui/components/chat/input/input-store.js"
88 ).read_text(encoding="utf-8")
89 chat_bar = (
90 PROJECT_ROOT / "webui/components/chat/input/chat-bar-input.html"
91 ).read_text(encoding="utf-8")
92
93 assert 'if (running && !hasInput) return "stop";' in input_store
94 assert 'if (state === "stop") return "Stop agent";' in input_store
95 assert 'await globalThis.sendJsonData("/stop", { context });' in input_store
96 assert '$store.chatInput.activateSendButton()' in chat_bar
97 assert ':aria-label="$store.chatInput.sendButtonTitle"' in chat_bar
98 assert "#send-button.stop" in chat_bar
99
100 # Enter keeps using sendMessage(), whose empty-input path sends the queue.
101 assert "$event.preventDefault();\n this.sendMessage();" in input_store
102 assert 'return "Press Enter to send queued messages";' in input_store
103
104
105 def test_terminal_stop_info_closes_the_active_process_group() -> None:
106 messages_js = (PROJECT_ROOT / "webui/js/messages.js").read_text(encoding="utf-8")
107
108 assert "delete displayKvps.finished;" in messages_js
109 assert "if (kvps?.finished) completeLastProcessGroup();" in messages_js