use anyhow::Result;
use selfware::api::types::Message;
use selfware::config::{
AgentConfig, Config, ExecutionMode, SafetyConfig, UiConfig, YoloFileConfig,
};
use std::env;
use std::time::Duration;
pub fn test_config() -> Config {
let endpoint =
env::var("SELFWARE_ENDPOINT").unwrap_or_else(|_| "http://localhost:8888/v1".to_string());
let model = env::var("SELFWARE_MODEL").unwrap_or_else(|_| "unsloth/Kimi-K2.5-GGUF".to_string());
let timeout: u64 = env::var("SELFWARE_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(300);
Config {
endpoint,
model,
max_tokens: 4096, temperature: 0.7,
api_key: env::var("SELFWARE_API_KEY")
.ok()
.map(selfware::config::RedactedString::new),
safety: SafetyConfig {
allowed_paths: vec!["/tmp/**".to_string(), "./**".to_string()],
denied_paths: vec![],
protected_branches: vec!["main".to_string()],
require_confirmation: vec![],
strict_permissions: false,
permissions: vec![],
},
agent: AgentConfig {
max_iterations: 10, step_timeout_secs: timeout,
token_budget: 50000,
native_function_calling: false,
streaming: false, ..Default::default()
},
yolo: YoloFileConfig::default(),
ui: UiConfig::default(),
execution_mode: ExecutionMode::Normal,
compact_mode: false,
verbose_mode: false,
show_tokens: false,
..Config::default()
}
}
pub fn skip_slow_tests() -> bool {
env::var("SELFWARE_SKIP_SLOW")
.map(|v| v == "1")
.unwrap_or(false)
}
pub async fn check_model_health(config: &Config) -> Result<bool> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.build()?;
let response = client
.get(format!("{}/models", config.endpoint))
.send()
.await;
match response {
Ok(r) => Ok(r.status().is_success()),
Err(_) => Ok(false),
}
}
pub async fn require_llm_endpoint(config: &Config) -> bool {
check_model_health(config).await.unwrap_or(false)
}
pub async fn require_llm_endpoint_url(endpoint: &str) -> bool {
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.build()
{
Ok(c) => c,
Err(_) => return false,
};
match client.get(format!("{}/models", endpoint)).send().await {
Ok(r) => r.status().is_success(),
Err(_) => false,
}
}
#[macro_export]
macro_rules! skip_if_no_model {
($config:expr) => {
if !$crate::helpers::check_model_health($config)
.await
.unwrap_or(false)
{
if std::env::var("CI").is_ok() || std::env::var("REQUIRE_MODEL").is_ok() {
panic!(
"Model endpoint not available at {} - REQUIRED in CI",
$config.endpoint
);
}
let test_path = module_path!();
println!(
"test {} ... SKIPPED (model endpoint not available at {})",
test_path, $config.endpoint
);
eprintln!(
"SKIPPED: {} - model endpoint not available at {}",
test_path, $config.endpoint
);
return;
}
};
}
#[macro_export]
macro_rules! skip_if_slow {
() => {
if skip_slow_tests() {
let test_path = module_path!();
println!("test {} ... SKIPPED (SELFWARE_SKIP_SLOW=1)", test_path);
eprintln!(
"SKIPPED: {} - slow tests disabled (SELFWARE_SKIP_SLOW=1)",
test_path
);
return;
}
};
}
pub fn file_read_prompt() -> &'static str {
"Read the file at ./Cargo.toml and tell me the package name. Use the file_read tool."
}
pub fn shell_prompt() -> &'static str {
"Run 'echo hello' using the shell_exec tool and tell me the output."
}
pub fn simple_question() -> &'static str {
"What is 2 + 2? Answer with just the number."
}
pub fn user_message(content: &str) -> Message {
Message::user(content)
}
pub fn test_system_prompt() -> Message {
Message::system(
"You are a helpful assistant being tested. Respond concisely. \
When asked to use a tool, use the XML format: \
<tool><name>TOOL_NAME</name><arguments>{...}</arguments></tool>",
)
}
pub fn test_timeout() -> Duration {
let secs: u64 = env::var("SELFWARE_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(600); Duration::from_secs(secs)
}
pub fn extended_timeout() -> Duration {
Duration::from_secs(test_timeout().as_secs() * 2)
}
pub fn assert_contains(response: &str, expected: &str) {
assert!(
response.to_lowercase().contains(&expected.to_lowercase()),
"Expected response to contain '{}', got: {}",
expected,
&response[..response.len().min(200)]
);
}
pub fn assert_has_tool_call(response: &str, tool_name: &str) {
assert!(
response.contains(&format!("<name>{}</name>", tool_name))
|| response.contains(&format!("\"name\": \"{}\"", tool_name)),
"Expected tool call to '{}', got: {}",
tool_name,
&response[..response.len().min(300)]
);
}