diff --git a/crates/oa-core/src/language/analysis_source/update/tests.rs b/crates/oa-core/src/language/analysis_source/update/tests.rs index 2a956897..fdb866ff 100644 --- a/crates/oa-core/src/language/analysis_source/update/tests.rs +++ b/crates/oa-core/src/language/analysis_source/update/tests.rs @@ -709,3 +709,36 @@ fn composition_checks_edit_record_ceiling_before_duplicate_membership() { "Source8 lowering occurrence limit exceeded." ); } + +#[test] +fn standalone_noop_apply_keeps_tighter_file_only_budget_and_output_boundary() { + let sources = sources(); + let mut inherited = budget(); + let (index, _) = Index::parse(&skeleton(), &sources, &mut inherited).unwrap(); + let plan = Plan::renames( + &index, + &BTreeMap::from([(global(0x100), "f".into())]), + &inherited, + ) + .unwrap(); + assert_eq!(plan.edit_count(), 0); + assert!(index.names().len() > sources.len()); + let mut limits = Limits::source8(ParseLimits::UNLIMITED); + limits.entries = sources.len(); + limits.references = 0; + limits.bytes = sources.values().map(String::len).sum(); + // Standalone apply checks file/edit records, not the already validated + // catalog/name-storage allocation from construction. Rechecking the catalog + // under this intentionally tighter probe budget would reject this no-op. + assert_eq!( + plan.apply(&sources, &Budget::new(limits).unwrap()).unwrap(), + sources + ); + limits.bytes -= 1; + assert_eq!( + plan.apply(&sources, &Budget::new(limits).unwrap()) + .unwrap_err() + .message, + "Source8 index/output byte limit exceeded." + ); +}