From cd065b60f0eb2d5119ea2fff673047b9876dfa3e Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 10:17:39 +0300 Subject: [PATCH 1/3] test(impact): prove reverse package exposure through CLI --- crates/commandf-cli/tests/impact_behavior.rs | 56 +++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/commandf-cli/tests/impact_behavior.rs b/crates/commandf-cli/tests/impact_behavior.rs index 1c73ff12..ff263fd6 100644 --- a/crates/commandf-cli/tests/impact_behavior.rs +++ b/crates/commandf-cli/tests/impact_behavior.rs @@ -70,6 +70,44 @@ fn impact_is_byte_identical_and_reports_dependency_evidence_without_severity() { let _ = fs::remove_dir_all(root); } +#[test] +fn impact_reports_reverse_package_exposure_for_changed_dependency() { + let root = unique_temp_dir("package-exposure"); + let (before_lock, before_cache, after_lock, after_cache) = write_impact_state(&root); + + let output = run_impact_for( + "acme.shared", + &before_lock, + &before_cache, + &after_lock, + &after_cache, + ); + assert_success(&output); + + let json = String::from_utf8(output.stdout).expect("UTF-8 impact JSON"); + assert!(json.contains("\"package_name\": \"acme.shared\"")); + + let package_impacts_start = json + .find("\"package_impacts\": [") + .expect("package impacts field"); + let unresolved_offset = json[package_impacts_start..] + .find("\"unresolved_boundaries\":") + .expect("unresolved boundaries field after package impacts"); + let package_impacts = &json[package_impacts_start..package_impacts_start + unresolved_offset]; + + assert!( + package_impacts.contains("\"name\": \"acme.subject\""), + "changed dependency must expose its reverse dependent package" + ); + assert!( + package_impacts.contains("\"declared_constraint\": \"1.0.0\"") + && package_impacts.contains("\"declared_constraint\": \"2.0.0\""), + "package exposure must preserve exact before/after dependency constraints" + ); + + let _ = fs::remove_dir_all(root); +} + #[test] fn impact_rejects_schema_v1_and_corrupt_cache_without_stdout() { let schema_root = unique_temp_dir("schema-v1"); @@ -179,11 +217,27 @@ fn run_impact( before_cache: &Path, after_lock: &Path, after_cache: &Path, +) -> Output { + run_impact_for( + "acme.subject", + before_lock, + before_cache, + after_lock, + after_cache, + ) +} + +fn run_impact_for( + package: &str, + before_lock: &Path, + before_cache: &Path, + after_lock: &Path, + after_cache: &Path, ) -> Output { commandf() .args([ "impact", - "acme.subject", + package, "--before-lock", before_lock.to_str().unwrap(), "--before-cache", From 6210db22c08a5e9a0b6e9f9b5c7653b771da0795 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 10:25:51 +0300 Subject: [PATCH 2/3] test(impact): bind constraints to package relations --- crates/commandf-cli/tests/impact_behavior.rs | 39 +++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/commandf-cli/tests/impact_behavior.rs b/crates/commandf-cli/tests/impact_behavior.rs index ff263fd6..53b354ba 100644 --- a/crates/commandf-cli/tests/impact_behavior.rs +++ b/crates/commandf-cli/tests/impact_behavior.rs @@ -95,14 +95,24 @@ fn impact_reports_reverse_package_exposure_for_changed_dependency() { .expect("unresolved boundaries field after package impacts"); let package_impacts = &json[package_impacts_start..package_impacts_start + unresolved_offset]; + let before_relation = package_impact_relation(package_impacts, "acme.subject", "1.0.0"); assert!( - package_impacts.contains("\"name\": \"acme.subject\""), - "changed dependency must expose its reverse dependent package" + before_relation.contains("\"side\": \"before\""), + "before dependent relation must retain its side" ); assert!( - package_impacts.contains("\"declared_constraint\": \"1.0.0\"") - && package_impacts.contains("\"declared_constraint\": \"2.0.0\""), - "package exposure must preserve exact before/after dependency constraints" + before_relation.contains("\"declared_constraint\": \"1.0.0\""), + "before dependent relation must retain its exact declared constraint" + ); + + let after_relation = package_impact_relation(package_impacts, "acme.subject", "2.0.0"); + assert!( + after_relation.contains("\"side\": \"after\""), + "after dependent relation must retain its side" + ); + assert!( + after_relation.contains("\"declared_constraint\": \"2.0.0\""), + "after dependent relation must retain its exact declared constraint" ); let _ = fs::remove_dir_all(root); @@ -144,6 +154,25 @@ fn impact_rejects_schema_v1_and_corrupt_cache_without_stdout() { let _ = fs::remove_dir_all(corrupt_root); } +fn package_impact_relation<'a>( + package_impacts: &'a str, + impacted_name: &str, + impacted_version: &str, +) -> &'a str { + let marker = format!( + "\"impacted\": {{\n \"name\": \"{impacted_name}\",\n \"version\": \"{impacted_version}\"" + ); + let start = package_impacts.find(&marker).unwrap_or_else(|| { + panic!("missing package impact for {impacted_name}@{impacted_version}") + }); + let remainder_start = start + marker.len(); + let end = package_impacts[remainder_start..] + .find("\"impacted\": {") + .map(|offset| remainder_start + offset) + .unwrap_or(package_impacts.len()); + &package_impacts[start..end] +} + fn write_impact_state(root: &Path) -> (PathBuf, PathBuf, PathBuf, PathBuf) { fs::create_dir_all(root).unwrap(); let before_cache_path = root.join("before-cache"); From c874c8c665a053d3022b6592a6dcf2a9f9c88349 Mon Sep 17 00:00:00 2001 From: "Abdulaziz M. Shehri" Date: Wed, 26 Aug 2026 10:28:20 +0300 Subject: [PATCH 3/3] style(impact): match rustfmt relation assertion --- crates/commandf-cli/tests/impact_behavior.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/commandf-cli/tests/impact_behavior.rs b/crates/commandf-cli/tests/impact_behavior.rs index 53b354ba..377f9ede 100644 --- a/crates/commandf-cli/tests/impact_behavior.rs +++ b/crates/commandf-cli/tests/impact_behavior.rs @@ -162,9 +162,9 @@ fn package_impact_relation<'a>( let marker = format!( "\"impacted\": {{\n \"name\": \"{impacted_name}\",\n \"version\": \"{impacted_version}\"" ); - let start = package_impacts.find(&marker).unwrap_or_else(|| { - panic!("missing package impact for {impacted_name}@{impacted_version}") - }); + let start = package_impacts + .find(&marker) + .unwrap_or_else(|| panic!("missing package impact for {impacted_name}@{impacted_version}")); let remainder_start = start + marker.len(); let end = package_impacts[remainder_start..] .find("\"impacted\": {")