use limit_cli::{AgentBridge, SessionManager, TuiBridge, TuiState};
use limit_llm::{BrowserConfigSection, CacheSettings, Config as LlmConfig};
use std::collections::HashMap;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
use tokio::sync::mpsc;
fn create_test_config() -> LlmConfig {
let mut providers = HashMap::new();
providers.insert(
"anthropic".to_string(),
limit_llm::ProviderConfig {
api_key: Some("test-key".to_string()),
model: "claude-3-5-sonnet-20241022".to_string(),
base_url: None,
max_tokens: 4096,
timeout: 60,
max_iterations: 100,
thinking_enabled: false,
clear_thinking: true,
},
);
LlmConfig {
provider: "anthropic".to_string(),
providers,
browser: BrowserConfigSection::default(),
compaction: limit_llm::CompactionSettings::default(),
cache: CacheSettings::default(),
}
}
#[test]
fn test_e2e_chat_with_mock_api() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
assert!(agent_bridge.is_ready(), "Agent bridge should be ready");
let tools = agent_bridge.get_tool_definitions();
assert!(!tools.is_empty(), "Should have tool definitions");
assert!(
tools.iter().any(|t| t.function.name == "file_read"),
"Should have file_read tool"
);
assert!(
tools.iter().any(|t| t.function.name == "bash"),
"Should have bash tool"
);
}
#[tokio::test]
async fn test_e2e_file_read_and_verify() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.txt");
let content = "Hello, World!\nThis is a test file.\nLine 3 here.";
std::fs::write(&test_file, content).expect("Failed to write test file");
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let tools = agent_bridge.get_tool_definitions();
let file_read_tool = tools
.iter()
.find(|t| t.function.name == "file_read")
.expect("file_read tool should exist");
assert!(
file_read_tool.function.description.contains("Read"),
"Tool should describe reading files"
);
assert!(
file_read_tool.function.parameters["properties"]["path"]["type"] == "string",
"Tool should have path parameter"
);
}
#[tokio::test]
async fn test_e2e_bash_command_and_verify() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let tools = agent_bridge.get_tool_definitions();
let bash_tool = tools
.iter()
.find(|t| t.function.name == "bash")
.expect("bash tool should exist");
assert!(
bash_tool.function.parameters["properties"]["command"]["type"] == "string",
"Bash tool should have command parameter"
);
assert!(
bash_tool.function.parameters["required"]
.as_array()
.unwrap()
.contains(&"command".into()),
"Command should be required"
);
}
#[tokio::test]
async fn test_e2e_git_status_and_verify() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let tools = agent_bridge.get_tool_definitions();
let git_tools: Vec<_> = tools
.iter()
.filter(|t| t.function.name.starts_with("git_"))
.collect();
assert!(!git_tools.is_empty(), "Should have git tools");
let tool_names: Vec<_> = git_tools.iter().map(|t| t.function.name.as_str()).collect();
assert!(tool_names.contains(&"git_status"), "Should have git_status");
assert!(tool_names.contains(&"git_diff"), "Should have git_diff");
assert!(tool_names.contains(&"git_log"), "Should have git_log");
assert!(tool_names.contains(&"git_add"), "Should have git_add");
assert!(tool_names.contains(&"git_commit"), "Should have git_commit");
}
#[test]
fn test_e2e_session_save_and_load() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let session_path = temp_dir.path().join(".limit");
std::fs::create_dir_all(&session_path).expect("Failed to create session dir");
let session_manager = SessionManager::new().expect("Failed to create session manager");
let session_id = session_manager
.create_new_session()
.expect("Failed to create session");
assert!(!session_id.is_empty(), "Session ID should not be empty");
let sessions = session_manager
.list_sessions()
.expect("Failed to list sessions");
assert!(!sessions.is_empty(), "Should have at least one session");
assert!(
sessions.iter().any(|s| s.id == session_id),
"Created session should be in list"
);
}
#[test]
fn test_e2e_tui_rendering_components() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let (tx, rx) = mpsc::unbounded_channel();
let mut tui_bridge = TuiBridge::new(agent_bridge, rx).unwrap();
assert_eq!(tui_bridge.state(), TuiState::Idle);
let op_id = tui_bridge.operation_id();
tx.send(limit_cli::AgentEvent::Thinking {
operation_id: op_id,
})
.unwrap();
tui_bridge.process_events().unwrap();
assert!(matches!(tui_bridge.state(), TuiState::Thinking));
tx.send(limit_cli::AgentEvent::ToolStart {
operation_id: op_id,
name: "file_read".to_string(),
args: serde_json::json!({"path": "/tmp/test.txt"}),
})
.unwrap();
tui_bridge.process_events().unwrap();
assert!(tui_bridge.activity_feed().lock().unwrap().has_in_progress());
tx.send(limit_cli::AgentEvent::ToolComplete {
operation_id: op_id,
name: "file_read".to_string(),
result: "File content here".to_string(),
})
.unwrap();
tui_bridge.process_events().unwrap();
tx.send(limit_cli::AgentEvent::Done {
operation_id: op_id,
})
.unwrap();
tui_bridge.process_events().unwrap();
assert_eq!(tui_bridge.state(), TuiState::Idle);
tx.send(limit_cli::AgentEvent::Error {
operation_id: op_id,
message: "Test error".to_string(),
})
.unwrap();
tui_bridge.process_events().unwrap();
assert_eq!(tui_bridge.state(), TuiState::Idle);
let chat_view = tui_bridge.chat_view();
let message_count = chat_view.lock().unwrap().message_count();
assert!(message_count > 0, "Chat view should have messages");
}
#[test]
fn test_e2e_all_tools_registered() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let tools = agent_bridge.get_tool_definitions();
let expected_tools = [
"file_read",
"file_write",
"file_edit",
"bash",
"git_status",
"git_diff",
"git_log",
"git_add",
"git_commit",
"git_push",
"git_pull",
"git_clone",
"ast_grep",
"web_search",
"web_fetch",
"browser",
];
let tool_names: Vec<_> = tools.iter().map(|t| t.function.name.as_str()).collect();
for expected in &expected_tools {
assert!(tool_names.contains(expected), "Missing tool: {}", expected);
}
}
#[test]
fn test_e2e_event_ordering() {
let config = create_test_config();
let agent_bridge = AgentBridge::new(config).expect("Failed to create agent bridge");
let (tx, rx) = mpsc::unbounded_channel();
let mut tui_bridge = TuiBridge::new(agent_bridge, rx).unwrap();
let op_id = tui_bridge.operation_id();
let events = vec![
limit_cli::AgentEvent::Thinking {
operation_id: op_id,
},
limit_cli::AgentEvent::ContentChunk {
operation_id: op_id,
chunk: "Hello".to_string(),
},
limit_cli::AgentEvent::ContentChunk {
operation_id: op_id,
chunk: " World".to_string(),
},
limit_cli::AgentEvent::ToolStart {
operation_id: op_id,
name: "file_read".to_string(),
args: serde_json::json!({"path": "/test.txt"}),
},
limit_cli::AgentEvent::ToolComplete {
operation_id: op_id,
name: "file_read".to_string(),
result: "content".to_string(),
},
limit_cli::AgentEvent::Done {
operation_id: op_id,
},
];
for event in events {
tx.send(event).unwrap();
tui_bridge.process_events().unwrap();
thread::sleep(Duration::from_millis(5));
}
assert_eq!(tui_bridge.state(), TuiState::Idle);
}