osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
use osp_cli::core::output::OutputFormat;
use osp_cli::dsl::{apply_pipeline, parse_pipeline};
use osp_cli::ports::LdapDirectory;
use osp_cli::ports::mock::MockLdapClient;
use osp_cli::ui::{RenderSettings, render_output};
use serde_json::json;

#[test]
fn dsl_pipeline_project_works_on_ldap_user_data() {
    let ldap = MockLdapClient::default();
    let rows = ldap
        .user("oistes", None, None)
        .expect("query should succeed");

    let parsed = parse_pipeline("ldap user oistes | P uid,cn").expect("valid pipeline");
    let transformed = apply_pipeline(rows, &parsed.stages).expect("pipeline should succeed");

    let settings = RenderSettings::test_plain(OutputFormat::Table);
    let output = render_output(&transformed, &settings);

    assert!(output.contains("uid"));
    assert!(output.contains("cn"));
    assert!(!output.contains("homeDirectory"));
}

#[test]
fn dsl_pipeline_values_works_on_netgroup_members() {
    let ldap = MockLdapClient::default();
    let rows = ldap
        .netgroup("ucore", None, None)
        .expect("query should succeed");

    let parsed =
        parse_pipeline("ldap netgroup ucore | P members | VAL members").expect("valid pipeline");
    let transformed = apply_pipeline(rows, &parsed.stages).expect("pipeline should succeed");

    let settings = RenderSettings::test_plain(OutputFormat::Value);
    let output = render_output(&transformed, &settings);

    assert!(output.contains("oistes"));
    assert!(!output.contains("description"));
}

#[test]
fn dsl_pipeline_filter_works() {
    let ldap = MockLdapClient::default();
    let rows = ldap
        .netgroup("ucore", None, None)
        .expect("query should succeed");

    let parsed = parse_pipeline("ldap netgroup ucore | F cn=ucore | P cn").expect("valid pipeline");
    let transformed = apply_pipeline(rows, &parsed.stages).expect("pipeline should succeed");

    let settings = RenderSettings::test_plain(OutputFormat::Mreg);
    let output = render_output(&transformed, &settings);

    assert!(output.contains("ucore"));
}

#[test]
fn dsl_pipeline_markdown_table_format_works() {
    let ldap = MockLdapClient::default();
    let rows = ldap
        .user("oistes", None, None)
        .expect("query should succeed");

    let parsed = parse_pipeline("ldap user oistes | P uid,cn").expect("valid pipeline");
    let transformed = apply_pipeline(rows, &parsed.stages).expect("pipeline should succeed");

    let mut settings = RenderSettings::test_plain(OutputFormat::Markdown);
    settings.width = Some(200);
    let output = render_output(&transformed, &settings);

    let mut lines = output.lines();
    let header = lines.next().unwrap_or_default();
    let separator = lines.next().unwrap_or_default();
    assert!(header.contains("uid") && header.contains("cn"));
    assert!(separator.contains("---"));
    assert!(output.contains("oistes"));
}

#[test]
fn dsl_pipeline_grouped_output_renders_without_flattening() {
    let rows = vec![
        json!({"dept": "sales", "host": "alpha"})
            .as_object()
            .cloned()
            .expect("row fixture"),
        json!({"dept": "sales", "host": "beta"})
            .as_object()
            .cloned()
            .expect("row fixture"),
        json!({"dept": "eng", "host": "gamma"})
            .as_object()
            .cloned()
            .expect("row fixture"),
    ];

    let transformed =
        apply_pipeline(rows, &["G dept".to_string()]).expect("pipeline should succeed");

    let settings = RenderSettings::test_plain(OutputFormat::Table);
    let output = render_output(&transformed, &settings);

    assert!(output.contains("dept"));
    assert!(output.contains("sales"));
    assert!(output.contains("eng"));
    assert!(output.contains("alpha"));
    assert!(output.contains("gamma"));
}

#[test]
fn dsl_pipeline_quick_uses_visible_dotted_row_values_when_no_structural_match_exists() {
    let rows = vec![
        json!({"key": "theme.name", "value": "dracula"})
            .as_object()
            .cloned()
            .expect("row fixture"),
        json!({"key": "theme.path", "value": "/tmp/themes"})
            .as_object()
            .cloned()
            .expect("row fixture"),
    ];

    let parsed = parse_pipeline("fixture | theme.name").expect("valid pipeline");
    let transformed = apply_pipeline(rows, &parsed.stages).expect("pipeline should succeed");

    let settings = RenderSettings::test_plain(OutputFormat::Table);
    let output = render_output(&transformed, &settings);

    assert!(output.contains("theme.name"));
    assert!(output.contains("dracula"));
    assert!(!output.contains("theme.path"));
}