use oxicode_agent::prelude::*;
use serde_json::json;
use std::os::unix::fs::symlink;
use tokio::fs;
async fn create_temp_dir(name: &str) -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = format!("/tmp/oxicode_edge_test_{}_{}", name, id);
let _ = fs::remove_dir_all(&path).await;
fs::create_dir_all(&path).await.unwrap();
path
}
async fn cleanup(path: &str) {
let _ = fs::remove_dir_all(path).await;
}
async fn execute_tool(tool: &dyn AgentTool, params: serde_json::Value) -> AgentToolResult {
tool.execute(
"test_call",
params,
None,
&oxicode_agent::ToolContext::default(),
)
.await
.unwrap()
}
#[tokio::test]
async fn test_grep_with_circular_symlink() {
let dir = create_temp_dir("grep_symlink").await;
let file_path = format!("{}/test.txt", dir);
fs::write(&file_path, "target pattern here").await.unwrap();
let external_dir = create_temp_dir("grep_symlink_ext").await;
let external_file = format!("{}/linked.txt", external_dir);
fs::write(&external_file, "external pattern match")
.await
.unwrap();
let link_path = format!("{}/linked.txt", dir);
std::os::unix::fs::symlink(&external_file, &link_path).expect("create symlink");
let tool = GrepTool::new();
let result = execute_tool(
&tool,
json!({
"pattern": "target pattern",
"path": dir
}),
)
.await;
assert!(
result.success,
"grep should succeed with symlinks: {}",
result.output
);
assert!(
result.output.contains("target pattern"),
"should find the pattern in test.txt"
);
cleanup(&dir).await;
cleanup(&external_dir).await;
}
#[tokio::test]
async fn test_find_with_circular_symlink() {
let dir = create_temp_dir("find_symlink").await;
fs::write(format!("{}/real_file.txt", dir), "")
.await
.unwrap();
let external_dir = create_temp_dir("find_symlink_ext").await;
fs::write(format!("{}/external.txt", external_dir), "")
.await
.unwrap();
let link_path = format!("{}/linked_dir", dir);
std::os::unix::fs::symlink(&external_dir, &link_path).expect("create dir symlink");
let tool = FindTool::new();
let result = execute_tool(
&tool,
json!({
"path": dir,
"max_depth": 2
}),
)
.await;
assert!(
result.success,
"find should succeed with symlinks: {}",
result.output
);
assert!(result.output.contains("real_file.txt"));
cleanup(&dir).await;
cleanup(&external_dir).await;
}
#[tokio::test]
async fn test_grep_with_broken_symlink() {
let dir = create_temp_dir("grep_broken_symlink").await;
fs::write(format!("{}/test.txt", dir), "findme")
.await
.unwrap();
let link_path = format!("{}/broken_link", dir);
symlink("/tmp/nonexistent_target_12345", &link_path).expect("create broken symlink");
let tool = GrepTool::new();
let result = tool
.execute(
"test_call",
json!({
"pattern": "findme",
"path": dir
}),
None,
&ToolContext::default(),
)
.await;
match result {
Ok(r) => {
assert!(r.success);
assert!(r.output.contains("findme"));
}
Err(_) => {
}
}
cleanup(&dir).await;
}
#[tokio::test]
async fn test_find_with_broken_symlink() {
let dir = create_temp_dir("find_broken_symlink").await;
fs::write(format!("{}/real.txt", dir), "").await.unwrap();
let link_path = format!("{}/broken_link", dir);
symlink("/tmp/nonexistent_target_12345", &link_path).expect("create broken symlink");
let tool = FindTool::new();
let result = execute_tool(&tool, json!({ "path": dir })).await;
assert!(result.success);
assert!(result.output.contains("real.txt"));
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_large_file() {
let dir = create_temp_dir("read_large").await;
let file_path = format!("{}/large.txt", dir);
let lines: Vec<String> = (0..5000)
.map(|i| format!("Line {}: Some content here with padding", i))
.collect();
let content = lines.join("\n");
fs::write(&file_path, &content).await.unwrap();
let tool = ReadTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"offset": 101,
"limit": 10
}),
)
.await;
assert!(
result.success,
"read with offset/limit should succeed: {}",
result.output
);
assert!(
result.output.contains("Line 100:"),
"should contain line 100: {}",
result.output
);
assert!(
result.output.contains("Line 109:"),
"should contain line 109: {}",
result.output
);
assert!(
!result.output.contains("Line 110:"),
"should not contain line 110"
);
assert!(
!result.output.contains("Line 99:"),
"should not contain line 99"
);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_file_with_long_lines() {
let dir = create_temp_dir("read_long_lines").await;
let file_path = format!("{}/longlines.txt", dir);
let long_line = "A".repeat(10_000);
let content = format!("short\n{}\nanother short", long_line);
fs::write(&file_path, &content).await.unwrap();
let tool = ReadTool::new();
let result = execute_tool(&tool, json!({ "path": file_path })).await;
assert!(result.success);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_empty_file() {
let dir = create_temp_dir("read_empty_edge").await;
let file_path = format!("{}/empty.txt", dir);
fs::write(&file_path, "").await.unwrap();
let tool = ReadTool::new();
let result = execute_tool(&tool, json!({ "path": file_path })).await;
assert!(result.success);
assert_eq!(result.output, "");
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_file_with_binary_content() {
let dir = create_temp_dir("read_binary").await;
let file_path = format!("{}/binary.bin", dir);
let binary_content: Vec<u8> = (0..255).collect();
fs::write(&file_path, &binary_content).await.unwrap();
let tool = ReadTool::new();
let result = tool
.execute(
"test_call",
json!({ "path": file_path }),
None,
&oxicode_agent::ToolContext::default(),
)
.await;
assert!(
result.is_err()
|| !result.as_ref().unwrap().success
|| result.unwrap().output.contains("binary")
);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_offset_beyond_file() {
let dir = create_temp_dir("read_offset_beyond").await;
let file_path = format!("{}/short.txt", dir);
fs::write(&file_path, "only 3 lines\nline 2\nline 3")
.await
.unwrap();
let tool = ReadTool::new();
let result = tool
.execute(
"test_call",
json!({
"path": file_path,
"offset": 1000,
"limit": 10
}),
None,
&ToolContext::default(),
)
.await;
match result {
Ok(r) => {
assert!(!r.success, "should fail when offset exceeds file length");
assert!(
r.output.contains("Offset"),
"should mention offset: {}",
r.output
);
}
Err(e) => {
assert!(e.contains("Offset") || e.contains("exceeds"));
}
}
cleanup(&dir).await;
}
#[tokio::test]
async fn test_edit_ambiguous_match_multiple_occurrences() {
let dir = create_temp_dir("edit_ambiguous").await;
let file_path = format!("{}/code.rs", dir);
fs::write(
&file_path,
"fn foo() {\n let x = 1;\n let x = 1;\n let x = 1;\n}",
)
.await
.unwrap();
let tool = EditTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"old_text": "let x = 1;",
"new_text": "let x = 2;"
}),
)
.await;
assert!(
!result.success,
"edit should fail when old_text matches multiple times"
);
assert!(
result.output.contains("unique")
|| result.output.contains("multiple")
|| result.output.contains("ambiguous"),
"error should mention uniqueness/ambiguity: {}",
result.output
);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_edit_ambiguous_match_with_context() {
let dir = create_temp_dir("edit_ambiguous_ctx").await;
let file_path = format!("{}/file.txt", dir);
fs::write(
&file_path,
"function a() {\n return 'hello';\n}\n\nfunction b() {\n return 'hello';\n}",
)
.await
.unwrap();
let tool = EditTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"old_text": "return 'hello';",
"new_text": "return 'world';"
}),
)
.await;
assert!(!result.success);
let result2 = execute_tool(
&tool,
json!({
"path": file_path,
"old_text": "function a() {\n return 'hello';\n}",
"new_text": "function a() {\n return 'world';\n}"
}),
)
.await;
assert!(result2.success, "should succeed with enough context");
let content = fs::read_to_string(&file_path).await.unwrap();
assert!(content.contains("return 'world'"));
assert!(content.contains("return 'hello'"));
cleanup(&dir).await;
}
#[tokio::test]
async fn test_edit_empty_old_text_rejected() {
let dir = create_temp_dir("edit_empty_old").await;
let file_path = format!("{}/file.txt", dir);
fs::write(&file_path, "some content").await.unwrap();
let tool = EditTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"old_text": "",
"new_text": "replacement"
}),
)
.await;
assert!(!result.success);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_edit_multi_edits_non_overlapping() {
let dir = create_temp_dir("edit_multi").await;
let file_path = format!("{}/multi.txt", dir);
fs::write(
&file_path,
"color = red\nsize = small\nshape = circle\nweight = light",
)
.await
.unwrap();
let tool = EditTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"edits": [
{ "old_text": "color = red", "new_text": "color = blue" },
{ "old_text": "size = small", "new_text": "size = large" }
]
}),
)
.await;
assert!(
result.success,
"multi-edit should succeed: {}",
result.output
);
let content = fs::read_to_string(&file_path).await.unwrap();
assert!(content.contains("color = blue"));
assert!(content.contains("size = large"));
assert!(content.contains("shape = circle")); assert!(content.contains("weight = light"));
cleanup(&dir).await;
}
#[tokio::test]
async fn test_edit_preserves_file_when_not_found() {
let dir = create_temp_dir("edit_preserve").await;
let file_path = format!("{}/file.txt", dir);
let original = "original content\nline 2\nline 3";
fs::write(&file_path, original).await.unwrap();
let tool = EditTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"old_text": "nonexistent",
"new_text": "replacement"
}),
)
.await;
assert!(!result.success);
let content = fs::read_to_string(&file_path).await.unwrap();
assert_eq!(content, original);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_bash_blocked_env_ld_preload() {
let tool = BashTool::new();
let _result = execute_tool(
&tool,
json!({
"command": "echo $LD_PRELOAD"
}),
)
.await;
let result_blocked = execute_tool(
&tool,
json!({
"command": "echo $LD_PRELOAD",
"env": {
"LD_PRELOAD": "/malicious/lib.so"
}
}),
)
.await;
assert!(
result_blocked.success,
"command should still run: {}",
result_blocked.output
);
assert!(
!result_blocked.output.contains("/malicious/lib.so"),
"LD_PRELOAD should have been stripped from env: {}",
result_blocked.output
);
}
#[tokio::test]
async fn test_bash_blocked_env_path() {
let tool = BashTool::new();
let result = execute_tool(
&tool,
json!({
"command": "echo $PATH",
"env": {
"PATH": "/malicious/bin"
}
}),
)
.await;
assert!(result.success, "command should still run");
assert!(
!result.output.contains("/malicious/bin"),
"PATH override should have been stripped: {}",
result.output
);
}
#[tokio::test]
async fn test_bash_blocked_env_dyld() {
let tool = BashTool::new();
let result = execute_tool(
&tool,
json!({
"command": "echo $DYLD_INSERT_LIBRARIES",
"env": {
"DYLD_INSERT_LIBRARIES": "/malicious.dylib"
}
}),
)
.await;
assert!(result.success, "command should still run");
assert!(
!result.output.contains("/malicious.dylib"),
"DYLD_INSERT_LIBRARIES should have been stripped: {}",
result.output
);
}
#[tokio::test]
async fn test_bash_allowed_env_var() {
let dir = create_temp_dir("bash_env").await;
let file_path = format!("{}/output.txt", dir);
let tool = BashTool::new();
let result = execute_tool(
&tool,
json!({
"command": format!("echo $MY_TEST_VAR > {}", file_path),
"env": {
"MY_TEST_VAR": "hello_from_test"
}
}),
)
.await;
assert!(
result.success,
"should allow non-blocked env vars: {}",
result.output
);
let content = fs::read_to_string(&file_path).await.unwrap_or_default();
assert!(
content.contains("hello_from_test"),
"MY_TEST_VAR should have been set, got: {}",
content
);
cleanup(&dir).await;
}
#[tokio::test]
async fn test_bash_multiple_blocked_env_vars() {
let tool = BashTool::new();
let result = execute_tool(
&tool,
json!({
"command": "echo $HOME $MY_SAFE_VAR",
"env": {
"HOME": "/evil",
"LD_PRELOAD": "/evil.so",
"MY_SAFE_VAR": "ok"
}
}),
)
.await;
assert!(result.success, "command should still run");
assert!(
result.output.contains("ok"),
"MY_SAFE_VAR should be set: {}",
result.output
);
assert!(
!result.output.contains("/evil"),
"Blocked vars HOME/LD_PRELOAD should be stripped: {}",
result.output
);
}
#[tokio::test]
async fn test_write_unicode_content() {
let dir = create_temp_dir("write_unicode").await;
let file_path = format!("{}/unicode.txt", dir);
let tool = WriteTool::new();
let result = execute_tool(
&tool,
json!({
"path": file_path,
"content": "Hello 🌍 世界 мир مرحبا"
}),
)
.await;
assert!(result.success);
let content = fs::read_to_string(&file_path).await.unwrap();
assert_eq!(content, "Hello 🌍 世界 мир مرحبا");
cleanup(&dir).await;
}
#[tokio::test]
async fn test_grep_literal_mode() {
let dir = create_temp_dir("grep_literal").await;
let file_path = format!("{}/test.txt", dir);
fs::write(&file_path, "file.rs contains [abc] pattern")
.await
.unwrap();
let tool = GrepTool::new();
let result = execute_tool(
&tool,
json!({
"pattern": "[abc]",
"path": dir,
"literal": true
}),
)
.await;
assert!(result.success);
assert!(result.output.contains("[abc]"), "should find literal [abc]");
cleanup(&dir).await;
}
#[tokio::test]
async fn test_bash_timeout() {
let tool = BashTool::new();
let result = tool
.execute(
"test_call",
json!({
"command": "sleep 10",
"timeout": 1
}),
None,
&ToolContext::default(),
)
.await;
assert!(
result.is_err() || !result.as_ref().map(|r| r.success).unwrap_or(false),
"long-running command should timeout"
);
}
#[tokio::test]
async fn test_find_symlink_to_file() {
let dir = create_temp_dir("find_symlink_file").await;
let file_path = format!("{}/real.txt", dir);
fs::write(&file_path, "content").await.unwrap();
let link_path = format!("{}/link.txt", dir);
symlink(&file_path, &link_path).expect("create file symlink");
let tool = FindTool::new();
let result = execute_tool(&tool, json!({ "path": dir })).await;
assert!(result.success);
assert!(result.output.contains("real.txt"));
cleanup(&dir).await;
}
#[tokio::test]
async fn test_read_symlink_to_file() {
let dir = create_temp_dir("read_symlink").await;
let file_path = format!("{}/real.txt", dir);
fs::write(&file_path, "hello via symlink").await.unwrap();
let link_path = format!("{}/link.txt", dir);
symlink(&file_path, &link_path).expect("create file symlink");
let tool = ReadTool::new();
let result = execute_tool(&tool, json!({ "path": link_path })).await;
assert!(result.success);
assert!(result.output.contains("hello via symlink"));
cleanup(&dir).await;
}