conproxy 0.2.1

Cache proxy server for heterogeneous RAG/vector search backends with cascade, scope, and MCP
Documentation
#![allow(
    clippy::arithmetic_side_effects,
    clippy::indexing_slicing,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic
)]

use super::*;
use crate::proxy::types::QueryRequest;
use std::time::Duration;

#[test]
fn test_pinecone_config_default() {
    let config = PineconeConfig::default();
    assert_eq!(config.base_url, "http://localhost:8080");
    assert!(config.include_metadata);
}

#[test]
fn test_pinecone_adapter_creation() {
    let adapter =
        PineconeAdapter::simple("http://localhost:8080", Duration::from_secs(30)).unwrap();
    assert_eq!(adapter.identifier(), "http://localhost:8080");
    assert_eq!(adapter.timeout(), Duration::from_secs(30));
    assert_eq!(adapter.query_mode(), QueryMode::VectorOnly);
    assert_eq!(adapter.metadata().adapter_type, "pinecone");
}

#[tokio::test]
async fn test_pinecone_query_text_unsupported() {
    let adapter = PineconeAdapter::simple("http://localhost:8080", Duration::from_secs(5)).unwrap();
    let req = QueryRequest {
        query: "hello".into(),
        top_k: Some(5),
        priority: None,
        upstream_id: None,
        upstream_type: None,
    };
    let err = adapter.query(&req).await.unwrap_err();
    assert!(matches!(err, UpstreamError::UnsupportedQueryType(_)));
}

#[tokio::test]
async fn test_pinecone_dim_mismatch_fail_fast() {
    let adapter = PineconeAdapter::new(PineconeConfig {
        base_url: "http://localhost:8080".into(),
        timeout: Duration::from_secs(5),
        dimensions: Some(8),
        ..Default::default()
    })
    .unwrap();
    let req = QueryRequest {
        query: String::new(),
        top_k: Some(5),
        priority: None,
        upstream_id: None,
        upstream_type: None,
    };
    let err = adapter
        .query_vector(&req, &[0.1, 0.2, 0.3])
        .await
        .unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("dimension mismatch"),
        "expected dim mismatch, got {msg}"
    );
}

#[tokio::test]
async fn test_pinecone_query_vector_mock() {
    use axum::routing::post;
    use axum::{Json, Router};
    use std::net::SocketAddr;
    use tokio::net::TcpListener;

    async fn handle_query(Json(body): Json<serde_json::Value>) -> Json<serde_json::Value> {
        assert!(body.get("vector").is_some());
        Json(serde_json::json!({
            "matches": [
                {
                    "id": "doc-1",
                    "score": 0.92,
                    "metadata": { "content": "pinecone hit", "tag": "a" }
                }
            ]
        }))
    }

    let app = Router::new().route("/query", post(handle_query));
    let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
        .await
        .unwrap();
    let addr = listener.local_addr().unwrap();
    tokio::spawn(async move {
        axum::serve(listener, app).await.unwrap();
    });

    let base = format!("http://{addr}");
    let adapter = PineconeAdapter::new(PineconeConfig {
        base_url: base,
        timeout: Duration::from_secs(5),
        dimensions: Some(4),
        api_key: Some("test-key".into()),
        ..Default::default()
    })
    .unwrap();

    let req = QueryRequest {
        query: String::new(),
        top_k: Some(5),
        priority: None,
        upstream_id: None,
        upstream_type: None,
    };
    let resp = adapter
        .query_vector(&req, &[0.1, 0.2, 0.3, 0.4])
        .await
        .expect("query_vector");
    assert_eq!(resp.results.len(), 1);
    assert_eq!(resp.results[0].id, "doc-1");
    assert!((resp.results[0].score - 0.92).abs() < 0.001);
    assert_eq!(resp.results[0].content, "pinecone hit");
}