use automapper_validation::display::{EdifactView, IssueNarrator, IssueView, TechnicalNarrator};
use automapper_validation::eval::NoOpExternalProvider;
use automapper_validation::fv2504::UtilmdStromConditionEvaluatorFV2504;
use automapper_validation::validator::validate::{AhbFieldRule, AhbWorkflow};
use automapper_validation::{
build_validated_tree, ConditionParser, EdifactValidator, ValidationIssue, ValidationLevel,
};
use mig_assembly::assembler::{
AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
};
use mig_types::segment::OwnedSegment;
use std::collections::BTreeMap;
fn narrate(issue: &ValidationIssue) -> String {
TechnicalNarrator.describe(issue, EdifactView.location(issue).as_deref())
}
fn make_tree(dtm92_value: &str, dtm137_value: &str) -> AssembledTree {
AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG4".to_string(),
repetitions: vec![AssembledGroupInstance {
segments: vec![
AssembledSegment {
tag: "DTM".to_string(),
elements: vec![vec![
"92".to_string(),
dtm92_value.to_string(),
"303".to_string(),
]],
mig_number: Some("0082".to_string()),
segment_number: None,
},
AssembledSegment {
tag: "DTM".to_string(),
elements: vec![vec![
"137".to_string(),
dtm137_value.to_string(),
"303".to_string(),
]],
mig_number: Some("0081".to_string()),
segment_number: None,
},
],
child_groups: vec![],
entry_mig_number: None,
variant_mig_numbers: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
}
}
fn make_workflow() -> AhbWorkflow {
let ub1_expr = ConditionParser::parse_raw("([931] ∧ [932] [490]) ⊻ ([931] ∧ [933] [491])")
.expect("UB1 should parse")
.expect("UB1 should not be empty");
let mut ub_definitions = BTreeMap::new();
ub_definitions.insert("UB1".to_string(), ub1_expr);
AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test workflow".to_string(),
communication_direction: None,
fields: vec![AhbFieldRule {
segment_path: "SG4/DTM/C507/2380".to_string(),
name: "Eingangsdatum".to_string(),
ahb_status: "X [UB1]".to_string(),
codes: vec![],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(1),
mig_number: Some("0082".to_string()),
}],
ub_definitions,
}
}
fn make_flat_segments(dtm92_value: &str, dtm137_value: &str) -> Vec<OwnedSegment> {
vec![
OwnedSegment {
id: "DTM".to_string(),
elements: vec![vec![
"92".to_string(),
dtm92_value.to_string(),
"303".to_string(),
]],
segment_number: 1,
},
OwnedSegment {
id: "DTM".to_string(),
elements: vec![vec![
"137".to_string(),
dtm137_value.to_string(),
"303".to_string(),
]],
segment_number: 2,
},
]
}
#[test]
fn ub1_passes_with_tree_based_validation() {
let tree = make_tree("202604302200+00", "202503311329+00");
let workflow = make_workflow();
let flat_segments = make_flat_segments("202604302200+00", "202503311329+00");
let validated_tree = build_validated_tree(&workflow, &tree);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let ahb003_errors: Vec<_> = report
.errors()
.filter(|issue| issue.code() == "AHB003")
.collect();
assert!(
ahb003_errors.is_empty(),
"UB1 should pass for DTM+92 with 202604302200+00, but got AHB003 errors: {ahb003_errors:?}"
);
}
#[test]
fn missing_group_variant_reports_error() {
use automapper_validation::validator::validate::AhbCodeRule;
let workflow = AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test".to_string(),
communication_direction: None,
fields: vec![
AhbFieldRule {
segment_path: "SG2/NAD/3035".to_string(),
name: "MP-ID Absender Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "MS".to_string(),
description: "Absender".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: Some("Muss".to_string()),
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("0010".to_string()),
},
AhbFieldRule {
segment_path: "SG2/NAD/3035".to_string(),
name: "MP-ID Empfänger Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "MR".to_string(),
description: "Empfänger".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: Some("Muss".to_string()),
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("0011".to_string()),
},
],
ub_definitions: BTreeMap::new(),
};
let tree = AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG2".to_string(),
repetitions: vec![AssembledGroupInstance {
segments: vec![AssembledSegment {
tag: "NAD".to_string(),
elements: vec![vec!["MR".to_string()]],
mig_number: Some("0011".to_string()),
segment_number: None,
}],
child_groups: vec![],
entry_mig_number: Some("0011".to_string()),
variant_mig_numbers: vec!["0011".to_string()],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
};
let flat_segments = vec![OwnedSegment {
id: "NAD".to_string(),
elements: vec![vec!["MR".to_string(), "9900269000000".to_string()]],
segment_number: 1,
}];
let validated_tree = build_validated_tree(&workflow, &tree);
assert_eq!(
validated_tree.unmatched_rules.len(),
1,
"NAD+MS rule should be unmatched"
);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let missing_errors: Vec<_> = report
.errors()
.filter(|issue| issue.code() == "AHB001")
.collect();
assert!(
!missing_errors.is_empty(),
"Should report missing NAD+MS field, but got no AHB001 errors. All issues: {:?}",
report.issues
);
let error = &missing_errors[0];
assert_eq!(error.field_path.as_deref(), Some("SG2/NAD/3035"));
assert!(
narrate(error).contains("MP-ID Absender"),
"Error should mention Absender, got: {}",
narrate(error)
);
}
#[test]
fn missing_segment_within_instance_reports_error() {
use automapper_validation::validator::validate::AhbCodeRule;
let workflow = AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test".to_string(),
communication_direction: None,
fields: vec![
AhbFieldRule {
segment_path: "SG4/DTM/C507/2005".to_string(),
name: "Eingangsdatum Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "92".to_string(),
description: "Eingangsdatum".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00023".to_string()),
},
AhbFieldRule {
segment_path: "SG4/DTM/C507/2380".to_string(),
name: "Eingangsdatum Wert".to_string(),
ahb_status: "X".to_string(),
codes: vec![],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(1),
mig_number: Some("00023".to_string()),
},
AhbFieldRule {
segment_path: "SG4/DTM/C507/2005".to_string(),
name: "Enddatum Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "93".to_string(),
description: "Enddatum".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00024".to_string()),
},
AhbFieldRule {
segment_path: "SG4/DTM/C507/2380".to_string(),
name: "Enddatum Wert".to_string(),
ahb_status: "X".to_string(),
codes: vec![],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(1),
mig_number: Some("00024".to_string()),
},
],
ub_definitions: BTreeMap::new(),
};
let tree = AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG4".to_string(),
repetitions: vec![AssembledGroupInstance {
segments: vec![AssembledSegment {
tag: "DTM".to_string(),
elements: vec![vec![
"93".to_string(),
"202512312300+00".to_string(),
"303".to_string(),
]],
mig_number: Some("00024".to_string()),
segment_number: None,
}],
child_groups: vec![],
entry_mig_number: None,
variant_mig_numbers: vec!["00023".to_string(), "00024".to_string()],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
};
let flat_segments = vec![OwnedSegment {
id: "DTM".to_string(),
elements: vec![vec![
"93".to_string(),
"202512312300+00".to_string(),
"303".to_string(),
]],
segment_number: 1,
}];
let validated_tree = build_validated_tree(&workflow, &tree);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let missing_errors: Vec<_> = report
.errors()
.filter(|issue| issue.code() == "AHB001")
.collect();
assert!(
!missing_errors.is_empty(),
"Should report missing DTM+92 fields, but got no AHB001 errors"
);
let dtm92_errors: Vec<_> = missing_errors
.iter()
.filter(|e| narrate(e).contains("Eingangsdatum"))
.collect();
assert!(
dtm92_errors.len() >= 2,
"Should report at least 2 DTM+92 missing errors (qualifier + value), got {}",
dtm92_errors.len()
);
let dtm93_errors: Vec<_> = missing_errors
.iter()
.filter(|e| narrate(e).contains("Enddatum"))
.collect();
assert!(
dtm93_errors.is_empty(),
"DTM+93 is present — should NOT be reported as missing. Got: {:?}",
dtm93_errors.iter().map(|e| narrate(e)).collect::<Vec<_>>()
);
}
#[test]
fn present_segments_with_mig_number_not_reported_missing() {
use automapper_validation::validator::validate::AhbCodeRule;
let workflow = AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test".to_string(),
communication_direction: None,
fields: vec![
AhbFieldRule {
segment_path: "SG4/DTM/C507/2005".to_string(),
name: "Eingangsdatum Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "92".to_string(),
description: "".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00023".to_string()),
},
AhbFieldRule {
segment_path: "SG4/DTM/C507/2005".to_string(),
name: "Enddatum Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "93".to_string(),
description: "".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00024".to_string()),
},
AhbFieldRule {
segment_path: "SG4/STS/C601/9015".to_string(),
name: "Statuskategorie".to_string(),
ahb_status: "X".to_string(),
codes: vec![],
parent_group_ahb_status: None,
segment_ahb_status: None,
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00035".to_string()),
},
],
ub_definitions: BTreeMap::new(),
};
let tree = AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG4".to_string(),
repetitions: vec![AssembledGroupInstance {
segments: vec![
AssembledSegment {
tag: "DTM".to_string(),
elements: vec![vec![
"92".to_string(),
"202505312200+00".to_string(),
"303".to_string(),
]],
mig_number: Some("00023".to_string()),
segment_number: None,
},
AssembledSegment {
tag: "DTM".to_string(),
elements: vec![vec![
"93".to_string(),
"202512312300+00".to_string(),
"303".to_string(),
]],
mig_number: Some("00024".to_string()),
segment_number: None,
},
AssembledSegment {
tag: "STS".to_string(),
elements: vec![vec!["7".to_string()], vec![], vec!["E01".to_string()]],
mig_number: Some("00035".to_string()),
segment_number: None,
},
],
child_groups: vec![],
entry_mig_number: None,
variant_mig_numbers: vec![
"00023".to_string(),
"00024".to_string(),
"00035".to_string(),
],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
};
let flat_segments = vec![];
let validated_tree = build_validated_tree(&workflow, &tree);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let missing_errors: Vec<_> = report
.errors()
.filter(|issue| issue.code() == "AHB001")
.collect();
assert!(
missing_errors.is_empty(),
"All segments are present — should have no AHB001 errors. Got:\n{}",
missing_errors
.iter()
.map(|e| format!(
" {} at {}",
narrate(e),
e.field_path.as_deref().unwrap_or("?")
))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn ub1_fails_when_dtm92_has_wrong_time() {
let tree = make_tree("202604301329+00", "202503311329+00");
let workflow = make_workflow();
let flat_segments = make_flat_segments("202604301329+00", "202503311329+00");
let validated_tree = build_validated_tree(&workflow, &tree);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let ahb003_errors: Vec<_> = report
.errors()
.filter(|issue| issue.code() == "AHB003")
.collect();
assert!(
!ahb003_errors.is_empty(),
"UB1 should fail for DTM+92 with 202604301329+00 (HHMM=1329 is neither 2200 nor 2300)"
);
let error = &ahb003_errors[0];
assert_eq!(
error.field_path.as_deref(),
Some("SG4/DTM/C507/2380"),
"Error should reference DTM+92 field path"
);
assert!(
error.rule.as_deref().unwrap_or("").contains("UB1"),
"Error rule should mention UB1"
);
}
#[test]
fn ahb003_issue_carries_segment_position_from_assembled_segment() {
let mut tree = make_tree("202604301329+00", "202503311329+00");
tree.groups[0].repetitions[0].segments[0].segment_number = Some(7);
let workflow = make_workflow();
let flat_segments = vec![
OwnedSegment {
id: "DTM".to_string(),
elements: vec![vec![
"92".to_string(),
"202604301329+00".to_string(),
"303".to_string(),
]],
segment_number: 7,
},
OwnedSegment {
id: "DTM".to_string(),
elements: vec![vec![
"137".to_string(),
"202503311329+00".to_string(),
"303".to_string(),
]],
segment_number: 3,
},
];
let validated_tree = build_validated_tree(&workflow, &tree);
let evaluator = UtilmdStromConditionEvaluatorFV2504::default();
let validator = EdifactValidator::new(evaluator);
let external = NoOpExternalProvider;
let report = validator.validate_tree(
&validated_tree,
&flat_segments,
&external,
ValidationLevel::Conditions,
None,
);
let ahb003 = report
.errors()
.find(|issue| issue.code() == "AHB003")
.expect("AHB003 should fire for DTM+92 with HHMM=1329");
let pos = ahb003
.segment_position
.expect("AHB003 issue must carry segment_position");
assert_eq!(
pos.segment_number, 7,
"segment_position must reflect the matched DTM+92's counter"
);
}
#[test]
fn a_group_present_although_its_condition_is_false_is_reported() {
use automapper_validation::validator::validate::AhbCodeRule;
use mig_assembly::navigator::AssembledTreeNavigator;
let seg = |tag: &str, elements: &[&[&str]], mig: &str| AssembledSegment {
tag: tag.to_string(),
elements: elements
.iter()
.map(|e| e.iter().map(|c| c.to_string()).collect())
.collect(),
mig_number: Some(mig.to_string()),
segment_number: None,
};
let instance = |segments: Vec<AssembledSegment>, child_groups: Vec<AssembledGroup>| {
AssembledGroupInstance {
entry_mig_number: segments[0].mig_number.clone(),
variant_mig_numbers: vec![],
segments,
child_groups,
skipped_segments: vec![],
skipped_positions: Vec::new(),
}
};
let workflow = AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test".to_string(),
communication_direction: None,
fields: vec![
AhbFieldRule {
segment_path: "SG4/IDE/7495".to_string(),
name: "Objekt, Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![],
parent_group_ahb_status: Some("Muss".to_string()),
segment_ahb_status: Some("Muss".to_string()),
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00020".to_string()),
},
AhbFieldRule {
segment_path: "SG4/SG5/LOC/3227".to_string(),
name: "Ortsangabe, Qualifier".to_string(),
ahb_status: "X".to_string(),
codes: vec![AhbCodeRule {
value: "Z22".to_string(),
description: "Ruhende Marktlokation".to_string(),
ahb_status: "X".to_string(),
}],
parent_group_ahb_status: Some("Muss [2061] ∧ [96]".to_string()),
segment_ahb_status: Some("Muss".to_string()),
element_index: Some(0),
component_index: Some(0),
mig_number: Some("00050".to_string()),
},
],
ub_definitions: BTreeMap::new(),
};
let warnings = |ergaenzung: &str| {
let tree = AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG4".to_string(),
repetitions: vec![instance(
vec![
seg("IDE", &[&["24"], &["TX1"]], "00020"),
seg("STS", &[&["7"], &[], &["E01"], &[ergaenzung]], "00030"),
],
vec![AssembledGroup {
group_id: "SG5".to_string(),
repetitions: vec![instance(
vec![seg("LOC", &[&["Z22"], &["12345678901"]], "00050")],
vec![],
)],
}],
)],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
};
let flat: Vec<OwnedSegment> = [
("IDE", vec![vec!["24"], vec!["TX1"]]),
(
"STS",
vec![vec!["7"], vec![], vec!["E01"], vec![ergaenzung]],
),
("LOC", vec![vec!["Z22"], vec!["12345678901"]]),
]
.into_iter()
.enumerate()
.map(|(i, (id, els))| OwnedSegment {
id: id.to_string(),
elements: els
.into_iter()
.map(|e| e.into_iter().map(str::to_string).collect())
.collect(),
segment_number: i as u32,
})
.collect();
let validated_tree = build_validated_tree(&workflow, &tree);
let navigator = AssembledTreeNavigator::new(&tree);
let validator = EdifactValidator::new(UtilmdStromConditionEvaluatorFV2504::default());
let report = validator.validate_tree(
&validated_tree,
&flat,
&NoOpExternalProvider,
ValidationLevel::Conditions,
Some(&navigator),
);
report
.issues
.iter()
.filter(|i| i.code() == "AHB002")
.map(|i| (format!("{:?}", i.severity), narrate(i)))
.collect::<Vec<_>>()
};
let zw4 = warnings("ZW4");
assert_eq!(zw4.len(), 1, "{zw4:#?}");
assert_eq!(zw4[0].0, "Warning");
assert!(
zw4[0].1.contains("SG4/SG5") && zw4[0].1.contains("[96]"),
"{zw4:#?}"
);
assert!(warnings("ZAP").is_empty());
}
#[test]
fn each_group_instance_is_judged_by_its_own_variants_status() {
use mig_assembly::navigator::AssembledTreeNavigator;
let seg = |tag: &str, elements: &[&[&str]], mig: &str| AssembledSegment {
tag: tag.to_string(),
elements: elements
.iter()
.map(|e| e.iter().map(|c| c.to_string()).collect())
.collect(),
mig_number: Some(mig.to_string()),
segment_number: None,
};
let instance = |segments: Vec<AssembledSegment>| AssembledGroupInstance {
entry_mig_number: segments[0].mig_number.clone(),
variant_mig_numbers: vec![segments[0].mig_number.clone().unwrap()],
segments,
child_groups: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
};
let rule = |path: &str, mig: Option<&str>, group: &str| AhbFieldRule {
segment_path: path.to_string(),
name: path.to_string(),
ahb_status: "X".to_string(),
codes: vec![],
parent_group_ahb_status: Some(group.to_string()),
segment_ahb_status: Some("Muss".to_string()),
element_index: Some(0),
component_index: Some(0),
mig_number: mig.map(str::to_string),
};
let workflow = AhbWorkflow {
pruefidentifikator: "55001".to_string(),
description: "Test".to_string(),
communication_direction: None,
fields: vec![
rule("SG4/IDE/7495", Some("00020"), "Muss"),
rule("SG4/SG8/PIA/4347", None, "Muss [96]"),
rule("SG4/SG8/SEQ/1229", Some("00101"), "Muss"),
rule("SG4/SG8/SEQ/1229", Some("00201"), "Muss [96]"),
],
ub_definitions: BTreeMap::new(),
};
let tree = AssembledTree {
segments: vec![],
groups: vec![AssembledGroup {
group_id: "SG4".to_string(),
repetitions: vec![AssembledGroupInstance {
segments: vec![
seg("IDE", &[&["24"], &["TX1"]], "00020"),
seg("STS", &[&["7"], &[], &["E01"], &["ZW4"]], "00030"),
],
child_groups: vec![AssembledGroup {
group_id: "SG8".to_string(),
repetitions: vec![
instance(vec![seg("SEQ", &[&["Z01"]], "00101")]),
instance(vec![seg("SEQ", &[&["ZH0"]], "00201")]),
],
}],
entry_mig_number: Some("00020".to_string()),
variant_mig_numbers: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}],
}],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
};
let flat: Vec<OwnedSegment> = [
("IDE", vec![vec!["24"], vec!["TX1"]]),
("STS", vec![vec!["7"], vec![], vec!["E01"], vec!["ZW4"]]),
("SEQ", vec![vec!["Z01"]]),
("SEQ", vec![vec!["ZH0"]]),
]
.into_iter()
.enumerate()
.map(|(i, (id, els))| OwnedSegment {
id: id.to_string(),
elements: els
.into_iter()
.map(|e| e.into_iter().map(str::to_string).collect())
.collect(),
segment_number: i as u32,
})
.collect();
let validated_tree = build_validated_tree(&workflow, &tree);
let navigator = AssembledTreeNavigator::new(&tree);
let report = EdifactValidator::new(UtilmdStromConditionEvaluatorFV2504::default())
.validate_tree(
&validated_tree,
&flat,
&NoOpExternalProvider,
ValidationLevel::Conditions,
Some(&navigator),
);
let refused: Vec<_> = report
.issues
.iter()
.filter(|i| i.code() == "AHB002")
.collect();
assert_eq!(refused.len(), 1, "{refused:#?}");
assert_eq!(refused[0].rule.as_deref(), Some("Muss [96]"));
}