query-forge 0.7.0

Run SQL queries and dataset diffs on XLSX/XML/CSV/JSON/JSONL/Markdown/HTML/Parquet inputs and export results as text, CSV, JSONL, Markdown, XML, HTML, XLSX, or Parquet
Documentation
use clap::Parser;

use super::{Cli, Commands, DiffOutputFormat, DiffShow, HeaderCase, JsonMode, MetadataFormat, XmlMode};

#[test]
fn parses_configurable_type_inference_flags() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--all-text",
        "--decimal-comma",
        "--date-format",
        "%d/%m/%Y",
        "--null-values",
        "NULL,N/A",
        "--true-values",
        "YES,Y",
        "--false-values",
        "NO,N",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.all_text);
    assert!(cmd.decimal_comma);
    assert_eq!(cmd.date_format.as_deref(), Some("%d/%m/%Y"));
    assert_eq!(cmd.null_values, vec!["NULL", "N/A"]);
    assert_eq!(cmd.true_values, vec!["YES", "Y"]);
    assert_eq!(cmd.false_values, vec!["NO", "N"]);
}

#[test]
fn parses_input_normalization_flags() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--trim",
        "--skip-empty-rows",
        "--normalize-headers",
        "--header-case",
        "snake",
        "--dedupe-headers",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.trim);
    assert!(cmd.skip_empty_rows);
    assert!(cmd.normalize_headers);
    assert!(matches!(cmd.header_case, Some(HeaderCase::Snake)));
    assert!(cmd.dedupe_headers);
}

#[test]
fn parses_header_case_camel() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--normalize-headers",
        "--header-case",
        "camel",
    ]);
    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };
    assert!(matches!(cmd.header_case, Some(HeaderCase::Camel)));
}

#[test]
fn parses_header_case_pascal() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--normalize-headers",
        "--header-case",
        "pascal",
    ]);
    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };
    assert!(matches!(cmd.header_case, Some(HeaderCase::Pascal)));
}

#[test]
fn parses_header_case_screaming_snake() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--normalize-headers",
        "--header-case",
        "screaming-snake",
    ]);
    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };
    assert!(matches!(cmd.header_case, Some(HeaderCase::ScreamingSnake)));
}

#[test]
fn rejects_infer_types_and_all_text_together() {
    let parsed = Cli::try_parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--infer-types",
        "--all-text",
    ]);

    assert!(parsed.is_err());
}

#[test]
fn parses_sql_file_flag() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql-file",
        "query.sql",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert_eq!(cmd.sql_file.as_deref(), Some(std::path::Path::new("query.sql")));
}

#[test]
fn parses_json_mode_flag() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.json",
        "--sql",
        "SELECT * FROM table",
        "--json-mode",
        "flatten",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(matches!(cmd.json_mode, Some(JsonMode::Flatten)));
}

#[test]
fn parses_xml_mode_flag() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.xml",
        "--sql",
        "SELECT * FROM table",
        "--xml-mode",
        "attributes",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(matches!(cmd.xml_mode, Some(XmlMode::Attributes)));
}

#[test]
fn json_mode_defaults_to_none_when_not_specified() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.json",
        "--sql",
        "SELECT * FROM table",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.json_mode.is_none());
    assert!(cmd.xml_mode.is_none());
}

#[test]
fn parses_meta_flag() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--meta",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.meta);
    assert!(cmd.meta_format.is_none());
}

#[test]
fn parses_meta_with_json_format() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--meta",
        "--meta-format",
        "json",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.meta);
    assert!(matches!(cmd.meta_format, Some(MetadataFormat::Json)));
}

#[test]
fn parses_meta_with_text_format() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
        "--meta",
        "--meta-format",
        "text",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(cmd.meta);
    assert!(matches!(cmd.meta_format, Some(MetadataFormat::Text)));
}

#[test]
fn meta_flag_is_false_when_not_specified() {
    let cli = Cli::parse_from([
        "qf",
        "query",
        "--input",
        "data.csv",
        "--sql",
        "SELECT * FROM table",
    ]);

    let Commands::Query(cmd) = cli.command else {
        panic!("expected query command");
    };

    assert!(!cmd.meta);
    assert!(cmd.meta_format.is_none());
}

#[test]
fn parses_diff_command_with_keys_and_show_filter() {
    let cli = Cli::parse_from([
        "qf",
        "diff",
        "--key",
        "id,tenant",
        "--show",
        "added,changed",
        "--ignore-columns",
        "updated_at",
        "before.csv",
        "after.csv",
    ]);

    let Commands::Diff(cmd) = cli.command else {
        panic!("expected diff command");
    };

    assert_eq!(cmd.left, "before.csv");
    assert_eq!(cmd.right, "after.csv");
    assert_eq!(cmd.key, vec!["id", "tenant"]);
    assert_eq!(cmd.ignore_columns, vec!["updated_at"]);
    assert_eq!(cmd.show.len(), 2);
    assert!(matches!(cmd.show[0], DiffShow::Added));
    assert!(matches!(cmd.show[1], DiffShow::Changed));
}

#[test]
fn parses_diff_schema_only_side_by_side_format() {
    let cli = Cli::parse_from([
        "qf",
        "diff",
        "--schema-only",
        "--side-by-side",
        "--format",
        "markdown",
        "left.json",
        "right.json",
    ]);

    let Commands::Diff(cmd) = cli.command else {
        panic!("expected diff command");
    };

    assert!(cmd.schema_only);
    assert!(cmd.side_by_side);
    assert!(matches!(cmd.format, Some(DiffOutputFormat::Markdown)));
}