main-spex 0.23.0

AI-first development framework that turns specifications into deterministic, verifiable code
#[cfg(test)]
mod tests {
    use super::super::*;
    use indoc::{formatdoc, indoc};
    use tempfile::tempdir;

    fn spec(id: &str, extra: &str) -> serde_json::Value {
        serde_json::from_str(&format!(r#"{{"id": "{id}"{extra}}}"#)).expect("valid json")
    }

    #[test]
    fn package_selected_defaults_to_true_when_empty() {
        assert!(package_selected(
            &spec("a", r#", "packages": ["api"]"#),
            &[]
        ));
    }

    #[test]
    fn package_selected_matches_short_name_in_spec_packages() {
        let api = ["api"];
        assert!(package_selected(
            &spec("a", r#", "packages": ["api"]"#),
            &api
        ));
        assert!(!package_selected(
            &spec("a", r#", "packages": ["frontend"]"#),
            &api
        ));
        assert!(!package_selected(&spec("a", ""), &api));
    }

    #[test]
    fn id_selected_defaults_to_true_when_empty() {
        assert!(id_selected(&spec("a", ""), &[]));
    }

    #[test]
    fn id_selected_filters_to_given_ids() {
        let ids = vec!["a".to_string(), "b".to_string()];
        assert!(id_selected(&spec("a", ""), &ids));
        assert!(!id_selected(&spec("c", ""), &ids));
    }

    fn write_project(dir: &std::path::Path) {
        std::fs::write(
            dir.join("spex.yaml"),
            indoc! {"
                project_name: demo
                cli_version: 0.0.0
                layout: multi-repo
                agent_root: .
                packages:
                  - package_name: demo-api
                    package_short_name: api
                    path: ../demo-api
                  - package_name: demo-frontend
                    package_short_name: frontend
                    path: ../demo-frontend
            "},
        )
        .expect("write spex.yaml");
        std::fs::create_dir_all(dir.join("specs")).expect("specs dir");
        std::fs::write(
            dir.join("specs/place-pizza-order.yaml"),
            indoc! {r#"
                id: place-pizza-order
                title: Place a pizza order
                description: Lets a customer place an order.
                acceptance_criteria:
                  - "[ ] MUST accept a cart"
                packages: [api]
            "#},
        )
        .expect("write spec");
        std::fs::write(
            dir.join("specs/list-pizza-menu.yaml"),
            indoc! {r#"
                id: list-pizza-menu
                title: List the pizza menu
                description: Lets a customer browse the menu.
                acceptance_criteria:
                  - "[ ] MUST list every active pizza"
                packages: [frontend]
            "#},
        )
        .expect("write spec");
    }

    fn project(dir: &std::path::Path) -> Spex {
        let config = crate::config::SpexConfig::load(&dir.join(crate::config::SPEX_CONFIG_FILE))
            .expect("load config");
        Spex::from_config(dir.to_path_buf(), config)
    }

    fn args(ai: bool, id: &[&str], package: &[&str]) -> SpecsArgs {
        SpecsArgs {
            ai,
            id: id.iter().map(|s| s.to_string()).collect(),
            package: package.iter().map(|s| s.to_string()).collect(),
            output: None,
        }
    }

    #[test]
    fn render_with_no_flags_prints_every_spec() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let yaml = render(&project, &args(false, &[], &[])).expect("render");

        assert_eq!(
            yaml,
            indoc! {"
                - id: list-pizza-menu
                  title: List the pizza menu
                  description: Lets a customer browse the menu.
                  acceptance_criteria:
                  - '[ ] MUST list every active pizza'
                  packages:
                  - frontend

                - id: place-pizza-order
                  title: Place a pizza order
                  description: Lets a customer place an order.
                  acceptance_criteria:
                  - '[ ] MUST accept a cart'
                  packages:
                  - api
            "}
        );
    }

    #[test]
    fn render_with_ai_flag_adds_path() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let yaml = render(&project, &args(true, &["place-pizza-order"], &[])).expect("render");

        assert_eq!(
            yaml,
            formatdoc! {"
                - id: place-pizza-order
                  path: {dir}/specs/place-pizza-order.yaml
                  title: Place a pizza order
                  description: Lets a customer place an order.
                  acceptance_criteria:
                  - '[ ] MUST accept a cart'
                  packages:
                  - api
            ", dir = dir.path().display()}
        );
    }

    #[test]
    fn render_with_single_package_returns_only_matching_specs() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let yaml = render(&project, &args(false, &[], &["frontend"])).expect("render");

        assert_eq!(
            yaml,
            indoc! {"
                - id: list-pizza-menu
                  title: List the pizza menu
                  description: Lets a customer browse the menu.
                  acceptance_criteria:
                  - '[ ] MUST list every active pizza'
                  packages:
                  - frontend
            "}
        );
    }

    #[test]
    fn render_with_full_package_name_returns_the_specs_of_that_package() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let by_full_name = render(&project, &args(false, &[], &["demo-frontend"])).expect("render");
        let by_short_name = render(&project, &args(false, &[], &["frontend"])).expect("render");

        assert_eq!(by_full_name, by_short_name);
        assert!(by_full_name.contains("list-pizza-menu"));
    }

    #[test]
    fn render_errors_on_unknown_package() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let result = render(&project, &args(false, &[], &["missing"]));

        assert!(result.is_err());
    }

    #[test]
    fn render_errors_on_unknown_id() {
        let dir = tempdir().expect("temp dir");
        write_project(dir.path());
        let project = project(dir.path());

        let result = render(&project, &args(false, &["missing"], &[]));

        assert!(result.is_err());
    }
}