main-spex 0.26.0

AI-first development framework that turns specifications into verified code
#[cfg(test)]
mod tests {
    use super::super::{
        file_url, normalize, relative_to, write_embedded_dir, write_embedded_dir_at,
    };
    use crate::templates::BUNDLED_TEMPLATES_DIR;
    use include_dir::{Dir, include_dir};
    use std::fs;
    use std::path::{Path, PathBuf};
    use tempfile::tempdir;

    static EMBEDDED: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/schemas");

    #[test]
    fn file_url_prefixes_an_absolute_path() {
        assert_eq!(
            file_url(PathBuf::from("/project/foo.yaml").as_path()),
            "file:///project/foo.yaml"
        );
    }

    #[test]
    fn file_url_encodes_spaces() {
        assert_eq!(
            file_url(PathBuf::from("/project/my package/foo.yaml").as_path()),
            "file:///project/my%20package/foo.yaml"
        );
    }

    #[test]
    fn file_url_encodes_characters_that_would_break_the_url() {
        assert_eq!(
            file_url(PathBuf::from("/project/proj#2/50%/a?.yaml").as_path()),
            "file:///project/proj%232/50%25/a%3F.yaml"
        );
    }

    #[test]
    fn normalize_resolves_parent_and_current_components() {
        assert_eq!(
            normalize(Path::new("/project/snake/spex/..")),
            Path::new("/project/snake")
        );
        assert_eq!(
            normalize(Path::new("/project/demo-spex/./../demo-api")),
            Path::new("/project/demo-api")
        );
        assert_eq!(
            normalize(Path::new("/project/demo-spex/.")),
            Path::new("/project/demo-spex")
        );
    }

    #[test]
    fn normalize_keeps_a_parent_component_it_cannot_resolve() {
        assert_eq!(normalize(Path::new("../api")), Path::new("../api"));
    }

    #[test]
    fn relative_to_drops_the_base_prefix() {
        assert_eq!(
            relative_to(
                Path::new("/project/spex/specs/a.yaml"),
                Path::new("/project/spex")
            ),
            Path::new("specs/a.yaml")
        );
    }

    #[test]
    fn relative_to_keeps_a_path_outside_the_base() {
        assert_eq!(
            relative_to(Path::new("/elsewhere/a.yaml"), Path::new("/project/spex")),
            Path::new("/elsewhere/a.yaml")
        );
    }

    #[test]
    fn write_embedded_dir_mirrors_bundled_paths_under_the_destination() {
        let dest = tempdir().expect("temp dir");

        write_embedded_dir(&EMBEDDED, dest.path()).expect("write embedded dir");

        for file in EMBEDDED.files() {
            assert_eq!(
                fs::read(dest.path().join(file.path())).expect("read written file"),
                file.contents(),
                "`{}` differs from the embedded file",
                file.path().display()
            );
        }
    }

    #[test]
    fn write_embedded_dir_at_writes_the_dir_contents_into_the_destination() {
        let dest = tempdir().expect("temp dir");
        let scripts = BUNDLED_TEMPLATES_DIR
            .get_dir("rules/writing-style")
            .expect("bundled script folder");

        write_embedded_dir_at(scripts, dest.path()).expect("write embedded dir");

        for file in scripts.files() {
            let name = file.path().file_name().expect("file name");
            assert_eq!(
                fs::read(dest.path().join(name)).expect("read written file"),
                file.contents(),
                "`{}` differs from the embedded file",
                file.path().display()
            );
        }
        assert!(!dest.path().join("rules").exists());
    }

    #[cfg(unix)]
    mod unix {
        use super::super::super::{make_files_executable, symlink_if_absent};
        use std::fs;
        use std::os::unix::fs::PermissionsExt;
        use std::path::Path;
        use tempfile::tempdir;

        #[test]
        fn symlink_if_absent_creates_the_link() {
            let dir = tempdir().expect("temp dir");
            let link = dir.path().join("CLAUDE.md");

            symlink_if_absent(&link, "AGENTS.md").expect("link");

            assert_eq!(
                fs::read_link(&link).expect("read link"),
                Path::new("AGENTS.md")
            );
        }

        #[test]
        fn symlink_if_absent_keeps_an_existing_file() {
            let dir = tempdir().expect("temp dir");
            let link = dir.path().join("CLAUDE.md");
            fs::write(&link, "own content").expect("existing file");

            symlink_if_absent(&link, "AGENTS.md").expect("link");

            assert_eq!(fs::read_to_string(&link).expect("read file"), "own content");
        }

        #[test]
        fn make_files_executable_covers_nested_files() {
            let dir = tempdir().expect("temp dir");
            fs::create_dir_all(dir.path().join("nested")).expect("nested dir");
            let top = dir.path().join("top.sh");
            let nested = dir.path().join("nested/inner.sh");
            fs::write(&top, "#!/bin/sh\n").expect("top");
            fs::write(&nested, "#!/bin/sh\n").expect("nested");

            make_files_executable(dir.path()).expect("make executable");

            for script in [top, nested] {
                let mode = fs::metadata(&script)
                    .expect("metadata")
                    .permissions()
                    .mode();
                assert_eq!(
                    mode & 0o111,
                    0o111,
                    "`{}` is not executable",
                    script.display()
                );
            }
        }

        #[test]
        fn make_files_executable_ignores_a_missing_directory() {
            let dir = tempdir().expect("temp dir");

            make_files_executable(&dir.path().join("missing")).expect("no-op");
        }
    }
}