osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
#[test]
fn validate_and_adapt_rejects_unknown_and_missing_required_keys() {
    let schema = ConfigSchema::default();
    let mut unknown = BTreeMap::new();
    unknown.insert(
        "mystery.key".to_string(),
        ResolvedValue {
            raw_value: ConfigValue::String("x".to_string()),
            value: ConfigValue::String("x".to_string()),
            source: ConfigSource::ConfigFile,
            scope: Scope::global(),
            origin: None,
        },
    );
    let err = schema
        .validate_and_adapt(&mut unknown)
        .expect_err("unknown keys should fail");
    assert!(matches!(
        err,
        crate::config::ConfigError::UnknownConfigKeys { keys } if keys == vec!["mystery.key".to_string()]
    ));

    let mut missing_required = BTreeMap::new();
    let err = schema
        .validate_and_adapt(&mut missing_required)
        .expect_err("missing required profile.active should fail");
    assert!(matches!(
        err,
        crate::config::ConfigError::MissingRequiredKey { key } if key == "profile.active"
    ));
}

#[test]
fn validate_and_adapt_converts_runtime_visible_string_values() {
    let schema = ConfigSchema::default();
    let mut values = BTreeMap::new();
    values.insert(
        "profile.active".to_string(),
        ResolvedValue {
            raw_value: ConfigValue::String("ops".to_string()),
            value: ConfigValue::String("ops".to_string()),
            source: ConfigSource::ConfigFile,
            scope: Scope::global(),
            origin: None,
        },
    );
    values.insert(
        "ui.prompt.secrets".to_string(),
        ResolvedValue {
            raw_value: ConfigValue::String("true".to_string()),
            value: ConfigValue::String("true".to_string()),
            source: ConfigSource::Environment,
            scope: Scope::global(),
            origin: None,
        },
    );
    values.insert(
        "session.cache.max_results".to_string(),
        ResolvedValue {
            raw_value: ConfigValue::String("15".to_string()),
            value: ConfigValue::String("15".to_string()),
            source: ConfigSource::Environment,
            scope: Scope::global(),
            origin: None,
        },
    );
    values.insert(
        "theme.path".to_string(),
        ResolvedValue {
            raw_value: ConfigValue::String("base, custom".to_string()),
            value: ConfigValue::String("base, custom".to_string()),
            source: ConfigSource::Environment,
            scope: Scope::global(),
            origin: None,
        },
    );

    schema
        .validate_and_adapt(&mut values)
        .expect("runtime-visible values should adapt");

    assert_eq!(
        values.get("ui.prompt.secrets").map(|entry| &entry.value),
        Some(&ConfigValue::Bool(true))
    );
    assert_eq!(
        values
            .get("session.cache.max_results")
            .map(|entry| &entry.value),
        Some(&ConfigValue::Integer(15))
    );
    assert_eq!(
        values.get("theme.path").map(|entry| &entry.value),
        Some(&ConfigValue::List(vec![
            ConfigValue::String("base".to_string()),
            ConfigValue::String("custom".to_string()),
        ]))
    );
}

#[test]
fn resolved_config_helpers_read_scalar_list_secret_and_alias_views_unit() {
    let resolved = ResolvedConfig {
        active_profile: "ops".to_string(),
        terminal: Some("repl".to_string()),
        known_profiles: BTreeSet::from(["default".to_string(), "ops".to_string()]),
        values: BTreeMap::from([
            (
                "ui.prompt.secrets".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::Bool(true),
                    value: ConfigValue::Bool(true),
                    source: ConfigSource::ConfigFile,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
            (
                "theme.path".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::String("base".to_string()).into_secret(),
                    value: ConfigValue::String("base".to_string()).into_secret(),
                    source: ConfigSource::Secrets,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
        ]),
        aliases: BTreeMap::from([(
            "alias.lookup".to_string(),
            ResolvedValue {
                raw_value: ConfigValue::String("ldap user".to_string()),
                value: ConfigValue::String("ldap user".to_string()),
                source: ConfigSource::ConfigFile,
                scope: Scope::global(),
                origin: None,
            },
        )]),
    };

    assert_eq!(resolved.active_profile(), "ops");
    assert_eq!(resolved.terminal(), Some("repl"));
    assert_eq!(resolved.get_bool("ui.prompt.secrets"), Some(true));
    assert_eq!(
        resolved.get_string_list("theme.path"),
        Some(vec!["base".to_string()])
    );
    assert!(resolved.get_alias_entry("lookup").is_some());
    assert!(resolved.get_alias_entry("alias.lookup").is_some());
    assert!(resolved.known_profiles().contains("default"));
    assert!(resolved.values().contains_key("ui.prompt.secrets"));
    assert!(resolved.aliases().contains_key("alias.lookup"));

    let resolved = ResolvedConfig {
        active_profile: "default".to_string(),
        terminal: Some("repl".to_string()),
        known_profiles: BTreeSet::new(),
        values: BTreeMap::from([
            (
                "list.secret".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::List(vec![
                        ConfigValue::String("plain".to_string()),
                        ConfigValue::Secret(SecretValue::new(ConfigValue::String(
                            "hidden".to_string(),
                        ))),
                    ]),
                    value: ConfigValue::List(vec![
                        ConfigValue::String("plain".to_string()),
                        ConfigValue::Secret(SecretValue::new(ConfigValue::String(
                            "hidden".to_string(),
                        ))),
                    ]),
                    source: ConfigSource::Secrets,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
            (
                "single.secret".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::Secret(SecretValue::new(ConfigValue::String(
                        "solo".to_string(),
                    ))),
                    value: ConfigValue::Secret(SecretValue::new(ConfigValue::String(
                        "solo".to_string(),
                    ))),
                    source: ConfigSource::Secrets,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
        ]),
        aliases: BTreeMap::new(),
    };
    assert_eq!(
        resolved.get_string_list("list.secret"),
        Some(vec!["plain".to_string(), "hidden".to_string()])
    );
    assert_eq!(
        resolved.get_string_list("single.secret"),
        Some(vec!["solo".to_string()])
    );

    let mut layer = ConfigLayer::default();
    layer.set("ui.format", "json");
    layer.set_for_terminal("repl", "ui.format", "table");
    assert_eq!(
        layer.remove_scoped("ui.format", &Scope::terminal("repl")),
        Some(ConfigValue::String("table".to_string()))
    );

    let resolved = ResolvedConfig {
        active_profile: "ops".to_string(),
        terminal: None,
        known_profiles: BTreeSet::new(),
        values: BTreeMap::from([
            (
                "theme.path".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::List(vec![
                        ConfigValue::String("base".to_string()),
                        ConfigValue::Integer(5),
                        ConfigValue::String("secret".to_string()).into_secret(),
                    ]),
                    value: ConfigValue::List(vec![
                        ConfigValue::String("base".to_string()),
                        ConfigValue::Integer(5),
                        ConfigValue::String("secret".to_string()).into_secret(),
                    ]),
                    source: ConfigSource::ConfigFile,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
            (
                "user.name".to_string(),
                ResolvedValue {
                    raw_value: ConfigValue::Integer(42).into_secret(),
                    value: ConfigValue::Integer(42).into_secret(),
                    source: ConfigSource::Secrets,
                    scope: Scope::global(),
                    origin: None,
                },
            ),
        ]),
        aliases: BTreeMap::new(),
    };

    assert_eq!(
        resolved.get_string_list("theme.path"),
        Some(vec!["base".to_string(), "secret".to_string()])
    );
    assert_eq!(resolved.get_string_list("user.name"), None);
}