mical-cli-config 0.0.2

Internal config evaluator for mical-cli
Documentation
use mical_cli_config::{Config, Value};
use proptest::{prelude::*, property_test};

fn q<'a>(config: &'a Config, key: &str) -> Vec<Value<'a>> {
    config.query(key).collect()
}

#[test]
fn empty_config_returns_nothing() {
    let config = Config::from_kv_entries(std::iter::empty());
    assert!(q(&config, "any").is_empty());
    assert!(q(&config, "").is_empty());
}

#[test]
fn single_entry_exact_match() {
    let config = Config::from_kv_entries([("key", Value::String("val"))]);
    assert_eq!(q(&config, "key"), [Value::String("val")]);
}

#[test]
fn single_entry_no_match() {
    let config = Config::from_kv_entries([("key", Value::String("val"))]);
    assert!(q(&config, "other").is_empty());
    assert!(q(&config, "ke").is_empty());
    assert!(q(&config, "keys").is_empty());
}

#[test]
fn multiple_distinct_keys() {
    let config = Config::from_kv_entries([
        ("a", Value::String("1")),
        ("b", Value::Integer("2")),
        ("c", Value::Bool(true)),
    ]);
    assert_eq!(q(&config, "a"), [Value::String("1")]);
    assert_eq!(q(&config, "b"), [Value::Integer("2")]);
    assert_eq!(q(&config, "c"), [Value::Bool(true)]);
}

#[test]
fn duplicate_keys_return_in_insertion_order() {
    let config = Config::from_kv_entries([
        ("k", Value::String("first")),
        ("other", Value::Bool(false)),
        ("k", Value::String("second")),
        ("k", Value::String("third")),
    ]);
    assert_eq!(
        q(&config, "k"),
        [Value::String("first"), Value::String("second"), Value::String("third"),]
    );
    assert_eq!(q(&config, "other"), [Value::Bool(false)]);
}

#[test]
fn interleaved_duplicates_preserve_per_key_order() {
    let config = Config::from_kv_entries([
        ("x", Value::String("x1")),
        ("y", Value::String("y1")),
        ("x", Value::String("x2")),
        ("y", Value::String("y2")),
        ("x", Value::String("x3")),
    ]);
    assert_eq!(q(&config, "x"), [Value::String("x1"), Value::String("x2"), Value::String("x3")]);
    assert_eq!(q(&config, "y"), [Value::String("y1"), Value::String("y2")]);
}

#[test]
fn empty_string_key() {
    let config = Config::from_kv_entries([
        ("", Value::String("empty_key")),
        ("a", Value::String("a")),
        ("", Value::Integer("42")),
    ]);
    assert_eq!(q(&config, ""), [Value::String("empty_key"), Value::Integer("42")]);
}

#[test]
fn all_value_types() {
    let config = Config::from_kv_entries([
        ("b", Value::Bool(true)),
        ("b", Value::Bool(false)),
        ("i", Value::Integer("0")),
        ("i", Value::Integer("-99")),
        ("s", Value::String("")),
        ("s", Value::String("hello world")),
    ]);
    assert_eq!(q(&config, "b"), [Value::Bool(true), Value::Bool(false)]);
    assert_eq!(q(&config, "i"), [Value::Integer("0"), Value::Integer("-99")]);
    assert_eq!(q(&config, "s"), [Value::String(""), Value::String("hello world")]);
}

#[test]
fn prefix_of_key_does_not_match() {
    let config = Config::from_kv_entries([("abc", Value::String("1")), ("ab", Value::String("2"))]);
    assert!(q(&config, "a").is_empty());
    assert_eq!(q(&config, "ab"), [Value::String("2")]);
    assert_eq!(q(&config, "abc"), [Value::String("1")]);
}

#[test]
fn many_duplicates_preserve_insertion_order() {
    let entries: Vec<(&str, Value)> = (0..100)
        .map(|i| ("key", Value::Integer(if i % 2 == 0 { "even" } else { "odd" })))
        .collect();
    let config = Config::from_kv_entries(entries);
    let result = q(&config, "key");
    assert_eq!(result.len(), 100);
    for (i, v) in result.iter().enumerate() {
        let expected = if i % 2 == 0 { Value::Integer("even") } else { Value::Integer("odd") };
        assert_eq!(*v, expected, "mismatch at index {}", i);
    }
}

#[test]
fn many_keys_interleaved_query_each() {
    let keys = ["alpha", "beta", "gamma"];
    let entries: Vec<(&str, Value)> = (0..90)
        .map(|i| {
            let k = keys[i % 3];
            let v = Value::String(k);
            (k, v)
        })
        .collect();
    let config = Config::from_kv_entries(entries);
    for k in &keys {
        let result = q(&config, k);
        assert_eq!(result.len(), 30);
        assert!(result.iter().all(|v| *v == Value::String(k)));
    }
    assert!(q(&config, "delta").is_empty());
}

fn reference_query<'a>(entries: &'a [(String, String)], key: &str) -> Vec<&'a str> {
    entries.iter().filter(|(k, _)| k == key).map(|(_, v)| v.as_str()).collect()
}

#[property_test]
fn matches_linear_scan(entries: Vec<(String, String)>, query_key: String) {
    let config = Config::from_kv_entries(
        entries.iter().map(|(k, v)| (k.as_str(), Value::String(v.as_str()))),
    );
    let actual: Vec<&str> = config
        .query(&query_key)
        .map(|v| match v {
            Value::String(s) => s,
            _ => unreachable!(),
        })
        .collect();
    let expected = reference_query(&entries, &query_key);
    prop_assert_eq!(actual, expected);
}

#[property_test]
fn high_collision_matches_linear_scan(
    #[strategy = prop::collection::vec(("[a-c]{1,2}", "[a-z]{0,8}"), 0..100)] entries: Vec<(
        String,
        String,
    )>,
    #[strategy = "[a-c]{1,2}"] query_key: String,
) {
    let config = Config::from_kv_entries(
        entries.iter().map(|(k, v)| (k.as_str(), Value::String(v.as_str()))),
    );
    let actual: Vec<&str> = config
        .query(&query_key)
        .map(|v| match v {
            Value::String(s) => s,
            _ => unreachable!(),
        })
        .collect();
    let expected = reference_query(&entries, &query_key);
    prop_assert_eq!(actual, expected);
}

#[property_test]
fn absent_key_returns_empty(
    #[strategy = prop::collection::vec(("[a-c]{1,3}", "[a-z]{0,5}"), 0..15)] entries: Vec<(
        String,
        String,
    )>,
    #[strategy = "[x-z]{1,3}"] query_key: String,
) {
    let config = Config::from_kv_entries(
        entries.iter().map(|(k, v)| (k.as_str(), Value::String(v.as_str()))),
    );
    prop_assert_eq!(config.query(&query_key).count(), 0);
}