Skip to content

Commit a2cb92c

Browse files
committed
test(acp-nats): add unit tests for terminal_wait_for_exit to satisfy coverage gate
1 parent 44d884c commit a2cb92c

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed

rsworkspace/crates/acp-nats/src/client/terminal_wait_for_exit.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)