@@ -105,3 +105,273 @@ fn parse_request(
105105
106106 Ok ( request)
107107}
108+
109+ #[ cfg( test) ]
110+ mod tests {
111+ use super :: * ;
112+ use agent_client_protocol:: {
113+ Request , RequestId , RequestPermissionRequest , RequestPermissionResponse ,
114+ SessionNotification , TerminalExitStatus , WaitForTerminalExitRequest ,
115+ WaitForTerminalExitResponse ,
116+ } ;
117+ use async_trait:: async_trait;
118+ use std:: time:: Duration ;
119+
120+ struct MockClient ;
121+
122+ #[ async_trait( ?Send ) ]
123+ impl agent_client_protocol:: Client for MockClient {
124+ async fn session_notification (
125+ & self ,
126+ _: SessionNotification ,
127+ ) -> agent_client_protocol:: Result < ( ) > {
128+ Ok ( ( ) )
129+ }
130+
131+ async fn request_permission (
132+ & self ,
133+ _: RequestPermissionRequest ,
134+ ) -> agent_client_protocol:: Result < RequestPermissionResponse > {
135+ Err ( agent_client_protocol:: Error :: new (
136+ -32603 ,
137+ "not implemented in test mock" ,
138+ ) )
139+ }
140+
141+ async fn wait_for_terminal_exit (
142+ & self ,
143+ _: WaitForTerminalExitRequest ,
144+ ) -> agent_client_protocol:: Result < WaitForTerminalExitResponse > {
145+ Ok ( WaitForTerminalExitResponse :: new (
146+ TerminalExitStatus :: new ( ) . exit_code ( 0u32 ) ,
147+ ) )
148+ }
149+ }
150+
151+ struct FailingClient ;
152+
153+ #[ async_trait( ?Send ) ]
154+ impl agent_client_protocol:: Client for FailingClient {
155+ async fn session_notification (
156+ & self ,
157+ _: SessionNotification ,
158+ ) -> agent_client_protocol:: Result < ( ) > {
159+ Ok ( ( ) )
160+ }
161+
162+ async fn request_permission (
163+ & self ,
164+ _: RequestPermissionRequest ,
165+ ) -> agent_client_protocol:: Result < RequestPermissionResponse > {
166+ Err ( agent_client_protocol:: Error :: new (
167+ -32603 ,
168+ "not implemented in test mock" ,
169+ ) )
170+ }
171+
172+ async fn wait_for_terminal_exit (
173+ & self ,
174+ _: WaitForTerminalExitRequest ,
175+ ) -> agent_client_protocol:: Result < WaitForTerminalExitResponse > {
176+ Err ( agent_client_protocol:: Error :: new (
177+ -32603 ,
178+ "mock wait_for_terminal_exit failure" ,
179+ ) )
180+ }
181+ }
182+
183+ struct TimeoutClient ;
184+
185+ #[ async_trait( ?Send ) ]
186+ impl agent_client_protocol:: Client for TimeoutClient {
187+ async fn session_notification (
188+ & self ,
189+ _: SessionNotification ,
190+ ) -> agent_client_protocol:: Result < ( ) > {
191+ Ok ( ( ) )
192+ }
193+
194+ async fn request_permission (
195+ & self ,
196+ _: RequestPermissionRequest ,
197+ ) -> agent_client_protocol:: Result < RequestPermissionResponse > {
198+ Err ( agent_client_protocol:: Error :: new (
199+ -32603 ,
200+ "not implemented in test mock" ,
201+ ) )
202+ }
203+
204+ async fn wait_for_terminal_exit (
205+ & self ,
206+ _: WaitForTerminalExitRequest ,
207+ ) -> agent_client_protocol:: Result < WaitForTerminalExitResponse > {
208+ tokio:: time:: sleep ( Duration :: from_secs ( 60 ) ) . await ;
209+ Ok ( WaitForTerminalExitResponse :: new (
210+ TerminalExitStatus :: new ( ) . exit_code ( 0u32 ) ,
211+ ) )
212+ }
213+ }
214+
215+ #[ tokio:: test]
216+ async fn handle_success_returns_serialized_response ( ) {
217+ let client = MockClient ;
218+ let envelope = Request {
219+ id : RequestId :: Number ( 1 ) ,
220+ method : std:: sync:: Arc :: from ( "terminal/wait_for_exit" ) ,
221+ params : Some ( WaitForTerminalExitRequest :: new ( "sess-1" , "term-001" ) ) ,
222+ } ;
223+ let payload = serde_json:: to_vec ( & envelope) . unwrap ( ) ;
224+
225+ let result = handle ( & payload, & client, "sess-1" , Duration :: from_secs ( 5 ) ) . await ;
226+
227+ assert ! ( result. is_ok( ) ) ;
228+ let bytes = result. unwrap ( ) ;
229+ let parsed: serde_json:: Value = serde_json:: from_slice ( & bytes) . unwrap ( ) ;
230+ assert_eq ! ( parsed. get( "id" ) , Some ( & serde_json:: Value :: from( 1 ) ) ) ;
231+ assert ! ( parsed. get( "result" ) . is_some( ) ) ;
232+ }
233+
234+ #[ tokio:: test]
235+ async fn handle_malformed_json_returns_error ( ) {
236+ let client = MockClient ;
237+
238+ let result = handle ( b"not json" , & client, "sess-1" , Duration :: from_secs ( 5 ) ) . await ;
239+
240+ assert ! ( matches!(
241+ result,
242+ Err ( TerminalWaitForExitError :: MalformedJson ( _) )
243+ ) ) ;
244+ }
245+
246+ #[ tokio:: test]
247+ async fn handle_invalid_params_returns_error ( ) {
248+ let client = MockClient ;
249+ let payload = br#"{"id":1,"method":"terminal/wait_for_exit","params":{}}"# ;
250+
251+ let result = handle ( payload, & client, "sess-1" , Duration :: from_secs ( 5 ) ) . await ;
252+
253+ assert ! ( matches!(
254+ result,
255+ Err ( TerminalWaitForExitError :: InvalidParams ( _) )
256+ ) ) ;
257+ }
258+
259+ #[ tokio:: test]
260+ async fn handle_params_null_returns_error ( ) {
261+ let client = MockClient ;
262+ let payload = br#"{"id":1,"method":"terminal/wait_for_exit","params":null}"# ;
263+
264+ let result = handle ( payload, & client, "sess-1" , Duration :: from_secs ( 5 ) ) . await ;
265+
266+ assert ! ( matches!(
267+ result,
268+ Err ( TerminalWaitForExitError :: InvalidParams ( _) )
269+ ) ) ;
270+ }
271+
272+ #[ tokio:: test]
273+ async fn handle_session_id_mismatch_returns_error ( ) {
274+ let client = MockClient ;
275+ let envelope = Request {
276+ id : RequestId :: Number ( 1 ) ,
277+ method : std:: sync:: Arc :: from ( "terminal/wait_for_exit" ) ,
278+ params : Some ( WaitForTerminalExitRequest :: new ( "sess-b" , "term-001" ) ) ,
279+ } ;
280+ let payload = serde_json:: to_vec ( & envelope) . unwrap ( ) ;
281+
282+ let result = handle ( & payload, & client, "sess-a" , Duration :: from_secs ( 5 ) ) . await ;
283+
284+ assert ! ( matches!(
285+ result,
286+ Err ( TerminalWaitForExitError :: InvalidParams ( _) )
287+ ) ) ;
288+ }
289+
290+ #[ tokio:: test]
291+ async fn handle_client_error_returns_error ( ) {
292+ let client = FailingClient ;
293+ let envelope = Request {
294+ id : RequestId :: Number ( 1 ) ,
295+ method : std:: sync:: Arc :: from ( "terminal/wait_for_exit" ) ,
296+ params : Some ( WaitForTerminalExitRequest :: new ( "sess-1" , "term-001" ) ) ,
297+ } ;
298+ let payload = serde_json:: to_vec ( & envelope) . unwrap ( ) ;
299+
300+ let result = handle ( & payload, & client, "sess-1" , Duration :: from_secs ( 5 ) ) . await ;
301+
302+ assert ! ( matches!(
303+ result,
304+ Err ( TerminalWaitForExitError :: ClientError ( _) )
305+ ) ) ;
306+ }
307+
308+ #[ tokio:: test]
309+ async fn handle_timeout_returns_error ( ) {
310+ let client = TimeoutClient ;
311+ let envelope = Request {
312+ id : RequestId :: Number ( 1 ) ,
313+ method : std:: sync:: Arc :: from ( "terminal/wait_for_exit" ) ,
314+ params : Some ( WaitForTerminalExitRequest :: new ( "sess-1" , "term-001" ) ) ,
315+ } ;
316+ let payload = serde_json:: to_vec ( & envelope) . unwrap ( ) ;
317+
318+ let result = handle ( & payload, & client, "sess-1" , Duration :: from_millis ( 10 ) ) . await ;
319+
320+ assert ! ( matches!( result, Err ( TerminalWaitForExitError :: TimedOut ) ) ) ;
321+ }
322+
323+ #[ test]
324+ fn error_code_and_message_malformed_json ( ) {
325+ let err = TerminalWaitForExitError :: MalformedJson (
326+ serde_json:: from_str :: < serde_json:: Value > ( "{" ) . unwrap_err ( ) ,
327+ ) ;
328+ let ( code, msg) = error_code_and_message ( & err) ;
329+ assert_eq ! ( code, ErrorCode :: ParseError ) ;
330+ assert ! ( msg. contains( "Malformed terminal/wait_for_exit" ) ) ;
331+ }
332+
333+ #[ test]
334+ fn error_code_and_message_invalid_params ( ) {
335+ let err = TerminalWaitForExitError :: InvalidParams ( agent_client_protocol:: Error :: new (
336+ -32602 ,
337+ "bad params" ,
338+ ) ) ;
339+ let ( code, msg) = error_code_and_message ( & err) ;
340+ assert_eq ! ( i32 :: from( code) , -32602 ) ;
341+ assert_eq ! ( msg, "bad params" ) ;
342+ }
343+
344+ #[ test]
345+ fn error_code_and_message_timed_out ( ) {
346+ let err = TerminalWaitForExitError :: TimedOut ;
347+ let ( code, msg) = error_code_and_message ( & err) ;
348+ assert_eq ! ( code, ErrorCode :: InternalError ) ;
349+ assert_eq ! ( msg, "Timed out waiting for terminal exit" ) ;
350+ }
351+
352+ #[ test]
353+ fn error_code_and_message_client_error ( ) {
354+ let err = TerminalWaitForExitError :: ClientError ( agent_client_protocol:: Error :: new (
355+ -32603 ,
356+ "client failed" ,
357+ ) ) ;
358+ let ( code, msg) = error_code_and_message ( & err) ;
359+ assert_eq ! ( i32 :: from( code) , -32603 ) ;
360+ assert_eq ! ( msg, "client failed" ) ;
361+ }
362+
363+ #[ test]
364+ fn terminal_wait_for_exit_error_display ( ) {
365+ let err = TerminalWaitForExitError :: TimedOut ;
366+ assert ! ( err. to_string( ) . contains( "Timed out" ) ) ;
367+ }
368+
369+ #[ test]
370+ fn terminal_wait_for_exit_error_source ( ) {
371+ let err = TerminalWaitForExitError :: TimedOut ;
372+ assert ! ( err. source( ) . is_none( ) ) ;
373+ let json_err =
374+ TerminalWaitForExitError :: MalformedJson ( serde_json:: from_str :: < ( ) > ( "{" ) . unwrap_err ( ) ) ;
375+ assert ! ( json_err. source( ) . is_some( ) ) ;
376+ }
377+ }
0 commit comments