conproxy 0.2.1

Cache proxy server for heterogeneous RAG/vector search backends with cascade, scope, and MCP
Documentation
use super::*;

fn test_config() -> PgvectorConfig {
    PgvectorConfig {
        url: "postgresql://localhost:5432/test".to_string(),
        table: "documents".to_string(),
        embedding_column: "embedding".to_string(),
        content_column: "content".to_string(),
        title_column: Some("title".to_string()),
        metadata_columns: vec!["source".to_string(), "category".to_string()],
        distance_metric: DistanceMetric::Cosine,
        dimensions: Some(384),
        timeout_secs: 30,
    }
}

#[test]
fn test_distance_metric_operators() {
    assert_eq!(DistanceMetric::Cosine.operator(), "<=>");
    assert_eq!(DistanceMetric::L2.operator(), "<->");
    assert_eq!(DistanceMetric::InnerProduct.operator(), "<#>");
}

#[test]
fn test_distance_metric_similarity_cosine() {
    let m = DistanceMetric::Cosine;
    assert!((m.to_similarity(0.0) - 1.0).abs() < 0.001);
    assert!((m.to_similarity(0.5) - 0.5).abs() < 0.001);
    assert!((m.to_similarity(1.0) - 0.0).abs() < 0.001);
}

#[test]
fn test_distance_metric_similarity_l2() {
    let m = DistanceMetric::L2;
    assert!((m.to_similarity(0.0) - 1.0).abs() < 0.001);
    assert!((m.to_similarity(1.0) - 0.5).abs() < 0.001);
}

#[test]
fn test_distance_metric_similarity_inner_product() {
    let m = DistanceMetric::InnerProduct;
    assert!((m.to_similarity(-0.9) - 0.9).abs() < 0.001);
    assert!((m.to_similarity(-0.5) - 0.5).abs() < 0.001);
}

#[test]
fn test_format_vector() {
    assert_eq!(format_vector(&[0.1, 0.2, 0.3]), "[0.1,0.2,0.3]");
    assert_eq!(format_vector(&[]), "[]");
    assert_eq!(format_vector(&[1.0]), "[1]");
}

#[test]
fn test_build_query_sql_cosine() {
    let config = test_config();
    let sql = build_query_sql(&config);
    assert!(sql.contains("SELECT"));
    assert!(sql.contains("content"));
    assert!(sql.contains("title"));
    assert!(sql.contains("<=>"));
    assert!(sql.contains("ORDER BY embedding <=> $1::vector"));
    assert!(sql.contains("LIMIT $2"));
}

#[test]
fn test_build_query_sql_l2() {
    let mut config = test_config();
    config.distance_metric = DistanceMetric::L2;
    let sql = build_query_sql(&config);
    assert!(sql.contains("<->"));
}

#[test]
fn test_build_query_sql_inner_product() {
    let mut config = test_config();
    config.distance_metric = DistanceMetric::InnerProduct;
    let sql = build_query_sql(&config);
    assert!(sql.contains("<#>"));
}

#[test]
fn test_build_select_columns_minimal() {
    let config = PgvectorConfig {
        url: "postgresql://localhost/db".to_string(),
        table: "docs".to_string(),
        embedding_column: "vec".to_string(),
        content_column: "body".to_string(),
        title_column: None,
        metadata_columns: vec![],
        distance_metric: DistanceMetric::Cosine,
        dimensions: None,
        timeout_secs: 30,
    };
    let cols = build_select_columns(&config);
    assert_eq!(cols, "body, vec <=> $1::vector AS distance");
}

#[test]
fn test_build_select_columns_full() {
    let config = test_config();
    let cols = build_select_columns(&config);
    assert!(cols.contains("content"));
    assert!(cols.contains("title"));
    assert!(cols.contains("source"));
    assert!(cols.contains("category"));
    assert!(cols.contains("AS distance"));
}

#[test]
fn test_config_validate_ok() {
    let config = test_config();
    assert!(config.validate().is_ok());
}

#[test]
fn test_config_validate_empty_url() {
    let mut config = test_config();
    config.url = String::new();
    assert!(config.validate().unwrap_err().contains("url"));
}

#[test]
fn test_config_validate_empty_table() {
    let mut config = test_config();
    config.table = String::new();
    assert!(config.validate().unwrap_err().contains("table"));
}

#[test]
fn test_config_validate_unsafe_identifier() {
    let mut config = test_config();
    config.table = "docs; DROP TABLE--".to_string();
    assert!(config.validate().unwrap_err().contains("unsafe"));
}

#[test]
fn test_safe_identifier() {
    assert!(is_safe_identifier("documents"));
    assert!(is_safe_identifier("my_table_123"));
    assert!(is_safe_identifier("col_A"));
    assert!(!is_safe_identifier(""));
    assert!(!is_safe_identifier("table name"));
    assert!(!is_safe_identifier("table;drop"));
    assert!(!is_safe_identifier("table\"name"));
    assert!(!is_safe_identifier(&"x".repeat(129)));
}

#[test]
fn test_distance_metric_default() {
    assert_eq!(DistanceMetric::default(), DistanceMetric::Cosine);
}

#[test]
fn test_config_serde_roundtrip() {
    let config = test_config();
    let json = serde_json::to_string(&config).unwrap();
    let restored: PgvectorConfig = serde_json::from_str(&json).unwrap();
    assert_eq!(restored.table, "documents");
    assert_eq!(restored.distance_metric, DistanceMetric::Cosine);
    assert_eq!(restored.dimensions, Some(384));
}