main-spex 0.26.0

AI-first development framework that turns specifications into verified code
#[cfg(test)]
mod tests {
    use super::super::*;
    use crate::commands::check::conformance::conformance::RULE_ID;
    use crate::spec;
    use indoc::indoc;
    use tempfile::tempdir;

    const OUT_OF_ORDER_SUFFIX: &str = "(order: MUST, MUST NOT, SHOULD, SHOULD NOT, MAY)";

    fn rebased(findings: Vec<Finding>, base: &Path) -> Vec<Finding> {
        findings
            .into_iter()
            .map(|finding| finding.relative_to(base))
            .collect()
    }

    fn spec_findings(yaml: &str) -> Vec<Finding> {
        let dir = tempdir().expect("temp dir");
        let specs_dir = dir.path().join(spec::SPECS_DIR);
        std::fs::create_dir_all(&specs_dir).expect("specs dir");
        std::fs::write(specs_dir.join("sample.yaml"), yaml).expect("spec");

        let specs = Spec::load_all(&specs_dir).expect("specs");
        rebased(validate_ac_order(&specs, &[]), dir.path())
    }

    fn rule_findings(yaml: &str) -> Vec<Finding> {
        let dir = tempdir().expect("temp dir");
        let rules_dir = dir.path().join(crate::rule::RULES_DIR);
        std::fs::create_dir_all(&rules_dir).expect("rules dir");
        std::fs::write(rules_dir.join("sample.yaml"), yaml).expect("rule");

        let rules = Rule::load_all(&rules_dir).expect("rules");
        rebased(validate_ac_order(&[], &rules), dir.path())
    }

    fn assert_out_of_order(finding: &Finding, location: &str, label: &str, prev_label: &str) {
        assert_eq!(
            finding.source.baseline_key(),
            Some((RULE_ID, AC_ORDER_CHECK))
        );
        assert_eq!(finding.location, Some(Location::parse(location)));
        assert_eq!(
            finding.message,
            format!(
                "acceptance criteria out of order: {label} must not follow {prev_label} {OUT_OF_ORDER_SUFFIX}"
            )
        );
    }

    #[test]
    fn spec_in_commitment_order_passes() {
        let findings = spec_findings(indoc! {"
            id: sample
            contract: none
            acceptance_criteria:
              - id: a
                must: a
              - id: b
                must_not: b
              - id: c
                should: c
              - id: d
                should_not: d
              - id: e
                may: e
        "});

        assert!(findings.is_empty());
    }

    #[test]
    fn spec_repeating_one_commitment_passes() {
        let findings = spec_findings(indoc! {"
            id: sample
            contract: none
            acceptance_criteria:
              - id: a
                must: a
              - id: b
                must: b
        "});

        assert!(findings.is_empty());
    }

    #[test]
    fn spec_flags_the_criterion_that_breaks_the_order() {
        let findings = spec_findings(indoc! {"
            id: sample
            contract: none
            acceptance_criteria:
              - id: first
                should: a
              - id: second
                must: b
        "});

        assert_eq!(findings.len(), 1);
        assert_out_of_order(&findings[0], "specs/sample.yaml:6", "MUST", "SHOULD");
    }

    #[test]
    fn spec_flags_every_criterion_that_breaks_the_order() {
        let findings = spec_findings(indoc! {"
            id: sample
            contract: none
            acceptance_criteria:
              - id: a
                may: a
              - id: b
                should: b
              - id: c
                must: c
        "});

        assert_eq!(findings.len(), 2);
        assert_out_of_order(&findings[0], "specs/sample.yaml:6", "SHOULD", "MAY");
        assert_out_of_order(&findings[1], "specs/sample.yaml:8", "MUST", "SHOULD");
    }

    #[test]
    fn rule_in_commitment_order_passes() {
        let findings = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - MUST a
              - MUST NOT b
              - SHOULD c
              - SHOULD NOT d
              - MAY e
        "});

        assert!(findings.is_empty());
    }

    #[test]
    fn rule_flags_the_criterion_that_breaks_the_order() {
        let findings = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - SHOULD a
              - MUST b
        "});

        assert_eq!(findings.len(), 1);
        assert_out_of_order(&findings[0], "rules/sample.yaml:4", "MUST", "SHOULD");
    }

    #[test]
    fn rule_flags_every_criterion_that_breaks_the_order() {
        let findings = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - MAY a
              - SHOULD b
              - MUST c
        "});

        assert_eq!(findings.len(), 2);
        assert_out_of_order(&findings[0], "rules/sample.yaml:4", "SHOULD", "MAY");
        assert_out_of_order(&findings[1], "rules/sample.yaml:5", "MUST", "SHOULD");
    }

    #[test]
    fn rule_criteria_without_a_commitment_are_skipped_and_do_not_reset_the_order() {
        let findings = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - SHOULD a
              - plain statement
              - MUST c
        "});

        assert_eq!(findings.len(), 1);
        assert_out_of_order(&findings[0], "rules/sample.yaml:5", "MUST", "SHOULD");
    }

    #[test]
    fn rule_must_not_is_read_as_its_own_commitment_not_as_must() {
        let in_order = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - MUST a
              - MUST NOT b
        "});
        assert!(in_order.is_empty());

        let out_of_order = rule_findings(indoc! {"
            id: sample
            acceptance_criteria:
              - MUST NOT a
              - MUST b
        "});
        assert_eq!(out_of_order.len(), 1);
        assert_out_of_order(&out_of_order[0], "rules/sample.yaml:4", "MUST", "MUST NOT");
    }
}