This commit is contained in:
2025-08-05 15:33:03 +02:00
parent 7856fc0a4e
commit 0c02d0e99f
326 changed files with 334 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
use sal_git::*;
use std::env;
#[test]
fn test_git_executor_initialization() {
let mut executor = GitExecutor::new();
// Test that executor can be initialized without panicking
// Even if Redis is not available, init should handle it gracefully
let result = executor.init();
assert!(
result.is_ok(),
"GitExecutor init should handle Redis unavailability gracefully"
);
}
#[test]
fn test_redis_connection_fallback() {
// Test that GitExecutor handles Redis connection failures gracefully
// Set an invalid Redis URL to force connection failure
env::set_var("REDIS_URL", "redis://invalid-host:9999/0");
let mut executor = GitExecutor::new();
let result = executor.init();
// Should succeed even with invalid Redis URL (graceful fallback)
assert!(
result.is_ok(),
"GitExecutor should handle Redis connection failures gracefully"
);
// Cleanup
env::remove_var("REDIS_URL");
}
#[test]
fn test_environment_variable_precedence() {
// Test REDIS_URL takes precedence over SAL_REDIS_URL
env::set_var("REDIS_URL", "redis://primary:6379/0");
env::set_var("SAL_REDIS_URL", "redis://fallback:6379/1");
// Create executor - should use REDIS_URL (primary)
let mut executor = GitExecutor::new();
let result = executor.init();
// Should succeed (even if connection fails, init handles it gracefully)
assert!(
result.is_ok(),
"GitExecutor should handle environment variables correctly"
);
// Test with only SAL_REDIS_URL
env::remove_var("REDIS_URL");
let mut executor2 = GitExecutor::new();
let result2 = executor2.init();
assert!(
result2.is_ok(),
"GitExecutor should use SAL_REDIS_URL as fallback"
);
// Cleanup
env::remove_var("SAL_REDIS_URL");
}
#[test]
fn test_git_command_argument_validation() {
let executor = GitExecutor::new();
// Test with empty arguments
let result = executor.execute(&[]);
assert!(result.is_err(), "Empty git command should fail");
// Test with invalid git command
let result = executor.execute(&["invalid-command"]);
assert!(result.is_err(), "Invalid git command should fail");
// Test with malformed URL (should fail due to URL validation, not injection)
let result = executor.execute(&["clone", "not-a-url"]);
assert!(result.is_err(), "Invalid URL should be rejected");
}
#[test]
fn test_git_executor_with_valid_commands() {
let executor = GitExecutor::new();
// Test git version command (should work if git is available)
let result = executor.execute(&["--version"]);
match result {
Ok(output) => {
// If git is available, version should be in output
let output_str = String::from_utf8_lossy(&output.stdout);
assert!(
output_str.contains("git version"),
"Git version output should contain 'git version'"
);
}
Err(_) => {
// If git is not available, that's acceptable in test environment
println!("Note: Git not available in test environment");
}
}
}
#[test]
fn test_credential_helper_environment_setup() {
use std::process::Command;
// Test that we can create and execute a simple credential helper script
let temp_dir = std::env::temp_dir();
let helper_script = temp_dir.join("test_git_helper");
// Create a test credential helper script
let script_content = "#!/bin/bash\necho username=testuser\necho password=testpass\n";
// Write the helper script
let write_result = std::fs::write(&helper_script, script_content);
assert!(
write_result.is_ok(),
"Should be able to write credential helper script"
);
// Make it executable (Unix only)
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&helper_script).unwrap().permissions();
perms.set_mode(0o755);
let perm_result = std::fs::set_permissions(&helper_script, perms);
assert!(
perm_result.is_ok(),
"Should be able to set script permissions"
);
}
// Test that the script can be executed
#[cfg(unix)]
{
let output = Command::new(&helper_script).output();
match output {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("username=testuser"),
"Script should output username"
);
assert!(
stdout.contains("password=testpass"),
"Script should output password"
);
}
Err(_) => {
println!("Note: Could not execute credential helper script (shell not available)");
}
}
}
// Clean up
let _ = std::fs::remove_file(&helper_script);
}
#[test]
fn test_redis_url_masking() {
// Test that sensitive Redis URLs are properly masked for logging
// This tests the internal URL masking functionality
// Test URLs with passwords
let test_cases = vec![
("redis://user:password@localhost:6379/0", true),
("redis://localhost:6379/0", false),
("redis://user@localhost:6379/0", false),
("invalid-url", false),
];
for (url, has_password) in test_cases {
// Set the Redis URL and create executor
std::env::set_var("REDIS_URL", url);
let mut executor = GitExecutor::new();
let result = executor.init();
// Should always succeed (graceful handling of connection failures)
assert!(result.is_ok(), "GitExecutor should handle URL: {}", url);
// The actual masking happens internally during logging
// We can't easily test the log output, but we verify the executor handles it
if has_password {
println!(
"Note: Tested URL with password (should be masked in logs): {}",
url
);
}
}
// Cleanup
std::env::remove_var("REDIS_URL");
}

View File

@@ -0,0 +1,178 @@
use sal_git::*;
use std::collections::HashMap;
#[test]
fn test_git_executor_new() {
let executor = GitExecutor::new();
// We can't directly access the config field since it's private,
// but we can test that the executor was created successfully
let _executor = executor;
}
#[test]
fn test_git_executor_default() {
let executor = GitExecutor::default();
let _executor = executor;
}
#[test]
fn test_git_config_status_serialization() {
let status_ok = GitConfigStatus::Ok;
let status_error = GitConfigStatus::Error;
let json_ok = serde_json::to_string(&status_ok).unwrap();
let json_error = serde_json::to_string(&status_error).unwrap();
assert_eq!(json_ok, "\"ok\"");
assert_eq!(json_error, "\"error\"");
}
#[test]
fn test_git_config_status_deserialization() {
let status_ok: GitConfigStatus = serde_json::from_str("\"ok\"").unwrap();
let status_error: GitConfigStatus = serde_json::from_str("\"error\"").unwrap();
assert_eq!(status_ok, GitConfigStatus::Ok);
assert_eq!(status_error, GitConfigStatus::Error);
}
#[test]
fn test_git_server_auth_serialization() {
let auth = GitServerAuth {
sshagent: Some(true),
key: None,
username: None,
password: None,
};
let json = serde_json::to_string(&auth).unwrap();
assert!(json.contains("\"sshagent\":true"));
}
#[test]
fn test_git_server_auth_deserialization() {
let json = r#"{"sshagent":true,"key":null,"username":null,"password":null}"#;
let auth: GitServerAuth = serde_json::from_str(json).unwrap();
assert_eq!(auth.sshagent, Some(true));
assert_eq!(auth.key, None);
assert_eq!(auth.username, None);
assert_eq!(auth.password, None);
}
#[test]
fn test_git_config_serialization() {
let mut auth_map = HashMap::new();
auth_map.insert(
"github.com".to_string(),
GitServerAuth {
sshagent: Some(true),
key: None,
username: None,
password: None,
},
);
let config = GitConfig {
status: GitConfigStatus::Ok,
auth: auth_map,
};
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("\"status\":\"ok\""));
assert!(json.contains("\"github.com\""));
}
#[test]
fn test_git_config_deserialization() {
let json = r#"{"status":"ok","auth":{"github.com":{"sshagent":true,"key":null,"username":null,"password":null}}}"#;
let config: GitConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.status, GitConfigStatus::Ok);
assert!(config.auth.contains_key("github.com"));
assert_eq!(config.auth["github.com"].sshagent, Some(true));
}
#[test]
fn test_git_executor_error_display() {
let error = GitExecutorError::GitCommandFailed("command failed".to_string());
assert_eq!(format!("{}", error), "Git command failed: command failed");
let error = GitExecutorError::SshAgentNotLoaded;
assert_eq!(format!("{}", error), "SSH agent is not loaded");
let error = GitExecutorError::AuthenticationError("auth failed".to_string());
assert_eq!(format!("{}", error), "Authentication error: auth failed");
}
#[test]
fn test_git_executor_error_from_redis_error() {
let redis_error = redis::RedisError::from((redis::ErrorKind::TypeError, "type error"));
let git_error = GitExecutorError::from(redis_error);
match git_error {
GitExecutorError::RedisError(_) => {}
_ => panic!("Expected RedisError variant"),
}
}
#[test]
fn test_git_executor_error_from_serde_error() {
let serde_error = serde_json::from_str::<GitConfig>("invalid json").unwrap_err();
let git_error = GitExecutorError::from(serde_error);
match git_error {
GitExecutorError::JsonError(_) => {}
_ => panic!("Expected JsonError variant"),
}
}
#[test]
fn test_git_executor_error_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let git_error = GitExecutorError::from(io_error);
match git_error {
GitExecutorError::CommandExecutionError(_) => {}
_ => panic!("Expected CommandExecutionError variant"),
}
}
#[test]
fn test_redis_url_configuration() {
// Test default Redis URL
std::env::remove_var("REDIS_URL");
std::env::remove_var("SAL_REDIS_URL");
// This is testing the internal function, but we can't access it directly
// Instead, we test that GitExecutor can be created without panicking
let executor = GitExecutor::new();
let _executor = executor; // Just verify it was created successfully
}
#[test]
fn test_redis_url_from_environment() {
// Test REDIS_URL environment variable
std::env::set_var("REDIS_URL", "redis://test:6379/1");
// Create executor - should use the environment variable
let executor = GitExecutor::new();
let _executor = executor; // Just verify it was created successfully
// Clean up
std::env::remove_var("REDIS_URL");
}
#[test]
fn test_sal_redis_url_from_environment() {
// Test SAL_REDIS_URL environment variable (fallback)
std::env::remove_var("REDIS_URL");
std::env::set_var("SAL_REDIS_URL", "redis://sal-test:6379/2");
// Create executor - should use the SAL_REDIS_URL
let executor = GitExecutor::new();
let _executor = executor; // Just verify it was created successfully
// Clean up
std::env::remove_var("SAL_REDIS_URL");
}

View File

@@ -0,0 +1,124 @@
use sal_git::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_clone_existing_repository() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path().to_str().unwrap();
let git_tree = GitTree::new(base_path).unwrap();
// First clone
let result1 = git_tree.get("https://github.com/octocat/Hello-World.git");
// Second clone of same repo - should return existing
let result2 = git_tree.get("https://github.com/octocat/Hello-World.git");
match (result1, result2) {
(Ok(repos1), Ok(repos2)) => {
// git_tree.get() returns Vec<GitRepo>, should have exactly 1 repo
assert_eq!(
repos1.len(),
1,
"First clone should return exactly 1 repository"
);
assert_eq!(
repos2.len(),
1,
"Second clone should return exactly 1 repository"
);
assert_eq!(
repos1[0].path(),
repos2[0].path(),
"Both clones should point to same path"
);
// Verify the path actually exists
assert!(
std::path::Path::new(repos1[0].path()).exists(),
"Repository path should exist"
);
}
(Err(e1), Err(e2)) => {
// Both failed - acceptable if network/git issues
println!("Note: Clone test skipped due to errors: {} / {}", e1, e2);
}
_ => {
panic!(
"Inconsistent results: one clone succeeded, other failed - this indicates a bug"
);
}
}
}
#[test]
fn test_repository_operations_on_cloned_repo() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path().to_str().unwrap();
let git_tree = GitTree::new(base_path).unwrap();
match git_tree.get("https://github.com/octocat/Hello-World.git") {
Ok(repos) if repos.len() == 1 => {
let repo = &repos[0];
// Test has_changes on fresh clone
match repo.has_changes() {
Ok(has_changes) => assert!(!has_changes, "Fresh clone should have no changes"),
Err(_) => println!("Note: has_changes test skipped due to git availability"),
}
// Test path is valid
assert!(repo.path().len() > 0);
assert!(std::path::Path::new(repo.path()).exists());
}
_ => {
println!(
"Note: Repository operations test skipped due to network/environment constraints"
);
}
}
}
#[test]
fn test_multiple_repositories_in_git_tree() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path().to_str().unwrap();
// Create some fake git repositories for testing
let repo1_path = temp_dir.path().join("github.com/user1/repo1");
let repo2_path = temp_dir.path().join("github.com/user2/repo2");
fs::create_dir_all(&repo1_path).unwrap();
fs::create_dir_all(&repo2_path).unwrap();
fs::create_dir_all(repo1_path.join(".git")).unwrap();
fs::create_dir_all(repo2_path.join(".git")).unwrap();
let git_tree = GitTree::new(base_path).unwrap();
let repos = git_tree.list().unwrap();
assert!(repos.len() >= 2, "Should find at least 2 repositories");
}
#[test]
fn test_invalid_git_repository_handling() {
let temp_dir = TempDir::new().unwrap();
let fake_repo_path = temp_dir.path().join("fake_repo");
fs::create_dir_all(&fake_repo_path).unwrap();
// Create a directory that looks like a repo but isn't (no .git directory)
let repo = GitRepo::new(fake_repo_path.to_str().unwrap().to_string());
// Operations should fail gracefully on non-git directories
// Note: has_changes might succeed if git is available and treats it as empty repo
// So we test the operations that definitely require .git directory
assert!(
repo.pull().is_err(),
"Pull should fail on non-git directory"
);
assert!(
repo.reset().is_err(),
"Reset should fail on non-git directory"
);
}

View File

@@ -0,0 +1,119 @@
use sal_git::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_parse_git_url_https() {
let (server, account, repo) = parse_git_url("https://github.com/user/repo.git");
assert_eq!(server, "github.com");
assert_eq!(account, "user");
assert_eq!(repo, "repo");
}
#[test]
fn test_parse_git_url_https_without_git_extension() {
let (server, account, repo) = parse_git_url("https://github.com/user/repo");
assert_eq!(server, "github.com");
assert_eq!(account, "user");
assert_eq!(repo, "repo");
}
#[test]
fn test_parse_git_url_ssh() {
let (server, account, repo) = parse_git_url("git@github.com:user/repo.git");
assert_eq!(server, "github.com");
assert_eq!(account, "user");
assert_eq!(repo, "repo");
}
#[test]
fn test_parse_git_url_ssh_without_git_extension() {
let (server, account, repo) = parse_git_url("git@github.com:user/repo");
assert_eq!(server, "github.com");
assert_eq!(account, "user");
assert_eq!(repo, "repo");
}
#[test]
fn test_parse_git_url_invalid() {
let (server, account, repo) = parse_git_url("invalid-url");
assert_eq!(server, "");
assert_eq!(account, "");
assert_eq!(repo, "");
}
#[test]
fn test_git_tree_new_creates_directory() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path().join("git_repos");
let base_path_str = base_path.to_str().unwrap();
let _git_tree = GitTree::new(base_path_str).unwrap();
assert!(base_path.exists());
assert!(base_path.is_dir());
}
#[test]
fn test_git_tree_new_existing_directory() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path().join("existing_dir");
fs::create_dir_all(&base_path).unwrap();
let base_path_str = base_path.to_str().unwrap();
let _git_tree = GitTree::new(base_path_str).unwrap();
}
#[test]
fn test_git_tree_new_invalid_path() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("file.txt");
fs::write(&file_path, "content").unwrap();
let file_path_str = file_path.to_str().unwrap();
let result = GitTree::new(file_path_str);
assert!(result.is_err());
if let Err(error) = result {
match error {
GitError::InvalidBasePath(_) => {}
_ => panic!("Expected InvalidBasePath error"),
}
}
}
#[test]
fn test_git_tree_list_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let base_path_str = temp_dir.path().to_str().unwrap();
let git_tree = GitTree::new(base_path_str).unwrap();
let repos = git_tree.list().unwrap();
assert!(repos.is_empty());
}
#[test]
fn test_git_repo_new() {
let repo = GitRepo::new("/path/to/repo".to_string());
assert_eq!(repo.path(), "/path/to/repo");
}
#[test]
fn test_git_repo_clone() {
let repo1 = GitRepo::new("/path/to/repo".to_string());
let repo2 = repo1.clone();
assert_eq!(repo1.path(), repo2.path());
}
#[test]
fn test_git_error_display() {
let error = GitError::InvalidUrl("bad-url".to_string());
assert_eq!(format!("{}", error), "Could not parse git URL: bad-url");
let error = GitError::NoRepositoriesFound;
assert_eq!(format!("{}", error), "No repositories found");
let error = GitError::RepositoryNotFound("pattern".to_string());
assert_eq!(
format!("{}", error),
"No repositories found matching 'pattern'"
);
}

View File

@@ -0,0 +1,70 @@
// 01_git_basic.rhai
// Tests for basic Git functionality like creating a GitTree, listing repositories, finding repositories, and cloning repositories
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Create a temporary directory for Git operations
let test_dir = "rhai_test_git";
mkdir(test_dir);
print(`Created test directory: ${test_dir}`);
// Test GitTree constructor
print("Testing GitTree constructor...");
let git_tree = git_tree_new(test_dir);
print("✓ GitTree created successfully");
// Test GitTree.list() with empty directory
print("Testing GitTree.list() with empty directory...");
let repos = git_tree.list();
assert_true(repos.len() == 0, "Expected empty list of repositories");
print(`✓ GitTree.list(): Found ${repos.len()} repositories (expected 0)`);
// Test GitTree.find() with empty directory
print("Testing GitTree.find() with empty directory...");
let found_repos = git_tree.find("*");
assert_true(found_repos.len() == 0, "Expected empty list of repositories");
print(`✓ GitTree.find(): Found ${found_repos.len()} repositories (expected 0)`);
// Test GitTree.get() with a URL to clone a repository
// We'll use a small, public repository for testing
print("Testing GitTree.get() with URL...");
let repo_url = "https://github.com/rhaiscript/playground.git";
let repo = git_tree.get(repo_url);
print(`✓ GitTree.get(): Repository cloned successfully to ${repo.path()}`);
// Test GitRepo.path()
print("Testing GitRepo.path()...");
let repo_path = repo.path();
assert_true(repo_path.contains(test_dir), "Repository path should contain test directory");
print(`✓ GitRepo.path(): ${repo_path}`);
// Test GitRepo.has_changes()
print("Testing GitRepo.has_changes()...");
let has_changes = repo.has_changes();
print(`✓ GitRepo.has_changes(): ${has_changes}`);
// Test GitTree.list() after cloning
print("Testing GitTree.list() after cloning...");
let repos_after_clone = git_tree.list();
assert_true(repos_after_clone.len() > 0, "Expected non-empty list of repositories");
print(`✓ GitTree.list(): Found ${repos_after_clone.len()} repositories`);
// Test GitTree.find() after cloning
print("Testing GitTree.find() after cloning...");
let found_repos_after_clone = git_tree.find("*");
assert_true(found_repos_after_clone.len() > 0, "Expected non-empty list of repositories");
print(`✓ GitTree.find(): Found ${found_repos_after_clone.len()} repositories`);
// Clean up
print("Cleaning up...");
delete(test_dir);
assert_true(!exist(test_dir), "Directory deletion failed");
print(`✓ Cleanup: Directory ${test_dir} removed`);
print("All basic Git tests completed successfully!");

View File

@@ -0,0 +1,61 @@
// 02_git_operations.rhai
// Tests for Git operations like pull, reset, commit, and push
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Create a temporary directory for Git operations
let test_dir = "rhai_test_git_ops";
mkdir(test_dir);
print(`Created test directory: ${test_dir}`);
// Create a GitTree
print("Creating GitTree...");
let git_tree = git_tree_new(test_dir);
print("✓ GitTree created successfully");
// Clone a repository
print("Cloning repository...");
let repo_url = "https://github.com/rhaiscript/playground.git";
let repo = git_tree.get(repo_url);
print(`✓ Repository cloned successfully to ${repo.path()}`);
// Test GitRepo.pull()
print("Testing GitRepo.pull()...");
try {
let pulled_repo = repo.pull();
print("✓ GitRepo.pull(): Pull operation completed successfully");
} catch(err) {
// Pull might fail if there are no changes or network issues
print(`Note: GitRepo.pull() failed (expected): ${err}`);
print("✓ GitRepo.pull(): Method exists and can be called");
}
// Test GitRepo.reset()
print("Testing GitRepo.reset()...");
try {
let reset_repo = repo.reset();
print("✓ GitRepo.reset(): Reset operation completed successfully");
} catch(err) {
print(`Error in GitRepo.reset(): ${err}`);
throw err;
}
// Note: We won't test commit and push as they would modify the remote repository
// Instead, we'll just verify that the methods exist and can be called
print("Note: Not testing commit and push to avoid modifying remote repositories");
print("✓ GitRepo.commit() and GitRepo.push() methods exist");
// Clean up
print("Cleaning up...");
delete(test_dir);
assert_true(!exist(test_dir), "Directory deletion failed");
print(`✓ Cleanup: Directory ${test_dir} removed`);
print("All Git operations tests completed successfully!");

View File

@@ -0,0 +1,151 @@
// run_all_tests.rhai
// Test runner for all Git module tests
// Custom assert function
fn assert_true(condition, message) {
if !condition {
print(`ASSERTION FAILED: ${message}`);
throw message;
}
}
// Test counters
let passed = 0;
let failed = 0;
print("=== Git Module Test Suite ===");
print("Running comprehensive tests for Git module functionality...");
// Test 1: Basic Git Operations
print("\n--- Running Basic Git Operations Tests ---");
try {
// Create a temporary directory for Git operations
let test_dir = "rhai_test_git";
mkdir(test_dir);
print(`Created test directory: ${test_dir}`);
// Test GitTree constructor
print("Testing GitTree constructor...");
let git_tree = git_tree_new(test_dir);
print("✓ GitTree created successfully");
// Test GitTree.list() with empty directory
print("Testing GitTree.list() with empty directory...");
let repos = git_tree.list();
assert_true(repos.len() == 0, "Expected empty list of repositories");
print(`✓ GitTree.list(): Found ${repos.len()} repositories (expected 0)`);
// Test GitTree.find() with empty directory
print("Testing GitTree.find() with empty directory...");
let found_repos = git_tree.find("*");
assert_true(found_repos.len() == 0, "Expected empty list of repositories");
print(`✓ GitTree.find(): Found ${found_repos.len()} repositories (expected 0)`);
// Clean up
print("Cleaning up...");
delete(test_dir);
assert_true(!exist(test_dir), "Directory deletion failed");
print(`✓ Cleanup: Directory ${test_dir} removed`);
print("--- Basic Git Operations Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Basic Git Operations Tests: ${err}`);
failed += 1;
}
// Test 2: Git Repository Operations
print("\n--- Running Git Repository Operations Tests ---");
try {
// Create a temporary directory for Git operations
let test_dir = "rhai_test_git_ops";
mkdir(test_dir);
print(`Created test directory: ${test_dir}`);
// Create a GitTree
print("Creating GitTree...");
let git_tree = git_tree_new(test_dir);
print("✓ GitTree created successfully");
// Clean up
print("Cleaning up...");
delete(test_dir);
assert_true(!exist(test_dir), "Directory deletion failed");
print(`✓ Cleanup: Directory ${test_dir} removed`);
print("--- Git Repository Operations Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Git Repository Operations Tests: ${err}`);
failed += 1;
}
// Test 3: Git Error Handling and Real Functionality
print("\n--- Running Git Error Handling and Real Functionality Tests ---");
try {
print("Testing git_clone with invalid URL...");
try {
git_clone("invalid-url-format");
print("!!! Expected error but got success");
failed += 1;
} catch(err) {
assert_true(err.contains("Git error"), "Expected Git error message");
print("✓ git_clone properly handles invalid URLs");
}
print("Testing git_clone with real repository...");
try {
let repo = git_clone("https://github.com/octocat/Hello-World.git");
let path = repo.path();
assert_true(path.len() > 0, "Repository path should not be empty");
print(`✓ git_clone successfully cloned repository to: ${path}`);
// Test repository operations
print("Testing repository operations...");
let has_changes = repo.has_changes();
print(`✓ Repository has_changes check: ${has_changes}`);
} catch(err) {
// Network issues or git not available are acceptable failures
if err.contains("Git error") || err.contains("command") || err.contains("Failed to clone") {
print(`Note: git_clone test skipped due to environment: ${err}`);
} else {
print(`!!! Unexpected error in git_clone: ${err}`);
failed += 1;
}
}
print("Testing GitTree with invalid path...");
try {
let git_tree = git_tree_new("/invalid/nonexistent/path");
print("Note: GitTree creation succeeded (directory was created)");
// Clean up if it was created
try {
delete("/invalid");
} catch(cleanup_err) {
// Ignore cleanup errors
}
} catch(err) {
print(`✓ GitTree properly handles invalid paths: ${err}`);
}
print("--- Git Error Handling Tests completed successfully ---");
passed += 1;
} catch(err) {
print(`!!! Error in Git Error Handling Tests: ${err}`);
failed += 1;
}
// Summary
print("\n=== Test Results ===");
print(`Passed: ${passed}`);
print(`Failed: ${failed}`);
print(`Total: ${passed + failed}`);
if failed == 0 {
print("🎉 All tests passed!");
} else {
print("❌ Some tests failed!");
}
print("=== Git Module Test Suite Complete ===");

View File

@@ -0,0 +1,121 @@
use rhai::Engine;
use sal_git::rhai::*;
#[test]
fn test_git_clone_with_various_url_formats() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
let test_cases = vec![
(
"https://github.com/octocat/Hello-World.git",
"HTTPS with .git",
),
(
"https://github.com/octocat/Hello-World",
"HTTPS without .git",
),
// SSH would require key setup: ("git@github.com:octocat/Hello-World.git", "SSH format"),
];
for (url, description) in test_cases {
let script = format!(
r#"
let result = "";
try {{
let repo = git_clone("{}");
let path = repo.path();
if path.len() > 0 {{
result = "success";
}} else {{
result = "no_path";
}}
}} catch(e) {{
if e.contains("Git error") {{
result = "git_error";
}} else {{
result = "unexpected_error";
}}
}}
result
"#,
url
);
let result = engine.eval::<String>(&script);
assert!(
result.is_ok(),
"Failed to execute script for {}: {:?}",
description,
result
);
let outcome = result.unwrap();
// Accept success or git_error (network issues)
assert!(
outcome == "success" || outcome == "git_error",
"Unexpected outcome for {}: {}",
description,
outcome
);
}
}
#[test]
fn test_git_tree_operations_comprehensive() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
let script = r#"
let results = [];
try {
// Test GitTree creation
let git_tree = git_tree_new("/tmp/rhai_comprehensive_test");
results.push("git_tree_created");
// Test list on empty directory
let repos = git_tree.list();
results.push("list_executed");
// Test find with pattern
let found = git_tree.find("nonexistent");
results.push("find_executed");
} catch(e) {
results.push("error_occurred");
}
results.len()
"#;
let result = engine.eval::<i64>(&script);
assert!(result.is_ok());
assert!(result.unwrap() >= 3, "Should execute at least 3 operations");
}
#[test]
fn test_error_message_quality() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
let script = r#"
let error_msg = "";
try {
git_clone("invalid-url-format");
} catch(e) {
error_msg = e;
}
error_msg
"#;
let result = engine.eval::<String>(&script);
assert!(result.is_ok());
let error_msg = result.unwrap();
assert!(
error_msg.contains("Git error"),
"Error should contain 'Git error'"
);
assert!(error_msg.len() > 10, "Error message should be descriptive");
}

View File

@@ -0,0 +1,101 @@
use rhai::Engine;
use sal_git::rhai::*;
#[test]
fn test_register_git_module() {
let mut engine = Engine::new();
let result = register_git_module(&mut engine);
assert!(result.is_ok());
}
#[test]
fn test_git_tree_new_function_registered() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
// Test that the function is registered by trying to call it
// This will fail because /nonexistent doesn't exist, but it proves the function is registered
let result = engine.eval::<String>(
r#"
let result = "";
try {
let git_tree = git_tree_new("/nonexistent");
result = "success";
} catch(e) {
result = "error_caught";
}
result
"#,
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "error_caught");
}
#[test]
fn test_git_clone_function_registered() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
// Test that git_clone function is registered by testing with invalid URL
let result = engine.eval::<String>(
r#"
let result = "";
try {
git_clone("invalid-url-format");
result = "unexpected_success";
} catch(e) {
// Should catch error for invalid URL
if e.contains("Git error") {
result = "error_caught_correctly";
} else {
result = "wrong_error_type";
}
}
result
"#,
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "error_caught_correctly");
}
#[test]
fn test_git_clone_with_valid_public_repo() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
// Test with a real public repository (small one for testing)
let result = engine.eval::<String>(
r#"
let result = "";
try {
let repo = git_clone("https://github.com/octocat/Hello-World.git");
// If successful, repo should have a valid path
let path = repo.path();
if path.len() > 0 {
result = "clone_successful";
} else {
result = "clone_failed_no_path";
}
} catch(e) {
// Network issues or git not available are acceptable failures
if e.contains("Git error") || e.contains("command") {
result = "acceptable_failure";
} else {
result = "unexpected_error";
}
}
result
"#,
);
assert!(result.is_ok());
let outcome = result.unwrap();
// Accept either successful clone or acceptable failure (network/git issues)
assert!(
outcome == "clone_successful" || outcome == "acceptable_failure",
"Unexpected outcome: {}",
outcome
);
}