/// Integration tests for temporal-compare pattern detection APIs /// /// This test suite verifies that the find_similar() and detect_pattern() APIs /// work correctly with the published crate. use midstreamer_temporal_compare::{TemporalComparator, Pattern, SimilarityMatch}; #[test] fn test_find_similar_with_f64() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Create a time series with repeating patterns let series = vec![1.0, 2.0, 3.0, 4.0, 5.0, 3.0, 4.0, 5.0, 6.0, 7.0]; let pattern = vec![3.0, 4.0, 5.0]; // Find similar patterns with a reasonable threshold let matches = comparator.find_similar(&series, &pattern, 1.0); // Verify we found the expected matches assert!(!matches.is_empty(), "Should find at least one match"); assert_eq!(matches.len(), 2, "Should find exactly 2 matches"); // Verify the indices are correct assert_eq!(matches[0].0, 2, "First match should be at index 2"); assert_eq!(matches[1].0, 5, "Second match should be at index 5"); // Verify distances are within threshold for (idx, distance) in &matches { assert!(*distance <= 1.0, "Distance {} at index {} exceeds threshold", distance, idx); } } #[test] fn test_detect_pattern_exists() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let series = vec![1.0, 2.0, 3.0, 4.0, 5.0, 3.0, 4.0, 5.0]; let pattern = vec![3.0, 4.0, 5.0]; // Detect if pattern exists let found = comparator.detect_pattern(&series, &pattern, 0.5); assert!(found, "Pattern should be detected in the series"); } #[test] fn test_detect_pattern_not_exists() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let series = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let pattern = vec![10.0, 20.0, 30.0]; // Detect if pattern exists with strict threshold let found = comparator.detect_pattern(&series, &pattern, 0.5); assert!(!found, "Pattern should not be detected (too different)"); } #[test] fn test_find_similar_generic_with_integers() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let haystack = vec![1, 2, 3, 4, 5, 3, 4, 5, 6]; let needle = vec![3, 4, 5]; // Use the generic API with normalized threshold let matches = comparator.find_similar_generic(&haystack, &needle, 0.1).unwrap(); assert_eq!(matches.len(), 2, "Should find 2 exact matches"); assert_eq!(matches[0].start_index, 2); assert_eq!(matches[1].start_index, 5); // Verify similarity scores for m in &matches { assert!(m.similarity > 0.9, "Exact matches should have high similarity"); } } #[test] fn test_detect_recurring_patterns() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Create sequence with recurring patterns let sequence = vec!['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']; // Detect patterns of length 3 let patterns = comparator.detect_recurring_patterns(&sequence, 3, 3).unwrap(); assert!(!patterns.is_empty(), "Should detect recurring patterns"); // Find the 'abc' pattern let abc_pattern = patterns.iter() .find(|p| p.sequence == vec!['a', 'b', 'c']); assert!(abc_pattern.is_some(), "Should find 'abc' pattern"); let pattern = abc_pattern.unwrap(); assert_eq!(pattern.frequency(), 3, "Pattern should occur 3 times"); assert!(pattern.confidence > 0.0, "Should have positive confidence"); } #[test] fn test_detect_fuzzy_patterns() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Sequence with similar but not identical patterns let sequence = vec![1, 2, 3, 1, 2, 4, 1, 2, 3]; // Detect fuzzy patterns (should group [1,2,3] and [1,2,4] together) let patterns = comparator.detect_fuzzy_patterns(&sequence, 3, 3, 0.7).unwrap(); assert!(!patterns.is_empty(), "Should detect fuzzy patterns"); // Should find at least one pattern that occurs multiple times let has_multiple = patterns.iter().any(|p| p.frequency() >= 2); assert!(has_multiple, "Should find patterns with multiple occurrences"); } #[test] fn test_pattern_struct_api() { let sequence = vec![1, 2, 3]; let occurrences = vec![0, 5, 10]; let confidence = 0.85; let pattern = Pattern::new(sequence.clone(), occurrences.clone(), confidence); // Verify Pattern API assert_eq!(pattern.sequence, sequence); assert_eq!(pattern.occurrences, occurrences); assert_eq!(pattern.confidence, confidence); assert_eq!(pattern.frequency(), 3); assert_eq!(pattern.length(), 3); } #[test] fn test_similarity_match_struct() { let match1 = SimilarityMatch::new(0, 0.5); assert_eq!(match1.start_index, 0); assert_eq!(match1.distance, 0.5); assert!(match1.similarity > 0.0 && match1.similarity <= 1.0); // Lower distance should give higher similarity let match2 = SimilarityMatch::new(0, 0.1); assert!(match2.similarity > match1.similarity, "Lower distance should yield higher similarity"); } #[test] fn test_edge_case_empty_pattern() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let series = vec![1.0, 2.0, 3.0]; let pattern: Vec = vec![]; let matches = comparator.find_similar(&series, &pattern, 1.0); assert!(matches.is_empty(), "Empty pattern should return no matches"); let found = comparator.detect_pattern(&series, &pattern, 1.0); assert!(!found, "Empty pattern should not be detected"); } #[test] fn test_edge_case_pattern_longer_than_series() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let series = vec![1.0, 2.0]; let pattern = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let matches = comparator.find_similar(&series, &pattern, 1.0); assert!(matches.is_empty(), "Pattern longer than series should return no matches"); } #[test] fn test_approximate_matching_with_threshold() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Series with approximate match let series = vec![1.0, 2.0, 3.1, 4.2, 5.0, 6.0]; let pattern = vec![3.0, 4.0, 5.0]; // Strict threshold - should not match let strict_matches = comparator.find_similar(&series, &pattern, 0.1); assert!(strict_matches.is_empty(), "Strict threshold should reject approximate match"); // Loose threshold - should match let loose_matches = comparator.find_similar(&series, &pattern, 1.5); assert!(!loose_matches.is_empty(), "Loose threshold should accept approximate match"); } #[test] fn test_results_sorted_by_quality() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Series with exact and approximate matches let series = vec![1.0, 2.0, 3.0, 4.0, 5.0, 3.5, 4.5, 5.5]; let pattern = vec![3.0, 4.0, 5.0]; let matches = comparator.find_similar(&series, &pattern, 2.0); assert!(!matches.is_empty(), "Should find matches"); // Verify results are sorted by distance (best first) for i in 0..matches.len().saturating_sub(1) { assert!(matches[i].1 <= matches[i + 1].1, "Results should be sorted by distance (ascending)"); } // First match should be the exact one assert!(matches[0].1 < 0.1, "Best match should have very low distance"); } #[test] fn test_caching_behavior() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); let haystack = vec![1, 2, 3, 4, 5]; let needle = vec![3, 4, 5]; // Clear cache to start fresh comparator.clear_cache(); // First call - should be cache miss let _ = comparator.find_similar_generic(&haystack, &needle, 0.1).unwrap(); // Second call - should be cache hit let _ = comparator.find_similar_generic(&haystack, &needle, 0.1).unwrap(); let stats = comparator.cache_stats(); assert!(stats.hits > 0, "Should have cache hits"); assert!(stats.hits + stats.misses > 0, "Should have cache activity"); } #[test] fn test_comprehensive_workflow() { let comparator: TemporalComparator = TemporalComparator::new(100, 1000); // Create a rich sequence let sequence = vec![ 1, 2, 3, 4, // Pattern A 1, 2, 3, 4, // Pattern A repeat 5, 6, 7, // Pattern B 5, 6, 7, // Pattern B repeat 1, 2, 3, 4, // Pattern A again ]; // Test 1: Exact pattern detection let exact_patterns = comparator.detect_recurring_patterns(&sequence, 3, 4).unwrap(); assert!(!exact_patterns.is_empty(), "Should detect exact patterns"); // Test 2: Fuzzy pattern detection let fuzzy_patterns = comparator.detect_fuzzy_patterns(&sequence, 3, 4, 0.8).unwrap(); assert!(!fuzzy_patterns.is_empty(), "Should detect fuzzy patterns"); // Test 3: Similarity search let needle = vec![1, 2, 3, 4]; let matches = comparator.find_similar_generic(&sequence, &needle, 0.1).unwrap(); assert_eq!(matches.len(), 3, "Should find 3 occurrences of pattern"); // Test 4: Simple detection let found = comparator.detect_pattern( &sequence.iter().map(|&x| x as f64).collect::>(), &needle.iter().map(|&x| x as f64).collect::>(), 1.0 ); assert!(found, "Pattern should be detected"); // Test 5: Verify caching is working let stats = comparator.cache_stats(); assert!(stats.size > 0, "Cache should have entries"); }