use selfware::tools::{git::GitStatus, Tool};
use serde_json::json;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn create_test_repo() -> TempDir {
let dir = TempDir::new().unwrap();
Command::new("git")
.args(["init"])
.current_dir(dir.path())
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(dir.path())
.output()
.expect("Failed to configure git email");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(dir.path())
.output()
.expect("Failed to configure git name");
fs::write(dir.path().join("README.md"), "# Test").unwrap();
Command::new("git")
.args(["add", "."])
.current_dir(dir.path())
.output()
.expect("Failed to stage files");
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(dir.path())
.output()
.expect("Failed to create initial commit");
dir
}
#[tokio::test]
async fn test_git_status_clean_repo() {
let dir = create_test_repo();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let staged = result.get("staged").unwrap().as_array().unwrap();
let unstaged = result.get("unstaged").unwrap().as_array().unwrap();
let untracked = result.get("untracked").unwrap().as_array().unwrap();
assert!(staged.is_empty());
assert!(unstaged.is_empty());
assert!(untracked.is_empty());
}
#[tokio::test]
async fn test_git_status_with_untracked() {
let dir = create_test_repo();
let new_file = dir.path().join("new_file.txt");
fs::write(&new_file, "content").unwrap();
assert!(new_file.exists(), "New file should exist");
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
assert!(result.get("branch").is_some());
assert!(result.get("untracked").is_some());
assert!(result.get("staged").is_some());
assert!(result.get("unstaged").is_some());
}
#[tokio::test]
async fn test_git_status_with_staged() {
let dir = create_test_repo();
fs::write(dir.path().join("staged.txt"), "content").unwrap();
Command::new("git")
.args(["add", "staged.txt"])
.current_dir(dir.path())
.output()
.expect("Failed to stage file");
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let staged = result.get("staged").unwrap().as_array().unwrap();
assert!(!staged.is_empty(), "Expected staged files");
}
#[tokio::test]
async fn test_git_status_with_modified() {
let dir = create_test_repo();
fs::write(dir.path().join("README.md"), "# Modified").unwrap();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let unstaged = result.get("unstaged").unwrap().as_array().unwrap();
assert!(!unstaged.is_empty(), "Expected modified files");
}
#[tokio::test]
async fn test_git_status_shows_branch() {
let dir = create_test_repo();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let branch = result.get("branch").unwrap().as_str().unwrap();
assert!(
branch == "main" || branch == "master",
"Expected main or master, got: {}",
branch
);
}
#[tokio::test]
async fn test_git_status_not_a_repo() {
let dir = TempDir::new().unwrap();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await;
assert!(result.is_err());
}
#[test]
fn test_git_status_metadata() {
let tool = GitStatus;
assert_eq!(tool.name(), "git_status");
assert!(!tool.description().is_empty());
let schema = tool.schema();
assert!(schema.get("properties").is_some());
}
#[tokio::test]
async fn test_git_status_with_multiple_changes() {
let dir = create_test_repo();
fs::write(dir.path().join("staged.txt"), "staged").unwrap();
Command::new("git")
.args(["add", "staged.txt"])
.current_dir(dir.path())
.output()
.expect("Failed to stage file");
fs::write(dir.path().join("README.md"), "modified").unwrap();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let staged = result.get("staged").unwrap().as_array().unwrap();
let unstaged = result.get("unstaged").unwrap().as_array().unwrap();
assert!(!staged.is_empty(), "Should have staged files: {:?}", result);
assert!(
!unstaged.is_empty(),
"Should have unstaged files: {:?}",
result
);
}
#[tokio::test]
async fn test_git_status_default_path() {
let tool = GitStatus;
let args = json!({});
let result = tool.execute(args).await.unwrap();
assert!(result.get("branch").is_some());
}
use selfware::tools::git::GitDiff;
#[tokio::test]
async fn test_git_diff_no_changes() {
let dir = create_test_repo();
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
assert!(!result["has_changes"].as_bool().unwrap());
assert_eq!(result["diff"].as_str().unwrap(), "");
}
#[tokio::test]
async fn test_git_diff_with_changes() {
let dir = create_test_repo();
fs::write(dir.path().join("README.md"), "# Changed content").unwrap();
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
assert!(result["has_changes"].as_bool().unwrap());
let diff = result["diff"].as_str().unwrap();
assert!(diff.contains("Changed content") || diff.contains("README"));
}
#[tokio::test]
async fn test_git_diff_staged() {
let dir = create_test_repo();
fs::write(dir.path().join("staged.txt"), "staged content").unwrap();
Command::new("git")
.args(["add", "staged.txt"])
.current_dir(dir.path())
.output()
.expect("Failed to stage file");
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap(),
"staged": true
});
let result = tool.execute(args).await.unwrap();
assert!(result["has_changes"].as_bool().unwrap());
}
#[tokio::test]
async fn test_git_diff_default_not_staged() {
let dir = create_test_repo();
fs::write(dir.path().join("new.txt"), "new content").unwrap();
Command::new("git")
.args(["add", "new.txt"])
.current_dir(dir.path())
.output()
.expect("Failed to stage file");
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap(),
"staged": false
});
let result = tool.execute(args).await.unwrap();
assert!(!result["has_changes"].as_bool().unwrap());
}
use selfware::tools::git::GitCommit;
#[tokio::test]
async fn test_git_commit_specific_files() {
let dir = create_test_repo();
fs::write(dir.path().join("file1.txt"), "content1").unwrap();
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "add", "file1.txt"])
.output()
.expect("Failed to stage file");
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "commit", "-m", "Test"])
.output()
.expect("Failed to commit");
let log = Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "log", "--oneline", "-1"])
.output()
.expect("Failed to get log");
let log_str = String::from_utf8_lossy(&log.stdout);
assert!(log_str.contains("Test"));
}
use selfware::tools::git::GitCheckpoint;
#[test]
fn test_git_diff_metadata() {
let tool = GitDiff;
assert_eq!(tool.name(), "git_diff");
assert!(tool.description().contains("diff"));
let schema = tool.schema();
assert!(schema["properties"]["staged"].is_object());
}
#[test]
fn test_git_commit_metadata() {
let tool = GitCommit;
assert_eq!(tool.name(), "git_commit");
assert!(tool.description().contains("commit"));
let schema = tool.schema();
assert!(schema["properties"]["message"].is_object());
assert!(schema["required"]
.as_array()
.unwrap()
.contains(&json!("message")));
}
#[test]
fn test_git_checkpoint_metadata() {
let tool = GitCheckpoint;
assert_eq!(tool.name(), "git_checkpoint");
assert!(tool.description().contains("checkpoint"));
let schema = tool.schema();
assert!(schema["properties"]["tag"].is_object());
assert!(schema["properties"]["auto_branch"].is_object());
}
#[tokio::test]
async fn test_git_status_with_deleted_file() {
let dir = create_test_repo();
fs::remove_file(dir.path().join("README.md")).unwrap();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let unstaged = result.get("unstaged").unwrap().as_array().unwrap();
assert!(
!unstaged.is_empty(),
"Should detect deleted file: {:?}",
result
);
}
#[tokio::test]
async fn test_git_status_with_index_deleted() {
let dir = create_test_repo();
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "rm", "README.md"])
.output()
.expect("Failed to stage deletion");
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let staged = result.get("staged").unwrap().as_array().unwrap();
assert!(
!staged.is_empty(),
"Should detect staged deletion: {:?}",
result
);
}
#[tokio::test]
async fn test_git_status_staged_and_unstaged() {
let dir = create_test_repo();
fs::write(dir.path().join("staged.txt"), "staged").unwrap();
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "add", "staged.txt"])
.output()
.expect("Failed to stage file");
fs::write(dir.path().join("README.md"), "modified").unwrap();
let tool = GitStatus;
let args = json!({
"repo_path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await.unwrap();
let staged = result.get("staged").unwrap().as_array().unwrap();
let unstaged = result.get("unstaged").unwrap().as_array().unwrap();
assert!(!staged.is_empty(), "Should have staged files");
assert!(!unstaged.is_empty(), "Should have unstaged files");
assert!(
result.get("untracked").is_some(),
"Should have untracked array"
);
}
#[tokio::test]
async fn test_git_diff_not_a_repo() {
let dir = TempDir::new().unwrap();
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap()
});
let result = tool.execute(args).await;
assert!(result.is_ok()); }
#[tokio::test]
async fn test_git_diff_multiple_files_changed() {
let dir = create_test_repo();
fs::write(dir.path().join("README.md"), "modified readme").unwrap();
fs::write(dir.path().join("file2.txt"), "new file").unwrap();
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "add", "file2.txt"])
.output()
.expect("Failed to add file2");
Command::new("git")
.args([
"-C",
dir.path().to_str().unwrap(),
"commit",
"-m",
"add file2",
])
.output()
.expect("Failed to commit");
fs::write(dir.path().join("file2.txt"), "modified file2").unwrap();
let tool = GitDiff;
let args = json!({
"path": dir.path().to_str().unwrap(),
"staged": false
});
let result = tool.execute(args).await.unwrap();
assert!(result["has_changes"].as_bool().unwrap());
let diff = result["diff"].as_str().unwrap();
assert!(diff.contains("README") || diff.contains("file2"));
}
#[tokio::test]
async fn test_git_diff_staged_vs_unstaged() {
let dir = create_test_repo();
fs::write(dir.path().join("staged.txt"), "staged content").unwrap();
Command::new("git")
.args(["-C", dir.path().to_str().unwrap(), "add", "staged.txt"])
.output()
.expect("Failed to stage");
fs::write(dir.path().join("README.md"), "unstaged change").unwrap();
let tool = GitDiff;
let staged_args = json!({
"path": dir.path().to_str().unwrap(),
"staged": true
});
let staged_result = tool.execute(staged_args).await.unwrap();
assert!(staged_result["has_changes"].as_bool().unwrap());
let staged_diff = staged_result["diff"].as_str().unwrap();
assert!(staged_diff.contains("staged.txt") || staged_diff.contains("staged content"));
let unstaged_args = json!({
"path": dir.path().to_str().unwrap(),
"staged": false
});
let unstaged_result = tool.execute(unstaged_args).await.unwrap();
assert!(unstaged_result["has_changes"].as_bool().unwrap());
let unstaged_diff = unstaged_result["diff"].as_str().unwrap();
assert!(unstaged_diff.contains("README") || unstaged_diff.contains("unstaged"));
}
#[tokio::test]
async fn test_git_commit_empty_files_array() {
let dir = create_test_repo();
fs::write(dir.path().join("newfile.txt"), "content").unwrap();
let tool = GitCommit;
let schema = tool.schema();
let files_schema = &schema["properties"]["files"];
assert_eq!(files_schema["type"], "array");
}
#[tokio::test]
async fn test_git_checkpoint_creates_tagged_commit() {
let tool = GitCheckpoint;
let schema = tool.schema();
let required = schema["required"].as_array().unwrap();
assert!(required.contains(&json!("message")));
let tag_prop = &schema["properties"]["tag"];
assert_eq!(tag_prop["type"], "string");
}
#[test]
fn test_git_checkpoint_auto_branch_default() {
let tool = GitCheckpoint;
let schema = tool.schema();
let auto_branch = &schema["properties"]["auto_branch"];
assert_eq!(auto_branch["default"], true);
}
#[test]
fn test_all_git_tools_have_object_schema() {
let tools: Vec<Box<dyn Tool>> = vec![
Box::new(GitStatus),
Box::new(GitDiff),
Box::new(GitCommit),
Box::new(GitCheckpoint),
];
for tool in tools {
let schema = tool.schema();
assert_eq!(
schema["type"],
"object",
"Tool {} should have object schema",
tool.name()
);
assert!(
schema.get("properties").is_some(),
"Tool {} should have properties",
tool.name()
);
}
}
#[test]
fn test_git_tools_have_descriptions() {
let tools: Vec<Box<dyn Tool>> = vec![
Box::new(GitStatus),
Box::new(GitDiff),
Box::new(GitCommit),
Box::new(GitCheckpoint),
];
for tool in tools {
assert!(
!tool.description().is_empty(),
"Tool {} should have description",
tool.name()
);
}
}