main-spex 0.19.1

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 scope_selected_defaults_to_true_when_empty() {
        assert!(scope_selected(&spec("a", r#", "scope": ["api"]"#), &[]));
    }

    #[test]
    fn scope_selected_matches_short_name_in_spec_scope() {
        let api = ["api".to_string()];
        assert!(scope_selected(&spec("a", r#", "scope": ["api"]"#), &api));
        assert!(!scope_selected(
            &spec("a", r#", "scope": ["frontend"]"#),
            &api
        ));
        assert!(!scope_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_workspace(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"
                scope: [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"
                scope: [frontend]
            "#},
        )
        .expect("write spec");
    }

    fn workspace(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], scope: &[&str]) -> SpecsArgs {
        SpecsArgs {
            ai,
            id: id.iter().map(|s| s.to_string()).collect(),
            scope: scope.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_workspace(dir.path());
        let workspace = workspace(dir.path());

        let yaml = render(&workspace, &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'
                  scope:
                  - frontend

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

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

        let yaml = render(&workspace, &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'
                  scope:
                  - api
            ", dir = dir.path().display()}
        );
    }

    #[test]
    fn render_with_single_scope_returns_only_matching_specs() {
        let dir = tempdir().expect("temp dir");
        write_workspace(dir.path());
        let workspace = workspace(dir.path());

        let yaml = render(&workspace, &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'
                  scope:
                  - frontend
            "}
        );
    }

    #[test]
    fn render_with_full_package_name_does_not_match_scope() {
        let dir = tempdir().expect("temp dir");
        write_workspace(dir.path());
        let workspace = workspace(dir.path());

        let result = render(&workspace, &args(false, &[], &["demo-api"]));

        assert!(result.is_err());
    }

    #[test]
    fn render_errors_on_unknown_scope() {
        let dir = tempdir().expect("temp dir");
        write_workspace(dir.path());
        let workspace = workspace(dir.path());

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

        assert!(result.is_err());
    }

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

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

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