docs: Enhance MONOREPO_CONVERSION_PLAN.md with improved details

- Specify production-ready implementation details for sal-git
  package.
- Add a detailed code review and quality assurance process
  section.
- Include comprehensive success metrics and validation checklists
  for production readiness.
- Improve security considerations and risk mitigation strategies.
- Add stricter code review criteria based on sal-git's conversion.
- Update README with security configurations and environment
  variables.
This commit is contained in:
Mahmoud-Emad
2025-06-18 15:15:07 +03:00
parent e031b03e04
commit 4d51518f31
11 changed files with 811 additions and 55 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

@@ -137,3 +137,42 @@ fn test_git_executor_error_from_io_error() {
_ => 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

@@ -80,12 +80,12 @@ try {
failed += 1;
}
// Test 3: Git Error Handling
print("\n--- Running Git Error Handling Tests ---");
// 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");
git_clone("invalid-url-format");
print("!!! Expected error but got success");
failed += 1;
} catch(err) {
@@ -93,6 +93,28 @@ try {
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");

View File

@@ -0,0 +1,104 @@
use sal_git::rhai::*;
use rhai::Engine;
#[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

@@ -1,5 +1,5 @@
use sal_git::rhai::*;
use rhai::Engine;
use sal_git::rhai::*;
#[test]
fn test_register_git_module() {
@@ -12,10 +12,11 @@ fn test_register_git_module() {
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 = engine.eval::<String>(
r#"
let result = "";
try {
let git_tree = git_tree_new("/nonexistent");
@@ -24,8 +25,9 @@ fn test_git_tree_new_function_registered() {
result = "error_caught";
}
result
"#);
"#,
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "error_caught");
}
@@ -34,19 +36,66 @@ fn test_git_tree_new_function_registered() {
fn test_git_clone_function_registered() {
let mut engine = Engine::new();
register_git_module(&mut engine).unwrap();
// Test that git_clone function is registered and returns an error as expected
let result = engine.eval::<String>(r#"
// Test that git_clone function is registered by testing with invalid URL
let result = engine.eval::<String>(
r#"
let result = "";
try {
git_clone("https://example.com/repo.git");
git_clone("invalid-url-format");
result = "unexpected_success";
} catch(e) {
result = "error_caught";
// 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");
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
);
}