osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
use super::*;

fn dispatch_plan_for(args: &[&str], profile_names: &[&str]) -> crate::app::dispatch::DispatchPlan {
    let mut cli = Cli::parse_from(args);
    build_dispatch_plan(&mut cli, &profiles(profile_names)).expect("dispatch plan should parse")
}

#[test]
fn cli_scan_and_runtime_load_options_strip_invocation_flags_unit() {
    let argv = [
        OsString::from("osp"),
        OsString::from("--json"),
        OsString::from("--mode"),
        OsString::from("plain"),
        OsString::from("--color=never"),
        OsString::from("--ascii"),
        OsString::from("-q"),
        OsString::from("config"),
        OsString::from("show"),
    ];

    let scanned = scan_cli_argv(&argv).expect("argv scan should succeed");

    assert_eq!(
        scanned.argv,
        vec![
            OsString::from("osp"),
            OsString::from("config"),
            OsString::from("show"),
        ]
    );
    assert_eq!(scanned.invocation.format, Some(OutputFormat::Json));
    assert_eq!(scanned.invocation.mode, Some(RenderMode::Plain));
    assert_eq!(scanned.invocation.color, Some(ColorMode::Never));
    assert_eq!(scanned.invocation.unicode, Some(UnicodeMode::Never));
    assert_eq!(scanned.invocation.quiet, 1);

    let cli = Cli::parse_from(["osp", "--no-env", "--no-config"]);
    let options = cli.runtime_load_options();
    assert!(!options.include_env);
    assert!(!options.include_config_file);
}

#[test]
fn invocation_ui_and_format_hints_overlay_runtime_defaults_unit() {
    let mut defaults = ConfigLayer::default();
    defaults.set("profile.default", "default");
    let mut resolver = ConfigResolver::default();
    resolver.set_defaults(defaults);
    let config = resolver
        .resolve(ResolveOptions::default().with_terminal("cli"))
        .expect("config should resolve");
    let ui = crate::app::UiState::new(
        RenderSettings::test_plain(OutputFormat::Table),
        MessageLevel::Success,
        1,
    );
    let invocation = InvocationOptions {
        format: Some(OutputFormat::Json),
        mode: Some(RenderMode::Rich),
        color: Some(ColorMode::Always),
        unicode: Some(UnicodeMode::Never),
        verbose: 2,
        quiet: 1,
        debug: 3,
        cache: false,
        plugin_provider: Some("beta".to_string()),
    };

    let resolved = resolve_invocation_ui(&config, &ui, &invocation);

    assert_eq!(resolved.ui.render_settings.format, OutputFormat::Json);
    assert!(resolved.ui.render_settings.format_explicit);
    assert_eq!(resolved.ui.render_settings.mode, RenderMode::Rich);
    assert_eq!(resolved.ui.render_settings.color, ColorMode::Always);
    assert_eq!(resolved.ui.render_settings.unicode, UnicodeMode::Never);
    assert_eq!(resolved.ui.message_verbosity, MessageLevel::Info);
    assert_eq!(resolved.ui.debug_verbosity, 3);
    assert_eq!(resolved.plugin_provider.as_deref(), Some("beta"));

    let base = RenderSettings::test_plain(OutputFormat::Auto);
    let hinted = resolve_render_settings_with_hint(&base, Some(OutputFormat::Table));
    assert_eq!(hinted.format, OutputFormat::Table);

    let pinned = resolve_render_settings_with_hint(
        &RenderSettings {
            format: OutputFormat::Json,
            ..base
        },
        Some(OutputFormat::Table),
    );
    assert_eq!(pinned.format, OutputFormat::Json);
}

#[test]
fn cli_cache_flag_is_rejected_outside_repl_unit() {
    let err = super::run_from(["osp", "--cache", "config", "show"])
        .expect_err("cache should be rejected outside repl");

    assert!(
        err.to_string()
            .contains("`--cache` is only available inside the interactive REPL")
    );
}

#[test]
fn cli_presentation_flags_map_to_session_overrides_unit() {
    for (args, expected) in [
        (&["osp", "--presentation", "compact"][..], "compact"),
        (&["osp", "--gammel-og-bitter"][..], "austere"),
    ] {
        let cli = Cli::parse_from(args);
        let layer = build_cli_session_layer(&cli)
            .expect("presentation flag should create session overrides");

        assert_eq!(
            layer_value(&layer, "ui.presentation"),
            Some(&ConfigValue::from(expected))
        );
    }
}

#[test]
fn dispatch_plan_keeps_structured_builtin_shape_and_profile_normalization_unit() {
    let plan = dispatch_plan_for(&["osp", "tsd"], &["uio", "tsd"]);
    assert_eq!(plan.profile_override.as_deref(), Some("tsd"));
    assert!(matches!(plan.action, RunAction::Repl));

    let plan = dispatch_plan_for(&["osp", "tsd", "plugins", "list"], &["uio", "tsd"]);
    assert_eq!(plan.profile_override.as_deref(), Some("tsd"));
    assert!(matches!(
        plan.action,
        RunAction::Plugins(crate::cli::PluginsArgs {
            command: PluginsCommands::List
        })
    ));

    let positional = dispatch_plan_for(&["osp", "tsd", "plugins", "list"], &["uio", "tsd"]);
    let explicit = dispatch_plan_for(
        &["osp", "--profile", "tsd", "plugins", "list"],
        &["uio", "tsd"],
    );
    assert_eq!(positional.profile_override, explicit.profile_override);
    assert!(matches!(positional.action, RunAction::Plugins(_)));
    assert!(matches!(explicit.action, RunAction::Plugins(_)));

    let normalized = dispatch_plan_for(&["osp", "--profile", "TSD"], &["tsd"]);
    assert_eq!(normalized.profile_override.as_deref(), Some("tsd"));

    let mut cli = Cli::parse_from(["osp", "plugins", "doctor"]);
    let plan = build_dispatch_plan(&mut cli, &profiles(&["uio", "tsd"]))
        .expect("dispatch plan should parse");
    assert_eq!(plan.profile_override, None);
    assert!(matches!(
        plan.action,
        RunAction::Plugins(crate::cli::PluginsArgs {
            command: PluginsCommands::Doctor
        })
    ));
    assert!(matches!(cli.command, None | Some(Commands::Plugins(_))));

    let mut cli = Cli::parse_from(["osp", "tsd", "config", "show", "--sources"]);
    let plan = build_dispatch_plan(&mut cli, &profiles(&["uio", "tsd"]))
        .expect("dispatch plan should parse");
    assert_eq!(plan.profile_override.as_deref(), Some("tsd"));
    match plan.action {
        RunAction::Config(args) => {
            assert!(matches!(
                args.command,
                ConfigCommands::Show(crate::cli::ConfigShowArgs {
                    output: crate::cli::ConfigReadOutputArgs {
                        sources: true,
                        raw: false,
                    },
                })
            ));
        }
        _ => panic!("expected config action"),
    }
}

#[test]
fn repl_dsl_capability_is_declared_per_command_unit() {
    let plugins_list = Commands::Plugins(crate::cli::PluginsArgs {
        command: PluginsCommands::List,
    });
    let plugins_enable = Commands::Plugins(crate::cli::PluginsArgs {
        command: PluginsCommands::Enable(crate::cli::PluginCommandStateArgs {
            target: crate::cli::PluginCommandTargetArgs {
                command: "ldap".to_string(),
                scope: crate::cli::PluginScopeArgs::default(),
            },
        }),
    });
    let theme_show = Commands::Theme(crate::cli::ThemeArgs {
        command: ThemeCommands::Show(crate::cli::ThemeShowArgs { name: None }),
    });
    let theme_use = Commands::Theme(crate::cli::ThemeArgs {
        command: ThemeCommands::Use(crate::cli::ThemeUseArgs {
            name: "nord".to_string(),
        }),
    });
    let config_show = Commands::Config(crate::cli::ConfigArgs {
        command: ConfigCommands::Show(crate::cli::ConfigShowArgs {
            output: crate::cli::ConfigReadOutputArgs {
                sources: false,
                raw: false,
            },
        }),
    });
    let config_set = Commands::Config(crate::cli::ConfigArgs {
        command: ConfigCommands::Set(crate::cli::ConfigSetArgs {
            key: "ui.mode".to_string(),
            value: "plain".to_string(),
            scope: crate::cli::ConfigScopeArgs::default(),
            store: crate::cli::ConfigStoreArgs::default(),
            dry_run: false,
            yes: false,
            explain: false,
        }),
    });
    let history_list = Commands::History(crate::cli::HistoryArgs {
        command: crate::cli::HistoryCommands::List,
    });
    let history_prune = Commands::History(crate::cli::HistoryArgs {
        command: crate::cli::HistoryCommands::Prune(crate::cli::HistoryPruneArgs { keep: 5 }),
    });

    assert!(repl::repl_command_spec(&plugins_list).supports_dsl);
    assert!(!repl::repl_command_spec(&plugins_enable).supports_dsl);
    assert!(repl::repl_command_spec(&theme_show).supports_dsl);
    assert!(!repl::repl_command_spec(&theme_use).supports_dsl);
    assert!(repl::repl_command_spec(&config_show).supports_dsl);
    assert!(!repl::repl_command_spec(&config_set).supports_dsl);
    assert!(repl::repl_command_spec(&history_list).supports_dsl);
    assert!(!repl::repl_command_spec(&history_prune).supports_dsl);
}

#[test]
fn external_inline_builtin_reuses_repl_dsl_policy_unit() {
    let mut state = make_completion_state(None);
    let command = Commands::Config(crate::cli::ConfigArgs {
        command: ConfigCommands::Set(crate::cli::ConfigSetArgs {
            key: "ui.mode".to_string(),
            value: "plain".to_string(),
            scope: crate::cli::ConfigScopeArgs::default(),
            store: crate::cli::ConfigStoreArgs::default(),
            dry_run: false,
            yes: false,
            explain: false,
        }),
    });

    let err = match run_inline_builtin_command(
        &mut state.runtime,
        &mut state.session,
        &state.clients,
        None,
        command,
        &["uid".to_string()],
    ) {
        Ok(_) => panic!("expected DSL rejection"),
        Err(err) => err,
    };
    assert_eq!(
        err.to_string(),
        "`config` does not support DSL pipeline stages"
    );
}