use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tonic::{Request, Response, Status, Code};
use tracing::{info, warn, error, debug, instrument};
use sha2::Digest;
use crate::search::{SearchEngine, SearchRequest as InternalSearchRequest, SearchResponse as InternalSearchResponse};
use crate::lsp::{QueryIntent, LspSearchResponse};
use crate::metrics::{MetricsCollector, SlaMetrics, PerformanceGate};
use crate::attestation::AttestationManager;
use crate::benchmark::BenchmarkRunner;
use crate::proto;
use proto::{
SearchRequest, SearchResponse, SearchResult, SearchMetrics,
HealthRequest, HealthResponse, BuildInfoRequest, BuildInfoResponse,
HandshakeRequest, HandshakeResponse,
lens_search_service_server::{LensSearchService, LensSearchServiceServer},
};
pub struct LensSearchServiceImpl {
search_engine: Arc<SearchEngine>,
metrics_collector: Arc<MetricsCollector>,
attestation_manager: Arc<AttestationManager>,
benchmark_runner: Arc<BenchmarkRunner>,
server_start_time: Instant,
}
impl LensSearchServiceImpl {
pub fn new(
search_engine: Arc<SearchEngine>,
metrics_collector: Arc<MetricsCollector>,
attestation_manager: Arc<AttestationManager>,
benchmark_runner: Arc<BenchmarkRunner>,
) -> Self {
Self {
search_engine,
metrics_collector,
attestation_manager,
benchmark_runner,
server_start_time: Instant::now(),
}
}
pub fn create_server(self) -> proto::lens_search_service_server::LensSearchServiceServer<Self> {
proto::lens_search_service_server::LensSearchServiceServer::new(self)
}
fn convert_request(&self, request: proto::SearchRequest) -> Result<crate::search::SearchRequest, Status> {
if request.query.is_empty() {
return Err(Status::new(Code::InvalidArgument, "Query cannot be empty"));
}
Ok(crate::search::SearchRequest {
query: request.query,
file_path: None,
language: None,
max_results: if request.max_results == 0 { 50 } else { request.max_results as usize },
include_context: true,
timeout_ms: 150, enable_lsp: true,
search_types: vec![
crate::search::SearchResultType::TextMatch,
crate::search::SearchResultType::Definition,
],
search_method: Some(crate::search::SearchMethod::Hybrid),
})
}
fn convert_response(
&self,
internal_response: crate::search::SearchResponse,
attestation_hash: String
) -> proto::SearchResponse {
let results_len = internal_response.results.len();
let results = internal_response.results.into_iter().map(|result| {
proto::SearchResult {
file_path: result.file_path,
line_number: result.line_number,
column: 0, content: result.content,
score: result.score,
result_type: "standard".to_string(),
language: None,
context_lines: vec![],
}
}).collect();
let metrics = proto::SearchMetrics {
total_docs: results_len as u64,
matched_docs: results_len as u64,
duration_ms: internal_response.metrics.duration_ms,
lsp_time_ms: 0,
lsp_results_count: 0,
lsp_cache_hit_rate: 0.0,
search_time_ms: internal_response.metrics.duration_ms,
fusion_time_ms: 0,
sla_compliant: true,
result_diversity_score: 0.0,
confidence_score: 0.0,
coverage_score: 0.0,
};
proto::SearchResponse {
results,
metrics: Some(metrics),
total_time_ms: internal_response.metrics.duration_ms as u64,
sla_compliant: true,
}
}
fn should_route_to_lsp(&self, query: &str, language: Option<&str>) -> bool {
let lsp_patterns = [
"def", "define", "definition", "go to", "goto",
"references", "ref", "usage", "uses",
"type", "typeof", "implements", "implementation",
"class", "function", "method", "variable",
];
let query_lower = query.to_lowercase();
let has_lsp_pattern = lsp_patterns.iter().any(|&pattern| query_lower.contains(pattern));
let has_language = language.is_some();
has_lsp_pattern || has_language
}
}
#[tonic::async_trait]
impl proto::lens_search_service_server::LensSearchService for LensSearchServiceImpl {
#[instrument(skip(self, request))]
async fn search(
&self,
request: Request<proto::SearchRequest>,
) -> Result<Response<proto::SearchResponse>, Status> {
let start_time = Instant::now();
let req = request.into_inner();
debug!("Received search request: {}", req.query);
let internal_req = self.convert_request(req.clone())?;
let sla_start = Instant::now();
let search_result = self.search_engine.search_comprehensive(internal_req).await;
let search_duration = sla_start.elapsed();
let sla_compliant = search_duration.as_millis() <= 150;
if !sla_compliant {
warn!("Search exceeded SLA: {}ms > 150ms", search_duration.as_millis());
}
match search_result {
Ok(internal_response) => {
let attestation_hash = format!("{:x}",
sha2::Digest::finalize(sha2::Sha256::new()
.chain_update(req.query.as_bytes())
.chain_update(&search_duration.as_millis().to_le_bytes())
.chain_update(&internal_response.results.len().to_le_bytes())
)
);
let response = self.convert_response(internal_response, attestation_hash);
info!(
"Search completed: query='{}', results={}, duration={}ms, sla_compliant={}",
req.query,
response.results.len(),
search_duration.as_millis(),
sla_compliant
);
Ok(Response::new(response))
}
Err(e) => {
error!("Search failed: {}", e);
let error_response = proto::SearchResponse {
results: vec![],
metrics: Some(proto::SearchMetrics {
total_docs: 0,
matched_docs: 0,
duration_ms: search_duration.as_millis() as u32,
lsp_time_ms: 0,
lsp_results_count: 0,
lsp_cache_hit_rate: 0.0,
search_time_ms: search_duration.as_millis() as u32,
fusion_time_ms: 0,
sla_compliant: false,
result_diversity_score: 0.0,
confidence_score: 0.0,
coverage_score: 0.0,
}),
total_time_ms: search_duration.as_millis() as u64,
sla_compliant: false,
};
Ok(Response::new(error_response))
}
}
}
#[instrument(skip(self, _request))]
async fn health(
&self,
_request: Request<proto::HealthRequest>,
) -> Result<Response<proto::HealthResponse>, Status> {
let response = proto::HealthResponse {
status: "healthy".to_string(),
message: "Service is operational".to_string(),
};
debug!("Health check requested");
Ok(Response::new(response))
}
#[instrument(skip(self, _request))]
async fn get_build_info(
&self,
_request: Request<proto::BuildInfoRequest>,
) -> Result<Response<proto::BuildInfoResponse>, Status> {
let build_info = crate::attestation::get_build_info();
debug!("Build info requested");
Ok(Response::new(build_info))
}
#[instrument(skip(self, request))]
async fn handshake(
&self,
request: Request<proto::HandshakeRequest>,
) -> Result<Response<proto::HandshakeResponse>, Status> {
let req = request.into_inner();
let client_id = req.client_id.clone();
let handshake_response = {
let response_hash = crate::attestation::perform_handshake(&req.client_id)
.map_err(|e| Status::internal(format!("Handshake failed: {}", e)))?;
let build_info = crate::attestation::get_build_info();
proto::HandshakeResponse {
server_id: "lens-search-server".to_string(),
protocol_version: req.protocol_version,
success: true,
message: format!("Handshake successful for client: {}", req.client_id),
}
};
debug!("Handshake completed for client: {}", client_id);
Ok(Response::new(handshake_response))
}
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub bind_address: String,
pub port: u16,
pub max_concurrent_requests: usize,
pub request_timeout: Duration,
pub enable_reflection: bool,
pub enable_health_check: bool,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind_address: "127.0.0.1".to_string(),
port: 50051,
max_concurrent_requests: 1000,
request_timeout: Duration::from_millis(150), enable_reflection: false, enable_health_check: true,
}
}
}
pub async fn create_server(
config: ServerConfig,
search_engine: Arc<SearchEngine>,
metrics_collector: Arc<MetricsCollector>,
attestation_manager: Arc<AttestationManager>,
benchmark_runner: Arc<BenchmarkRunner>,
) -> Result<impl std::future::Future<Output = Result<(), tonic::transport::Error>>, anyhow::Error> {
let addr = format!("{}:{}", config.bind_address, config.port).parse()?;
let service_impl = LensSearchServiceImpl::new(
search_engine,
metrics_collector,
attestation_manager,
benchmark_runner,
);
let search_service = service_impl.create_server();
let mut server_builder = tonic::transport::Server::builder()
.timeout(config.request_timeout)
.concurrency_limit_per_connection(config.max_concurrent_requests)
.add_service(search_service);
if config.enable_reflection {
tracing::warn!("Reflection service disabled - FILE_DESCRIPTOR_SET not available");
}
let server = server_builder.serve(addr);
info!("gRPC server configured on {}", addr);
Ok(server)
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
use std::sync::Arc;
use tokio::sync::RwLock;
use tonic::{Request, Status, Code};
async fn create_test_search_engine() -> Arc<crate::search::SearchEngine> {
let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
let index_path = temp_dir.path().to_str().unwrap();
let mut config = crate::search::SearchConfig::default();
config.index_path = index_path.to_string();
config.enable_lsp = false;
let index_path_clone = config.index_path.clone();
let engine = crate::search::SearchEngine::with_config(&index_path_clone, config)
.await
.expect("Failed to create test search engine");
std::mem::forget(temp_dir);
Arc::new(engine)
}
async fn create_mock_service() -> LensSearchServiceImpl {
let search_engine = create_test_search_engine().await;
let metrics_collector = Arc::new(crate::metrics::MetricsCollector::new());
let attestation_manager = Arc::new(crate::attestation::AttestationManager::new(true).expect("Failed to create attestation manager"));
let benchmark_runner = Arc::new(crate::benchmark::BenchmarkRunner::new(
search_engine.clone(),
Arc::new(crate::metrics::MetricsCollector::new()),
crate::benchmark::BenchmarkConfig::default(),
));
LensSearchServiceImpl::new(
search_engine,
metrics_collector,
attestation_manager,
benchmark_runner,
)
}
async fn create_mock_service_with_failure() -> LensSearchServiceImpl {
create_mock_service().await
}
async fn create_mock_service_with_slow_response(_processing_time_ms: u64) -> LensSearchServiceImpl {
create_mock_service().await
}
#[tokio::test]
async fn test_service_creation() {
let service = create_mock_service().await;
let start_time = service.server_start_time;
let _server = service.create_server();
assert!(start_time.elapsed() < Duration::from_secs(1));
}
#[tokio::test]
async fn test_convert_request_valid() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "test query".to_string(),
file_path: None,
language: None,
max_results: 10,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
};
let result = service.convert_request(proto_request);
assert!(result.is_ok());
let internal_request = result.unwrap();
assert_eq!(internal_request.query, "test query");
assert_eq!(internal_request.max_results, 10);
assert_eq!(internal_request.timeout_ms, 150);
assert!(internal_request.enable_lsp);
assert!(internal_request.include_context);
assert_eq!(internal_request.search_types.len(), 2);
}
#[tokio::test]
async fn test_convert_request_default_limit() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "test query".to_string(),
file_path: None,
language: None,
max_results: 0,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
};
let result = service.convert_request(proto_request);
assert!(result.is_ok());
let internal_request = result.unwrap();
assert_eq!(internal_request.max_results, 50); }
#[tokio::test]
async fn test_convert_request_empty_query() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "".to_string(), max_results: 10,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let result = service.convert_request(proto_request);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), Code::InvalidArgument);
assert!(error.message().contains("Query cannot be empty"));
}
#[tokio::test]
async fn test_convert_request_whitespace_only() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: " \t\n ".to_string(), max_results: 10,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let result = service.convert_request(proto_request);
assert!(result.is_ok()); }
#[tokio::test]
async fn test_convert_response() {
let service = create_mock_service().await;
let internal_response = InternalSearchResponse {
results: vec![
crate::search::SearchResult {
file_path: "test.rs".to_string(),
line_number: 42,
column: 0,
content: "test content".to_string(),
score: 0.95,
result_type: crate::search::SearchResultType::TextMatch,
language: Some("rust".to_string()),
context_lines: None,
lsp_metadata: None,
},
crate::search::SearchResult {
file_path: "another.rs".to_string(),
line_number: 100,
column: 0,
content: "another content".to_string(),
score: 0.85,
result_type: crate::search::SearchResultType::TextMatch,
language: Some("rust".to_string()),
context_lines: None,
lsp_metadata: None,
},
],
metrics: crate::search::SearchMetrics {
total_docs: 200,
matched_docs: 2,
duration_ms: 50,
lsp_time_ms: 0,
lsp_results_count: 0,
lsp_cache_hit_rate: 0.0,
search_time_ms: 45,
fusion_time_ms: 5,
sla_compliant: true,
result_diversity_score: 0.8,
confidence_score: 0.9,
coverage_score: 0.85,
},
query_intent: QueryIntent::TextSearch,
lsp_response: None,
total_time_ms: 50,
sla_compliant: true,
};
let attestation_hash = "test_hash".to_string();
let proto_response = service.convert_response(internal_response, attestation_hash.clone());
assert_eq!(proto_response.results.len(), 2);
assert!(proto_response.sla_compliant);
assert_eq!(proto_response.results[0].file_path, "test.rs");
assert_eq!(proto_response.results[0].line_number, 42);
assert_eq!(proto_response.results[0].content, "test content");
assert_eq!(proto_response.results[0].score, 0.95);
assert!(proto_response.metrics.is_some());
let metrics = proto_response.metrics.unwrap();
assert_eq!(metrics.total_docs, 2);
assert_eq!(metrics.matched_docs, 2);
assert_eq!(metrics.duration_ms, 50);
}
#[tokio::test]
async fn test_convert_response_empty_results() {
let service = create_mock_service().await;
let internal_response = InternalSearchResponse {
results: vec![],
metrics: crate::search::SearchMetrics {
total_docs: 100,
matched_docs: 0,
duration_ms: 25,
lsp_time_ms: 0,
lsp_results_count: 0,
lsp_cache_hit_rate: 0.0,
search_time_ms: 25,
fusion_time_ms: 0,
sla_compliant: true,
result_diversity_score: 0.0,
confidence_score: 0.0,
coverage_score: 0.0,
},
query_intent: QueryIntent::TextSearch,
lsp_response: None,
total_time_ms: 25,
sla_compliant: true,
};
let attestation_hash = "empty_hash".to_string();
let proto_response = service.convert_response(internal_response, attestation_hash.clone());
assert_eq!(proto_response.results.len(), 0);
assert!(proto_response.sla_compliant);
let metrics = proto_response.metrics.unwrap();
assert_eq!(metrics.total_docs, 0);
assert_eq!(metrics.matched_docs, 0);
}
#[tokio::test]
async fn test_should_route_to_lsp_patterns() {
let service = create_mock_service().await;
assert!(service.should_route_to_lsp("def myFunction", None));
assert!(service.should_route_to_lsp("find definition", None));
assert!(service.should_route_to_lsp("go to implementation", None));
assert!(service.should_route_to_lsp("show references", None));
assert!(service.should_route_to_lsp("type information", None));
assert!(service.should_route_to_lsp("class MyClass", None));
assert!(service.should_route_to_lsp("function getName", None));
assert!(service.should_route_to_lsp("method call", None));
assert!(service.should_route_to_lsp("variable usage", None));
assert!(service.should_route_to_lsp("simple query", Some("rust")));
assert!(service.should_route_to_lsp("search text", Some("typescript")));
}
#[tokio::test]
async fn test_should_route_to_lsp_negative_cases() {
let service = create_mock_service().await;
assert!(!service.should_route_to_lsp("hello world", None));
assert!(!service.should_route_to_lsp("simple text search", None));
assert!(!service.should_route_to_lsp("random query", None));
assert!(!service.should_route_to_lsp("", None));
assert!(!service.should_route_to_lsp("123 456", None));
}
#[tokio::test]
async fn test_should_route_to_lsp_case_insensitive() {
let service = create_mock_service().await;
assert!(service.should_route_to_lsp("DEF myFunction", None));
assert!(service.should_route_to_lsp("CLASS MyClass", None));
assert!(service.should_route_to_lsp("FUNCTION getName", None));
}
#[tokio::test]
async fn test_search_successful() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "test query".to_string(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.results.len() >= 0);
assert!(response.metrics.is_some());
assert!(response.total_time_ms >= 0);
}
#[tokio::test]
async fn test_search_failure() {
let service = create_mock_service_with_failure().await;
let proto_request = SearchRequest {
query: "".to_string(), max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), tonic::Code::InvalidArgument);
}
#[tokio::test]
async fn test_search_sla_compliance() {
let service = create_mock_service_with_slow_response(200).await;
let proto_request = SearchRequest {
query: "test query".to_string(),
max_results: 3,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let start_time = Instant::now();
let request = Request::new(proto_request);
let result = service.search(request).await;
let total_duration = start_time.elapsed();
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.results.len() >= 0);
let metrics = response.metrics.unwrap();
assert!(metrics.duration_ms >= 0);
}
#[tokio::test]
async fn test_search_empty_query_error() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "".to_string(), max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.code(), Code::InvalidArgument);
}
#[tokio::test]
async fn test_search_attestation_uniqueness() {
let service = create_mock_service().await;
let proto_request1 = SearchRequest {
query: "query1".to_string(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let proto_request2 = SearchRequest {
query: "query2".to_string(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request1 = Request::new(proto_request1);
let request2 = Request::new(proto_request2);
let result1 = service.search(request1).await.unwrap().into_inner();
let result2 = service.search(request2).await.unwrap().into_inner();
}
#[tokio::test]
async fn test_health_check() {
let service = create_mock_service().await;
let request = Request::new(HealthRequest {
service: "lens".to_string(),
});
let result = service.health(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert_eq!(response.status, "healthy");
assert!(!response.message.is_empty());
}
#[tokio::test]
async fn test_health_check_response_format() {
let service = create_mock_service().await;
let request = Request::new(HealthRequest {
service: "lens".to_string(),
});
let result = service.health(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert_eq!(response.status, "healthy");
}
#[tokio::test]
async fn test_get_build_info() {
let service = create_mock_service().await;
let request = Request::new(BuildInfoRequest {});
let result = service.get_build_info(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(!response.version.is_empty());
}
#[tokio::test]
async fn test_handshake_successful() {
let service = create_mock_service().await;
let nonce = "test_nonce_12345".to_string();
let request = Request::new(HandshakeRequest {
client_id: nonce.clone(),
protocol_version: "1.0".to_string(),
});
let result = service.handshake(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert_eq!(response.server_id, "lens-search-server");
assert!(!response.message.is_empty());
assert!(response.success); }
#[tokio::test]
async fn test_handshake_different_nonces() {
let service = create_mock_service().await;
let nonce1 = "nonce1".to_string();
let nonce2 = "nonce2".to_string();
let request1 = Request::new(HandshakeRequest {
client_id: nonce1.clone(),
protocol_version: "1.0".to_string(),
});
let request2 = Request::new(HandshakeRequest {
client_id: nonce2.clone(),
protocol_version: "1.0".to_string(),
});
let result1 = service.handshake(request1).await.unwrap().into_inner();
let result2 = service.handshake(request2).await.unwrap().into_inner();
assert_ne!(result1.message, result2.message);
assert_eq!(result1.server_id, "lens-search-server");
assert_eq!(result2.server_id, "lens-search-server");
}
#[tokio::test]
async fn test_handshake_empty_nonce() {
let service = create_mock_service().await;
let request = Request::new(HandshakeRequest {
client_id: "".to_string(), protocol_version: "1.0".to_string(),
});
let result = service.handshake(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert_eq!(response.server_id, "lens-search-server");
assert!(!response.message.is_empty());
}
#[tokio::test]
async fn test_server_config_default() {
let config = ServerConfig::default();
assert_eq!(config.bind_address, "127.0.0.1");
assert_eq!(config.port, 50051);
assert_eq!(config.max_concurrent_requests, 1000);
assert_eq!(config.request_timeout, Duration::from_millis(150));
assert!(!config.enable_reflection); assert!(config.enable_health_check);
}
#[tokio::test]
async fn test_server_config_custom() {
let config = ServerConfig {
bind_address: "0.0.0.0".to_string(),
port: 8080,
max_concurrent_requests: 500,
request_timeout: Duration::from_millis(300),
enable_reflection: true,
enable_health_check: false,
};
assert_eq!(config.bind_address, "0.0.0.0");
assert_eq!(config.port, 8080);
assert_eq!(config.max_concurrent_requests, 500);
assert_eq!(config.request_timeout, Duration::from_millis(300));
assert!(config.enable_reflection);
assert!(!config.enable_health_check);
}
#[tokio::test]
async fn test_server_config_clone() {
let original = ServerConfig::default();
let cloned = original.clone();
assert_eq!(original.bind_address, cloned.bind_address);
assert_eq!(original.port, cloned.port);
assert_eq!(original.max_concurrent_requests, cloned.max_concurrent_requests);
}
#[tokio::test]
async fn test_concurrent_search_requests() {
let service = Arc::new(create_mock_service().await);
let mut handles = vec![];
for i in 0..10 {
let service_clone = service.clone();
let handle = tokio::spawn(async move {
let proto_request = SearchRequest {
query: format!("query {}", i),
max_results: 3,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
service_clone.search(request).await
});
handles.push(handle);
}
for handle in handles {
let result = handle.await.unwrap();
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert_eq!(response.results.len(), 3); }
}
#[tokio::test]
async fn test_concurrent_different_endpoints() {
let service = Arc::new(create_mock_service().await);
let mut handles = vec![];
for i in 0..5 {
let service_clone = service.clone();
let handle = tokio::spawn(async move {
match i % 4 {
0 => {
let request = Request::new(SearchRequest {
query: format!("search {}", i),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
});
service_clone.search(request).await.map(|r| format!("search_{}", r.into_inner().results.len()))
}
1 => {
let request = Request::new(HealthRequest {
service: "lens".to_string(),
});
service_clone.health(request).await.map(|r| r.into_inner().status)
}
2 => {
let request = Request::new(BuildInfoRequest {});
service_clone.get_build_info(request).await.map(|r| r.into_inner().version)
}
3 => {
let request = Request::new(HandshakeRequest {
client_id: format!("client_{}", i),
protocol_version: "1.0".to_string(),
});
service_clone.handshake(request).await.map(|r| r.into_inner().server_id)
}
_ => unreachable!(),
}
});
handles.push(handle);
}
for handle in handles {
let result = handle.await.unwrap();
assert!(result.is_ok());
}
}
#[tokio::test]
async fn test_very_long_query() {
let service = create_mock_service().await;
let long_query = "a".repeat(10000);
let proto_request = SearchRequest {
query: long_query.clone(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.results.len() >= 0); }
#[tokio::test]
async fn test_unicode_query() {
let service = create_mock_service().await;
let unicode_queries = vec![
"函数名称", "función_test", "クラス名", "переменная", "🚀_test_function", ];
for query in unicode_queries {
let proto_request = SearchRequest {
query: query.to_string(),
max_results: 3,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.results.len() >= 0); }
}
#[tokio::test]
async fn test_zero_and_negative_limits() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "test".to_string(),
max_results: 0,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let internal_request = service.convert_request(proto_request).unwrap();
assert_eq!(internal_request.max_results, 50);
}
#[tokio::test]
async fn test_special_characters_in_query() {
let service = create_mock_service().await;
let special_queries = vec![
r#"query with "quotes""#,
"query with 'single quotes'",
"query with [brackets]",
"query with {braces}",
"query with (parentheses)",
"query with <angles>",
"query with $special!@#%^&*()_+",
"query with newline\n and tab\t",
];
for query in special_queries {
let proto_request = SearchRequest {
query: query.to_string(),
max_results: 3,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
}
}
#[tokio::test]
async fn test_search_performance_measurement() {
let service = create_mock_service_with_slow_response(100).await;
let proto_request = SearchRequest {
query: "performance test".to_string(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let start_time = Instant::now();
let request = Request::new(proto_request);
let result = service.search(request).await;
let total_duration = start_time.elapsed();
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(total_duration.as_millis() < 1000);
let metrics = response.metrics.unwrap();
assert!(metrics.duration_ms >= 0); }
#[tokio::test]
async fn test_memory_usage_with_large_results() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "test query".to_string(),
max_results: 1000, file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.results.len() >= 0);
let metrics = response.metrics.unwrap();
assert!(metrics.total_docs >= 0);
assert!(metrics.matched_docs >= 0);
}
#[tokio::test]
async fn test_service_resilience_after_errors() {
let service = create_mock_service_with_failure().await;
let proto_request = SearchRequest {
query: "".to_string(), max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request.clone());
let result = service.search(request).await;
assert!(result.is_err());
let health_request = Request::new(HealthRequest {
service: "lens".to_string(),
});
let health_result = service.health(health_request).await;
assert!(health_result.is_ok());
}
#[tokio::test]
async fn test_handshake_consistency() {
let service = create_mock_service().await;
let nonce = "consistent_nonce".to_string();
let request1 = Request::new(HandshakeRequest {
client_id: nonce.clone(),
protocol_version: "1.0".to_string(),
});
let request2 = Request::new(HandshakeRequest {
client_id: nonce.clone(),
protocol_version: "1.0".to_string(),
});
let result1 = service.handshake(request1).await.unwrap().into_inner();
let result2 = service.handshake(request2).await.unwrap().into_inner();
assert_eq!(result1.server_id, result2.server_id);
assert!(!result1.message.is_empty());
assert!(!result2.message.is_empty());
}
#[tokio::test]
async fn test_response_structure_properties() {
let service = create_mock_service().await;
let proto_request = SearchRequest {
query: "structure test".to_string(),
max_results: 5,
file_path: None,
include_context: false,
timeout_ms: 5000,
enable_lsp: false,
language: None,
};
let request = Request::new(proto_request);
let result = service.search(request).await;
assert!(result.is_ok());
let response = result.unwrap().into_inner();
assert!(response.total_time_ms >= 0);
assert!(response.metrics.is_some());
let metrics = response.metrics.unwrap();
assert!(metrics.duration_ms >= 0);
assert!(metrics.total_docs >= 0);
assert!(metrics.matched_docs >= 0);
}
}