poolster-core 0.5.0-alpha.1

Language-neutral OpenAPI code generation primitives
Documentation
use poolster_core::{GeneratedFile, GeneratedTree};

#[test]
fn generated_files_cannot_escape_the_output_directory() {
    assert!(GeneratedFile::new("../outside.ts", "").is_err());
    assert!(GeneratedFile::new("/absolute.ts", "").is_err());
    assert!(GeneratedTree::default().get("missing.ts").is_none());
}

fn tree(entries: &[(&str, &str)]) -> GeneratedTree {
    let mut tree = GeneratedTree::default();
    for (path, contents) in entries {
        tree.insert(GeneratedFile::new(path, *contents).unwrap())
            .unwrap();
    }
    tree
}

fn snapshot(root: &std::path::Path) -> std::collections::BTreeMap<std::path::PathBuf, Vec<u8>> {
    fn visit(
        root: &std::path::Path,
        directory: &std::path::Path,
        result: &mut std::collections::BTreeMap<std::path::PathBuf, Vec<u8>>,
    ) {
        for entry in std::fs::read_dir(directory).unwrap() {
            let path = entry.unwrap().path();
            if path.is_dir() {
                visit(root, &path, result);
            } else {
                result.insert(
                    path.strip_prefix(root).unwrap().into(),
                    std::fs::read(path).unwrap(),
                );
            }
        }
    }
    let mut result = std::collections::BTreeMap::new();
    visit(root, root, &mut result);
    result
}

#[test]
fn edited_stale_file_blocks_all_package_writes_and_can_be_resolved() {
    let root = tempfile::tempdir().unwrap();
    let mut original = tree(&[
        ("typescript/client.ts", "client v1"),
        ("typescript/obsolete.ts", "old operation"),
        ("go/client.go", "client v1"),
    ]);
    original
        .insert_custom(GeneratedFile::new("typescript/custom.ts", "starter").unwrap())
        .unwrap();
    original.write_to(root.path()).unwrap();
    std::fs::write(
        root.path().join("typescript/custom.ts"),
        "customer middleware",
    )
    .unwrap();
    std::fs::write(
        root.path().join("typescript/obsolete.ts"),
        "customer edited operation",
    )
    .unwrap();
    let next = tree(&[
        ("typescript/client.ts", "client v2"),
        ("typescript/new.ts", "new operation"),
        ("go/client.go", "client v2"),
    ]);
    let before = snapshot(root.path());
    assert!(next.write_to(root.path()).is_err());
    assert_eq!(
        snapshot(root.path()),
        before,
        "preflight failure must preserve every package and ownership manifest"
    );
    // Resolve the edit by restoring the previously generated bytes, then regenerate.
    std::fs::write(root.path().join("typescript/obsolete.ts"), "old operation").unwrap();
    next.write_to(root.path()).unwrap();
    assert!(!root.path().join("typescript/obsolete.ts").exists());
    assert_eq!(
        std::fs::read_to_string(root.path().join("go/client.go")).unwrap(),
        "client v2"
    );
    assert_eq!(
        std::fs::read_to_string(root.path().join("typescript/custom.ts")).unwrap(),
        "customer middleware"
    );
    assert!(next.check(root.path()).unwrap().is_empty());
}

#[test]
fn unowned_late_collision_blocks_earlier_updates_additions_and_deletions() {
    let root = tempfile::tempdir().unwrap();
    tree(&[("a/update.ts", "v1"), ("a/obsolete.ts", "remove later")])
        .write_to(root.path())
        .unwrap();
    std::fs::create_dir(root.path().join("z")).unwrap();
    std::fs::write(root.path().join("z/customer.ts"), "handwritten").unwrap();
    let next = tree(&[
        ("a/update.ts", "v2"),
        ("a/new.ts", "new"),
        ("z/customer.ts", "collision"),
    ]);
    let before = snapshot(root.path());
    assert!(next.check(root.path()).is_err());
    assert!(next.write_to(root.path()).is_err());
    assert_eq!(snapshot(root.path()), before);
}

#[test]
fn drift_check_is_read_only_and_missing_owned_files_are_repaired() {
    let root = tempfile::tempdir().unwrap();
    let generated = tree(&[
        ("typescript/client.ts", "typed client"),
        ("go/client.go", "native client"),
    ]);
    generated.write_to(root.path()).unwrap();
    std::fs::remove_file(root.path().join("typescript/client.ts")).unwrap();
    std::fs::write(root.path().join("go/client.go"), "customer edit").unwrap();
    let before = snapshot(root.path());
    let drift = generated.check(root.path()).unwrap();
    assert_eq!(
        drift.added,
        [std::path::PathBuf::from("typescript/client.ts")]
    );
    assert_eq!(drift.modified, [std::path::PathBuf::from("go/client.go")]);
    assert_eq!(snapshot(root.path()), before);
    assert!(generated.write_to(root.path()).is_err());
    assert_eq!(snapshot(root.path()), before);
    std::fs::write(root.path().join("go/client.go"), "native client").unwrap();
    generated.write_to(root.path()).unwrap();
    assert_eq!(
        std::fs::read_to_string(root.path().join("typescript/client.ts")).unwrap(),
        "typed client"
    );
    assert!(generated.check(root.path()).unwrap().is_empty());
}