use nyl::git::GitManager;
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn setup_test_env() {
env::set_var("GIT_TERMINAL_PROMPT", "0");
env::set_var("GIT_SSH_COMMAND", "echo 'SSH disabled in tests' && exit 1");
}
fn path_to_file_url(path: &Path) -> String {
#[cfg(windows)]
{
let path_str = path.display().to_string().replace('\\', "/");
format!("file:///{}", path_str)
}
#[cfg(not(windows))]
{
format!("file://{}", path.display())
}
}
fn create_test_git_repo(repo_dir: &Path) {
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(repo_dir)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git user");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git email");
Command::new("git")
.args(["config", "credential.helper", ""])
.current_dir(repo_dir)
.output()
.expect("Failed to disable credential helper");
Command::new("git")
.args(["config", "commit.gpgsign", "false"])
.current_dir(repo_dir)
.output()
.expect("Failed to disable commit signing");
Command::new("git")
.args(["config", "tag.gpgsign", "false"])
.current_dir(repo_dir)
.output()
.expect("Failed to disable tag signing");
fs::write(repo_dir.join("test.txt"), "Hello, World!").expect("Failed to create test file");
fs::create_dir_all(repo_dir.join("subdir")).expect("Failed to create subdir");
fs::write(repo_dir.join("subdir/chart.yaml"), "name: test-chart").expect("Failed to create chart.yaml");
Command::new("git")
.args(["add", "."])
.current_dir(repo_dir)
.output()
.expect("Failed to git add");
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_dir)
.output()
.expect("Failed to git commit");
Command::new("git")
.args(["branch", "test-branch"])
.current_dir(repo_dir)
.output()
.expect("Failed to create branch");
Command::new("git")
.args(["tag", "v1.0.0"])
.current_dir(repo_dir)
.output()
.expect("Failed to create tag");
Command::new("git")
.args(["tag", "-a", "v1.1.0", "-m", "Annotated release v1.1.0"])
.current_dir(repo_dir)
.output()
.expect("Failed to create annotated tag");
}
#[test]
fn test_git_manager_resolve_ref_main_branch() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
let content = fs::read_to_string(result.join("test.txt")).unwrap();
assert_eq!(content, "Hello, World!");
}
#[test]
fn test_git_manager_resolve_ref_with_subpath() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), Some("subdir"))
.unwrap();
assert!(result.exists());
assert!(result.join("chart.yaml").exists());
let content = fs::read_to_string(result.join("chart.yaml")).unwrap();
assert_eq!(content, "name: test-chart");
}
#[test]
fn test_git_manager_resolve_ref_branch() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("test-branch"), None)
.unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
}
#[test]
fn test_git_manager_resolve_ref_tag() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("v1.0.0"), None)
.unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
}
#[test]
fn test_git_manager_multiple_refs_same_repo() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let main_result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
let branch_result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("test-branch"), None)
.unwrap();
assert!(main_result.exists());
assert!(branch_result.exists());
assert_ne!(main_result, branch_result);
assert!(main_result.join("test.txt").exists());
assert!(branch_result.join("test.txt").exists());
}
#[test]
fn test_git_manager_cache_reuse() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let cache_path = cache_dir.path().to_path_buf();
{
let mut manager = GitManager::with_cache_dir(&cache_path);
let _result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
}
{
let mut manager = GitManager::with_cache_dir(&cache_path);
let result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
}
}
#[test]
fn test_git_manager_cache_reuse_annotated_tag() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let cache_path = cache_dir.path().to_path_buf();
let repo_url = path_to_file_url(temp_repo.path());
{
let mut manager = GitManager::with_cache_dir(&cache_path);
let result = manager.resolve_ref(&repo_url, Some("v1.1.0"), None).unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
}
{
let mut manager = GitManager::with_cache_dir(&cache_path);
let result = manager.resolve_ref(&repo_url, Some("v1.1.0"), None).unwrap();
assert!(result.exists());
assert!(result.join("test.txt").exists());
}
}
#[test]
fn test_cache_directory_structure() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let _result = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
let git_cache = cache_dir.path().join("git");
assert!(git_cache.exists());
let bare_dir = git_cache.join("bare");
assert!(bare_dir.exists());
let worktrees_dir = git_cache.join("worktrees");
assert!(worktrees_dir.exists());
let bare_repos: Vec<_> = fs::read_dir(&bare_dir).unwrap().filter_map(|e| e.ok()).collect();
assert_eq!(bare_repos.len(), 1);
let worktrees: Vec<_> = fs::read_dir(&worktrees_dir).unwrap().filter_map(|e| e.ok()).collect();
assert_eq!(worktrees.len(), 1);
}
fn create_test_helm_chart_repo(repo_dir: &Path) {
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(repo_dir)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git user");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git email");
Command::new("git")
.args(["config", "commit.gpgsign", "false"])
.current_dir(repo_dir)
.output()
.expect("Failed to disable commit signing");
fs::write(
repo_dir.join("Chart.yaml"),
"apiVersion: v2\nname: root-chart\nversion: 1.0.0\n",
)
.expect("Failed to create Chart.yaml");
fs::write(repo_dir.join("values.yaml"), "key: value\n").expect("Failed to create values.yaml");
fs::create_dir_all(repo_dir.join("charts/subchart")).expect("Failed to create charts/subchart");
fs::write(
repo_dir.join("charts/subchart/Chart.yaml"),
"apiVersion: v2\nname: subchart\nversion: 2.0.0\n",
)
.expect("Failed to create subchart Chart.yaml");
Command::new("git")
.args(["add", "."])
.current_dir(repo_dir)
.output()
.expect("Failed to git add");
Command::new("git")
.args(["commit", "-m", "Add Helm charts"])
.current_dir(repo_dir)
.output()
.expect("Failed to git commit");
Command::new("git")
.args(["tag", "v1.0.0"])
.current_dir(repo_dir)
.output()
.expect("Failed to create tag");
}
#[test]
fn test_git_chart_with_https_protocol_prefix() {
setup_test_env();
use nyl::helm::HelmChartResolver;
use nyl::resources::ChartRef;
let temp_repo = TempDir::new().unwrap();
create_test_helm_chart_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let resolver = HelmChartResolver::with_cache_dir(
vec![],
temp_repo.path().to_path_buf(),
Some(cache_dir.path().to_path_buf()),
);
let chart_ref = ChartRef {
repository: Some(format!("git+{}", path_to_file_url(temp_repo.path()))),
version: Some("main".to_string()),
name: None, };
let resolved = resolver.resolve_chart(&chart_ref).unwrap();
assert!(resolved.path.exists());
assert!(resolved.path.join("Chart.yaml").exists());
let content = fs::read_to_string(resolved.path.join("Chart.yaml")).unwrap();
assert!(content.contains("name: root-chart"));
}
#[test]
fn test_git_chart_with_subpath() {
setup_test_env();
use nyl::helm::HelmChartResolver;
use nyl::resources::ChartRef;
let temp_repo = TempDir::new().unwrap();
create_test_helm_chart_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let resolver = HelmChartResolver::with_cache_dir(
vec![],
temp_repo.path().to_path_buf(),
Some(cache_dir.path().to_path_buf()),
);
let chart_ref = ChartRef {
repository: Some(format!("git+{}", path_to_file_url(temp_repo.path()))),
version: Some("main".to_string()),
name: Some("charts/subchart".to_string()), };
let resolved = resolver.resolve_chart(&chart_ref).unwrap();
assert!(resolved.path.exists());
assert!(resolved.path.join("Chart.yaml").exists());
let content = fs::read_to_string(resolved.path.join("Chart.yaml")).unwrap();
assert!(content.contains("name: subchart"));
}
#[test]
fn test_git_chart_with_tag_version() {
setup_test_env();
use nyl::helm::HelmChartResolver;
use nyl::resources::ChartRef;
let temp_repo = TempDir::new().unwrap();
create_test_helm_chart_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let resolver = HelmChartResolver::with_cache_dir(
vec![],
temp_repo.path().to_path_buf(),
Some(cache_dir.path().to_path_buf()),
);
let chart_ref = ChartRef {
repository: Some(format!("git+{}", path_to_file_url(temp_repo.path()))),
version: Some("v1.0.0".to_string()),
name: None,
};
let resolved = resolver.resolve_chart(&chart_ref).unwrap();
assert!(resolved.path.exists());
assert!(resolved.path.join("Chart.yaml").exists());
}
fn create_test_helm_chart_with_dependencies_repo(repo_dir: &Path) {
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(repo_dir)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git user");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(repo_dir)
.output()
.expect("Failed to set git email");
Command::new("git")
.args(["config", "commit.gpgsign", "false"])
.current_dir(repo_dir)
.output()
.expect("Failed to disable commit signing");
let chart_yaml_content = r#"apiVersion: v2
name: chart-with-deps
version: 1.0.0
dependencies:
- name: common
version: "^1.0"
repository: "oci://registry-1.docker.io/bitnamicharts"
"#;
fs::write(repo_dir.join("Chart.yaml"), chart_yaml_content).expect("Failed to create Chart.yaml");
fs::write(repo_dir.join("values.yaml"), "key: value\n").expect("Failed to create values.yaml");
fs::create_dir_all(repo_dir.join("templates")).expect("Failed to create templates dir");
fs::write(
repo_dir.join("templates/configmap.yaml"),
"apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n",
)
.expect("Failed to create template");
Command::new("git")
.args(["add", "."])
.current_dir(repo_dir)
.output()
.expect("Failed to git add");
Command::new("git")
.args(["commit", "-m", "Add Helm chart with dependencies"])
.current_dir(repo_dir)
.output()
.expect("Failed to git commit");
Command::new("git")
.args(["tag", "v1.0.0"])
.current_dir(repo_dir)
.output()
.expect("Failed to create tag");
}
#[test]
fn test_git_chart_with_dependencies() {
setup_test_env();
use nyl::helm::HelmChartResolver;
use nyl::resources::ChartRef;
let temp_repo = TempDir::new().unwrap();
create_test_helm_chart_with_dependencies_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let resolver = HelmChartResolver::with_cache_dir(
vec![],
temp_repo.path().to_path_buf(),
Some(cache_dir.path().to_path_buf()),
);
let chart_ref = ChartRef {
repository: Some(format!("git+{}", path_to_file_url(temp_repo.path()))),
version: Some("main".to_string()),
name: None,
};
let resolved = match resolver.resolve_chart(&chart_ref) {
Ok(resolved) => resolved,
Err(err) => {
let msg = err.to_string();
if msg.contains("operation not permitted")
|| msg.contains("could not retrieve list of tags")
|| msg.contains("Temporary failure in name resolution")
{
eprintln!("Skipping network-dependent dependency build assertion: {msg}");
return;
}
panic!("Unexpected resolve_chart error: {msg}");
}
};
assert!(resolved.path.exists());
assert!(resolved.path.join("Chart.yaml").exists());
assert!(resolved.path.join("charts").exists());
assert!(resolved.path.join("Chart.lock").exists());
}
#[test]
fn test_git_chart_without_dependencies() {
setup_test_env();
use nyl::helm::HelmChartResolver;
use nyl::resources::ChartRef;
let temp_repo = TempDir::new().unwrap();
create_test_helm_chart_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let resolver = HelmChartResolver::with_cache_dir(
vec![],
temp_repo.path().to_path_buf(),
Some(cache_dir.path().to_path_buf()),
);
let chart_ref = ChartRef {
repository: Some(format!("git+{}", path_to_file_url(temp_repo.path()))),
version: Some("main".to_string()),
name: None,
};
let resolved = resolver.resolve_chart(&chart_ref).unwrap();
assert!(resolved.path.exists());
assert!(resolved.path.join("Chart.yaml").exists());
}
#[test]
fn test_git_manager_fetches_latest_version() {
setup_test_env();
let temp_repo = TempDir::new().unwrap();
create_test_git_repo(temp_repo.path());
let cache_dir = TempDir::new().unwrap();
let mut manager = GitManager::with_cache_dir(cache_dir.path());
let result1 = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
let content1 = fs::read_to_string(result1.join("test.txt")).unwrap();
assert_eq!(content1, "Hello, World!");
fs::write(temp_repo.path().join("test.txt"), "Updated content!").expect("Failed to update file");
let add_output = Command::new("git")
.args(["add", "."])
.current_dir(temp_repo.path())
.output()
.expect("Failed to launch git add");
assert!(
add_output.status.success(),
"git add failed: {}",
String::from_utf8_lossy(&add_output.stderr)
);
let commit_output = Command::new("git")
.args(["commit", "-m", "Update content"])
.current_dir(temp_repo.path())
.output()
.expect("Failed to launch git commit");
assert!(
commit_output.status.success(),
"git commit failed: {}",
String::from_utf8_lossy(&commit_output.stderr)
);
let result2 = manager
.resolve_ref(&path_to_file_url(temp_repo.path()), Some("main"), None)
.unwrap();
let content2 = fs::read_to_string(result2.join("test.txt")).unwrap();
assert_eq!(
content2, "Updated content!",
"Should fetch and checkout the latest version"
);
}