
- Remove Rust formatting check from CI workflow since formatting is now applied - Standardize import ordering and organization throughout codebase - Fix indentation, spacing, and line breaks for consistency - Clean up trailing whitespace and formatting inconsistencies - Apply rustfmt to all Rust source files including checkpoint, sandbox, commands, and test modules This establishes a consistent code style baseline for the project.
295 lines
9.4 KiB
Rust
295 lines
9.4 KiB
Rust
//! End-to-end tests for agent execution with sandbox profiles
|
|
use crate::sandbox::common::*;
|
|
use crate::skip_if_unsupported;
|
|
use serial_test::serial;
|
|
|
|
/// Test agent execution with minimal sandbox profile
|
|
#[test]
|
|
#[serial]
|
|
fn test_agent_with_minimal_profile() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test environment
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
let test_db = TEST_DB.lock();
|
|
test_db.reset().expect("Failed to reset database");
|
|
|
|
// Create minimal sandbox profile
|
|
let rules = profiles::minimal(&test_fs.project_path.to_string_lossy());
|
|
let profile_id = test_db
|
|
.create_test_profile("minimal_agent_test", rules)
|
|
.expect("Failed to create test profile");
|
|
|
|
// Create test agent
|
|
test_db.conn.execute(
|
|
"INSERT INTO agents (name, icon, system_prompt, model, sandbox_profile_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
rusqlite::params![
|
|
"Test Agent",
|
|
"🤖",
|
|
"You are a test agent. Only perform the requested task.",
|
|
"sonnet",
|
|
profile_id
|
|
],
|
|
).expect("Failed to create agent");
|
|
|
|
let _agent_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Execute real Claude command with minimal profile
|
|
let result = execute_claude_task(
|
|
&test_fs.project_path,
|
|
&tasks::multi_operation(),
|
|
Some("You are a test agent. Only perform the requested task."),
|
|
Some("sonnet"),
|
|
Some(profile_id),
|
|
20, // 20 second timeout
|
|
)
|
|
.expect("Failed to execute Claude command");
|
|
|
|
// Debug output
|
|
eprintln!("=== Claude Output ===");
|
|
eprintln!("Exit code: {}", result.exit_code);
|
|
eprintln!("STDOUT:\n{}", result.stdout);
|
|
eprintln!("STDERR:\n{}", result.stderr);
|
|
eprintln!("Duration: {:?}", result.duration);
|
|
eprintln!("===================");
|
|
|
|
// Basic verification - just check Claude ran
|
|
assert!(
|
|
result.exit_code == 0 || result.exit_code == 124, // 0 = success, 124 = timeout
|
|
"Claude should execute (exit code: {})",
|
|
result.exit_code
|
|
);
|
|
}
|
|
|
|
/// Test agent execution with standard sandbox profile
|
|
#[test]
|
|
#[serial]
|
|
fn test_agent_with_standard_profile() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test environment
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
let test_db = TEST_DB.lock();
|
|
test_db.reset().expect("Failed to reset database");
|
|
|
|
// Create standard sandbox profile
|
|
let rules = profiles::standard(&test_fs.project_path.to_string_lossy());
|
|
let profile_id = test_db
|
|
.create_test_profile("standard_agent_test", rules)
|
|
.expect("Failed to create test profile");
|
|
|
|
// Create test agent
|
|
test_db.conn.execute(
|
|
"INSERT INTO agents (name, icon, system_prompt, model, sandbox_profile_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
rusqlite::params![
|
|
"Standard Agent",
|
|
"🔧",
|
|
"You are a test agent with standard permissions.",
|
|
"sonnet",
|
|
profile_id
|
|
],
|
|
).expect("Failed to create agent");
|
|
|
|
let _agent_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Execute real Claude command with standard profile
|
|
let result = execute_claude_task(
|
|
&test_fs.project_path,
|
|
&tasks::multi_operation(),
|
|
Some("You are a test agent with standard permissions."),
|
|
Some("sonnet"),
|
|
Some(profile_id),
|
|
20, // 20 second timeout
|
|
)
|
|
.expect("Failed to execute Claude command");
|
|
|
|
// Debug output
|
|
eprintln!("=== Claude Output (Standard Profile) ===");
|
|
eprintln!("Exit code: {}", result.exit_code);
|
|
eprintln!("STDOUT:\n{}", result.stdout);
|
|
eprintln!("STDERR:\n{}", result.stderr);
|
|
eprintln!("===================");
|
|
|
|
// Basic verification
|
|
assert!(
|
|
result.exit_code == 0 || result.exit_code == 124,
|
|
"Claude should execute with standard profile (exit code: {})",
|
|
result.exit_code
|
|
);
|
|
}
|
|
|
|
/// Test agent execution without sandbox (control test)
|
|
#[test]
|
|
#[serial]
|
|
fn test_agent_without_sandbox() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test environment
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
let test_db = TEST_DB.lock();
|
|
test_db.reset().expect("Failed to reset database");
|
|
|
|
// Create agent without sandbox profile
|
|
test_db
|
|
.conn
|
|
.execute(
|
|
"INSERT INTO agents (name, icon, system_prompt, model) VALUES (?1, ?2, ?3, ?4)",
|
|
rusqlite::params![
|
|
"Unsandboxed Agent",
|
|
"⚠️",
|
|
"You are a test agent without sandbox restrictions.",
|
|
"sonnet"
|
|
],
|
|
)
|
|
.expect("Failed to create agent");
|
|
|
|
let _agent_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Execute real Claude command without sandbox profile
|
|
let result = execute_claude_task(
|
|
&test_fs.project_path,
|
|
&tasks::multi_operation(),
|
|
Some("You are a test agent without sandbox restrictions."),
|
|
Some("sonnet"),
|
|
None, // No sandbox profile
|
|
20, // 20 second timeout
|
|
)
|
|
.expect("Failed to execute Claude command");
|
|
|
|
// Debug output
|
|
eprintln!("=== Claude Output (No Sandbox) ===");
|
|
eprintln!("Exit code: {}", result.exit_code);
|
|
eprintln!("STDOUT:\n{}", result.stdout);
|
|
eprintln!("STDERR:\n{}", result.stderr);
|
|
eprintln!("===================");
|
|
|
|
// Basic verification
|
|
assert!(
|
|
result.exit_code == 0 || result.exit_code == 124,
|
|
"Claude should execute without sandbox (exit code: {})",
|
|
result.exit_code
|
|
);
|
|
}
|
|
|
|
/// Test agent run violation logging
|
|
#[test]
|
|
#[serial]
|
|
fn test_agent_run_violation_logging() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test environment
|
|
let test_db = TEST_DB.lock();
|
|
test_db.reset().expect("Failed to reset database");
|
|
|
|
// Create a test profile first
|
|
let profile_id = test_db
|
|
.create_test_profile("violation_test", vec![])
|
|
.expect("Failed to create test profile");
|
|
|
|
// Create a test agent
|
|
test_db.conn.execute(
|
|
"INSERT INTO agents (name, icon, system_prompt, model, sandbox_profile_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
rusqlite::params![
|
|
"Violation Test Agent",
|
|
"⚠️",
|
|
"Test agent for violation logging.",
|
|
"sonnet",
|
|
profile_id
|
|
],
|
|
).expect("Failed to create agent");
|
|
|
|
let agent_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Create a test agent run
|
|
test_db.conn.execute(
|
|
"INSERT INTO agent_runs (agent_id, agent_name, agent_icon, task, model, project_path) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
rusqlite::params![
|
|
agent_id,
|
|
"Violation Test Agent",
|
|
"⚠️",
|
|
"Test task",
|
|
"sonnet",
|
|
"/test/path"
|
|
],
|
|
).expect("Failed to create agent run");
|
|
|
|
let agent_run_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Insert test violations
|
|
test_db.conn.execute(
|
|
"INSERT INTO sandbox_violations (profile_id, agent_id, agent_run_id, operation_type, pattern_value)
|
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
rusqlite::params![profile_id, agent_id, agent_run_id, "file_read_all", "/etc/passwd"],
|
|
).expect("Failed to insert violation");
|
|
|
|
// Query violations
|
|
let count: i64 = test_db
|
|
.conn
|
|
.query_row(
|
|
"SELECT COUNT(*) FROM sandbox_violations WHERE agent_id = ?1",
|
|
rusqlite::params![agent_id],
|
|
|row| row.get(0),
|
|
)
|
|
.expect("Failed to query violations");
|
|
|
|
assert_eq!(count, 1, "Should have recorded one violation");
|
|
}
|
|
|
|
/// Test profile switching between agent runs
|
|
#[test]
|
|
#[serial]
|
|
fn test_profile_switching() {
|
|
skip_if_unsupported!();
|
|
|
|
// Create test environment
|
|
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
|
let test_db = TEST_DB.lock();
|
|
test_db.reset().expect("Failed to reset database");
|
|
|
|
// Create two different profiles
|
|
let minimal_rules = profiles::minimal(&test_fs.project_path.to_string_lossy());
|
|
let minimal_id = test_db
|
|
.create_test_profile("minimal_switch", minimal_rules)
|
|
.expect("Failed to create minimal profile");
|
|
|
|
let standard_rules = profiles::standard(&test_fs.project_path.to_string_lossy());
|
|
let standard_id = test_db
|
|
.create_test_profile("standard_switch", standard_rules)
|
|
.expect("Failed to create standard profile");
|
|
|
|
// Create agent initially with minimal profile
|
|
test_db.conn.execute(
|
|
"INSERT INTO agents (name, icon, system_prompt, model, sandbox_profile_id) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
rusqlite::params![
|
|
"Switchable Agent",
|
|
"🔄",
|
|
"Test agent for profile switching.",
|
|
"sonnet",
|
|
minimal_id
|
|
],
|
|
).expect("Failed to create agent");
|
|
|
|
let agent_id = test_db.conn.last_insert_rowid();
|
|
|
|
// Update agent to use standard profile
|
|
test_db
|
|
.conn
|
|
.execute(
|
|
"UPDATE agents SET sandbox_profile_id = ?1 WHERE id = ?2",
|
|
rusqlite::params![standard_id, agent_id],
|
|
)
|
|
.expect("Failed to update agent profile");
|
|
|
|
// Verify profile was updated
|
|
let current_profile: i64 = test_db
|
|
.conn
|
|
.query_row(
|
|
"SELECT sandbox_profile_id FROM agents WHERE id = ?1",
|
|
rusqlite::params![agent_id],
|
|
|row| row.get(0),
|
|
)
|
|
.expect("Failed to query agent profile");
|
|
|
|
assert_eq!(current_profile, standard_id, "Profile should be updated");
|
|
}
|