edifact-mapper 0.28.1

EDIFACT to BO4E bidirectional conversion for the German energy market
Documentation
//! Every generated sample's answers are codes of the lists they name.
//!
//! A response names the EBD or Codeliste its answer comes from beside the
//! answer — AJT 1082 beside AJT 4465, STS C556/1131 beside C556/9013. The AHB
//! says which lists a PID may name, not what they hold, so
//! `sample_conformance_gate` (errors only, AHB only) accepted
//! `AJT+0A251MKL9DWE+G_0059` and `AJT+ZB5+G_0059` alike. The validator's
//! `AnswerNotInList` warning knows the lists; this gate holds every sample —
//! branch samples included, since `branch_samples` runs after the generator
//! fits the answers — to it. Reported by mako-twin against 0.26.1.

use std::path::PathBuf;

use automapper_validation::{IssueKind, ValidationLevel};
use edifact_mapper::{DataDir, Mapper};

#[test]
fn every_sample_answer_is_a_code_of_its_list() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
    let mapper = match Mapper::from_data_dir(DataDir::path(root.join("dist"))) {
        Ok(m) => m,
        Err(e) => panic!("the data bundles are a build artefact — run `bundle-data` first: {e}"),
    };
    let mut checked = 0usize;
    let mut out = Vec::new();
    let mut fvs: Vec<_> = std::fs::read_dir(root.join("fixtures/generated"))
        .expect("fixtures")
        .flatten()
        .filter(|d| d.path().is_dir())
        .collect();
    fvs.sort_by_key(|d| d.file_name());
    for fv_dir in fvs {
        let fv = fv_dir.file_name().to_string_lossy().to_uppercase();
        let mut msgs: Vec<_> = std::fs::read_dir(fv_dir.path())
            .expect("fv dir")
            .flatten()
            .filter(|d| d.path().is_dir())
            .collect();
        msgs.sort_by_key(|d| d.file_name());
        for msg_dir in msgs {
            let msg = msg_dir.file_name().to_string_lossy().to_uppercase();
            let mut files: Vec<_> = std::fs::read_dir(msg_dir.path())
                .expect("msg dir")
                .flatten()
                .map(|f| f.path())
                .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("edi"))
                .collect();
            files.sort();
            for p in files {
                let stem = p
                    .file_stem()
                    .unwrap_or_default()
                    .to_string_lossy()
                    .into_owned();
                let pid = mig_types::paths::sample_pid(&stem)
                    .unwrap_or(&stem)
                    .to_string();
                let Ok(edi) = std::fs::read_to_string(&p) else {
                    continue;
                };
                let variants: Vec<String> = if msg == "UTILMD" {
                    vec![format!("{msg}_Strom"), format!("{msg}_Gas")]
                } else {
                    vec![msg.clone()]
                };
                let Some(report) = variants.iter().find_map(|v| {
                    mapper
                        .validate_edifact_for_pid(&edi, &fv, v, &pid, ValidationLevel::Full)
                        .ok()
                }) else {
                    continue;
                };
                checked += 1;
                for issue in &report.issues {
                    if let IssueKind::AnswerNotInList { answer, list, .. } = &issue.kind {
                        out.push(format!(
                            "{fv} {msg} {stem}: {answer} not in {list} ({})",
                            issue.field_path.as_deref().unwrap_or("-")
                        ));
                    }
                }
            }
        }
    }
    assert!(checked > 1500, "only {checked} samples validated");
    assert!(
        out.is_empty(),
        "{} answer(s) outside the list they name:\n{}",
        out.len(),
        out.join("\n")
    );
}