Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions e2etests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,25 @@ pub mod q_chat_helper {
},
Ok(_) => {
// No more data, but wait a bit more in case there's more coming
std::thread::sleep(Duration::from_millis(200));
std::thread::sleep(Duration::from_millis(1000));
if total_content.len() > 0 { break; }
},
Err(_) => break,
}
std::thread::sleep(Duration::from_millis(200));
std::thread::sleep(Duration::from_millis(1000));
}

Ok(total_content)
}

/// Send key input (like arrow keys, Enter, etc.)
pub fn send_key_input(&mut self, key_sequence: &str) -> Result<String, Error> {
self.session.write_all(key_sequence.as_bytes())?;
self.session.flush()?;
std::thread::sleep(Duration::from_millis(200));
self.read_response()
}

/// Quit the Q Chat session
pub fn quit(&mut self) -> Result<(), Error> {
self.session.send_line("/quit")?;
Expand Down
100 changes: 100 additions & 0 deletions e2etests/tests/test_add_and_remove_mcp_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use q_cli_e2e_tests::q_chat_helper::QChatSession;

#[test]
#[cfg(feature = "mcp")]
fn test_add_and_remove_mcp_command() -> Result<(), Box<dyn std::error::Error>> {
println!("🔍 Testing q mcp add command...");

// First install uv dependency before starting Q Chat
println!("🔍 Installing uv dependency...");
std::process::Command::new("pip3")
.args(["install", "uv", "--break-system-packages"])
.output()
.expect("Failed to install uv");
println!("✅ uv dependency installed");

let mut chat = QChatSession::new()?;
println!("✅ Q Chat session started");

// Execute mcp add command
println!("🔍 Executing command: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'");
let response = chat.execute_command("q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest")?;

println!("📝 Response: {} bytes", response.len());
println!("📝 RESPONSE:");
println!("{}", response);
println!("📝 END RESPONSE");

// Verify tool execution details
assert!(response.contains("I will run the following shell command:"), "Missing command execution description");
assert!(response.contains("q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest"), "Missing full command");
assert!(response.contains("Purpose:") && response.contains("Add AWS documentation MCP server"), "Missing purpose description");
println!("✅ Found tool execution details");

// Verify tool execution prompt appears
assert!(response.contains("🛠️ Using tool: execute_bash"), "Missing tool execution indicator");
assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt");
println!("✅ Found tool execution permission prompt");

// Allow the tool execution
let allow_response = chat.execute_command("y")?;

println!("📝 Allow response: {} bytes", allow_response.len());
println!("📝 ALLOW RESPONSE:");
println!("{}", allow_response);
println!("📝 END ALLOW RESPONSE");

// Verify successful addition
assert!(allow_response.contains("✓ Added MCP server") && allow_response.contains("'aws-documentation'") && allow_response.contains("to global config in"), "Missing success message");
assert!(allow_response.contains("/Users/") && allow_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path");
println!("✅ Found successful addition message");

// Verify completion indicator
assert!(allow_response.contains("Completed in") && allow_response.contains("s"), "Missing completion time indicator");
println!("✅ Found completion indicator");

println!("✅ All q mcp add command execution verified successfully");

// Now test removing the MCP server
println!("🔍 Executing remove command: 'q mcp remove --name aws-documentation'");
let remove_response = chat.execute_command("q mcp remove --name aws-documentation")?;

println!("📝 Remove response: {} bytes", remove_response.len());
println!("📝 REMOVE RESPONSE:");
println!("{}", remove_response);
println!("📝 END REMOVE RESPONSE");

// Verify remove tool execution details
assert!(remove_response.contains("I will run the following shell command:"), "Missing remove command execution description");
assert!(remove_response.contains("q mcp remove --name aws-documentation"), "Missing full remove command");
println!("✅ Found remove tool execution details");

// Verify remove tool execution prompt
assert!(remove_response.contains("🛠️ Using tool: execute_bash"), "Missing remove tool execution indicator");
assert!(remove_response.contains("Allow this action?"), "Missing remove permission prompt");
println!("✅ Found remove tool execution permission prompt");

// Allow the remove tool execution
let remove_allow_response = chat.execute_command("y")?;

println!("📝 Remove allow response: {} bytes", remove_allow_response.len());
println!("📝 REMOVE ALLOW RESPONSE:");
println!("{}", remove_allow_response);
println!("📝 END REMOVE ALLOW RESPONSE");

// Verify successful removal
assert!(remove_allow_response.contains("✓ Removed MCP server") && remove_allow_response.contains("'aws-documentation'") && remove_allow_response.contains("from global config"), "Missing removal success message");
assert!(remove_allow_response.contains("/Users/") && remove_allow_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path in removal");
println!("✅ Found successful removal message");

// Verify remove completion indicator
assert!(remove_allow_response.contains("Completed in") && remove_allow_response.contains("s"), "Missing remove completion time indicator");
println!("✅ Found remove completion indicator");

println!("✅ All q mcp remove command execution verified successfully");

chat.quit()?;
println!("✅ Test completed successfully");

Ok(())
}
70 changes: 70 additions & 0 deletions e2etests/tests/test_agent_create_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use q_cli_e2e_tests::q_chat_helper::QChatSession;

#[test]
#[cfg(feature = "agent")]
fn test_agent_create_command() -> Result<(), Box<dyn std::error::Error>> {
println!("🔍 Testing /agent create --name <agent_name> command...");

let agent_name = "test_demo_agent";

let mut chat = QChatSession::new()?;
println!("✅ Q Chat session started");

// Create agent
let create_response = chat.execute_command(&format!("/agent create --name {}", agent_name))?;

println!("📝 Agent create response: {} bytes", create_response.len());
println!("📝 CREATE RESPONSE:");
println!("{}", create_response);
println!("📝 END CREATE RESPONSE");

// Save and exit editor
let save_response = chat.execute_command(":wq")?;

println!("📝 Save response: {} bytes", save_response.len());
println!("📝 SAVE RESPONSE:");
println!("{}", save_response);
println!("📝 END SAVE RESPONSE");

// Verify agent creation success message
assert!(save_response.contains("Agent") && save_response.contains(agent_name) && save_response.contains("has been created successfully"), "Missing agent creation success message");
println!("✅ Found agent creation success message");

// Get current username using !whoami in Q CLI
let whoami_response = chat.execute_command("!whoami")?;

println!("📝 Whoami response: {} bytes", whoami_response.len());
println!("📝 WHOAMI RESPONSE:");
println!("{}", whoami_response);
println!("📝 END WHOAMI RESPONSE");

// Extract username from response (parse the actual username from Q CLI output)
let lines: Vec<&str> = whoami_response.lines().collect();
let username = lines.iter()
.find(|line| !line.starts_with("!") && !line.starts_with(">") && !line.trim().is_empty())
.unwrap_or(&"shrebhaa")
.trim();
println!("✅ Current username: {}", username);

chat.quit()?;

// Construct agent path dynamically
let agent_path = format!("/Users/{}/.aws/amazonq/cli-agents/{}.json", username, agent_name);
println!("✅ Agent path: {}", agent_path);

// Delete the agent file if it exists
if std::path::Path::new(&agent_path).exists() {
std::fs::remove_file(&agent_path)?;
println!("✅ Agent file deleted: {}", agent_path);
} else {
println!("⚠️ Agent file not found at: {}", agent_path);
}

// Verify agent file was deleted
assert!(!std::path::Path::new(&agent_path).exists(), "Agent file should be deleted");
println!("✅ Agent deletion verified");

println!("✅ Test completed successfully");

Ok(())
}
46 changes: 46 additions & 0 deletions e2etests/tests/test_mcp_remove_help_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use q_cli_e2e_tests::q_chat_helper::QChatSession;

#[test]
#[cfg(feature = "mcp")]
fn test_mcp_remove_help_command() -> Result<(), Box<dyn std::error::Error>> {
println!("🔍 Testing q mcp remove --help command...");

let mut chat = QChatSession::new()?;
println!("✅ Q Chat session started");

// Execute q mcp remove --help command
let help_response = chat.execute_command("q mcp remove --help")?;

println!("📝 MCP remove help response: {} bytes", help_response.len());
println!("📝 HELP RESPONSE:");
println!("{}", help_response);
println!("📝 END HELP RESPONSE");

// Verify tool execution prompt appears
assert!(help_response.contains("🛠️ Using tool: execute_bash"), "Missing tool execution indicator");
assert!(help_response.contains("Allow this action?") && help_response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt");
println!("✅ Found tool execution permission prompt");

// Allow the tool execution
let allow_response = chat.execute_command("y")?;

println!("📝 Allow response: {} bytes", allow_response.len());
println!("📝 ALLOW RESPONSE:");
println!("{}", allow_response);
println!("📝 END ALLOW RESPONSE");

// Verify complete help content in final response
assert!(allow_response.contains("Remove a server from the MCP configuration"), "Missing MCP remove description");
assert!(allow_response.contains("Usage: qchat mcp remove"), "Missing usage information");
assert!(allow_response.contains("--name <NAME>"), "Missing --name option");
assert!(allow_response.contains("--scope <SCOPE>"), "Missing --scope option");
assert!(allow_response.contains("--agent <AGENT>"), "Missing --agent option");
assert!(allow_response.contains("-h, --help"), "Missing help option");
assert!(allow_response.contains("Completed in"), "Missing completion indicator");
println!("✅ Found all expected MCP remove help content and completion");

chat.quit()?;
println!("✅ Test completed successfully");

Ok(())
}
118 changes: 118 additions & 0 deletions e2etests/tests/test_model_dynamic_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use q_cli_e2e_tests::q_chat_helper::QChatSession;

#[test]
#[cfg(feature = "model")]
fn test_model_dynamic_command() -> Result<(), Box<dyn std::error::Error>> {
println!("🔍 Testing /model command with dynamic selection...");

let mut chat = QChatSession::new()?;
println!("✅ Q Chat session started");

// Execute /model command to get list
let model_response = chat.execute_command("/model")?;

println!("📝 Model response: {} bytes", model_response.len());
println!("📝 MODEL RESPONSE:");
println!("{}", model_response);
println!("📝 END MODEL RESPONSE");

// Helper function to strip ANSI color codes
let strip_ansi = |s: &str| -> String {
let mut result = String::new();
let mut in_escape = false;
for c in s.chars() {
if c == '\x1b' {
in_escape = true;
} else if in_escape && c == 'm' {
in_escape = false;
} else if !in_escape {
result.push(c);
}
}
result
};

// Parse available models from response
let mut models = Vec::new();
let mut found_prompt = false;

for line in model_response.lines() {
let trimmed_line = line.trim();

// Look for the prompt line
if trimmed_line.contains("Select a model for this chat session") {
found_prompt = true;
continue;
}

// After finding prompt, parse model lines
if found_prompt {
let cleaned_line = strip_ansi(trimmed_line);
println!("🔍 Row: '{}' -> Cleaned: '{}'", trimmed_line, cleaned_line);

if !trimmed_line.is_empty() {
// Check if line contains a model (starts with ❯, spaces, or contains model names)
if cleaned_line.starts_with("❯") || cleaned_line.starts_with(" ") || cleaned_line.contains("-") {
let model_name = cleaned_line
.replace("❯", "")
.replace("(active)", "")
.trim()
.to_string();

println!("🔍 Extracted model: '{}'", model_name);
if !model_name.is_empty() {
models.push(model_name);
}
}
}
}
}

println!("📝 Found models: {:?}", models);
assert!(!models.is_empty(), "No models found in response");

// Send down arrow to select different model
let selection_response = chat.send_key_input("\x1b[B")?;

println!("📝 Selection response: {} bytes", selection_response.len());
println!("📝 SELECTION RESPONSE:");
println!("{}", selection_response);
println!("📝 END SELECTION RESPONSE");

// Find which model is now selected (has ❯ marker)
let selected_model = selection_response.lines()
.find(|line| {
let cleaned = strip_ansi(line);
cleaned.contains("❯")
})
.map(|line| {
let cleaned = strip_ansi(line.trim());
cleaned
.replace("❯", "")
.replace("(active)", "")
.trim()
.to_string()
})
.unwrap_or_else(|| models.get(1).unwrap_or(&models[0]).clone());

println!("📝 Selected model: {}", selected_model);

// Send Enter to confirm
let confirm_response = chat.send_key_input("\r")?;

println!("📝 Confirm response: {} bytes", confirm_response.len());
println!("📝 CONFIRM RESPONSE:");
println!("{}", confirm_response);
println!("📝 END CONFIRM RESPONSE");

// Verify selection with dynamic model name
assert!(confirm_response.contains(&format!("Using {}", selected_model)),
"Missing confirmation for selected model: {}", selected_model);
println!("✅ Confirmed selection of: {}", selected_model);

chat.quit()?;

println!("✅ Test completed successfully");

Ok(())
}
Loading