use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use automapper_validation::eval::{
ConditionEvaluator, ConditionResult, EvaluationContext, GroupScope, MapExternalProvider,
NoOpExternalProvider,
};
use automapper_validation::validator::validate::{AhbFieldRule, AhbWorkflow};
use automapper_validation::{build_validated_tree, EdifactValidator, ValidationLevel};
use mig_assembly::assembler::{
AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
};
use mig_assembly::navigator::AssembledTreeNavigator;
use mig_types::segment::OwnedSegment;
fn seg(tag: &str, mig: &str, elements: &[&[&str]]) -> AssembledSegment {
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,
}
}
fn instance(
segments: Vec<AssembledSegment>,
child_groups: Vec<AssembledGroup>,
) -> AssembledGroupInstance {
AssembledGroupInstance {
segments,
child_groups,
entry_mig_number: None,
variant_mig_numbers: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}
}
fn group(id: &str, repetitions: Vec<AssembledGroupInstance>) -> AssembledGroup {
AssembledGroup {
group_id: id.to_string(),
repetitions,
}
}
fn tree() -> AssembledTree {
let seq = || seg("SEQ", "001", &[&["Z03"]]);
AssembledTree {
segments: vec![],
groups: vec![group(
"SG4",
vec![
instance(
vec![seg("IDE", "000", &[&["24", "TX0"]])],
vec![group("SG8", vec![instance(vec![seq()], vec![])])],
),
instance(
vec![seg("IDE", "000", &[&["24", "TX1"]])],
vec![group(
"SG8",
vec![
instance(vec![seq()], vec![]),
instance(
vec![seq(), seg("RFF", "002", &[&["Z14", "1ESG0000000018"]])],
vec![group(
"SG10",
vec![instance(
vec![seg("CCI", "003", &[&[""], &[], &["E13"]])],
vec![],
)],
)],
),
],
)],
),
],
)],
post_group_start: 0,
inter_group_segments: BTreeMap::new(),
}
}
type Path = Vec<(String, usize)>;
#[derive(Default)]
struct RecordingEvaluator {
seen: Arc<Mutex<Vec<(u32, Option<Path>)>>>,
}
impl ConditionEvaluator for RecordingEvaluator {
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
let path = ctx
.scope
.map(|s| s.path.iter().map(|(id, i)| (id.to_string(), *i)).collect());
self.seen.lock().unwrap().push((condition, path));
ConditionResult::True
}
fn is_external(&self, _: u32) -> bool {
false
}
fn message_type(&self) -> &str {
"UTILMD"
}
fn format_version(&self) -> &str {
"FV2604"
}
}
fn rule(path: &str, status: &str, mig: &str) -> AhbFieldRule {
AhbFieldRule {
segment_path: path.to_string(),
name: path.to_string(),
ahb_status: status.to_string(),
element_index: Some(0),
component_index: Some(0),
mig_number: Some(mig.to_string()),
..Default::default()
}
}
fn p(steps: &[(&str, usize)]) -> Option<Path> {
Some(steps.iter().map(|(id, i)| (id.to_string(), *i)).collect())
}
#[test]
fn each_field_is_evaluated_in_its_own_nested_instance() {
let tree = tree();
let workflow = AhbWorkflow {
pruefidentifikator: "55043".to_string(),
description: String::new(),
communication_direction: None,
fields: vec![
rule("SG4/SG8/SEQ/1229", "Muss [1]", "001"),
rule("SG4/SG8/SG10/CCI/7059", "Muss [2]", "003"),
],
ub_definitions: BTreeMap::new(),
};
let validated = build_validated_tree(&workflow, &tree);
let evaluator = RecordingEvaluator::default();
let seen = Arc::clone(&evaluator.seen);
let validator = EdifactValidator::new(evaluator);
let navigator = AssembledTreeNavigator::new(&tree);
validator.validate_tree(
&validated,
&[],
&NoOpExternalProvider,
ValidationLevel::Conditions,
Some(&navigator),
);
let seen = seen.lock().unwrap().clone();
let scopes_of = |condition: u32| -> Vec<Option<Path>> {
seen.iter()
.filter(|(c, _)| *c == condition)
.map(|(_, path)| path.clone())
.collect()
};
assert_eq!(
scopes_of(1),
[
p(&[("SG4", 0), ("SG8", 0)]),
p(&[("SG4", 1), ("SG8", 0)]),
p(&[("SG4", 1), ("SG8", 1)]),
]
);
assert_eq!(scopes_of(2), [p(&[("SG4", 1), ("SG8", 1), ("SG10", 0)])]);
}
#[test]
fn a_condition_reads_the_enclosing_instance_of_the_group_it_names() {
let tree = tree();
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let flat = [OwnedSegment {
id: "RFF".to_string(),
elements: vec![vec!["Z14".to_string(), "1ESG0000000018".to_string()]],
segment_number: 7,
}];
let ctx = EvaluationContext::with_navigator("55043", &external, &flat, &navigator);
let rff_z14 = |ctx: &EvaluationContext, group: &str| {
ctx.scoped_find_segments_in(group, "RFF")
.iter()
.any(|s| s.get_element(0) == "Z14")
};
let in_sg10 = [("SG4", 1), ("SG8", 1), ("SG10", 0)];
assert!(rff_z14(
&ctx.with_scope(GroupScope { path: &in_sg10 }),
"SG8"
));
let other_meter = [("SG4", 1), ("SG8", 0)];
assert!(!rff_z14(
&ctx.with_scope(GroupScope { path: &other_meter }),
"SG8"
));
let other_transaction = [("SG4", 0), ("SG8", 0)];
assert!(!rff_z14(
&ctx.with_scope(GroupScope {
path: &other_transaction
}),
"SG8"
));
let meter = [("SG4", 1), ("SG8", 1)];
assert_eq!(
ctx.with_scope(GroupScope { path: &meter })
.scoped_find_segments("CCI")
.len(),
1
);
let transaction = [("SG4", 0)];
assert!(rff_z14(
&ctx.with_scope(GroupScope { path: &transaction }),
"SG8"
));
}
#[test]
fn a_qualified_lookup_reads_the_enclosing_instance_too() {
let tree = tree();
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let ctx = EvaluationContext::with_navigator("55043", &external, &[], &navigator);
let z14 = |path: &[(&str, usize)]| {
ctx.with_scope(GroupScope { path })
.scoped_find_segments_in_with_qualifier("SG8", "RFF", 0, "Z14")
.len()
};
assert_eq!(z14(&[("SG4", 1), ("SG8", 1), ("SG10", 0)]), 1);
assert_eq!(z14(&[("SG4", 1), ("SG8", 0)]), 0);
}
#[test]
fn this_group_helpers_read_the_enclosing_instance() {
let tree = tree();
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let flat = [OwnedSegment {
id: "RFF".to_string(),
elements: vec![vec!["Z14".to_string(), "1ESG0000000018".to_string()]],
segment_number: 7,
}];
let ctx = EvaluationContext::with_navigator("55043", &external, &flat, &navigator);
let in_scope = |path: &[(&str, usize)]| {
let scoped = ctx.with_scope(GroupScope { path });
(
scoped.this_group_has_qualifier("RFF", 0, "Z14", &["SG4", "SG8"]),
scoped.this_group_has_any_qualifier("RFF", 0, &["Z14", "Z15"], &["SG8"]),
scoped.this_group_has_qualified_value(
"RFF",
0,
"Z14",
0,
1,
&["1ESG0000000018"],
&["SG8"],
),
scoped.this_group_values("RFF", 0, 1, &["SG8"]),
)
};
let with_smgw = in_scope(&[("SG4", 1), ("SG8", 1), ("SG10", 0)]);
assert_eq!(
(with_smgw.0, with_smgw.1, with_smgw.2),
(
ConditionResult::True,
ConditionResult::True,
ConditionResult::True
)
);
assert_eq!(with_smgw.3, vec![(0, "1ESG0000000018".to_string())]);
let without = in_scope(&[("SG4", 1), ("SG8", 0)]);
assert_eq!(
(without.0, without.1, without.2),
(
ConditionResult::False,
ConditionResult::False,
ConditionResult::False
)
);
assert!(without.3.is_empty());
assert_eq!(
ctx.with_scope(GroupScope {
path: &[("SG4", 0)]
})
.this_group_has_qualifier("RFF", 0, "Z14", &["SG8"]),
ctx.any_group_has_qualifier("RFF", 0, "Z14", &["SG8"])
);
}
#[test]
fn scoped_pattern_helpers_read_the_enclosing_instance() {
let tree = tree();
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let ctx = EvaluationContext::with_navigator("55043", &external, &[], &navigator);
let in_sg8 = |i: usize| [("SG4", 1), ("SG8", i)];
let without = |path: &[(&str, usize)]| {
ctx.with_scope(GroupScope { path })
.this_group_has_qualifier_without("SEQ", 0, "Z03", "RFF", 0, "Z14", &["SG4", "SG8"])
};
assert_eq!(
without(&in_sg8(0)),
ConditionResult::True,
"a meter without SMGW"
);
assert_eq!(
without(&in_sg8(1)),
ConditionResult::False,
"the meter naming one"
);
let parent_child = |path: &[(&str, usize)]| {
ctx.with_scope(GroupScope { path })
.this_group_parent_child_has_qualifier(
&["SG4", "SG8"],
"SEQ",
0,
"Z03",
"SG10",
"CCI",
2,
"E13",
)
};
assert_eq!(parent_child(&in_sg8(1)), ConditionResult::True);
assert_eq!(parent_child(&in_sg8(0)), ConditionResult::False);
let deep = [("SG4", 1), ("SG8", 1), ("SG10", 0)];
assert_eq!(
ctx.with_scope(GroupScope { path: &deep })
.enclosing_scope("SG8"),
Some(&deep[..2])
);
}
#[test]
fn the_child_groups_of_the_enclosing_instance() {
let tree = tree();
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let ctx = EvaluationContext::with_navigator("55043", &external, &[], &navigator);
let sg10s = |path: &[(&str, usize)]| {
ctx.with_scope(GroupScope { path })
.enclosing_child_segments("SG8", "SG10", &["CCI", "CAV"])
.map(|groups| {
groups
.iter()
.map(|segs| segs.iter().map(|s| s.id.clone()).collect::<Vec<_>>())
.collect::<Vec<_>>()
})
};
assert_eq!(
sg10s(&[("SG4", 1), ("SG8", 1), ("SG10", 0)]),
Some(vec![vec!["CCI".to_string()]])
);
assert_eq!(sg10s(&[("SG4", 1), ("SG8", 0)]), Some(vec![]));
assert_eq!(sg10s(&[("SG4", 1)]), None, "no enclosing SG8");
}