robin_cli_tool 1.1.0

A CLI tool to run scripts for any project
Documentation
use robin::config::RobinConfig;
use std::fs;
use tempfile::tempdir;

#[test]
fn load_missing_file_gives_helpful_error() {
    let dir = tempdir().unwrap();
    let path = dir.path().join(".robin.json");

    let err = RobinConfig::load(&path).unwrap_err();
    assert!(
        err.to_string().contains("No .robin.json found"),
        "unexpected error: {err}"
    );
}

#[test]
fn load_malformed_json_gives_helpful_error() {
    let dir = tempdir().unwrap();
    let path = dir.path().join(".robin.json");
    fs::write(&path, "{ this is not valid json ").unwrap();

    let err = RobinConfig::load(&path).unwrap_err();
    assert!(
        err.to_string().contains("malformed JSON"),
        "unexpected error: {err}"
    );
}

#[test]
fn load_defaults_include_to_empty() {
    // `include` is optional; a config without it must still load.
    let dir = tempdir().unwrap();
    let path = dir.path().join(".robin.json");
    fs::write(&path, r#"{"scripts":{"build":"cargo build"}}"#).unwrap();

    let config = RobinConfig::load(&path).unwrap();
    assert!(config.include.is_empty());
    assert_eq!(config.scripts.len(), 1);
}

#[test]
fn includes_are_merged_with_base_taking_precedence() {
    let dir = tempdir().unwrap();
    let base = dir.path().join(".robin.json");
    let child = dir.path().join("child.json");

    fs::write(
        &base,
        r#"{"include":["child.json"],"scripts":{"shared":"base wins","only_base":"b"}}"#,
    )
    .unwrap();
    fs::write(
        &child,
        r#"{"scripts":{"shared":"child loses","only_child":"c"}}"#,
    )
    .unwrap();

    let config = RobinConfig::load(&base).unwrap();

    assert_eq!(
        config.scripts.get("shared").unwrap().as_str().unwrap(),
        "base wins"
    );
    assert_eq!(
        config.scripts.get("only_base").unwrap().as_str().unwrap(),
        "b"
    );
    assert_eq!(
        config.scripts.get("only_child").unwrap().as_str().unwrap(),
        "c"
    );
    assert_eq!(config.scripts.len(), 3);
}

#[test]
fn includes_resolve_relative_to_config_dir() {
    // The included path is resolved against the parent of the config file,
    // not the process's current directory.
    let dir = tempdir().unwrap();
    let sub = dir.path().join("sub");
    fs::create_dir(&sub).unwrap();

    let base = sub.join(".robin.json");
    fs::write(&base, r#"{"include":["extra.json"],"scripts":{"a":"1"}}"#).unwrap();
    fs::write(sub.join("extra.json"), r#"{"scripts":{"b":"2"}}"#).unwrap();

    let config = RobinConfig::load(&base).unwrap();
    assert!(config.scripts.contains_key("a"));
    assert!(config.scripts.contains_key("b"));
}

#[test]
fn missing_include_reports_which_file_failed() {
    let dir = tempdir().unwrap();
    let base = dir.path().join(".robin.json");
    fs::write(&base, r#"{"include":["does_not_exist.json"],"scripts":{}}"#).unwrap();

    let err = RobinConfig::load(&base).unwrap_err();
    assert!(
        err.to_string().contains("does_not_exist.json"),
        "error should name the missing include: {err}"
    );
}

#[test]
fn save_then_load_roundtrips() {
    let dir = tempdir().unwrap();
    let path = dir.path().join(".robin.json");

    let config = RobinConfig::create_template();
    config.save(&path).unwrap();
    assert!(path.exists());

    let loaded = RobinConfig::load(&path).unwrap();
    assert_eq!(loaded.scripts.len(), config.scripts.len());
}