xtalforge-core 0.0.2

Core crystal-structure types for xtalforge: elements, lattices, sites and structures.
Documentation
//! Integration tests for typed parameter descriptions (Task 3.8.1).

#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::indexing_slicing
)]

use serde_json::json;
use xtalforge_core::{ParamSet, ParamSpec, ParamType};

#[test]
fn parameters_validate_and_expose_a_schema() {
    let set = ParamSet::new()
        .with_param(ParamSpec {
            name: "symprec".to_owned(),
            kind: ParamType::Float {
                min: Some(0.0),
                max: Some(1.0),
                unit: Some("Angstrom".to_owned()),
            },
            default: json!(0.001),
            help: "Position tolerance".to_owned(),
            required: false,
            group: Some("Symmetry".to_owned()),
        })
        .unwrap()
        .with_param(ParamSpec {
            name: "mode".to_owned(),
            kind: ParamType::Choice {
                choices: vec!["detect".to_owned(), "impose".to_owned()],
            },
            default: json!("detect"),
            help: "Operation mode".to_owned(),
            required: true,
            group: None,
        })
        .unwrap();
    let schema = set.schema();
    assert_eq!(schema["properties"]["symprec"]["type"], "number");
    assert_eq!(schema["required"][0], "mode");
    let mut set = set;
    assert!(set.set("symprec", json!(0.5)).is_ok());
    assert!(set.set("symprec", json!(2.0)).is_err());
    assert!(set.set("mode", json!("bad")).is_err());
    assert!(set.set("unknown", json!(1)).is_err());
    assert!(set.check_required().is_ok());
}

#[test]
fn duplicate_and_invalid_defaults_are_rejected() {
    let duplicate = ParamSet::new()
        .with_param(ParamSpec {
            name: "x".to_owned(),
            kind: ParamType::Boolean,
            default: json!(true),
            help: String::new(),
            required: false,
            group: None,
        })
        .unwrap()
        .with_param(ParamSpec {
            name: "x".to_owned(),
            kind: ParamType::Boolean,
            default: json!(false),
            help: String::new(),
            required: false,
            group: None,
        });
    assert!(duplicate.is_err());
    let invalid = ParamSet::new().with_param(ParamSpec {
        name: "x".to_owned(),
        kind: ParamType::Integer {
            min: Some(1),
            max: None,
        },
        default: json!(0),
        help: String::new(),
        required: false,
        group: None,
    });
    assert!(invalid.is_err());
}

#[test]
fn parameter_sets_round_trip_and_validate() {
    let set = ParamSet::new()
        .with_param(ParamSpec {
            name: "cutoff".to_owned(),
            kind: ParamType::Float {
                min: Some(0.0),
                max: None,
                unit: Some("Angstrom".to_owned()),
            },
            default: json!(12.0),
            help: String::new(),
            required: false,
            group: None,
        })
        .unwrap();
    let text = set.to_json().unwrap();
    let back = ParamSet::from_json(&text).unwrap();
    assert_eq!(back.get("cutoff"), Some(&json!(12.0)));
    let bad = text.replace("12.0", "\"twelve\"");
    assert!(ParamSet::from_json(&bad).is_err());
}