osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
use serde_json::json;

use super::{SortCast, apply_value_with_plan, apply_with_plan, compile};
use crate::core::{
    output_model::{Group, OutputItems},
    row::Row,
};

fn row(value: serde_json::Value) -> Row {
    value
        .as_object()
        .cloned()
        .expect("fixture should be an object")
}

#[test]
fn compile_parses_descending_keys_and_casts_unit() {
    let plan = compile("!score AS num host AS ip name AS str").expect("sort should compile");

    assert_eq!(plan.keys.len(), 3);
    assert!(plan.keys[0].descending);
    assert_eq!(plan.keys[0].key_spec.token, "score");
    assert_eq!(plan.keys[0].cast, SortCast::Num);
    assert_eq!(plan.keys[1].cast, SortCast::Ip);
    assert_eq!(plan.keys[2].cast, SortCast::Str);
}

#[test]
fn compile_rejects_empty_specs_and_unknown_casts_unit() {
    assert!(compile("").is_err());
    let err = compile("score AS bogus").expect_err("unknown cast should fail");
    assert!(err.to_string().contains("unsupported cast"));
}

#[test]
fn apply_with_plan_sorts_rows_and_groups_using_requested_casts_unit() {
    let rows = vec![
        row(json!({"score": "10", "host": "10.0.0.2", "name": "beta"})),
        row(json!({"score": 2, "host": "10.0.0.10", "name": "Alpha"})),
        row(json!({"name": "omega"})),
    ];

    let sorted = apply_with_plan(
        OutputItems::Rows(rows),
        &compile("score AS num host AS ip").unwrap(),
    )
    .expect("row sort should work");
    let OutputItems::Rows(sorted_rows) = sorted else {
        panic!("expected row output");
    };
    assert_eq!(
        sorted_rows
            .iter()
            .map(|row| row
                .get("name")
                .and_then(|value| value.as_str())
                .unwrap_or(""))
            .collect::<Vec<_>>(),
        vec!["Alpha", "beta", "omega"]
    );

    let grouped = apply_with_plan(
        OutputItems::Groups(vec![
            Group {
                groups: row(json!({"team": "beta"})),
                aggregates: row(json!({"count": 1})),
                rows: Vec::new(),
            },
            Group {
                groups: row(json!({"team": "alpha"})),
                aggregates: row(json!({"count": 2})),
                rows: Vec::new(),
            },
        ]),
        &compile("count AS num team AS str").unwrap(),
    )
    .expect("group sort should work");
    let OutputItems::Groups(groups) = grouped else {
        panic!("expected grouped output");
    };
    assert_eq!(
        groups
            .iter()
            .map(|group| group
                .groups
                .get("team")
                .and_then(|value| value.as_str())
                .unwrap())
            .collect::<Vec<_>>(),
        vec!["beta", "alpha"]
    );
}

#[test]
fn apply_value_with_plan_sorts_scalars_and_nested_collection_rows_unit() {
    let sorted_scalars =
        apply_value_with_plan(json!(["b", "a", "c"]), &compile("ignored").unwrap())
            .expect("scalar arrays sort lexically");
    assert_eq!(sorted_scalars, json!(["a", "b", "c"]));

    let nested = json!({
        "hosts": [
            {"host": "10.0.0.10"},
            {"host": "10.0.0.2"}
        ]
    });
    let sorted_nested = apply_value_with_plan(nested, &compile("host AS ip").unwrap())
        .expect("nested collection sort should work");
    assert_eq!(
        sorted_nested,
        json!({
            "hosts": [
                {"host": "10.0.0.2"},
                {"host": "10.0.0.10"}
            ]
        })
    );
}