xberg-cli 1.0.0-rc.13

Command-line interface for Xberg document intelligence
//! CLI configuration tests validating flags, aliases, and deprecation handling.
//!
//! This test suite verifies that:
//! 1. --output-format flag works correctly for all format options
//! 2. CLI flags properly override config file settings
//! 3. Config merge precedence is maintained (CLI args > config file > defaults)
//! 4. Configuration JSON can be passed inline
//! 5. Alias handling for deprecated flags works as expected

#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::field_reassign_with_default)]

use std::path::PathBuf;
use tempfile::TempDir;

/// Helper to create a temporary config file
#[allow(dead_code)]
fn create_test_config(dir: &TempDir, name: &str, content: &str) -> PathBuf {
    let config_path = dir.path().join(name);
    std::fs::write(&config_path, content).expect("Failed to write config file");
    config_path
}

#[test]
fn test_output_format_flag_plain() {
    // Test that --output-format plain works
    // This test verifies the flag is properly recognized

    let config = xberg::core::config::ExtractionConfig::default();
    assert_eq!(
        config.output_format,
        xberg::core::config::OutputFormat::Plain,
        "Default output format should be Plain"
    );
}

#[test]
fn test_output_format_flag_markdown() {
    // Test that --output-format markdown is parsed correctly
    let markdown_format = xberg::core::config::OutputFormat::Markdown;
    assert_eq!(
        format!("{:?}", markdown_format),
        "Markdown",
        "Markdown format should have correct debug representation"
    );
}

#[test]
fn test_output_format_flag_html() {
    // Test that --output-format html is parsed correctly
    let html_format = xberg::core::config::OutputFormat::Html;
    assert_eq!(
        format!("{:?}", html_format),
        "Html",
        "Html format should have correct debug representation"
    );
}

#[test]
fn test_extraction_config_with_output_format() {
    // Test that ExtractionConfig can be created with specific output_format
    let mut config = xberg::core::config::ExtractionConfig::default();

    config.output_format = xberg::core::config::OutputFormat::Markdown;
    assert_eq!(
        config.output_format,
        xberg::core::config::OutputFormat::Markdown,
        "output_format should be Markdown after assignment"
    );

    let serialized = serde_json::to_value(&config).expect("Failed to serialize");
    assert_eq!(
        serialized["output_format"], "markdown",
        "Serialized output_format should be 'markdown' (lowercase)"
    );
}

#[test]
fn test_config_json_parsing_complete() {
    // Test that complete JSON config can be parsed
    let json = serde_json::json!({
        "use_cache": true,
        "enable_quality_processing": true,
        "force_ocr": false,
        "output_format": "markdown",
        "result_format": "unified",
        "max_concurrent_extractions": 4,
    });

    let config: xberg::core::config::ExtractionConfig =
        serde_json::from_value(json).expect("Failed to parse config JSON");

    assert!(config.use_cache);
    assert!(config.enable_quality_processing);
    assert_eq!(config.force_ocr, false);
    assert_eq!(config.output_format, xberg::core::config::OutputFormat::Markdown);
    assert_eq!(config.max_concurrent_extractions, Some(4));
}

#[test]
fn test_config_merge_precedence_cli_overrides_default() {
    // Test that CLI arguments override defaults
    let mut config = xberg::core::config::ExtractionConfig::default();

    // Simulate CLI override
    config.use_cache = false;
    config.force_ocr = true;

    assert_eq!(config.use_cache, false, "CLI override should change use_cache to false");
    assert_eq!(config.force_ocr, true, "CLI override should change force_ocr to true");
}

#[test]
fn test_config_merge_precedence_cli_overrides_file() {
    // Test that CLI arguments override config file settings
    let mut file_config = xberg::core::config::ExtractionConfig::default();
    file_config.use_cache = true;
    file_config.force_ocr = false;

    // Simulate CLI override
    let mut final_config = file_config.clone();
    final_config.use_cache = false;

    assert_eq!(
        final_config.use_cache, false,
        "CLI should override file config for use_cache"
    );
    assert!(!final_config.force_ocr, "CLI should not affect fields not overridden");
}

#[test]
fn test_config_file_precedence_over_defaults() {
    // Test that config file values override defaults
    let json = serde_json::json!({
        "use_cache": false,
        "force_ocr": true,
    });

    let file_config: xberg::core::config::ExtractionConfig = serde_json::from_value(json).expect("Failed to parse");

    let default_config = xberg::core::config::ExtractionConfig::default();

    assert_ne!(
        file_config.use_cache, default_config.use_cache,
        "File config should override default for use_cache"
    );
    assert_ne!(
        file_config.force_ocr, default_config.force_ocr,
        "File config should override default for force_ocr"
    );
}

#[test]
fn test_output_format_serialization() {
    // Test that output_format serializes to expected string values
    let plain = xberg::core::config::OutputFormat::Plain;
    let plain_json = serde_json::to_value(plain).expect("Failed to serialize Plain");
    assert_eq!(plain_json, "plain");

    let markdown = xberg::core::config::OutputFormat::Markdown;
    let markdown_json = serde_json::to_value(markdown).expect("Failed to serialize Markdown");
    assert_eq!(markdown_json, "markdown");

    let html = xberg::core::config::OutputFormat::Html;
    let html_json = serde_json::to_value(html).expect("Failed to serialize Html");
    assert_eq!(html_json, "html");
}

#[test]
fn test_output_format_deserialization() {
    // Test that output_format can be deserialized from string values
    let plain: xberg::core::config::OutputFormat =
        serde_json::from_value(serde_json::json!("plain")).expect("Failed to deserialize plain");
    assert_eq!(plain, xberg::core::config::OutputFormat::Plain);

    let markdown: xberg::core::config::OutputFormat =
        serde_json::from_value(serde_json::json!("markdown")).expect("Failed to deserialize markdown");
    assert_eq!(markdown, xberg::core::config::OutputFormat::Markdown);

    let html: xberg::core::config::OutputFormat =
        serde_json::from_value(serde_json::json!("html")).expect("Failed to deserialize html");
    assert_eq!(html, xberg::core::config::OutputFormat::Html);
}

#[test]
fn test_extraction_config_roundtrip_with_output_format() {
    // Test that output_format survives serialization roundtrip
    let original = xberg::core::config::ExtractionConfig {
        output_format: xberg::core::config::OutputFormat::Markdown,
        ..xberg::core::config::ExtractionConfig::default()
    };

    let json_string = serde_json::to_string(&original).expect("Failed to serialize");
    let restored: xberg::core::config::ExtractionConfig =
        serde_json::from_str(&json_string).expect("Failed to deserialize");

    assert_eq!(
        original.output_format, restored.output_format,
        "output_format should survive serialization roundtrip"
    );
}

#[test]
fn test_config_with_all_output_formats() {
    // Test that all output format variants can be set and retrieved
    let formats = vec![
        xberg::core::config::OutputFormat::Plain,
        xberg::core::config::OutputFormat::Markdown,
        xberg::core::config::OutputFormat::Html,
    ];

    for format in formats {
        let config = xberg::core::config::ExtractionConfig {
            output_format: format.clone(),
            ..xberg::core::config::ExtractionConfig::default()
        };

        let json = serde_json::to_value(&config).expect("Failed to serialize");
        let restored: xberg::core::config::ExtractionConfig =
            serde_json::from_value(json).expect("Failed to deserialize");

        assert_eq!(
            format, restored.output_format,
            "Format should be preserved for {:?}",
            format
        );
    }
}

#[test]
fn test_config_partial_json_with_output_format() {
    // Test that partial JSON config with only output_format is valid
    let json = serde_json::json!({
        "output_format": "markdown",
    });

    let config: xberg::core::config::ExtractionConfig =
        serde_json::from_value(json).expect("Failed to parse partial config");

    assert_eq!(
        config.output_format,
        xberg::core::config::OutputFormat::Markdown,
        "output_format should be set from partial config"
    );

    // Other fields should have defaults
    assert!(config.use_cache, "use_cache should have default value");
}

#[test]
fn test_config_complete_json_structure() {
    // Test that a complete config JSON has all necessary fields
    let config = xberg::core::config::ExtractionConfig::default();
    let json = serde_json::to_value(&config).expect("Failed to serialize");
    let obj = json.as_object().expect("Should be object");

    // Verify critical fields are present
    assert!(obj.contains_key("output_format"), "Should have output_format");
    assert!(obj.contains_key("use_cache"), "Should have use_cache");
    assert!(
        obj.contains_key("enable_quality_processing"),
        "Should have enable_quality_processing"
    );
    assert!(obj.contains_key("force_ocr"), "Should have force_ocr");
    assert!(obj.contains_key("result_format"), "Should have result_format");
}

#[test]
fn test_unknown_output_format_accepted_as_custom() {
    // OutputFormat has a Custom(String) catch-all variant with #[serde(untagged)],
    // so unknown strings are accepted as custom renderer names rather than rejected.
    let json = serde_json::json!({
        "output_format": "my_custom_renderer",
    });

    let result: Result<xberg::core::config::ExtractionConfig, _> = serde_json::from_value(json);

    assert!(
        result.is_ok(),
        "Unknown output_format should be accepted as Custom variant; got: {:?}",
        result.err()
    );
    assert_eq!(
        result.unwrap().output_format,
        xberg::core::config::OutputFormat::Custom("my_custom_renderer".to_string()),
        "Unknown format string must deserialize as OutputFormat::Custom"
    );
}

#[test]
fn test_config_case_sensitivity() {
    // Test that format values are case-insensitive due to rename_all = "lowercase"
    let plain_lowercase = serde_json::json!({"output_format": "plain"});
    let result: Result<xberg::core::config::ExtractionConfig, _> = serde_json::from_value(plain_lowercase);

    assert!(result.is_ok(), "lowercase 'plain' should be accepted");
    let config = result.unwrap();
    assert_eq!(config.output_format, xberg::core::config::OutputFormat::Plain);
}

#[test]
fn test_output_format_field_is_required_in_serialization() {
    // Test that output_format is always included in serialization
    let config = xberg::core::config::ExtractionConfig::default();
    let json = serde_json::to_value(&config).expect("Failed to serialize");

    assert!(
        json.get("output_format").is_some(),
        "output_format should always be present in serialization"
    );
}

#[test]
fn test_result_format_and_output_format_independent() {
    // Test that result_format and output_format are independent fields
    let mut config = xberg::core::config::ExtractionConfig::default();

    // Set both to different values
    config.output_format = xberg::core::config::OutputFormat::Markdown;

    let json = serde_json::to_value(&config).expect("Failed to serialize");

    assert_eq!(json["output_format"], "markdown");
    assert!(
        json["result_format"].is_string(),
        "result_format should also be present"
    );
}

#[test]
fn test_extraction_config_clone_preserves_format() {
    // Test that cloning config preserves output_format
    let original = xberg::core::config::ExtractionConfig {
        output_format: xberg::core::config::OutputFormat::Html,
        ..xberg::core::config::ExtractionConfig::default()
    };

    let cloned = original.clone();

    assert_eq!(
        original.output_format, cloned.output_format,
        "Cloned config should preserve output_format"
    );
}