use std::path::PathBuf;
use std::process::Command;
#[tokio::test]
async fn test_platform_specific_binary_detection() {
let binary_path = crate::integration::test_helpers::get_binary_path();
assert!(
binary_path.exists(),
"Binary should exist at: {}",
binary_path.display()
);
assert!(
cascade_cli::utils::platform::is_executable(&binary_path),
"Binary should be executable: {}",
binary_path.display()
);
let expected_name = cascade_cli::utils::platform::executable_name("ca");
assert!(
binary_path.file_name().unwrap().to_string_lossy() == expected_name,
"Binary should have correct name: expected '{}', got '{}'",
expected_name,
binary_path.file_name().unwrap().to_string_lossy()
);
}
#[tokio::test]
async fn test_platform_specific_file_operations() {
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
let test_file = repo_path.join("test.json");
let test_data = serde_json::json!({
"test": "data",
"platform": std::env::consts::OS
});
cascade_cli::utils::atomic_file::write_json(&test_file, &test_data).unwrap();
assert!(test_file.exists());
let content = std::fs::read_to_string(&test_file).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["test"], "data");
assert_eq!(parsed["platform"], std::env::consts::OS);
}
#[tokio::test]
async fn test_platform_specific_git_hooks() {
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
cascade_cli::config::initialize_repo(
&repo_path,
Some("https://test.bitbucket.com".to_string()),
)
.unwrap();
let hook_content =
cascade_cli::utils::platform::create_git_hook_content("test-hook", "echo 'test command'");
#[cfg(windows)]
{
assert!(hook_content.contains("@echo off"));
assert!(hook_content.contains("ERRORLEVEL"));
}
#[cfg(not(windows))]
{
assert!(hook_content.starts_with("#!/bin/sh"));
assert!(hook_content.contains("exec"));
}
let hooks_dir = repo_path.join(".git").join("hooks");
std::fs::create_dir_all(&hooks_dir).unwrap();
let hook_filename = format!(
"test-hook{}",
cascade_cli::utils::platform::git_hook_extension()
);
let hook_path = hooks_dir.join(&hook_filename);
std::fs::write(&hook_path, hook_content).unwrap();
cascade_cli::utils::platform::make_executable(&hook_path).unwrap();
assert!(cascade_cli::utils::platform::is_executable(&hook_path));
}
#[tokio::test]
async fn test_platform_specific_shell_detection() {
let separator = cascade_cli::utils::platform::path_separator();
#[cfg(windows)]
assert_eq!(separator, ";");
#[cfg(not(windows))]
assert_eq!(separator, ":");
let completion_dirs = cascade_cli::utils::platform::shell_completion_dirs();
assert!(
!completion_dirs.is_empty(),
"Should have at least one completion directory"
);
for (name, dir) in &completion_dirs {
assert!(
dir.is_absolute(),
"Completion directory should be absolute: {name} -> {dir:?}"
);
}
let default_shell = cascade_cli::utils::platform::default_shell();
if let Some(shell) = default_shell {
let shell_path = which_shell(&shell);
assert!(
shell_path.is_some(),
"Default shell '{shell}' should exist in PATH"
);
}
}
#[tokio::test]
async fn test_platform_specific_line_endings() {
let test_content = "line1\r\nline2\rline3\nline4";
let normalized = cascade_cli::utils::platform::normalize_line_endings(test_content);
assert_eq!(normalized, "line1\nline2\nline3\nline4");
assert_eq!(cascade_cli::utils::platform::normalize_line_endings(""), "");
let unix_content = "line1\nline2\nline3";
assert_eq!(
cascade_cli::utils::platform::normalize_line_endings(unix_content),
unix_content
);
}
#[tokio::test]
async fn test_platform_specific_temp_directories() {
let temp_dir = cascade_cli::utils::platform::secure_temp_dir().unwrap();
assert!(temp_dir.exists());
assert!(temp_dir.is_dir());
assert!(temp_dir
.file_name()
.unwrap()
.to_string_lossy()
.contains(&std::process::id().to_string()));
let test_file = temp_dir.join("test.txt");
std::fs::write(&test_file, "test content").unwrap();
assert!(test_file.exists());
std::fs::remove_dir_all(&temp_dir).unwrap();
}
#[tokio::test]
async fn test_cross_platform_command_execution() {
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
let binary_path = crate::integration::test_helpers::get_binary_path();
let output = Command::new(&binary_path)
.args(["--help"])
.current_dir(&repo_path)
.output()
.expect("Help command should work");
assert!(output.status.success(), "Help command should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Cascade CLI"),
"Help should contain app name"
);
let output = Command::new(&binary_path)
.args(["version"])
.current_dir(&repo_path)
.output()
.expect("Version command should work");
assert!(output.status.success(), "Version command should succeed");
}
#[tokio::test]
async fn test_platform_specific_file_locking() {
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
let test_file = repo_path.join("concurrent_test.json");
let test_data1 = serde_json::json!({"test": "data1"});
let test_data2 = serde_json::json!({"test": "data2"});
cascade_cli::utils::atomic_file::write_json(&test_file, &test_data1).unwrap();
let file1 = test_file.clone();
let file2 = test_file.clone();
let handle1 = tokio::task::spawn_blocking(move || {
cascade_cli::utils::atomic_file::write_json(&file1, &test_data1)
});
let handle2 = tokio::task::spawn_blocking(move || {
std::thread::sleep(std::time::Duration::from_millis(50));
cascade_cli::utils::atomic_file::write_json(&file2, &test_data2)
});
let (result1, result2) = tokio::join!(handle1, handle2);
let success_count = [&result1, &result2]
.iter()
.filter(|r| r.as_ref().unwrap().is_ok())
.count();
assert!(
success_count > 0,
"At least one concurrent write should succeed"
);
assert!(test_file.exists());
let content = std::fs::read_to_string(&test_file).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content)
.expect("File should contain valid JSON after concurrent access");
assert!(
parsed["test"] == "data1" || parsed["test"] == "data2",
"File should contain valid test data: {parsed:?}"
);
}
#[tokio::test]
async fn test_platform_specific_error_handling() {
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
let invalid_path = if cfg!(windows) {
repo_path.join("con.txt") } else {
repo_path.join("test/../../outside")
};
let result =
cascade_cli::utils::path_validation::validate_config_path(&invalid_path, &repo_path);
#[cfg(not(windows))]
{
if result.is_err() {
println!("Path traversal was prevented (good)");
} else {
println!("Path validation passed for non-existing path (also acceptable)");
}
}
#[cfg(windows)]
{
let _ = result; }
}
#[tokio::test]
async fn test_platform_environment_handling() {
let original_editor = std::env::var("EDITOR").ok();
let original_visual = std::env::var("VISUAL").ok();
std::env::remove_var("EDITOR");
std::env::remove_var("VISUAL");
let default_editor = cascade_cli::utils::platform::default_editor();
assert!(default_editor.is_some(), "Should have a default editor");
let editor = default_editor.unwrap();
#[cfg(windows)]
{
if editor != "notepad" {
assert!(!editor.is_empty());
}
}
#[cfg(not(windows))]
{
assert!(["nano", "vim", "vi"].contains(&editor.as_str()));
}
if let Some(editor) = original_editor {
std::env::set_var("EDITOR", editor);
}
if let Some(visual) = original_visual {
std::env::set_var("VISUAL", visual);
}
}
fn which_shell(shell_name: &str) -> Option<PathBuf> {
let path_var = std::env::var("PATH").ok()?;
let executable_name = cascade_cli::utils::platform::executable_name(shell_name);
for path_dir in path_var.split(cascade_cli::utils::platform::path_separator()) {
let shell_path = PathBuf::from(path_dir).join(&executable_name);
if cascade_cli::utils::platform::is_executable(&shell_path) {
return Some(shell_path);
}
}
None
}
#[tokio::test]
async fn test_ci_environment_compatibility() {
std::env::set_var("CI", "true");
let (_temp_dir, repo_path) = crate::integration::test_helpers::create_test_git_repo().await;
let test_file = repo_path.join("ci_test.json");
let test_data = serde_json::json!({"ci": true});
let result = cascade_cli::utils::atomic_file::write_json(&test_file, &test_data);
assert!(result.is_ok(), "File operations should work in CI");
let binary_path = crate::integration::test_helpers::get_binary_path();
assert!(binary_path.exists(), "Binary should be found in CI");
std::env::remove_var("CI");
}
#[cfg(test)]
mod platform_specific_tests {
#[cfg(windows)]
mod windows_tests {
#[tokio::test]
async fn test_windows_specific_features() {
assert!(cascade_cli::utils::platform::executable_name("test").ends_with(".exe"));
assert_eq!(cascade_cli::utils::platform::path_separator(), ";");
assert_eq!(cascade_cli::utils::platform::git_hook_extension(), ".bat");
}
#[tokio::test]
async fn test_windows_completion_paths() {
let completion_dirs = cascade_cli::utils::platform::shell_completion_dirs();
let has_powershell = completion_dirs
.iter()
.any(|(name, _)| name.contains("PowerShell"));
let has_git_bash = completion_dirs
.iter()
.any(|(name, _)| name.contains("Git Bash"));
assert!(
has_powershell || has_git_bash,
"Should have Windows-specific completion paths"
);
}
}
#[cfg(unix)]
mod unix_tests {
#[tokio::test]
async fn test_unix_specific_features() {
assert_eq!(
cascade_cli::utils::platform::executable_name("test"),
"test"
);
assert_eq!(cascade_cli::utils::platform::path_separator(), ":");
assert_eq!(cascade_cli::utils::platform::git_hook_extension(), "");
}
#[tokio::test]
async fn test_unix_file_permissions() {
let (_temp_dir, repo_path) =
crate::integration::test_helpers::create_test_git_repo().await;
let test_file = repo_path.join("permission_test.sh");
std::fs::write(&test_file, "#!/bin/sh\necho 'test'\n").unwrap();
assert!(!cascade_cli::utils::platform::is_executable(&test_file));
cascade_cli::utils::platform::make_executable(&test_file).unwrap();
assert!(cascade_cli::utils::platform::is_executable(&test_file));
}
#[tokio::test]
async fn test_unix_completion_paths() {
let completion_dirs = cascade_cli::utils::platform::shell_completion_dirs();
let has_bash = completion_dirs
.iter()
.any(|(name, _)| name.contains("bash"));
let has_zsh = completion_dirs.iter().any(|(name, _)| name.contains("zsh"));
let has_fish = completion_dirs
.iter()
.any(|(name, _)| name.contains("fish"));
assert!(
has_bash || has_zsh || has_fish,
"Should have Unix shell completion paths"
);
}
}
}