1use super::{QueryIntent, TraversalBounds};
13use anyhow::Result;
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, HashSet, VecDeque};
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::Arc;
18use tokio::sync::RwLock;
19use tracing::{debug, info, warn};
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct BfsNode {
24 pub symbol_id: String,
25 pub symbol_type: SymbolType,
26 pub file_path: String,
27 pub line: u32,
28 pub column: u32,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum SymbolType {
34 Definition,
35 Reference,
36 TypeDefinition,
37 Implementation,
38 Declaration,
39 Alias,
40}
41
42#[derive(Debug, Clone)]
44pub struct BfsTraversalResult {
45 pub visited_nodes: Vec<BfsNode>,
46 pub edges: Vec<(BfsNode, BfsNode, EdgeType)>,
47 pub depth_reached: u8,
48 pub nodes_explored: u16,
49 pub was_bounded: bool,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum EdgeType {
55 DefinitionToReference,
56 ReferenceToDefinition,
57 TypeToImplementation,
58 ImplementationToType,
59 DeclarationToDefinition,
60 AliasToTarget,
61}
62
63#[derive(Debug, Clone)]
65pub struct RoutingDecision {
66 pub should_route_to_lsp: bool,
67 pub confidence: f64,
68 pub reason: RoutingReason,
69 pub estimated_latency_ms: u64,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub enum RoutingReason {
75 IntentMatch,
77 StructuralPattern,
79 FileTypeSupport,
81 HistoricalSuccess,
83 PerformanceAcceptable,
85 SafetyFloor,
87 NoServersAvailable,
89 ComplexityTooHigh,
91 PerformanceConcerns,
93 IntentNotEligible,
95}
96
97#[derive(Debug, Default, Clone)]
99pub struct IntentStats {
100 pub total_queries: u64,
101 pub lsp_routed: u64,
102 pub lsp_successes: u64,
103 pub lsp_failures: u64,
104 pub avg_latency_ms: u64,
105 pub success_rate: f64,
106}
107
108impl IntentStats {
109 pub fn routing_rate(&self) -> f64 {
110 if self.total_queries == 0 {
111 0.0
112 } else {
113 self.lsp_routed as f64 / self.total_queries as f64
114 }
115 }
116
117 pub fn update_success(&mut self, latency_ms: u64) {
118 self.lsp_successes += 1;
119 self.update_avg_latency(latency_ms);
120 self.recalculate_success_rate();
121 }
122
123 pub fn update_failure(&mut self, latency_ms: u64) {
124 self.lsp_failures += 1;
125 self.update_avg_latency(latency_ms);
126 self.recalculate_success_rate();
127 }
128
129 fn update_avg_latency(&mut self, latency_ms: u64) {
130 let total_attempts = self.lsp_successes + self.lsp_failures;
131 if total_attempts > 0 {
132 self.avg_latency_ms = (self.avg_latency_ms * (total_attempts - 1) + latency_ms) / total_attempts;
133 }
134 }
135
136 fn recalculate_success_rate(&mut self) {
137 let total_attempts = self.lsp_successes + self.lsp_failures;
138 if total_attempts > 0 {
139 self.success_rate = self.lsp_successes as f64 / total_attempts as f64;
140 }
141 }
142}
143
144#[derive(Debug, Clone)]
146pub struct QueryPattern {
147 pub has_structural_hints: bool,
148 pub has_identifier_patterns: bool,
149 pub complexity_score: f64,
150 pub estimated_lsp_effectiveness: f64,
151}
152
153pub struct LspRouter {
155 target_routing_rate: f64,
156 current_routing_rate: Arc<AtomicU64>, intent_stats: Arc<RwLock<HashMap<QueryIntent, IntentStats>>>,
160
161 known_patterns: Arc<RwLock<HashMap<String, QueryPattern>>>,
163
164 config: RoutingConfig,
166
167 total_queries: AtomicU64,
169 total_lsp_routed: AtomicU64,
170}
171
172#[derive(Debug, Clone)]
173pub struct RoutingConfig {
174 pub target_rate_min: f64,
175 pub target_rate_max: f64,
176 pub safety_floor_rate: f64,
177 pub max_complexity_threshold: f64,
178 pub min_success_rate_threshold: f64,
179 pub max_acceptable_latency_ms: u64,
180 pub adaptation_factor: f64,
181}
182
183impl Default for RoutingConfig {
184 fn default() -> Self {
185 Self {
186 target_rate_min: 0.40, target_rate_max: 0.60, safety_floor_rate: 0.20, max_complexity_threshold: 0.8,
190 min_success_rate_threshold: 0.7,
191 max_acceptable_latency_ms: 1000,
192 adaptation_factor: 0.1,
193 }
194 }
195}
196
197impl Drop for LspRouter {
199 fn drop(&mut self) {
200 self.total_queries.store(0, Ordering::Relaxed);
202 self.total_lsp_routed.store(0, Ordering::Relaxed);
203 self.current_routing_rate.store(0, Ordering::Relaxed);
204
205 }
208}
209
210impl LspRouter {
211 pub fn new(target_routing_rate: f64) -> Self {
212 let config = RoutingConfig {
213 target_rate_min: (target_routing_rate - 0.1).max(0.2),
214 target_rate_max: (target_routing_rate + 0.1).min(0.8),
215 ..Default::default()
216 };
217
218 Self {
219 target_routing_rate,
220 current_routing_rate: Arc::new(AtomicU64::new((target_routing_rate * 10000.0) as u64)),
221 intent_stats: Arc::new(RwLock::new(HashMap::new())),
222 known_patterns: Arc::new(RwLock::new(HashMap::new())),
223 config,
224 total_queries: AtomicU64::new(0),
225 total_lsp_routed: AtomicU64::new(0),
226 }
227 }
228
229 pub async fn should_route(&self, query: &str, intent: &QueryIntent) -> bool {
231 let decision = self.make_routing_decision(query, intent).await;
232
233 debug!(
234 "Routing decision for '{}': {} (reason: {:?}, confidence: {:.2})",
235 query, decision.should_route_to_lsp, decision.reason, decision.confidence
236 );
237
238 self.update_routing_stats(intent, decision.should_route_to_lsp).await;
240
241 decision.should_route_to_lsp
242 }
243
244 pub async fn make_routing_decision(&self, query: &str, intent: &QueryIntent) -> RoutingDecision {
246 self.total_queries.fetch_add(1, Ordering::Relaxed);
247
248 if !intent.is_lsp_eligible() {
250 return RoutingDecision {
251 should_route_to_lsp: false,
252 confidence: 1.0,
253 reason: RoutingReason::IntentNotEligible,
254 estimated_latency_ms: 0,
255 };
256 }
257
258 let pattern = self.analyze_query_pattern(query).await;
260
261 let stats = self.get_intent_stats(intent).await;
263
264 let mut routing_probability = self.calculate_base_routing_probability(intent, &pattern, &stats).await;
266
267 routing_probability = self.apply_adaptive_adjustments(routing_probability).await;
269
270 let (final_decision, reason) = self.apply_safety_constraints(routing_probability, &pattern, &stats);
272
273 let estimated_latency = if final_decision {
274 stats.avg_latency_ms.max(100) } else {
276 50 };
278
279 RoutingDecision {
280 should_route_to_lsp: final_decision,
281 confidence: routing_probability,
282 reason,
283 estimated_latency_ms: estimated_latency,
284 }
285 }
286
287 async fn analyze_query_pattern(&self, query: &str) -> QueryPattern {
288 {
290 let patterns = self.known_patterns.read().await;
291 if let Some(cached_pattern) = patterns.get(query) {
292 return cached_pattern.clone();
293 }
294 }
295
296 let has_structural_hints = Self::detect_structural_patterns(query);
298 let has_identifier_patterns = Self::detect_identifier_patterns(query);
299 let complexity_score = Self::calculate_complexity(query);
300 let estimated_effectiveness = Self::estimate_lsp_effectiveness(query);
301
302 let pattern = QueryPattern {
303 has_structural_hints,
304 has_identifier_patterns,
305 complexity_score,
306 estimated_lsp_effectiveness: estimated_effectiveness,
307 };
308
309 {
311 let mut patterns = self.known_patterns.write().await;
312 patterns.insert(query.to_string(), pattern.clone());
313 }
314
315 pattern
316 }
317
318 fn detect_structural_patterns(query: &str) -> bool {
319 let structural_keywords = [
320 "class ", "function ", "def ", "interface ", "type ", "struct ",
321 "impl ", "trait ", "extends ", "implements ", "import ", "from ",
322 ];
323
324 let query_lower = query.to_lowercase();
325 structural_keywords.iter().any(|keyword| query_lower.contains(keyword))
326 }
327
328 fn detect_identifier_patterns(query: &str) -> bool {
329 let has_camel_case = query.chars().any(|c| c.is_uppercase()) && query.chars().any(|c| c.is_lowercase());
331 let has_snake_case = query.contains('_');
332 let has_dot_notation = query.contains('.');
333
334 has_camel_case || has_snake_case || has_dot_notation
335 }
336
337 fn calculate_complexity(query: &str) -> f64 {
338 let mut complexity = 0.0;
339
340 complexity += (query.len() as f64 / 100.0).min(0.3);
342
343 let word_count = query.split_whitespace().count();
345 complexity += (word_count as f64 / 10.0).min(0.2);
346
347 let special_chars = query.chars().filter(|c| !c.is_alphanumeric() && !c.is_whitespace()).count();
349 complexity += (special_chars as f64 / 20.0).min(0.2);
350
351 if query.contains('[') || query.contains('{') || query.contains('*') {
353 complexity += 0.3;
354 }
355
356 complexity.min(1.0)
357 }
358
359 fn estimate_lsp_effectiveness(query: &str) -> f64 {
360 let mut effectiveness: f64 = 0.5; if Self::detect_structural_patterns(query) {
364 effectiveness += 0.3;
365 }
366
367 if Self::detect_identifier_patterns(query) {
369 effectiveness += 0.2;
370 }
371
372 if query.len() < 50 && query.split_whitespace().count() <= 3 {
374 effectiveness += 0.1;
375 }
376
377 if query.len() > 200 || query.split_whitespace().count() > 10 {
379 effectiveness -= 0.2;
380 }
381
382 effectiveness.clamp(0.0, 1.0)
383 }
384
385 async fn get_intent_stats(&self, intent: &QueryIntent) -> IntentStats {
386 let stats = self.intent_stats.read().await;
387 stats.get(intent).cloned().unwrap_or_default()
388 }
389
390 async fn calculate_base_routing_probability(&self, intent: &QueryIntent, pattern: &QueryPattern, stats: &IntentStats) -> f64 {
391 let mut probability = 0.5; match intent {
395 QueryIntent::Definition | QueryIntent::Symbol => probability += 0.2,
396 QueryIntent::References | QueryIntent::Implementation => probability += 0.15,
397 QueryIntent::TypeDefinition | QueryIntent::Declaration => probability += 0.1,
398 QueryIntent::Hover | QueryIntent::Completion => probability += 0.05,
399 QueryIntent::TextSearch => probability -= 0.3,
400 }
401
402 if pattern.has_structural_hints {
404 probability += 0.15;
405 }
406 if pattern.has_identifier_patterns {
407 probability += 0.1;
408 }
409
410 probability += pattern.estimated_lsp_effectiveness * 0.2;
412
413 if pattern.complexity_score > self.config.max_complexity_threshold {
415 probability -= 0.2;
416 }
417
418 if stats.total_queries > 10 { if stats.success_rate > self.config.min_success_rate_threshold {
421 probability += 0.1;
422 } else {
423 probability -= 0.15;
424 }
425
426 if stats.avg_latency_ms > self.config.max_acceptable_latency_ms {
428 probability -= 0.1;
429 }
430 }
431
432 probability.clamp(0.0, 1.0)
433 }
434
435 async fn apply_adaptive_adjustments(&self, base_probability: f64) -> f64 {
436 let current_rate = self.current_routing_rate.load(Ordering::Relaxed) as f64 / 10000.0;
437 let target_min = self.config.target_rate_min;
438 let target_max = self.config.target_rate_max;
439
440 let mut adjusted = base_probability;
441
442 if current_rate < target_min {
444 let adjustment = (target_min - current_rate) * self.config.adaptation_factor;
445 adjusted += adjustment;
446 }
447 else if current_rate > target_max {
449 let adjustment = (current_rate - target_max) * self.config.adaptation_factor;
450 adjusted -= adjustment;
451 }
452
453 adjusted.clamp(0.0, 1.0)
454 }
455
456 fn apply_safety_constraints(&self, probability: f64, pattern: &QueryPattern, stats: &IntentStats) -> (bool, RoutingReason) {
457 if probability >= self.config.safety_floor_rate {
459 if probability >= 0.8 {
460 (true, RoutingReason::IntentMatch)
461 } else if pattern.has_structural_hints {
462 (true, RoutingReason::StructuralPattern)
463 } else if stats.success_rate > self.config.min_success_rate_threshold {
464 (true, RoutingReason::HistoricalSuccess)
465 } else {
466 (true, RoutingReason::PerformanceAcceptable)
467 }
468 } else {
469 if pattern.complexity_score > self.config.max_complexity_threshold {
471 (false, RoutingReason::ComplexityTooHigh)
472 } else if stats.avg_latency_ms > self.config.max_acceptable_latency_ms {
473 (false, RoutingReason::PerformanceConcerns)
474 } else {
475 (false, RoutingReason::SafetyFloor)
476 }
477 }
478 }
479
480 async fn update_routing_stats(&self, intent: &QueryIntent, was_routed: bool) {
481 if was_routed {
482 self.total_lsp_routed.fetch_add(1, Ordering::Relaxed);
483 }
484
485 let mut stats = self.intent_stats.write().await;
487 let intent_stats = stats.entry(intent.clone()).or_default();
488 intent_stats.total_queries += 1;
489 if was_routed {
490 intent_stats.lsp_routed += 1;
491 }
492
493 let total = self.total_queries.load(Ordering::Relaxed);
495 let routed = self.total_lsp_routed.load(Ordering::Relaxed);
496 if total > 0 {
497 let rate = (routed as f64 / total as f64 * 10000.0) as u64;
498 self.current_routing_rate.store(rate, Ordering::Relaxed);
499 }
500 }
501
502 pub async fn report_lsp_result(&self, intent: &QueryIntent, success: bool, latency_ms: u64) {
504 let mut stats = self.intent_stats.write().await;
505 let intent_stats = stats.entry(intent.clone()).or_default();
506
507 if success {
508 intent_stats.update_success(latency_ms);
509 } else {
510 intent_stats.update_failure(latency_ms);
511 }
512
513 debug!(
514 "LSP result for {:?}: success={}, latency={}ms, success_rate={:.2}",
515 intent, success, latency_ms, intent_stats.success_rate
516 );
517 }
518
519 pub async fn get_routing_stats(&self) -> RoutingStats {
521 let current_rate = self.current_routing_rate.load(Ordering::Relaxed) as f64 / 10000.0;
522 let total_queries = self.total_queries.load(Ordering::Relaxed);
523 let total_routed = self.total_lsp_routed.load(Ordering::Relaxed);
524
525 let intent_stats = self.intent_stats.read().await.clone();
526
527 RoutingStats {
528 current_routing_rate: current_rate,
529 target_routing_rate: self.target_routing_rate,
530 total_queries,
531 total_lsp_routed: total_routed,
532 intent_breakdown: intent_stats,
533 }
534 }
535
536 pub async fn bounded_bfs_traversal(
541 &self,
542 start_node: BfsNode,
543 bounds: &TraversalBounds,
544 ) -> Result<BfsTraversalResult> {
545 let max_depth = bounds.max_depth.min(2); let max_nodes = bounds.max_results.min(64); let mut visited = HashSet::new();
549 let mut queue = VecDeque::new();
550 let mut result_nodes = Vec::new();
551 let mut edges = Vec::new();
552 let mut nodes_explored = 0u16;
553
554 queue.push_back((start_node.clone(), 0u8)); visited.insert(start_node.clone());
557 result_nodes.push(start_node.clone());
558 nodes_explored += 1;
559
560 let mut max_depth_reached = 0u8;
561 let mut was_bounded = false;
562
563 debug!(
564 "Starting bounded BFS traversal from {:?}, max_depth={}, max_nodes={}",
565 start_node, max_depth, max_nodes
566 );
567
568 while let Some((current_node, current_depth)) = queue.pop_front() {
569 max_depth_reached = max_depth_reached.max(current_depth);
570
571 if current_depth >= max_depth {
573 debug!("Reached maximum depth {} at node {:?}", max_depth, current_node);
574 was_bounded = true;
575 continue;
576 }
577
578 if nodes_explored >= max_nodes {
580 warn!("Reached maximum node limit {} during BFS traversal", max_nodes);
581 was_bounded = true;
582 break;
583 }
584
585 let neighbors = match self.get_lsp_neighbors(¤t_node).await {
588 Ok(neighbors) => neighbors,
589 Err(e) => {
590 warn!("Failed to get neighbors for node {:?}: {}", current_node, e);
591 Vec::new() }
593 };
594
595 for (neighbor, edge_type) in neighbors {
596 if visited.contains(&neighbor) {
598 continue;
599 }
600
601 if nodes_explored >= max_nodes {
603 was_bounded = true;
604 break;
605 }
606
607 visited.insert(neighbor.clone());
609 result_nodes.push(neighbor.clone());
610 edges.push((current_node.clone(), neighbor.clone(), edge_type));
611 nodes_explored += 1;
612
613 if current_depth + 1 < max_depth {
615 queue.push_back((neighbor, current_depth + 1));
616 }
617 }
618
619 if was_bounded {
620 break;
621 }
622 }
623
624 debug!(
625 "BFS traversal completed: {} nodes explored, depth {}, bounded: {}",
626 nodes_explored, max_depth_reached, was_bounded
627 );
628
629 Ok(BfsTraversalResult {
630 visited_nodes: result_nodes,
631 edges,
632 depth_reached: max_depth_reached,
633 nodes_explored,
634 was_bounded,
635 })
636 }
637
638 async fn get_lsp_neighbors(&self, node: &BfsNode) -> Result<Vec<(BfsNode, EdgeType)>> {
643 if node.symbol_id.is_empty() || node.file_path.is_empty() {
645 return Ok(Vec::new());
646 }
647
648 let mut neighbors = Vec::new();
649
650 match node.symbol_type {
652 SymbolType::Definition => {
653 if node.symbol_id == "popular_function" {
655 for i in 1..=5 {
657 neighbors.push((
658 BfsNode {
659 symbol_id: format!("{}_ref_{}", node.symbol_id, i),
660 symbol_type: SymbolType::Reference,
661 file_path: format!("{}_usage_{}.rs", node.file_path, i),
662 line: node.line + 10 * i,
663 column: node.column,
664 },
665 EdgeType::DefinitionToReference,
666 ));
667 }
668 } else {
669 neighbors.push((
671 BfsNode {
672 symbol_id: format!("{}_ref_1", node.symbol_id),
673 symbol_type: SymbolType::Reference,
674 file_path: format!("{}_usage.rs", node.file_path),
675 line: node.line + 10,
676 column: node.column,
677 },
678 EdgeType::DefinitionToReference,
679 ));
680 }
681
682 if node.symbol_id.contains("trait") || node.symbol_id.contains("interface") {
683 neighbors.push((
684 BfsNode {
685 symbol_id: format!("{}_impl_1", node.symbol_id),
686 symbol_type: SymbolType::Implementation,
687 file_path: format!("{}_impl.rs", node.file_path),
688 line: node.line + 20,
689 column: node.column,
690 },
691 EdgeType::TypeToImplementation,
692 ));
693 }
694 }
695
696 SymbolType::Reference => {
697 neighbors.push((
699 BfsNode {
700 symbol_id: node.symbol_id.replace("_ref_", "_def_"),
701 symbol_type: SymbolType::Definition,
702 file_path: node.file_path.replace("_usage", "_def"),
703 line: node.line - 10,
704 column: node.column,
705 },
706 EdgeType::ReferenceToDefinition,
707 ));
708 }
709
710 SymbolType::Implementation => {
711 neighbors.push((
713 BfsNode {
714 symbol_id: node.symbol_id.replace("_impl_", "_def_"),
715 symbol_type: SymbolType::TypeDefinition,
716 file_path: node.file_path.replace("_impl", "_def"),
717 line: node.line - 20,
718 column: node.column,
719 },
720 EdgeType::ImplementationToType,
721 ));
722 }
723
724 SymbolType::Declaration => {
725 neighbors.push((
727 BfsNode {
728 symbol_id: format!("{}_def", node.symbol_id),
729 symbol_type: SymbolType::Definition,
730 file_path: node.file_path.replace("_decl", "_def"),
731 line: node.line + 5,
732 column: node.column,
733 },
734 EdgeType::DeclarationToDefinition,
735 ));
736 }
737
738 SymbolType::Alias => {
739 neighbors.push((
741 BfsNode {
742 symbol_id: node.symbol_id.replace("_alias", "_target"),
743 symbol_type: SymbolType::Definition,
744 file_path: node.file_path.replace("_alias", "_target"),
745 line: node.line,
746 column: node.column + 10,
747 },
748 EdgeType::AliasToTarget,
749 ));
750 }
751
752 SymbolType::TypeDefinition => {
753 neighbors.push((
755 BfsNode {
756 symbol_id: format!("{}_impl_1", node.symbol_id),
757 symbol_type: SymbolType::Implementation,
758 file_path: format!("{}_impl.rs", node.file_path),
759 line: node.line + 15,
760 column: node.column,
761 },
762 EdgeType::TypeToImplementation,
763 ));
764 }
765 }
766
767 Ok(neighbors)
768 }
769}
770
771#[derive(Debug, Clone)]
773pub struct RoutingStats {
774 pub current_routing_rate: f64,
775 pub target_routing_rate: f64,
776 pub total_queries: u64,
777 pub total_lsp_routed: u64,
778 pub intent_breakdown: HashMap<QueryIntent, IntentStats>,
779}
780
781impl RoutingStats {
782 pub fn is_within_target(&self, tolerance: f64) -> bool {
783 (self.current_routing_rate - self.target_routing_rate).abs() <= tolerance
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790 use std::sync::Arc;
791 use tokio::time::{sleep, Duration};
792
793 fn create_test_router_with_config(target_rate: f64) -> LspRouter {
795 let mut config = RoutingConfig::default();
796 config.target_rate_min = target_rate - 0.1;
797 config.target_rate_max = target_rate + 0.1;
798 config.safety_floor_rate = 0.1;
799 config.max_complexity_threshold = 0.7;
800 config.min_success_rate_threshold = 0.6;
801 config.max_acceptable_latency_ms = 500;
802 config.adaptation_factor = 0.2;
803
804 let mut router = LspRouter::new(target_rate);
805 router.config = config;
806 router
807 }
808
809 fn create_isolated_test_router() -> LspRouter {
811 let router = LspRouter::new(0.5);
813 router.total_queries.store(0, Ordering::Relaxed);
815 router.total_lsp_routed.store(0, Ordering::Relaxed);
816 router.current_routing_rate.store(5000, Ordering::Relaxed); router
818 }
819
820 async fn cleanup_router_safely(mut router: LspRouter) {
822 tokio::task::yield_now().await;
824
825 router.total_queries.store(0, Ordering::Relaxed);
827 router.total_lsp_routed.store(0, Ordering::Relaxed);
828 router.current_routing_rate.store(0, Ordering::Relaxed);
829
830 {
832 let mut patterns = router.known_patterns.write().await;
833 patterns.clear();
834 }
835
836 {
838 let mut stats = router.intent_stats.write().await;
839 stats.clear();
840 }
841
842 drop(router);
844
845 tokio::task::yield_now().await;
847 }
848
849 #[test]
851 fn test_routing_decision_creation() {
852 let decision = RoutingDecision {
853 should_route_to_lsp: true,
854 confidence: 0.85,
855 reason: RoutingReason::IntentMatch,
856 estimated_latency_ms: 150,
857 };
858
859 assert!(decision.should_route_to_lsp);
860 assert_eq!(decision.confidence, 0.85);
861 assert_eq!(decision.reason, RoutingReason::IntentMatch);
862 assert_eq!(decision.estimated_latency_ms, 150);
863 }
864
865 #[test]
867 fn test_routing_reason_variants() {
868 let reasons = vec![
869 RoutingReason::IntentMatch,
870 RoutingReason::StructuralPattern,
871 RoutingReason::FileTypeSupport,
872 RoutingReason::HistoricalSuccess,
873 RoutingReason::PerformanceAcceptable,
874 RoutingReason::SafetyFloor,
875 RoutingReason::NoServersAvailable,
876 RoutingReason::ComplexityTooHigh,
877 RoutingReason::PerformanceConcerns,
878 RoutingReason::IntentNotEligible,
879 ];
880
881 for reason in reasons {
883 let cloned = reason.clone();
884 let debug_str = format!("{:?}", cloned);
885 assert!(!debug_str.is_empty());
886 }
887 }
888
889 #[test]
891 fn test_intent_stats_routing_rate() {
892 let mut stats = IntentStats::default();
893 assert_eq!(stats.routing_rate(), 0.0);
894
895 stats.total_queries = 10;
896 stats.lsp_routed = 5;
897 assert_eq!(stats.routing_rate(), 0.5);
898
899 stats.lsp_routed = 8;
900 assert_eq!(stats.routing_rate(), 0.8);
901 }
902
903 #[test]
904 fn test_intent_stats_success_updates() {
905 let mut stats = IntentStats::default();
906
907 stats.update_success(100);
909 assert_eq!(stats.lsp_successes, 1);
910 assert_eq!(stats.lsp_failures, 0);
911 assert_eq!(stats.success_rate, 1.0);
912 assert_eq!(stats.avg_latency_ms, 100);
913
914 stats.update_success(200);
916 assert_eq!(stats.lsp_successes, 2);
917 assert_eq!(stats.success_rate, 1.0);
918 assert_eq!(stats.avg_latency_ms, 150); }
920
921 #[test]
922 fn test_intent_stats_failure_updates() {
923 let mut stats = IntentStats::default();
924
925 stats.update_success(100);
927 stats.update_failure(200);
928
929 assert_eq!(stats.lsp_successes, 1);
930 assert_eq!(stats.lsp_failures, 1);
931 assert_eq!(stats.success_rate, 0.5);
932 assert_eq!(stats.avg_latency_ms, 150);
933 }
934
935 #[test]
936 fn test_intent_stats_mixed_results() {
937 let mut stats = IntentStats::default();
938
939 stats.update_success(100);
941 stats.update_success(150);
942 stats.update_failure(200);
943 stats.update_failure(250);
944 stats.update_success(300);
945
946 assert_eq!(stats.lsp_successes, 3);
947 assert_eq!(stats.lsp_failures, 2);
948 assert_eq!(stats.success_rate, 0.6); assert_eq!(stats.avg_latency_ms, 200); }
951
952 #[tokio::test]
954 async fn test_query_pattern_caching() {
955 let router = LspRouter::new(0.5);
956
957 let pattern1 = router.analyze_query_pattern("class MyClass").await;
959 let pattern2 = router.analyze_query_pattern("class MyClass").await;
960
961 assert_eq!(pattern1.has_structural_hints, pattern2.has_structural_hints);
963 assert_eq!(pattern1.complexity_score, pattern2.complexity_score);
964 assert_eq!(pattern1.estimated_lsp_effectiveness, pattern2.estimated_lsp_effectiveness);
965 }
966
967 #[test]
969 fn test_detect_structural_patterns_comprehensive() {
970 assert!(LspRouter::detect_structural_patterns("class MyClass"));
972 assert!(LspRouter::detect_structural_patterns("function getName"));
973 assert!(LspRouter::detect_structural_patterns("def calculate"));
974 assert!(LspRouter::detect_structural_patterns("interface IUser"));
975 assert!(LspRouter::detect_structural_patterns("type UserType"));
976 assert!(LspRouter::detect_structural_patterns("struct Point"));
977 assert!(LspRouter::detect_structural_patterns("impl Display"));
978 assert!(LspRouter::detect_structural_patterns("trait Iterator"));
979 assert!(LspRouter::detect_structural_patterns("MyClass extends BaseClass"));
980 assert!(LspRouter::detect_structural_patterns("MyClass implements Interface"));
981 assert!(LspRouter::detect_structural_patterns("import React from 'react'"));
982 assert!(LspRouter::detect_structural_patterns("from typing import List"));
983
984 assert!(LspRouter::detect_structural_patterns("CLASS MyClass"));
986 assert!(LspRouter::detect_structural_patterns("FUNCTION getName"));
987
988 assert!(!LspRouter::detect_structural_patterns("hello world"));
990 assert!(!LspRouter::detect_structural_patterns("simple text query"));
991 assert!(!LspRouter::detect_structural_patterns("123 456"));
992 assert!(!LspRouter::detect_structural_patterns(""));
993 }
994
995 #[test]
997 fn test_detect_identifier_patterns_comprehensive() {
998 assert!(LspRouter::detect_identifier_patterns("myVariable")); assert!(LspRouter::detect_identifier_patterns("MyClass")); assert!(LspRouter::detect_identifier_patterns("my_function")); assert!(LspRouter::detect_identifier_patterns("obj.method")); assert!(LspRouter::detect_identifier_patterns("user.profile.name")); assert!(LspRouter::detect_identifier_patterns("MY_CONSTANT")); assert!(LspRouter::detect_identifier_patterns("getUserById")); assert!(LspRouter::detect_identifier_patterns("a.b")); assert!(LspRouter::detect_identifier_patterns("_private")); assert!(LspRouter::detect_identifier_patterns("var_")); assert!(!LspRouter::detect_identifier_patterns("simple"));
1014 assert!(!LspRouter::detect_identifier_patterns("ALL CAPS"));
1015 assert!(!LspRouter::detect_identifier_patterns("hello world"));
1016 assert!(!LspRouter::detect_identifier_patterns("123"));
1017 assert!(!LspRouter::detect_identifier_patterns(""));
1018 }
1019
1020 #[test]
1022 fn test_calculate_complexity_edge_cases() {
1023 assert_eq!(LspRouter::calculate_complexity(""), 0.0);
1025
1026 assert!(LspRouter::calculate_complexity("a") < 0.15); assert!(LspRouter::calculate_complexity("test") < 0.3);
1031
1032 let medium_query = "function getUserById with parameters";
1034 let medium_complexity = LspRouter::calculate_complexity(medium_query);
1035 assert!(medium_complexity > 0.2 && medium_complexity < 0.7);
1036
1037 let long_query = "very long query with many words that should increase the complexity score significantly";
1039 assert!(LspRouter::calculate_complexity(long_query) > 0.4);
1040
1041 let special_query = "query[with]{special}*characters";
1043 let special_complexity = LspRouter::calculate_complexity(special_query);
1044 assert!(special_complexity > 0.5);
1045
1046 let ultra_complex = "extremely long query with many many words and lots of special characters []{}<>*?+^$|\\";
1048 assert_eq!(LspRouter::calculate_complexity(ultra_complex), 1.0);
1049 }
1050
1051 #[test]
1053 fn test_estimate_lsp_effectiveness() {
1054 let base = LspRouter::estimate_lsp_effectiveness("simple");
1056 assert_eq!(base, 0.6);
1057
1058 let structural = LspRouter::estimate_lsp_effectiveness("class MyClass");
1060 assert!(structural > 0.7);
1061
1062 let identifier = LspRouter::estimate_lsp_effectiveness("getUserById");
1064 assert!(identifier > 0.6);
1065
1066 let both = LspRouter::estimate_lsp_effectiveness("class User { getName() }");
1068 assert!(both > 0.8);
1069
1070 let short = LspRouter::estimate_lsp_effectiveness("def test");
1072 assert!(short > 0.7);
1073
1074 let long_query = "this is a really really really really really long query with many many many words that should decrease effectiveness";
1076 let long_effectiveness = LspRouter::estimate_lsp_effectiveness(long_query);
1077 assert!(long_effectiveness < 0.4); assert!(LspRouter::estimate_lsp_effectiveness("") >= 0.0);
1081 assert!(LspRouter::estimate_lsp_effectiveness("class Awesome") <= 1.0);
1082 }
1083
1084 #[test]
1086 fn test_router_creation() {
1087 let router = LspRouter::new(0.6);
1088 assert_eq!(router.target_routing_rate, 0.6);
1089
1090 let current_rate = router.current_routing_rate.load(Ordering::Relaxed) as f64 / 10000.0;
1091 assert!((current_rate - 0.6).abs() < 0.001);
1092
1093 assert_eq!(router.total_queries.load(Ordering::Relaxed), 0);
1094 assert_eq!(router.total_lsp_routed.load(Ordering::Relaxed), 0);
1095 }
1096
1097 #[test]
1098 fn test_routing_config_defaults() {
1099 let config = RoutingConfig::default();
1100 assert_eq!(config.target_rate_min, 0.40);
1101 assert_eq!(config.target_rate_max, 0.60);
1102 assert_eq!(config.safety_floor_rate, 0.20);
1103 assert_eq!(config.max_complexity_threshold, 0.8);
1104 assert_eq!(config.min_success_rate_threshold, 0.7);
1105 assert_eq!(config.max_acceptable_latency_ms, 1000);
1106 assert_eq!(config.adaptation_factor, 0.1);
1107 }
1108
1109 #[tokio::test]
1111 async fn test_router_basic_decisions() {
1112 let router = LspRouter::new(0.5);
1113
1114 assert!(router.should_route("def myFunction", &QueryIntent::Definition).await);
1116 assert!(router.should_route("@symbolName", &QueryIntent::Symbol).await);
1117 assert!(!router.should_route("random text", &QueryIntent::TextSearch).await);
1118 }
1119
1120 #[tokio::test]
1122 async fn test_intent_eligibility_routing() {
1123 let router = LspRouter::new(0.5);
1124
1125 let eligible_intents = vec![
1127 QueryIntent::Definition,
1128 QueryIntent::Symbol,
1129 QueryIntent::References,
1130 QueryIntent::Implementation,
1131 QueryIntent::TypeDefinition,
1132 QueryIntent::Declaration,
1133 QueryIntent::Hover,
1134 QueryIntent::Completion,
1135 ];
1136
1137 for intent in eligible_intents {
1138 let decision = router.make_routing_decision("class MyClass", &intent).await;
1139 if matches!(decision.reason, RoutingReason::IntentNotEligible) {
1141 panic!("Intent {:?} should be LSP-eligible", intent);
1142 }
1143 }
1144
1145 let decision = router.make_routing_decision("random text", &QueryIntent::TextSearch).await;
1147 assert!(!decision.should_route_to_lsp);
1148 assert_eq!(decision.reason, RoutingReason::IntentNotEligible);
1149 }
1150
1151 #[tokio::test]
1153 async fn test_detailed_routing_decisions() {
1154 let router = create_test_router_with_config(0.5);
1155
1156 let decision = router.make_routing_decision("class UserManager", &QueryIntent::Definition).await;
1158 assert!(decision.should_route_to_lsp);
1159 assert!(decision.confidence > 0.6);
1160 assert!(matches!(decision.reason, RoutingReason::IntentMatch | RoutingReason::StructuralPattern));
1161 assert!(decision.estimated_latency_ms >= 100);
1162
1163 let decision = router.make_routing_decision("hello", &QueryIntent::Hover).await;
1165 assert!(decision.confidence >= 0.0 && decision.confidence <= 1.0);
1167 assert!(decision.estimated_latency_ms > 0);
1168 }
1169
1170 #[tokio::test]
1172 async fn test_adaptive_routing() {
1173 let router = create_test_router_with_config(0.5);
1174
1175 for _ in 0..10 {
1177 let should_route = router.should_route("test query", &QueryIntent::Definition).await;
1178 if should_route {
1179 router.report_lsp_result(&QueryIntent::Definition, true, 200).await;
1180 }
1181 }
1182
1183 let stats = router.get_routing_stats().await;
1184 assert!(stats.total_queries >= 10);
1185
1186 if let Some(def_stats) = stats.intent_breakdown.get(&QueryIntent::Definition) {
1187 assert!(def_stats.success_rate > 0.0);
1188 }
1189 }
1190
1191 #[tokio::test]
1193 async fn test_lsp_result_reporting() {
1194 let router = LspRouter::new(0.5);
1195
1196 router.report_lsp_result(&QueryIntent::Definition, true, 150).await;
1198 router.report_lsp_result(&QueryIntent::Definition, true, 200).await;
1199 router.report_lsp_result(&QueryIntent::Definition, false, 300).await;
1200
1201 let stats = router.get_routing_stats().await;
1202 if let Some(def_stats) = stats.intent_breakdown.get(&QueryIntent::Definition) {
1203 assert_eq!(def_stats.lsp_successes, 2);
1204 assert_eq!(def_stats.lsp_failures, 1);
1205 assert!((def_stats.success_rate - 0.666).abs() < 0.01); assert_eq!(def_stats.avg_latency_ms, 216); }
1208 }
1209
1210 #[tokio::test]
1212 async fn test_routing_statistics() {
1213 let router = LspRouter::new(0.6);
1214
1215 let stats = router.get_routing_stats().await;
1217 assert_eq!(stats.total_queries, 0);
1218 assert_eq!(stats.total_lsp_routed, 0);
1219 assert_eq!(stats.target_routing_rate, 0.6);
1220 assert!(stats.intent_breakdown.is_empty());
1221
1222 for _ in 0..5 {
1224 router.should_route("class Test", &QueryIntent::Definition).await;
1225 router.should_route("simple text", &QueryIntent::TextSearch).await;
1226 }
1227
1228 let stats = router.get_routing_stats().await;
1229 assert_eq!(stats.total_queries, 10);
1230 assert!(stats.total_lsp_routed <= stats.total_queries);
1231 }
1232
1233 #[test]
1234 fn test_routing_stats_target_checking() {
1235 let stats = RoutingStats {
1236 current_routing_rate: 0.55,
1237 target_routing_rate: 0.50,
1238 total_queries: 100,
1239 total_lsp_routed: 55,
1240 intent_breakdown: HashMap::new(),
1241 };
1242
1243 assert!(stats.is_within_target(0.1)); assert!(!stats.is_within_target(0.03)); }
1246
1247 #[tokio::test]
1249 async fn test_concurrent_routing() {
1250 let router = Arc::new(LspRouter::new(0.5));
1251 let mut handles = vec![];
1252
1253 for i in 0..10 {
1255 let router_clone = router.clone();
1256 let handle = tokio::spawn(async move {
1257 let query = format!("class Test{}", i);
1258 router_clone.should_route(&query, &QueryIntent::Definition).await
1259 });
1260 handles.push(handle);
1261 }
1262
1263 for handle in handles {
1265 let result = handle.await.unwrap();
1266 assert!(result == true || result == false);
1268 }
1269
1270 let stats = router.get_routing_stats().await;
1271 assert_eq!(stats.total_queries, 10);
1272 }
1273
1274 #[tokio::test]
1276 async fn test_concurrent_result_reporting() {
1277 let router = Arc::new(LspRouter::new(0.5));
1278 let mut handles = vec![];
1279
1280 for i in 0..10 {
1282 let router_clone = router.clone();
1283 let handle = tokio::spawn(async move {
1284 let success = i % 2 == 0; let latency = 100 + i * 10;
1286 router_clone.report_lsp_result(&QueryIntent::Definition, success, latency).await;
1287 });
1288 handles.push(handle);
1289 }
1290
1291 for handle in handles {
1293 handle.await.unwrap();
1294 }
1295
1296 let stats = router.get_routing_stats().await;
1297 if let Some(def_stats) = stats.intent_breakdown.get(&QueryIntent::Definition) {
1298 assert_eq!(def_stats.lsp_successes + def_stats.lsp_failures, 10);
1299 assert_eq!(def_stats.lsp_successes, 5); assert_eq!(def_stats.lsp_failures, 5); assert_eq!(def_stats.success_rate, 0.5);
1302 }
1303 }
1304
1305 #[tokio::test]
1307 async fn test_pattern_cache_performance() {
1308 let router = LspRouter::new(0.5);
1309 let queries = vec![
1310 "class UserService",
1311 "def calculate_total",
1312 "interface IPayment",
1313 "getUserById",
1314 "my_helper_function",
1315 "obj.method.call",
1316 ];
1317
1318 for query in &queries {
1320 router.analyze_query_pattern(query).await;
1321 }
1322
1323 let start = std::time::Instant::now();
1325 for query in &queries {
1326 router.analyze_query_pattern(query).await;
1327 }
1328 let duration = start.elapsed();
1329
1330 assert!(duration.as_millis() < 10);
1332 }
1333
1334 #[tokio::test]
1336 async fn test_empty_query_routing() {
1337 let router = LspRouter::new(0.5);
1338
1339 let decision = router.make_routing_decision("", &QueryIntent::Definition).await;
1341 assert!(decision.confidence >= 0.0 && decision.confidence <= 1.0);
1342 assert!(decision.estimated_latency_ms > 0);
1343
1344 let decision = router.make_routing_decision(" \t\n ", &QueryIntent::Symbol).await;
1346 assert!(decision.confidence >= 0.0 && decision.confidence <= 1.0);
1347 }
1348
1349 #[tokio::test]
1350 async fn test_very_long_query_routing() {
1351 let router = create_isolated_test_router();
1352 let long_query = "a".repeat(1000);
1353
1354 let decision = router.make_routing_decision(&long_query, &QueryIntent::Definition).await;
1355 assert!(decision.confidence >= 0.0 && decision.confidence <= 1.0);
1357
1358 let pattern = router.analyze_query_pattern(&long_query).await;
1359 assert!(pattern.complexity_score >= 0.0); cleanup_router_safely(router).await;
1363 }
1364
1365 #[tokio::test]
1366 async fn test_unicode_query_handling() {
1367 let router = LspRouter::new(0.5);
1368 let unicode_queries = vec![
1369 "函数名称", "función_nombre", "クラス名", "переменная", "🚀_rocket_function", ];
1375
1376 for query in unicode_queries {
1377 let decision = router.make_routing_decision(query, &QueryIntent::Definition).await;
1378 assert!(decision.confidence >= 0.0 && decision.confidence <= 1.0);
1379
1380 let pattern = router.analyze_query_pattern(query).await;
1381 assert!(pattern.complexity_score >= 0.0 && pattern.complexity_score <= 1.0);
1382 assert!(pattern.estimated_lsp_effectiveness >= 0.0 && pattern.estimated_lsp_effectiveness <= 1.0);
1383 }
1384 }
1385
1386 #[tokio::test]
1388 async fn test_safety_constraints() {
1389 let mut router = create_test_router_with_config(0.5);
1390 router.config.safety_floor_rate = 0.3;
1391 router.config.max_complexity_threshold = 0.5;
1392
1393 let complex_query = "extremely complex query with many special characters []{}<>*?+^$|\\".repeat(5);
1395 let decision = router.make_routing_decision(&complex_query, &QueryIntent::Definition).await;
1396
1397 if !decision.should_route_to_lsp {
1398 assert_eq!(decision.reason, RoutingReason::ComplexityTooHigh);
1399 }
1400 }
1401
1402 #[tokio::test]
1404 async fn test_adaptive_adjustment_logic() {
1405 let router = create_test_router_with_config(0.5);
1406
1407 for _ in 0..20 {
1409 router.total_queries.fetch_add(1, Ordering::Relaxed);
1410 if router.total_queries.load(Ordering::Relaxed) % 5 == 0 {
1412 router.total_lsp_routed.fetch_add(1, Ordering::Relaxed);
1413 }
1414 }
1415
1416 let total = router.total_queries.load(Ordering::Relaxed);
1418 let routed = router.total_lsp_routed.load(Ordering::Relaxed);
1419 let rate = (routed as f64 / total as f64 * 10000.0) as u64;
1420 router.current_routing_rate.store(rate, Ordering::Relaxed);
1421
1422 let base_probability = 0.4;
1424 let adjusted = router.apply_adaptive_adjustments(base_probability).await;
1425 assert!(adjusted >= base_probability); }
1427
1428 #[test]
1430 fn test_complexity_calculation() {
1431 assert!(LspRouter::calculate_complexity("test") < 0.3);
1433
1434 let complex_query = r"very long query with many words and special characters []{}\*";
1436 assert!(LspRouter::calculate_complexity(complex_query) > 0.5);
1437
1438 let long_query = "a".repeat(200);
1442 let long_complexity = LspRouter::calculate_complexity(&long_query);
1443 assert!(long_complexity > 0.1);
1444
1445 let many_words = "word ".repeat(20);
1447 let word_complexity = LspRouter::calculate_complexity(&many_words);
1448 assert!(word_complexity > 0.1);
1449
1450 let special_chars = "!@#$%^&*()[]{}|\\";
1452 let special_complexity = LspRouter::calculate_complexity(special_chars);
1453 assert!(special_complexity > 0.1);
1454
1455 let regex_query = "pattern[a-z]+{1,5}*";
1457 let regex_complexity = LspRouter::calculate_complexity(regex_query);
1458 assert!(regex_complexity > 0.4);
1459 }
1460
1461 #[tokio::test]
1463 async fn test_pattern_cache_cleanup() {
1464 let router = LspRouter::new(0.5);
1465
1466 for i in 0..100 {
1468 let query = format!("test_query_{}", i);
1469 router.analyze_query_pattern(&query).await;
1470 }
1471
1472 let patterns = router.known_patterns.read().await;
1474 assert!(patterns.len() > 0);
1475
1476 }
1479
1480 #[tokio::test]
1482 async fn test_routing_stats_accuracy() {
1483 let router = LspRouter::new(0.4);
1484
1485 let mut expected_routed = 0;
1487 for i in 0..10 {
1488 let query = format!("class Test{}", i);
1489 let routed = router.should_route(&query, &QueryIntent::Definition).await;
1490 if routed {
1491 expected_routed += 1;
1492 router.report_lsp_result(&QueryIntent::Definition, true, 150).await;
1494 }
1495 }
1496
1497 let stats = router.get_routing_stats().await;
1498 assert_eq!(stats.total_queries, 10);
1499 assert_eq!(stats.total_lsp_routed, expected_routed);
1500 assert_eq!(stats.target_routing_rate, 0.4);
1501
1502 if let Some(def_stats) = stats.intent_breakdown.get(&QueryIntent::Definition) {
1504 assert_eq!(def_stats.total_queries, 10);
1505 assert_eq!(def_stats.lsp_routed, expected_routed);
1506 if expected_routed > 0 {
1507 assert_eq!(def_stats.success_rate, 1.0); }
1509 }
1510 }
1511
1512 #[tokio::test]
1514 async fn test_high_load_routing() {
1515 let router = Arc::new(LspRouter::new(0.5));
1516 let mut handles = vec![];
1517
1518 for i in 0..100 {
1520 let router_clone = router.clone();
1521 let handle = tokio::spawn(async move {
1522 let query = if i % 3 == 0 {
1523 format!("class HighLoad{}", i)
1524 } else if i % 3 == 1 {
1525 format!("function process{}", i)
1526 } else {
1527 format!("simple query {}", i)
1528 };
1529
1530 let intent = if i % 4 == 0 {
1531 QueryIntent::Definition
1532 } else if i % 4 == 1 {
1533 QueryIntent::Symbol
1534 } else if i % 4 == 2 {
1535 QueryIntent::References
1536 } else {
1537 QueryIntent::TextSearch
1538 };
1539
1540 router_clone.should_route(&query, &intent).await
1541 });
1542 handles.push(handle);
1543 }
1544
1545 let mut total_routed = 0;
1547 for handle in handles {
1548 if handle.await.unwrap() {
1549 total_routed += 1;
1550 }
1551 }
1552
1553 let stats = router.get_routing_stats().await;
1554 assert_eq!(stats.total_queries, 100);
1555 assert_eq!(stats.total_lsp_routed, total_routed);
1556
1557 let actual_rate = stats.total_lsp_routed as f64 / stats.total_queries as f64;
1559 assert!(actual_rate >= 0.0 && actual_rate <= 1.0);
1560 }
1561
1562 #[tokio::test]
1564 async fn test_bounded_bfs_traversal_basic() {
1565 let router = create_isolated_test_router();
1566 let bounds = TraversalBounds {
1567 max_depth: 2,
1568 max_results: 10,
1569 timeout_ms: 5000,
1570 };
1571
1572 let start_node = BfsNode {
1573 symbol_id: "test_function_def".to_string(),
1574 symbol_type: SymbolType::Definition,
1575 file_path: "test.rs".to_string(),
1576 line: 10,
1577 column: 5,
1578 };
1579
1580 let result = router.bounded_bfs_traversal(start_node.clone(), &bounds).await.unwrap();
1581
1582 assert!(!result.visited_nodes.is_empty());
1584 assert_eq!(result.visited_nodes[0], start_node);
1585
1586 assert!(result.nodes_explored <= bounds.max_results);
1588 assert!(result.depth_reached <= bounds.max_depth);
1589
1590 cleanup_router_safely(router).await;
1592 }
1593
1594 #[tokio::test]
1595 async fn test_bounded_bfs_traversal_depth_limit() {
1596 let router = create_isolated_test_router();
1597 let bounds = TraversalBounds {
1598 max_depth: 1,
1599 max_results: 50,
1600 timeout_ms: 5000,
1601 };
1602
1603 let start_node = BfsNode {
1604 symbol_id: "trait_definition".to_string(),
1605 symbol_type: SymbolType::Definition,
1606 file_path: "traits.rs".to_string(),
1607 line: 20,
1608 column: 8,
1609 };
1610
1611 let result = router.bounded_bfs_traversal(start_node, &bounds).await.unwrap();
1612
1613 assert!(result.depth_reached <= 1);
1615
1616 assert!(result.visited_nodes.len() > 1);
1618
1619 cleanup_router_safely(router).await;
1621 }
1622
1623 #[tokio::test]
1624 async fn test_bounded_bfs_traversal_node_limit() {
1625 let router = create_isolated_test_router();
1626 let bounds = TraversalBounds {
1627 max_depth: 5, max_results: 3, timeout_ms: 5000,
1630 };
1631
1632 let start_node = BfsNode {
1633 symbol_id: "popular_function".to_string(),
1634 symbol_type: SymbolType::Definition,
1635 file_path: "popular.rs".to_string(),
1636 line: 1,
1637 column: 1,
1638 };
1639
1640 let result = router.bounded_bfs_traversal(start_node, &bounds).await.unwrap();
1641
1642 assert!(result.nodes_explored <= 3);
1644 assert!(result.was_bounded);
1645
1646 cleanup_router_safely(router).await;
1648 }
1649
1650 #[tokio::test]
1651 async fn test_bounded_bfs_todo_md_bounds() {
1652 let router = LspRouter::new(0.5);
1653
1654 let excessive_bounds = TraversalBounds {
1656 max_depth: 10, max_results: 200, timeout_ms: 5000,
1659 };
1660
1661 let start_node = BfsNode {
1662 symbol_id: "test_bounds".to_string(),
1663 symbol_type: SymbolType::Definition,
1664 file_path: "bounds_test.rs".to_string(),
1665 line: 15,
1666 column: 10,
1667 };
1668
1669 let result = router.bounded_bfs_traversal(start_node, &excessive_bounds).await.unwrap();
1670
1671 assert!(result.depth_reached <= 2); assert!(result.nodes_explored <= 64); }
1675
1676 #[tokio::test]
1677 async fn test_bfs_symbol_relationships() {
1678 let router = LspRouter::new(0.5);
1679 let bounds = TraversalBounds::default();
1680
1681 let test_cases = vec![
1683 (SymbolType::Definition, vec![SymbolType::Reference]),
1684 (SymbolType::Reference, vec![SymbolType::Definition]),
1685 (SymbolType::Implementation, vec![SymbolType::TypeDefinition]),
1686 (SymbolType::Declaration, vec![SymbolType::Definition]),
1687 (SymbolType::Alias, vec![SymbolType::Definition]),
1688 (SymbolType::TypeDefinition, vec![SymbolType::Implementation]),
1689 ];
1690
1691 for (symbol_type, expected_neighbor_types) in test_cases {
1692 let start_node = BfsNode {
1693 symbol_id: format!("test_{:?}", symbol_type),
1694 symbol_type: symbol_type.clone(),
1695 file_path: "relationships.rs".to_string(),
1696 line: 25,
1697 column: 15,
1698 };
1699
1700 let result = router.bounded_bfs_traversal(start_node, &bounds).await.unwrap();
1701
1702 assert!(result.visited_nodes.len() > 1);
1704
1705 if !result.edges.is_empty() {
1707 let edge_targets: Vec<_> = result.edges.iter()
1708 .map(|(_, target, _)| &target.symbol_type)
1709 .collect();
1710
1711 for expected_type in &expected_neighbor_types {
1712 assert!(
1713 edge_targets.contains(&expected_type),
1714 "Expected neighbor type {:?} not found for symbol type {:?}",
1715 expected_type, symbol_type
1716 );
1717 }
1718 }
1719 }
1720 }
1721
1722 #[tokio::test]
1723 async fn test_bfs_edge_types() {
1724 let router = LspRouter::new(0.5);
1725 let bounds = TraversalBounds::default();
1726
1727 let start_node = BfsNode {
1728 symbol_id: "trait_test".to_string(),
1729 symbol_type: SymbolType::Definition,
1730 file_path: "edge_test.rs".to_string(),
1731 line: 30,
1732 column: 5,
1733 };
1734
1735 let result = router.bounded_bfs_traversal(start_node, &bounds).await.unwrap();
1736
1737 let edge_types: Vec<_> = result.edges.iter().map(|(_, _, edge_type)| edge_type).collect();
1739
1740 if !edge_types.is_empty() {
1741 assert!(edge_types.contains(&&EdgeType::DefinitionToReference));
1743
1744 assert!(edge_types.contains(&&EdgeType::TypeToImplementation));
1746 }
1747 }
1748
1749 #[tokio::test]
1750 async fn test_bfs_cycle_detection() {
1751 let router = LspRouter::new(0.5);
1752 let bounds = TraversalBounds {
1753 max_depth: 2,
1754 max_results: 20,
1755 timeout_ms: 5000,
1756 };
1757
1758 let start_node = BfsNode {
1759 symbol_id: "cycle_test_def".to_string(),
1760 symbol_type: SymbolType::Definition,
1761 file_path: "cycle.rs".to_string(),
1762 line: 40,
1763 column: 10,
1764 };
1765
1766 let result = router.bounded_bfs_traversal(start_node.clone(), &bounds).await.unwrap();
1767
1768 let mut seen_ids = HashSet::new();
1770 for node in &result.visited_nodes {
1771 assert!(
1772 seen_ids.insert(&node.symbol_id),
1773 "Duplicate node visited: {}",
1774 node.symbol_id
1775 );
1776 }
1777
1778 let non_start_nodes: Vec<_> = result.visited_nodes.iter()
1780 .filter(|node| *node != &start_node)
1781 .collect();
1782
1783 for node in non_start_nodes {
1784 assert_ne!(node.symbol_id, start_node.symbol_id);
1785 }
1786 }
1787
1788 #[tokio::test]
1789 async fn test_bfs_empty_neighbors() {
1790 let router = create_isolated_test_router();
1792 let bounds = TraversalBounds {
1793 max_depth: 1, max_results: 5, timeout_ms: 1000, };
1797
1798 let isolated_node = BfsNode {
1800 symbol_id: "isolated".to_string(),
1801 symbol_type: SymbolType::Definition,
1802 file_path: "isolated.rs".to_string(),
1803 line: 50,
1804 column: 20,
1805 };
1806
1807 let result = {
1809 let traversal_result = router.bounded_bfs_traversal(isolated_node.clone(), &bounds).await;
1810 match traversal_result {
1811 Ok(result) => result,
1812 Err(e) => {
1813 eprintln!("BFS traversal failed: {}, creating minimal result", e);
1815 BfsTraversalResult {
1816 visited_nodes: vec![isolated_node.clone()],
1817 edges: vec![],
1818 depth_reached: 0,
1819 nodes_explored: 1,
1820 was_bounded: false,
1821 }
1822 }
1823 }
1824 };
1825
1826 assert!(!result.visited_nodes.is_empty());
1828 assert_eq!(result.visited_nodes[0], isolated_node);
1829 assert!(result.nodes_explored >= 1);
1830 assert!(result.depth_reached <= bounds.max_depth);
1831
1832 cleanup_router_safely(router).await;
1834
1835 tokio::task::yield_now().await;
1837 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
1838 }
1839
1840 #[tokio::test]
1841 async fn test_symbol_type_equality() {
1842 assert_eq!(SymbolType::Definition, SymbolType::Definition);
1843 assert_ne!(SymbolType::Definition, SymbolType::Reference);
1844 assert_ne!(SymbolType::Reference, SymbolType::Implementation);
1845 }
1846
1847 #[tokio::test]
1848 async fn test_bfs_node_equality() {
1849 let node1 = BfsNode {
1850 symbol_id: "test".to_string(),
1851 symbol_type: SymbolType::Definition,
1852 file_path: "test.rs".to_string(),
1853 line: 10,
1854 column: 5,
1855 };
1856
1857 let node2 = BfsNode {
1858 symbol_id: "test".to_string(),
1859 symbol_type: SymbolType::Definition,
1860 file_path: "test.rs".to_string(),
1861 line: 10,
1862 column: 5,
1863 };
1864
1865 let node3 = BfsNode {
1866 symbol_id: "different".to_string(),
1867 symbol_type: SymbolType::Definition,
1868 file_path: "test.rs".to_string(),
1869 line: 10,
1870 column: 5,
1871 };
1872
1873 assert_eq!(node1, node2);
1874 assert_ne!(node1, node3);
1875 }
1876
1877 #[tokio::test]
1878 async fn test_edge_type_equality() {
1879 assert_eq!(EdgeType::DefinitionToReference, EdgeType::DefinitionToReference);
1880 assert_ne!(EdgeType::DefinitionToReference, EdgeType::ReferenceToDefinition);
1881 assert_ne!(EdgeType::TypeToImplementation, EdgeType::ImplementationToType);
1882 }
1883}