use ai_agent::Agent;
use ai_agent::env::EnvConfig;
use ai_agent::test_utils::clear_all_test_state;
use ai_agent::types::{Message, MessageRole, ToolDefinition, ToolInputSchema};
use std::sync::Mutex;
use uuid::Uuid;
fn has_required_env_vars() -> bool {
let config = EnvConfig::load();
config.base_url.is_some() && config.model.is_some() && config.auth_token.is_some()
}
fn sample_messages() -> Vec<Message> {
vec![
Message {
role: MessageRole::User,
content: "Hello, this is a test message.".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "Hi there! How can I help you?".to_string(),
..Default::default()
},
]
}
#[serial_test::serial]
#[tokio::test]
async fn test_session_save_load_roundtrip() {
let _session_id = format!("int-session-roundtrip-{}", Uuid::new_v4());
let session_id = _session_id.as_str();
let messages = sample_messages();
ai_agent::session::save_session(&session_id, messages.clone(), None)
.await
.unwrap();
let loaded = ai_agent::session::load_session(&session_id).await.unwrap();
assert!(loaded.is_some());
let data = loaded.unwrap();
assert_eq!(data.messages.len(), 2);
assert_eq!(data.messages[0].role, MessageRole::User);
assert_eq!(data.messages[0].content, "Hello, this is a test message.");
assert_eq!(data.messages[1].content, "Hi there! How can I help you?");
ai_agent::session::delete_session(&session_id).await.unwrap();
}
#[serial_test::serial]
#[tokio::test]
async fn test_load_nonexistent_session() {
let loaded = ai_agent::session::load_session("int-nonexistent-xyz")
.await
.unwrap();
assert!(loaded.is_none());
}
#[tokio::test]
#[serial_test::serial]
async fn test_append_to_session() {
let _sid = format!("int-append-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, vec![], None)
.await
.unwrap();
ai_agent::session::append_to_session(
sid,
Message {
role: MessageRole::User,
content: "Appended message".to_string(),
..Default::default()
},
)
.await
.unwrap();
let loaded = ai_agent::session::load_session(sid).await.unwrap().unwrap();
assert_eq!(loaded.messages.len(), 1);
assert_eq!(loaded.messages[0].content, "Appended message");
ai_agent::session::delete_session(sid).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_fork_session() {
let _source = format!("int-fork-source-{}", Uuid::new_v4());
let source = _source.as_str();
ai_agent::session::save_session(
source,
vec![
Message {
role: MessageRole::User,
content: "Original message".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "Original response".to_string(),
..Default::default()
},
],
None,
)
.await
.unwrap();
let fork_id = ai_agent::session::fork_session(source, None).await.unwrap();
assert!(fork_id.is_some());
let fork_id = fork_id.unwrap();
let fork_msgs = ai_agent::session::get_session_messages(&fork_id)
.await
.unwrap();
assert_eq!(fork_msgs.len(), 2);
assert_eq!(fork_msgs[0].content, "Original message");
let src_msgs = ai_agent::session::get_session_messages(source)
.await
.unwrap();
assert_eq!(src_msgs.len(), 2);
ai_agent::session::delete_session(source).await.unwrap();
ai_agent::session::delete_session(&fork_id).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_rename_session() {
let _sid = format!("int-rename-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, vec![], None)
.await
.unwrap();
ai_agent::session::rename_session(sid, "My Integration Test Session")
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert_eq!(
info.summary,
Some("My Integration Test Session".to_string())
);
ai_agent::session::delete_session(sid).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_tag_session() {
let _sid = format!("int-tag-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, vec![], None)
.await
.unwrap();
ai_agent::session::tag_session(sid, Some("integration"))
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert_eq!(info.tag, Some("integration".to_string()));
ai_agent::session::tag_session(sid, None).await.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert!(info.tag.is_none());
ai_agent::session::delete_session(sid).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_list_sessions() {
let _sid1 = format!("int-list-1-{}", Uuid::new_v4());
let sid1 = _sid1.as_str();
let _sid2 = format!("int-list-2-{}", Uuid::new_v4());
let sid2 = _sid2.as_str();
ai_agent::session::save_session(sid1, vec![], None)
.await
.unwrap();
ai_agent::session::save_session(sid2, vec![], None)
.await
.unwrap();
let sessions = ai_agent::session::list_sessions().await.unwrap();
let found_ids: Vec<&str> = sessions.iter().map(|s| s.id.as_str()).collect();
assert!(found_ids.contains(&sid1));
assert!(found_ids.contains(&sid2));
ai_agent::session::delete_session(sid1).await.unwrap();
ai_agent::session::delete_session(sid2).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_delete_session() {
let _sid = format!("int-delete-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, sample_messages(), None)
.await
.unwrap();
let result = ai_agent::session::delete_session(sid).await.unwrap();
assert!(result);
let loaded = ai_agent::session::load_session(sid).await.unwrap();
assert!(loaded.is_none());
let result = ai_agent::session::delete_session(sid).await.unwrap();
assert!(!result);
}
#[test]
fn test_agent_creates_session_id() {
let agent = Agent::new("test-model").max_turns(5);
let sid = agent.get_session_id();
assert!(!sid.is_empty());
assert_eq!(sid.len(), 36);
assert_eq!(sid.chars().nth(8), Some('-'));
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_accumulates_messages_on_prompt() {
clear_all_test_state();
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let agent = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.system_prompt("You are a helpful assistant.");
let result = agent
.query("Say 'Hello Integration Test' and nothing else.")
.await;
assert!(result.is_ok(), "prompt should succeed: {:?}", result.err());
let resp = result.unwrap();
assert!(!resp.text.is_empty());
let messages = agent.get_messages();
assert!(
messages.len() >= 2,
"Should have at least user+assistant messages, got {}",
messages.len()
);
assert_eq!(messages[0].role, MessageRole::User);
assert!(messages[0].content.contains("Hello Integration Test"));
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_emits_done_event() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let events: std::sync::Arc<Mutex<Vec<ai_agent::types::AgentEvent>>> =
std::sync::Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
let agent = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.on_event(move |event| {
events_clone.lock().unwrap().push(event);
});
let result = agent.query("Say 'Done event test' and nothing else.").await;
assert!(result.is_ok());
let events = events.lock().unwrap();
let event_types: Vec<&str> = events
.iter()
.map(|e| match e {
ai_agent::types::AgentEvent::Done { .. } => "Done",
ai_agent::types::AgentEvent::Thinking { .. } => "Thinking",
ai_agent::types::AgentEvent::MessageStart { .. } => "MessageStart",
ai_agent::types::AgentEvent::MessageStop => "MessageStop",
ai_agent::types::AgentEvent::ContentBlockDelta { .. } => "ContentBlockDelta",
ai_agent::types::AgentEvent::ContentBlockStart { .. } => "ContentBlockStart",
ai_agent::types::AgentEvent::ContentBlockStop { .. } => "ContentBlockStop",
ai_agent::types::AgentEvent::ToolStart { .. } => "ToolStart",
ai_agent::types::AgentEvent::ToolComplete { .. } => "ToolComplete",
ai_agent::types::AgentEvent::ToolError { .. } => "ToolError",
_ => "Other",
})
.collect();
eprintln!("Events received: {:?}", event_types);
assert!(!event_types.is_empty(), "Should have received some events");
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_max_turns_reason() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let result = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.query("Run `echo hello` and return the output.")
.await;
assert!(
result.is_ok(),
"Agent should return a result even with max_turns=1"
);
let resp = result.unwrap();
match resp.exit_reason {
ai_agent::types::ExitReason::Completed | ai_agent::types::ExitReason::MaxTurns { .. } => {}
other => eprintln!("Unexpected exit reason: {:?}", other),
}
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_session_persists_to_disk() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let agent = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.system_prompt("You are a test assistant.");
let agent_sid = agent.get_session_id().to_string();
let messages_before = agent.get_messages().len();
let result = agent.query("Say 'PersistTest' and nothing else.").await;
assert!(result.is_ok(), "Prompt should succeed");
let messages_after = agent.get_messages().len();
assert!(
messages_after > messages_before,
"Agent should have accumulated messages"
);
let loaded = ai_agent::session::load_session(&agent_sid).await.unwrap();
match loaded {
Some(data) => {
assert!(
data.messages.len() >= 2,
"Disk session should have messages, got {}",
data.messages.len()
);
}
None => {
eprintln!("Session not persisted to disk (expected in some configurations)");
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_session_continuation() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let agent1 = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.system_prompt("You are a test assistant.");
let _ = agent1.get_session_id();
let _result1 = agent1.query("Say 'FirstAgentSaid' and nothing else.").await;
let agent2 = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.system_prompt("You are a test assistant.");
let result2 = agent2
.query("What did the first agent say? Reply with 'Continued' and nothing else.")
.await;
assert!(result2.is_ok());
let resp2 = result2.unwrap();
assert!(!resp2.text.is_empty());
let msgs1 = agent1.get_messages();
let msgs2 = agent2.get_messages();
assert!(msgs1.len() >= 2, "Agent1 should have messages");
assert!(msgs2.len() >= 2, "Agent2 should have messages");
}
#[test]
fn test_memory_base_dir() {
let base_dir = ai_agent::memdir::get_memory_base_dir();
assert!(base_dir.to_string_lossy().contains(".ai"));
}
#[test]
fn test_memory_entrypoint_path() {
let entrypoint = ai_agent::memdir::get_memory_entrypoint();
assert!(entrypoint.is_absolute());
assert_eq!(entrypoint.file_name().unwrap(), "MEMORY.md");
}
#[test]
fn test_memory_types_list() {
let types = ai_agent::memdir::MEMORY_TYPES;
assert_eq!(types.len(), 4);
assert_eq!(types[0], "user");
assert_eq!(types[1], "feedback");
assert_eq!(types[2], "project");
assert_eq!(types[3], "reference");
}
#[test]
fn test_parse_memory_type() {
assert_eq!(
ai_agent::memdir::parse_memory_type("user"),
Some(ai_agent::memdir::MemoryType::User)
);
assert_eq!(
ai_agent::memdir::parse_memory_type("feedback"),
Some(ai_agent::memdir::MemoryType::Feedback)
);
assert_eq!(
ai_agent::memdir::parse_memory_type("project"),
Some(ai_agent::memdir::MemoryType::Project)
);
assert_eq!(
ai_agent::memdir::parse_memory_type("reference"),
Some(ai_agent::memdir::MemoryType::Reference)
);
}
#[test]
fn test_build_memory_prompt() {
let prompt = ai_agent::memdir::build_memory_prompt(ai_agent::memdir::BuildMemoryPromptParams {
display_name: "test memory",
extra_guidelines: None,
});
assert!(!prompt.is_empty());
assert!(prompt.contains("test memory"));
}
#[tokio::test]
async fn test_ensure_memory_dir_exists() {
let base_dir = ai_agent::memdir::get_memory_base_dir();
let result = ai_agent::memdir::ensure_memory_dir_exists(&base_dir);
assert!(result.is_ok());
assert!(base_dir.exists());
}
#[test]
fn test_is_auto_memory_enabled() {
let _enabled = ai_agent::memdir::is_auto_memory_enabled();
}
#[test]
fn test_compact_low_token_state() {
let state = ai_agent::compact::calculate_token_warning_state(50_000, "claude-sonnet-4-6");
assert!(!state.is_above_warning_threshold);
assert!(!state.is_above_error_threshold);
assert!(!state.is_above_auto_compact_threshold);
assert!(state.percent_left > 50.0);
}
#[test]
fn test_compact_near_auto_compact() {
let state = ai_agent::compact::calculate_token_warning_state(170_000, "claude-sonnet-4-6");
assert!(state.is_above_warning_threshold);
assert!(state.is_above_auto_compact_threshold);
}
#[test]
fn test_should_compact_small() {
assert!(!ai_agent::compact::should_compact(
10_000,
"claude-sonnet-4-6"
));
assert!(!ai_agent::compact::should_compact(
50_000,
"claude-sonnet-4-6"
));
}
#[test]
fn test_should_compact_large() {
assert!(ai_agent::compact::should_compact(
170_000,
"claude-sonnet-4-6"
));
}
#[test]
fn test_estimate_token_count_positive() {
let messages = vec![
Message {
role: MessageRole::User,
content: "Hello, how are you today?".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "I'm doing well, thank you for asking!".to_string(),
..Default::default()
},
];
let count = ai_agent::compact::estimate_token_count(&messages, 0);
assert!(count > 0);
}
#[test]
fn test_truncate_messages_for_summary() {
let messages: Vec<Message> = (0..10)
.map(|i| Message {
role: MessageRole::User,
content: format!("Message number {}", i),
..Default::default()
})
.collect();
let (truncated, _tokens) =
ai_agent::compact::truncate_messages_for_summary(&messages, "claude-sonnet-4-6", 20_000);
assert!(truncated.len() <= messages.len());
if truncated.len() > 1 {
assert!(truncated[0].content < truncated[1].content);
}
}
#[test]
fn test_strip_images_from_messages() {
let messages = vec".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "No images here.".to_string(),
..Default::default()
},
];
let stripped = ai_agent::compact::strip_images_from_messages(&messages);
assert_eq!(stripped.len(), 2);
assert!(stripped[0].content.contains("[image]"));
assert!(!stripped[0].content.contains("screenshot"));
assert_eq!(stripped[1].content, "No images here.");
}
#[test]
fn test_strip_reinjected_attachments() {
let messages = vec![
Message {
role: MessageRole::System,
content: "skill_discovery: tool listing content".to_string(),
..Default::default()
},
Message {
role: MessageRole::System,
content: "Normal system prompt".to_string(),
..Default::default()
},
];
let stripped = ai_agent::compact::strip_reinjected_attachments(&messages);
assert_eq!(stripped.len(), 2);
assert!(
stripped[0]
.content
.contains("Skill attachment content cleared")
);
assert_eq!(stripped[1].content, "Normal system prompt");
}
#[test]
fn test_compact_prompt_exists() {
let prompt = ai_agent::compact::get_compact_prompt();
assert!(!prompt.is_empty());
assert!(prompt.contains("CRITICAL"));
assert!(prompt.contains("<analysis>"));
assert!(prompt.contains("<summary>"));
}
#[test]
fn test_session_memory_default_config() {
clear_all_test_state();
let config = ai_agent::session_memory::get_session_memory_config();
assert_eq!(config.minimum_message_tokens_to_init, 10_000);
assert_eq!(config.minimum_tokens_between_update, 5_000);
assert_eq!(config.tool_calls_between_updates, 3);
}
#[test]
fn test_session_memory_set_config() {
clear_all_test_state();
let original = ai_agent::session_memory::get_session_memory_config();
ai_agent::session_memory::set_session_memory_config(
ai_agent::session_memory::SessionMemoryConfig {
minimum_message_tokens_to_init: 500,
minimum_tokens_between_update: 200,
tool_calls_between_updates: 1,
},
);
let config = ai_agent::session_memory::get_session_memory_config();
assert_eq!(config.minimum_message_tokens_to_init, 500);
assert_eq!(config.minimum_tokens_between_update, 200);
assert_eq!(config.tool_calls_between_updates, 1);
ai_agent::session_memory::set_session_memory_config(original);
}
#[test]
fn test_session_memory_init_threshold() {
clear_all_test_state();
ai_agent::session_memory::set_session_memory_config(
ai_agent::session_memory::SessionMemoryConfig {
minimum_message_tokens_to_init: 1000,
..Default::default()
},
);
assert!(ai_agent::session_memory::has_met_initialization_threshold(
1000
));
assert!(ai_agent::session_memory::has_met_initialization_threshold(
5000
));
assert!(!ai_agent::session_memory::has_met_initialization_threshold(
999
));
ai_agent::session_memory::set_session_memory_config(
ai_agent::session_memory::DEFAULT_SESSION_MEMORY_CONFIG,
);
}
#[test]
fn test_session_memory_update_threshold() {
clear_all_test_state();
let original = ai_agent::session_memory::get_session_memory_config();
ai_agent::session_memory::set_session_memory_config(
ai_agent::session_memory::SessionMemoryConfig {
minimum_tokens_between_update: 100,
..Default::default()
},
);
ai_agent::session_memory::record_extraction_token_count(1000);
assert!(ai_agent::session_memory::has_met_update_threshold(1100));
assert!(!ai_agent::session_memory::has_met_update_threshold(1099));
ai_agent::session_memory::set_session_memory_config(original);
ai_agent::session_memory::record_extraction_token_count(0);
}
#[test]
fn test_session_memory_state_management() {
clear_all_test_state();
ai_agent::session_memory::mark_extraction_started();
ai_agent::session_memory::mark_extraction_completed();
}
#[test]
fn test_should_extract_memory_not_ready() {
clear_all_test_state();
let original = ai_agent::session_memory::get_session_memory_config();
ai_agent::session_memory::set_session_memory_config(
ai_agent::session_memory::SessionMemoryConfig {
minimum_message_tokens_to_init: 1_000_000,
..Default::default()
},
);
let short_messages = vec![
Message {
role: MessageRole::User,
content: "Hi".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "Hello!".to_string(),
..Default::default()
},
];
assert!(!ai_agent::session_memory::should_extract_memory(
&short_messages
));
ai_agent::session_memory::set_session_memory_config(original);
}
#[test]
fn test_session_memory_paths() {
let dir = ai_agent::session_memory::get_session_memory_dir();
assert!(dir.contains("session-memory"));
let path = ai_agent::session_memory::get_session_memory_path();
assert!(path.ends_with("summary.md"));
}
#[test]
fn test_tool_definition_with_schema() {
let mut props = std::collections::HashMap::new();
props.insert(
"prompt".to_string(),
serde_json::json!({ "type": "string" }),
);
let tool = ToolDefinition {
name: "test_tool".to_string(),
description: "A test tool".to_string(),
input_schema: ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!(props),
required: Some(vec!["prompt".to_string()]),
},
annotations: None,
should_defer: None,
always_load: None,
is_mcp: None,
search_hint: None,
aliases: None,
user_facing_name: None,
interrupt_behavior: None,
};
assert_eq!(tool.name, "test_tool");
assert_eq!(tool.input_schema.schema_type, "object");
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_message_accumulation() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let events: std::sync::Arc<Mutex<Vec<ai_agent::types::AgentEvent>>> =
std::sync::Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
let agent = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(3)
.on_event(move |event| {
events_clone.lock().unwrap().push(event);
})
.system_prompt("You are a concise assistant. Respond briefly.");
let result = agent
.query("Count from 1 to 3. Say '1 2 3' and nothing else.")
.await;
assert!(result.is_ok(), "Agent should respond: {:?}", result.err());
let resp = result.unwrap();
assert!(!resp.text.is_empty(), "Response should not be empty");
let messages = agent.get_messages();
assert!(
messages.len() >= 2,
"Expected at least 2 messages, got {}",
messages.len()
);
assert_eq!(messages[0].role, MessageRole::User);
assert_eq!(messages[1].role, MessageRole::Assistant);
}
#[tokio::test]
#[serial_test::serial]
async fn test_token_estimation_on_agent_messages() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let agent = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(1)
.system_prompt("You are concise.");
let _ = agent
.query("Say 'TokenEstimationTest' and nothing else.")
.await;
let messages = agent.get_messages();
let token_count = ai_agent::compact::estimate_token_count(&messages, 20_000);
assert!(token_count > 0, "Token count should be positive");
assert!(
token_count < 30000,
"Token count should be reasonable, got {}",
token_count
);
}
#[tokio::test]
#[serial_test::serial]
async fn test_fork_preserves_conversation() {
let _source = format!("int-fork-convo-{}", Uuid::new_v4());
let source = _source.as_str();
let messages = vec![
Message {
role: MessageRole::User,
content: "What is Rust?".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "Rust is a systems programming language.".to_string(),
..Default::default()
},
Message {
role: MessageRole::User,
content: "Why is it fast?".to_string(),
..Default::default()
},
Message {
role: MessageRole::Assistant,
content: "Rust uses ownership and borrowing for zero-cost abstractions.".to_string(),
..Default::default()
},
];
ai_agent::session::save_session(source, messages.clone(), None)
.await
.unwrap();
let fork_id = ai_agent::session::fork_session(source, None)
.await
.unwrap()
.unwrap();
let src_messages = ai_agent::session::get_session_messages(source)
.await
.unwrap();
assert_eq!(src_messages.len(), 4);
let fork_messages = ai_agent::session::get_session_messages(&fork_id)
.await
.unwrap();
assert_eq!(fork_messages.len(), 4);
for (i, (src, fork)) in src_messages.iter().zip(fork_messages.iter()).enumerate() {
assert_eq!(src.content, fork.content, "Message {} should match", i);
assert_eq!(src.role, fork.role, "Message {} role should match", i);
}
ai_agent::session::delete_session(source).await.unwrap();
ai_agent::session::delete_session(&fork_id).await.unwrap();
}
#[tokio::test]
async fn test_agent_prompt_without_model_fails_gracefully() {
let result = Agent::new("test-model")
.max_turns(1)
.query("This should fail or be handled gracefully.")
.await;
match result {
Ok(resp) => {
eprintln!(
"No-model agent returned result (exit_reason={:?}): {}",
resp.exit_reason,
resp.text.chars().take(50).collect::<String>()
);
}
Err(_) => {
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn test_agent_zero_max_turns() {
if !has_required_env_vars() {
eprintln!("Skipping: no API config");
return;
}
let config = EnvConfig::load();
let result = Agent::new("test-model")
.model(config.model.clone().unwrap().as_str())
.max_turns(0)
.query("This should hit max turns.")
.await;
match result {
Ok(resp) => {
eprintln!(
"Zero max turns still got a response (exit_reason={:?}): {}",
resp.exit_reason,
resp.text.chars().take(50).collect::<String>()
);
}
Err(e) => {
eprintln!("Zero max turns returned error as expected: {:?}", e);
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn test_session_metadata_message_count() {
let _sid = format!("int-metadata-count-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, vec![], None)
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert_eq!(info.message_count, 0);
ai_agent::session::append_to_session(
sid,
Message {
role: MessageRole::User,
content: "First".to_string(),
..Default::default()
},
)
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert_eq!(info.message_count, 1);
ai_agent::session::append_to_session(
sid,
Message {
role: MessageRole::Assistant,
content: "Second".to_string(),
..Default::default()
},
)
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert_eq!(info.message_count, 2);
ai_agent::session::delete_session(sid).await.unwrap();
}
#[tokio::test]
#[serial_test::serial]
async fn test_session_metadata_timestamps() {
let _sid = format!("int-metadata-ts-{}", Uuid::new_v4());
let sid = _sid.as_str();
ai_agent::session::save_session(sid, vec![], None)
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
assert!(!info.created_at.is_empty());
assert!(!info.updated_at.is_empty());
let created = chrono::DateTime::parse_from_rfc3339(&info.created_at).unwrap();
let updated = chrono::DateTime::parse_from_rfc3339(&info.updated_at).unwrap();
assert!(updated >= created);
std::thread::sleep(std::time::Duration::from_millis(100));
ai_agent::session::append_to_session(
sid,
Message {
role: MessageRole::User,
content: "After sleep".to_string(),
..Default::default()
},
)
.await
.unwrap();
let info = ai_agent::session::get_session_info(sid)
.await
.unwrap()
.unwrap();
let updated = chrono::DateTime::parse_from_rfc3339(&info.updated_at).unwrap();
assert!(updated > created, "updated_at should be after created_at");
ai_agent::session::delete_session(sid).await.unwrap();
}
#[test]
fn test_memory_frontmatter_parsing() {
let yaml = r#"---
name: test-memory
description: A test memory
type: user
---
Content here."#;
let fm = ai_agent::memdir::parse_frontmatter(yaml);
assert!(fm.is_some());
let fm = fm.unwrap();
assert_eq!(fm.name, "test-memory");
assert_eq!(fm.description, "A test memory");
assert_eq!(fm.memory_type, ai_agent::memdir::MemoryType::User);
}
#[test]
fn test_extract_content_from_frontmatter() {
let yaml = r#"---
type: user
---
This is the content body."#;
let content = ai_agent::memdir::extract_content(yaml);
assert!(content.contains("This is the content body"));
}
#[tokio::test]
async fn test_agent_engine_config() {
let agent = ai_agent::Agent::new("test-model")
.api_key("test-key")
.base_url("http://localhost:9999")
.max_turns(1)
.max_tokens(1000);
let result = agent.query("Hello").await;
assert!(
result.is_err(),
"Should fail without real API server: {:?}",
result.err()
);
}
#[test]
fn test_tool_definition_creation() {
let tool = ToolDefinition {
name: "test_tool".to_string(),
description: "A test tool".to_string(),
input_schema: ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({}),
required: None,
},
annotations: None,
should_defer: None,
always_load: None,
is_mcp: None,
search_hint: None,
aliases: None,
user_facing_name: None,
interrupt_behavior: None,
};
assert_eq!(tool.name, "test_tool");
assert_eq!(tool.description, "A test tool");
}
#[test]
fn test_session_path_construction() {
let path = ai_agent::session::get_session_path("test-session-id");
assert!(path.to_string_lossy().contains("test-session-id"));
assert!(path.to_string_lossy().contains(".open-agent-sdk"));
}
#[test]
fn test_sessions_dir() {
let dir = ai_agent::session::get_sessions_dir();
assert!(dir.to_string_lossy().contains(".open-agent-sdk"));
assert!(dir.to_string_lossy().contains("sessions"));
}