traitclaw_test_utils/
tools.rs1use async_trait::async_trait;
20use serde::{Deserialize, Serialize};
21
22use traitclaw_core::traits::tool::Tool;
23use traitclaw_core::{Error, Result};
24
25#[derive(Debug, Deserialize, schemars::JsonSchema)]
29pub struct EchoInput {
30 pub text: String,
32}
33
34#[derive(Debug, Serialize)]
36pub struct EchoOutput {
37 pub echo: String,
39}
40
41pub struct EchoTool;
60
61#[async_trait]
62impl Tool for EchoTool {
63 type Input = EchoInput;
64 type Output = EchoOutput;
65
66 fn name(&self) -> &'static str {
67 "echo"
68 }
69
70 fn description(&self) -> &'static str {
71 "Echoes input text back as output"
72 }
73
74 async fn execute(&self, input: Self::Input) -> Result<Self::Output> {
75 Ok(EchoOutput {
76 echo: input.text.clone(),
77 })
78 }
79}
80
81#[derive(Debug, Deserialize, schemars::JsonSchema)]
85pub struct FailInput {
86 #[allow(dead_code)]
88 pub message: Option<String>,
89}
90
91pub struct FailTool;
110
111#[async_trait]
112impl Tool for FailTool {
113 type Input = FailInput;
114 type Output = serde_json::Value;
115
116 fn name(&self) -> &'static str {
117 "fail"
118 }
119
120 fn description(&self) -> &'static str {
121 "Always fails with an error"
122 }
123
124 async fn execute(&self, _input: Self::Input) -> Result<Self::Output> {
125 Err(Error::Runtime("tool failure".into()))
126 }
127}
128
129#[derive(Debug, Deserialize, schemars::JsonSchema)]
133pub struct DangerousInput {
134 #[allow(dead_code)]
136 pub payload: String,
137}
138
139#[derive(Debug, Serialize)]
141pub struct DangerousOutput {
142 pub result: String,
144}
145
146pub struct DangerousTool;
160
161#[async_trait]
162impl Tool for DangerousTool {
163 type Input = DangerousInput;
164 type Output = DangerousOutput;
165
166 fn name(&self) -> &'static str {
167 "dangerous_operation"
168 }
169
170 fn description(&self) -> &'static str {
171 "A dangerous tool for hook interception tests"
172 }
173
174 async fn execute(&self, _input: Self::Input) -> Result<Self::Output> {
175 Ok(DangerousOutput {
176 result: "SHOULD NOT RUN".into(),
177 })
178 }
179}
180
181pub struct DenyGuard;
197
198impl traitclaw_core::traits::guard::Guard for DenyGuard {
199 fn name(&self) -> &'static str {
200 "deny-all"
201 }
202
203 fn check(
204 &self,
205 _action: &traitclaw_core::types::action::Action,
206 ) -> traitclaw_core::traits::guard::GuardResult {
207 traitclaw_core::traits::guard::GuardResult::Deny {
208 reason: "blocked by test guard".into(),
209 severity: traitclaw_core::traits::guard::GuardSeverity::High,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[tokio::test]
219 async fn test_echo_tool_returns_input() {
220 let result = EchoTool
221 .execute(EchoInput {
222 text: "hello world".into(),
223 })
224 .await
225 .unwrap();
226 assert_eq!(result.echo, "hello world");
227 }
228
229 #[tokio::test]
230 async fn test_echo_tool_handles_empty_string() {
231 let result = EchoTool
232 .execute(EchoInput {
233 text: String::new(),
234 })
235 .await
236 .unwrap();
237 assert_eq!(result.echo, "");
238 }
239
240 #[tokio::test]
241 async fn test_fail_tool_returns_error() {
242 let result = FailTool.execute(FailInput { message: None }).await;
243 assert!(result.is_err());
244 let err_str = result.unwrap_err().to_string();
245 assert!(err_str.contains("tool failure"), "got: {err_str}");
246 }
247
248 #[test]
249 fn test_echo_tool_name() {
250 assert_eq!(EchoTool.name(), "echo");
251 assert_eq!(EchoTool.description(), "Echoes input text back as output");
252 }
253
254 #[test]
255 fn test_fail_tool_name() {
256 assert_eq!(FailTool.name(), "fail");
257 assert_eq!(FailTool.description(), "Always fails with an error");
258 }
259
260 #[test]
261 fn test_dangerous_tool_name() {
262 assert_eq!(DangerousTool.name(), "dangerous_operation");
263 }
264
265 #[test]
266 fn test_deny_guard_name() {
267 use traitclaw_core::traits::guard::Guard;
268 assert_eq!(DenyGuard.name(), "deny-all");
269 }
270
271 #[test]
272 fn test_tools_are_send_sync() {
273 fn assert_send_sync<T: Send + Sync>() {}
274 assert_send_sync::<EchoTool>();
275 assert_send_sync::<FailTool>();
276 assert_send_sync::<DangerousTool>();
277 assert_send_sync::<DenyGuard>();
278 }
279}