use super::*;
use crate::cli::test_support::TestEnv;
use anyhow::{Context, Result, ensure};
use rstest::rstest;
use tempfile::{TempDir, tempdir};
use super::event_assertions::{EventAssertion, capture_events, find_event};
use super::layers::collect_file_layers_with_normalizer;
use super::paths::FailingPathNormalizer;
#[derive(Debug, Clone, Copy)]
enum LayerScenario {
ExplicitConfig,
Discovery,
}
fn scenario_cli(scenario: LayerScenario, temp: &TempDir) -> Result<Cli> {
match scenario {
LayerScenario::ExplicitConfig => {
let config_path = temp.path().join("config.toml");
test_support::fs::write(&config_path, "theme = \"ascii\"\n")
.with_context(|| format!("write {}", config_path.display()))?;
Ok(Cli {
config: Some(config_path),
..Cli::default()
})
}
LayerScenario::Discovery => Ok(Cli {
directory: Some(temp.path().to_path_buf()),
..Cli::default()
}),
}
}
#[rstest]
#[case::explicit_config_path(LayerScenario::ExplicitConfig, false, "using explicit config path")]
#[case::isolated_directory_discovery(LayerScenario::Discovery, true, "using config discovery")]
fn collect_diag_file_layers_logs_selected_branch(
#[case] scenario: LayerScenario,
#[case] should_be_empty: bool,
#[case] expected_event: &str,
) -> Result<()> {
let temp = tempdir().context("create temp dir")?;
let cli = scenario_cli(scenario, &temp)?;
let env = TestEnv::default();
let (layers, events) = capture_events(|| collect_diag_file_layers_with_env(&cli, &env))?;
let branch_event = find_event(&events, expected_event)?;
ensure!(
layers.is_empty() == should_be_empty,
"layer collection result should match {scenario:?}"
);
ensure!(
branch_event.contains(&format!("message={expected_event:?}"))
|| branch_event.contains(&format!("message={expected_event}")),
"branch should emit the expected event: {branch_event}"
);
if matches!(scenario, LayerScenario::ExplicitConfig) {
EventAssertion::new(
branch_event,
cli.config.as_deref().context("explicit config")?,
)
.ensure_bounded_path_fields()?;
ensure!(
!branch_event.contains("path="),
"explicit path branch should avoid raw path fields: {branch_event}"
);
}
Ok(())
}
#[test]
fn existing_project_scope_layer_is_not_appended_twice() -> Result<()> {
let temp = tempdir().context("create temp dir")?;
let project_dir = temp.path().join("project");
test_support::fs::create_dir(&project_dir).context("create project dir")?;
test_support::fs::write(
project_dir.join(".netsuke.toml"),
"default_targets = [\"alpha\"]\n",
)
.context("write project config")?;
let non_canonical = project_dir.join(".");
let (layers, events) = capture_events(|| collect_file_layers(Some(non_canonical.as_path())))?;
let project_layers = layers
.iter()
.filter(|layer| {
layer
.path()
.is_some_and(|path| path.as_str().ends_with(".netsuke.toml"))
})
.count();
ensure!(
project_layers == 1,
"project-scope layer should appear exactly once, found {project_layers}: {layers:?}"
);
find_event(&events, "discovery included project-scope layers")?;
Ok(())
}
#[test]
fn normalization_failure_does_not_fail_discovery() -> Result<()> {
let temp = tempdir().context("create temp dir")?;
let project_dir = temp.path().join("project");
test_support::fs::create_dir(&project_dir).context("create project dir")?;
test_support::fs::write(
project_dir.join(".netsuke.toml"),
"default_targets = [\"alpha\"]\n",
)
.context("write project config")?;
let layers =
collect_file_layers_with_normalizer(Some(project_dir.as_path()), &FailingPathNormalizer)
.context("discovery must succeed despite normalization failure")?;
ensure!(layers.len() == 1, "expected one project layer: {layers:?}");
Ok(())
}
#[cfg(unix)]
#[test]
fn non_utf8_directory_does_not_duplicate_project_layer() -> Result<()> {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
let temp = tempdir().context("create temp dir")?;
let project_dir = temp
.path()
.join(OsString::from_vec(b"project-\xff".to_vec()));
test_support::fs::create_dir(&project_dir).context("create project dir")?;
let lossy_project_dir = temp.path().join("project-\u{fffd}");
test_support::fs::symlink(&project_dir, &lossy_project_dir)
.context("create lossy project alias")?;
test_support::fs::write(
project_dir.join(".netsuke.toml"),
"default_targets = [\"alpha\"]\n",
)
.context("write project config")?;
let layers = collect_file_layers(Some(project_dir.as_path()))?;
ensure!(layers.len() == 1, "expected one project layer: {layers:?}");
Ok(())
}