reflex-cache 0.2.2

Episodic memory and high-speed semantic cache for LLM responses
Documentation
use super::*;
use std::path::PathBuf;

#[test]
fn test_config_default() {
    let config = RerankerConfig::default();

    assert!(config.model_path.is_none());
    assert_eq!(config.threshold, DEFAULT_THRESHOLD);
}

#[test]
fn test_config_new() {
    let config = RerankerConfig::new("/models/modernbert.gguf");

    assert_eq!(
        config.model_path,
        Some(PathBuf::from("/models/modernbert.gguf"))
    );
    assert_eq!(config.threshold, DEFAULT_THRESHOLD);
}

#[test]
fn test_config_stub() {
    let config = RerankerConfig::stub();

    assert!(config.model_path.is_none());
}

#[test]
fn test_config_with_threshold() {
    let config = RerankerConfig::default().with_threshold(0.95);

    assert_eq!(config.threshold, 0.95);
}

#[test]
#[should_panic(expected = "threshold must be between 0.0 and 1.0")]
fn test_config_invalid_threshold_high() {
    let _ = RerankerConfig::default().with_threshold(1.5);
}

#[test]
#[should_panic(expected = "threshold must be between 0.0 and 1.0")]
fn test_config_invalid_threshold_negative() {
    let _ = RerankerConfig::default().with_threshold(-0.1);
}

#[test]
fn test_config_validate() {
    let valid = RerankerConfig::default();
    assert!(valid.validate().is_ok());

    let invalid = RerankerConfig {
        threshold: 1.5,
        ..Default::default()
    };
    assert!(invalid.validate().is_err());
}

#[test]
fn test_stub_checker_creation() {
    let checker = Reranker::stub().unwrap();

    assert!(!checker.is_model_loaded());
    assert_eq!(checker.threshold(), DEFAULT_THRESHOLD);
}

#[test]
fn test_load_with_missing_model() {
    let config = RerankerConfig::new("/nonexistent/path/model.gguf");
    let result = Reranker::load(config);

    assert!(result.is_err());
    assert!(matches!(
        result.unwrap_err(),
        RerankerError::ModelLoadFailed { .. }
    ));
}

#[test]
fn test_load_stub_config() {
    let config = RerankerConfig::stub();
    let result = Reranker::load(config);

    assert!(result.is_ok());
    let checker = result.unwrap();
    assert!(!checker.is_model_loaded());
}

#[test]
fn test_score_basic() {
    let checker = Reranker::stub().unwrap();

    let score = checker.score("query", "candidate").unwrap();

    assert!(score >= 0.0);
    assert!(score <= 1.0);
}

#[test]
fn test_score_determinism() {
    let checker = Reranker::stub().unwrap();

    let score1 = checker
        .score("What is Rust?", "Rust is a language")
        .unwrap();
    let score2 = checker
        .score("What is Rust?", "Rust is a language")
        .unwrap();

    assert_eq!(score1, score2);
}

#[test]
fn test_score_similar_texts_higher() {
    let checker = Reranker::stub().unwrap();

    let similar_score = checker
        .score("What is Rust?", "Rust is a systems programming language")
        .unwrap();
    let dissimilar_score = checker
        .score("What is Rust?", "Python is great for data science")
        .unwrap();

    assert!(similar_score > dissimilar_score);
}

#[test]
fn test_rerank_ordering() {
    let checker = Reranker::stub().unwrap();

    let candidates = vec![
        "Python is a scripting language",
        "Rust is a systems programming language",
        "JavaScript runs in browsers",
    ];

    let ranked = checker.rerank("What is Rust?", &candidates).unwrap();

    assert_eq!(ranked.len(), 3);
    assert!(ranked[0].1 >= ranked[1].1);
    assert!(ranked[1].1 >= ranked[2].1);
}

#[test]
fn test_rerank_with_threshold() {
    let config = RerankerConfig::stub().with_threshold(0.5);
    let checker = Reranker::load(config).unwrap();

    let candidates = vec!["Rust Rust Rust Rust Rust", "completely different topic"];

    let hits = checker
        .rerank_with_threshold("What is Rust?", &candidates)
        .unwrap();

    assert!(!hits.is_empty() || checker.threshold() > 0.9);
}

#[test]
fn test_rerank_empty_candidates() {
    let checker = Reranker::stub().unwrap();

    let candidates: Vec<&str> = vec![];
    let ranked = checker.rerank("query", &candidates).unwrap();

    assert!(ranked.is_empty());
}

#[test]
fn test_is_hit() {
    let config = RerankerConfig::stub().with_threshold(0.5);
    let checker = Reranker::load(config).unwrap();

    assert!(checker.is_hit(0.6));
    assert!(!checker.is_hit(0.5));
    assert!(!checker.is_hit(0.4));
}

#[test]
fn test_error_invalid_config() {
    let config = RerankerConfig {
        threshold: 2.0,
        ..Default::default()
    };

    let result = Reranker::load(config);
    assert!(result.is_err());

    let err = result.unwrap_err();
    assert!(matches!(err, RerankerError::InvalidConfig { .. }));
}

#[test]
fn test_error_messages_descriptive() {
    let err = RerankerError::ModelNotFound {
        path: PathBuf::from("/some/path"),
    };
    assert!(err.to_string().contains("/some/path"));

    let err = RerankerError::NotAvailable {
        reason: "test reason".to_string(),
    };
    assert!(err.to_string().contains("test reason"));
}