starweaver-cli 0.0.4

Command-line interface for Starweaver
Documentation
#![allow(clippy::unwrap_used)]

use super::*;

fn resolver_with_current_dir(root: &Path, current_dir: &Path) -> ConfigResolver {
    ConfigResolver {
        global_dir: Some(root.join("global")),
        project_dir: None,
        shared_agents_dir: Some(root.join("shared-agents")),
        current_dir: Some(current_dir.to_path_buf()),
    }
}

#[test]
fn default_workspace_root_uses_invocation_cwd_without_project_config() {
    let temp = tempfile::tempdir().unwrap();
    let workspace = temp.path().join("workspace");
    fs::create_dir_all(&workspace).unwrap();
    let cli =
        crate::args::parse(["starweaver-cli".to_string(), "diagnostics".to_string()]).unwrap();

    let config = resolver_with_current_dir(temp.path(), &workspace)
        .resolve(&cli)
        .unwrap();

    assert_eq!(config.project_dir, temp.path().join("global"));
    assert_eq!(config.workspace_root, workspace);
}

#[test]
fn default_workspace_root_uses_discovered_project_parent() {
    let temp = tempfile::tempdir().unwrap();
    let workspace = temp.path().join("workspace");
    let nested = workspace.join("crates/example");
    fs::create_dir_all(workspace.join(".starweaver")).unwrap();
    fs::create_dir_all(&nested).unwrap();
    fs::write(workspace.join(".starweaver/config.toml"), "").unwrap();
    let cli =
        crate::args::parse(["starweaver-cli".to_string(), "diagnostics".to_string()]).unwrap();

    let config = resolver_with_current_dir(temp.path(), &nested)
        .resolve(&cli)
        .unwrap();

    assert_eq!(config.project_dir, workspace.join(".starweaver"));
    assert_eq!(config.workspace_root, workspace);
}

#[test]
fn default_project_discovery_ignores_home_global_config() {
    let temp = tempfile::tempdir().unwrap();
    let home = temp.path().join("home");
    let workspace = home.join("workspace");
    fs::create_dir_all(home.join(".starweaver")).unwrap();
    fs::create_dir_all(&workspace).unwrap();
    fs::write(home.join(".starweaver/config.toml"), "").unwrap();
    let cli =
        crate::args::parse(["starweaver-cli".to_string(), "diagnostics".to_string()]).unwrap();
    let resolver = ConfigResolver {
        global_dir: Some(home.join(".starweaver")),
        project_dir: None,
        shared_agents_dir: Some(temp.path().join("shared-agents")),
        current_dir: Some(workspace.clone()),
    };

    let config = resolver.resolve(&cli).unwrap();

    assert_eq!(config.project_dir, home.join(".starweaver"));
    assert_eq!(config.workspace_root, workspace);
}

#[test]
fn project_setup_targets_invocation_cwd() {
    let temp = tempfile::tempdir().unwrap();
    let workspace = temp.path().join("workspace");
    fs::create_dir_all(&workspace).unwrap();
    let cli = crate::args::parse([
        "starweaver-cli".to_string(),
        "setup".to_string(),
        "--project".to_string(),
    ])
    .unwrap();

    let config = resolver_with_current_dir(temp.path(), &workspace)
        .resolve(&cli)
        .unwrap();

    assert_eq!(config.project_dir, workspace.join(".starweaver"));
    assert_eq!(config.workspace_root, workspace);
}

#[test]
fn shell_review_config_parses_security_table_and_getters() {
    let temp = tempfile::tempdir().unwrap();
    let workspace = temp.path().join("project");
    let project_config = workspace.join(".starweaver");
    fs::create_dir_all(&project_config).unwrap();
    fs::write(
        project_config.join("config.toml"),
        r#"
[security.shell_review]
enabled = true
model = "local_echo"
model_settings = "openai_responses_medium"
on_needs_approval = "deny"
risk_threshold = "extra-high"
system_prompt = "Review safely."
"#,
    )
    .unwrap();
    let cli =
        crate::args::parse(["starweaver-cli".to_string(), "diagnostics".to_string()]).unwrap();
    let resolver = ConfigResolver {
        global_dir: Some(temp.path().join("global")),
        project_dir: Some(project_config),
        shared_agents_dir: Some(temp.path().join("shared-agents")),
        current_dir: Some(workspace),
    };

    let config = resolver.resolve(&cli).unwrap();

    assert!(config.shell_review.enabled);
    assert_eq!(config.shell_review.model.as_deref(), Some("local_echo"));
    assert_eq!(
        config.shell_review.model_settings.as_deref(),
        Some("openai_responses_medium")
    );
    assert_eq!(config.shell_review.on_needs_approval, "deny");
    assert_eq!(config.shell_review.risk_threshold, "extra_high");
    assert_eq!(
        config.shell_review.system_prompt.as_deref(),
        Some("Review safely.")
    );
    assert_eq!(
        get_config_value(&config, "security.shell_review.risk_threshold").unwrap(),
        "extra_high\n"
    );
}

#[test]
fn shell_review_validation_requires_model_when_enabled() {
    let missing_model = CliShellReviewConfig {
        enabled: true,
        model: None,
        ..CliShellReviewConfig::default()
    };
    assert!(matches!(missing_model.validate(), Err(CliError::Config(_))));
    assert!(validate_shell_review_action("approve").is_err());
    assert!(validate_shell_review_risk("critical").is_err());
}

#[test]
fn set_config_value_writes_nested_shell_review_table() {
    let temp = tempfile::tempdir().unwrap();
    let cli =
        crate::args::parse(["starweaver-cli".to_string(), "diagnostics".to_string()]).unwrap();
    let config = ConfigResolver::for_tests(temp.path())
        .resolve(&cli)
        .unwrap();

    set_config_value(
        &config,
        ConfigScope::Project,
        "security.shell_review.enabled",
        "true",
    )
    .unwrap();
    set_config_value(
        &config,
        ConfigScope::Project,
        "security.shell_review.risk_threshold",
        "extra-high",
    )
    .unwrap();

    let content = fs::read_to_string(config.project_dir.join("config.toml")).unwrap();
    let parsed = content.parse::<Value>().unwrap();
    assert_eq!(
        parsed["security"]["shell_review"]["enabled"].as_bool(),
        Some(true)
    );
    assert_eq!(
        parsed["security"]["shell_review"]["risk_threshold"].as_str(),
        Some("extra_high")
    );
}