osp-cli 1.5.1

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

use super::{
    JqError, apply_value_with_expr, apply_with_expr, compile, compile_program, run_jaq,
    value_to_group,
};
use crate::core::{
    output_model::{Group, OutputItems, rows_from_value},
    row::Row,
};

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

#[test]
fn compile_rejects_empty_expressions_and_normalizes_quotes_unit() {
    assert!(matches!(compile("   "), Err(JqError::MissingExpression)));
    assert_eq!(compile("' .[] '").unwrap(), " .[] ");
    assert_eq!(compile("| map(.uid)").unwrap(), "map(.uid)");
}

#[test]
fn json_helpers_wrap_scalars_and_restore_group_fallback_metadata_unit() {
    assert_eq!(
        rows_from_value(json!(["alice", {"uid": "bob"}])),
        vec![row(json!({"value": "alice"})), row(json!({"uid": "bob"}))]
    );

    let fallback = Group {
        groups: row(json!({"team": "ops"})),
        aggregates: row(json!({"count": 2})),
        rows: vec![row(json!({"uid": "alice"}))],
    };
    let rebuilt = value_to_group(
        &json!({
            "rows": [{"uid": "bob"}]
        }),
        &fallback,
    )
    .expect("rows should rebuild a group");
    assert_eq!(rebuilt.groups, fallback.groups);
    assert_eq!(rebuilt.aggregates, fallback.aggregates);
    assert_eq!(rebuilt.rows, vec![row(json!({"uid": "bob"}))]);
    assert!(value_to_group(&json!({"uid": "alice"}), &fallback).is_none());
}

#[test]
fn run_jaq_reports_compile_eval_non_json_and_empty_output_unit() {
    assert!(matches!(
        compile_program(".["),
        Err(JqError::CompileFailed { .. })
    ));

    let failing = compile_program("error(\"boom\")").expect("program should compile");
    assert!(matches!(
        run_jaq(&failing, &json!(null)),
        Err(JqError::EvaluationFailed { .. })
    ));

    let non_json = compile_program("{(1): 2}").expect("program should compile");
    assert!(matches!(
        run_jaq(&non_json, &json!(null)),
        Err(JqError::InvalidJsonOutput { .. })
    ));

    let empty = compile_program("empty").expect("program should compile");
    assert_eq!(run_jaq(&empty, &json!(null)).unwrap(), None);
}

#[test]
fn run_jaq_collects_multiple_outputs_into_one_json_array_unit() {
    let program = compile_program(".[]").expect("program should compile");
    assert_eq!(
        run_jaq(&program, &json!(["alice", "bob"])).unwrap(),
        Some(json!(["alice", "bob"]))
    );
}

#[test]
fn apply_with_expr_uses_real_jaq_for_rows_groups_and_values_unit() {
    let rows = apply_with_expr(
        OutputItems::Rows(vec![
            row(json!({"uid": "alice", "team": "ops"})),
            row(json!({"uid": "bob", "team": "eng"})),
        ]),
        "map({uid})",
    )
    .expect("row jq should work");
    let OutputItems::Rows(row_items) = rows else {
        panic!("expected row output");
    };
    assert_eq!(
        row_items,
        vec![row(json!({"uid": "alice"})), row(json!({"uid": "bob"}))]
    );

    let groups = apply_with_expr(
        OutputItems::Groups(vec![Group {
            groups: row(json!({"team": "ops"})),
            aggregates: row(json!({"count": 2})),
            rows: vec![row(json!({"uid": "alice"})), row(json!({"uid": "bob"}))],
        }]),
        "{rows: (.rows | map({uid}))}",
    )
    .expect("group jq should work");
    let OutputItems::Groups(group_items) = groups else {
        panic!("expected grouped output");
    };
    assert_eq!(group_items[0].groups, row(json!({"team": "ops"})));
    assert_eq!(group_items[0].aggregates, row(json!({"count": 2})));
    assert_eq!(
        group_items[0].rows,
        vec![row(json!({"uid": "alice"})), row(json!({"uid": "bob"}))]
    );

    let value = apply_value_with_expr(json!([{"uid": "alice"}, {"uid": "bob"}]), "map(.uid)")
        .expect("value jq should work");
    assert_eq!(value, json!(["alice", "bob"]));
}

#[test]
fn apply_with_expr_returns_empty_rows_when_jaq_emits_no_output_unit() {
    let rows = apply_with_expr(OutputItems::Rows(Vec::new()), ".").expect("empty rows should work");
    let OutputItems::Rows(rendered_rows) = rows else {
        panic!("expected row output");
    };
    assert!(rendered_rows.is_empty());
}