ocre-cli 0.2.0

Command-line tool for Ocre: create, generate, migrate, run and deploy apps.
use super::*;

fn hunk(after: Option<&str>, removed: &[&str], added: &[&str]) -> Hunk {
    let owned = |lines: &[&str]| lines.iter().map(|l| (*l).to_owned()).collect();
    Hunk { after: after.map(str::to_owned), removed: owned(removed), added: owned(added) }
}

fn args(list: &[&str]) -> Vec<String> {
    list.iter().map(|a| (*a).to_owned()).collect()
}

#[test]
fn diff_finds_insertions_replacements_and_removals() {
    assert_eq!(diff("a\nb\n", "a\nb\n"), vec![]);
    assert_eq!(diff("a\nc\n", "a\nb\nc\n"), vec![hunk(Some("a"), &[], &["b"])]);
    assert_eq!(diff("a\n", "x\na\n"), vec![hunk(None, &[], &["x"])]);
    assert_eq!(diff("a\nold\nc\n", "a\nnew\nc\n"), vec![hunk(Some("a"), &["old"], &["new"])]);
    assert_eq!(diff("a\nb\nc\n", "a\nc\n"), vec![hunk(Some("a"), &["b"], &[])]);
    assert_eq!(
        diff("m\n1\nn\n2\n", "m\nx\n1\nn\ny\n2\n"),
        vec![hunk(Some("m"), &[], &["x"]), hunk(Some("n"), &[], &["y"])],
        "separate hunks keep their own context"
    );
}

#[test]
fn revert_undoes_each_kind_of_hunk() {
    for (old, new) in [("a\nc\n", "a\nb\nc\n"), ("a\n", "x\na\n"), ("a\nold\nc", "a\nnew\nc"), ("a\nb\nc\n", "a\nc\n")]
    {
        let mut text = new.to_owned();
        for h in diff(old, new).iter().rev() {
            text = revert(&text, h).unwrap();
        }
        assert_eq!(text, old, "{new:?} back to {old:?}");
    }
    assert_eq!(revert("b\n", &hunk(None, &["a"], &["b"])).unwrap(), "a\n");
    assert_eq!(revert("b", &hunk(None, &[], &["b"])).unwrap(), "", "nothing left");
}

#[test]
fn revert_finds_moved_lines_only_when_unambiguous() {
    let inserted = hunk(Some("// marker"), &[], &["mod posts;"]);
    let later = "// marker\nmod tags;\nmod posts;\n";
    assert_eq!(revert(later, &inserted).unwrap(), "// marker\nmod tags;\n");
    assert_eq!(revert("// marker\nmod tags;\n", &inserted), None, "gone");
    let twice = "mod posts;\n// marker\nmod tags;\nmod posts;\n";
    assert_eq!(revert(twice, &inserted), None, "ambiguous");
    assert_eq!(revert("x\n", &hunk(Some("// marker"), &["gone"], &[])), None, "no context for a removal");
    assert_eq!(revert("", &hunk(None, &[], &["b"])), None);
}

#[test]
fn records_name_the_typed_command() {
    let record = Record::new("generate api", &args(&["g", "--pretend", "scaffold", "Post", "title:string", "--json"]));
    assert_eq!((record.generator.as_str(), record.name.as_str()), ("scaffold", "Post"));
    assert_eq!(record.command, "ocre g --pretend scaffold Post title:string");
    let record = Record::new("generate schedule", &args(&["generate", "schedule", "nightly", "0 3 * * *", "it's"]));
    assert_eq!(record.command, "ocre generate schedule nightly '0 3 * * *' 'it'\\''s'");
    let record = Record::new("generate cache", &[]);
    assert_eq!(
        (record.command.as_str(), record.generator.as_str(), record.name.as_str()),
        ("ocre generate cache", "cache", "")
    );
    assert!(record.is_empty());
    let mut record = Record::new("generate x", &args(&["g", "x", "Blog Post!"]));
    record.created("a", "");
    let root = std::env::temp_dir().join(format!("ocre-record-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&root);
    assert_eq!(record.save(&root).unwrap(), ".ocre/generated/0001_x_blog_post_.json");
    assert_eq!(Record::new("generate y", &[]).save(&root).unwrap(), ".ocre/generated/0002_y.json");
    let loaded = load_all(&root).unwrap();
    assert_eq!(loaded[0].2, record);
    assert_eq!(quote(""), "''");
    std::fs::remove_dir_all(&root).unwrap();
}