use std::path::PathBuf;
use clippier::OutputType;
use clippier::feature_validator::{
FeatureValidator, ParentValidationConfig, PrefixOverride, ValidatorConfig,
};
use insta::assert_snapshot;
fn setup() {
clippier_test_utilities::seed_clippier_test_resources();
}
fn get_test_workspace_path() -> PathBuf {
setup();
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("test-resources")
.join("workspaces")
.join("parent-validation-test")
}
fn create_parent_config(
packages: Vec<String>,
depth: Option<u8>,
skip_features: Vec<String>,
prefix_overrides: Vec<PrefixOverride>,
) -> ValidatorConfig {
ValidatorConfig {
features: None,
skip_features: None,
workspace_only: true,
output_format: OutputType::Json,
strict_optional_propagation: false,
cli_overrides: vec![],
override_options: Default::default(),
ignore_packages: vec![],
ignore_features: vec![],
parent_config: ParentValidationConfig {
cli_packages: packages,
cli_depth: depth,
cli_skip_features: skip_features,
cli_prefix_overrides: prefix_overrides,
use_config: false, },
}
}
#[switchy_async::test]
async fn test_parent_validation_detects_missing_features() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(vec!["parent".to_string()], None, vec![], vec![]);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
assert!(
!result.parent_results.is_empty(),
"Expected parent validation results"
);
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
assert!(
!parent_result.missing_exposures.is_empty(),
"Expected missing feature exposures"
);
let json = serde_json::to_string_pretty(&result.parent_results).unwrap();
assert_snapshot!("parent_validation_missing_features", json);
}
#[switchy_async::test]
async fn test_parent_validation_with_depth_limit() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["parent".to_string()],
Some(1), vec![],
vec![],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
let nested_level2_missing: Vec<_> = parent_result
.missing_exposures
.iter()
.filter(|e| e.dependency == "parent_nested_level2")
.collect();
assert!(
nested_level2_missing.is_empty(),
"With depth=1, should not check nested_level2"
);
let json = serde_json::to_string_pretty(&result.parent_results).unwrap();
assert_snapshot!("parent_validation_depth_1", json);
}
#[switchy_async::test]
async fn test_parent_validation_with_depth_2() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(vec!["parent".to_string()], Some(2), vec![], vec![]);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
let nested_level3_missing: Vec<_> = parent_result
.missing_exposures
.iter()
.filter(|e| e.dependency == "parent_nested_level3")
.collect();
assert!(
nested_level3_missing.is_empty(),
"With depth=2, should not check nested_level3"
);
let json = serde_json::to_string_pretty(&result.parent_results).unwrap();
assert_snapshot!("parent_validation_depth_2", json);
}
#[switchy_async::test]
async fn test_parent_validation_with_custom_prefix() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["parent".to_string()],
Some(1),
vec![],
vec![
PrefixOverride {
dependency: "parent_child_a".to_string(),
prefix: "a".to_string(),
},
PrefixOverride {
dependency: "parent_child_b".to_string(),
prefix: "b".to_string(),
},
],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
let child_a_missing: Vec<_> = parent_result
.missing_exposures
.iter()
.filter(|e| e.dependency == "parent_child_a")
.collect();
assert!(
child_a_missing.len() >= 2,
"Should have at least 2 missing features for child_a"
);
let json = serde_json::to_string_pretty(&result.parent_results).unwrap();
assert_snapshot!("parent_validation_custom_prefix", json);
}
#[switchy_async::test]
async fn test_parent_validation_with_skip_features() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["parent".to_string()],
Some(1),
vec!["internal-*".to_string()], vec![PrefixOverride {
dependency: "parent_child_a".to_string(),
prefix: "a".to_string(),
}],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
let internal_missing: Vec<_> = parent_result
.missing_exposures
.iter()
.filter(|e| e.dependency_feature.starts_with("internal-"))
.collect();
assert!(
internal_missing.is_empty(),
"Should skip internal-* features"
);
let json = serde_json::to_string_pretty(&result.parent_results).unwrap();
assert_snapshot!("parent_validation_skip_features", json);
}
#[switchy_async::test]
async fn test_parent_validation_no_missing_when_all_exposed() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["parent".to_string()],
Some(1),
vec![],
vec![PrefixOverride {
dependency: "parent_child_b".to_string(),
prefix: "b".to_string(),
}],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let parent_result = result
.parent_results
.iter()
.find(|r| r.package == "parent")
.expect("Should have parent package result");
let child_b_missing: Vec<_> = parent_result
.missing_exposures
.iter()
.filter(|e| e.dependency == "parent_child_b")
.collect();
assert!(
child_b_missing.is_empty(),
"child_b should have all features exposed"
);
}
#[switchy_async::test]
async fn test_parent_validation_json_output_structure() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["parent".to_string()],
Some(1),
vec![],
vec![PrefixOverride {
dependency: "parent_child_a".to_string(),
prefix: "a".to_string(),
}],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
let json = serde_json::to_string_pretty(&result).unwrap();
assert!(json.contains("parent_results"));
assert!(json.contains("missing_exposures"));
assert_snapshot!("parent_validation_full_json_output", json);
}
#[switchy_async::test]
async fn test_parent_validation_with_no_parent_packages() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(vec![], None, vec![], vec![]);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
assert!(
result.parent_results.is_empty(),
"Should have no parent results when no parent packages specified"
);
}
#[switchy_async::test]
async fn test_parent_validation_nonexistent_package() {
let workspace_path = get_test_workspace_path();
let config = create_parent_config(
vec!["nonexistent_package".to_string()],
None,
vec![],
vec![],
);
let validator = FeatureValidator::new(Some(workspace_path), config).unwrap();
let result = validator.validate().unwrap();
assert!(
result.parent_results.is_empty()
|| result
.parent_results
.iter()
.all(|r| r.package != "nonexistent_package"),
"Should handle nonexistent package gracefully"
);
}