midstream 0.2.0

Real-time LLM streaming with inflight analysis
Documentation
/// 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<f64> = 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<f64> = 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<f64> = 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<i32> = 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<char> = 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<i32> = 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<f64> = TemporalComparator::new(100, 1000);

    let series = vec![1.0, 2.0, 3.0];
    let pattern: Vec<f64> = 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<f64> = 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<f64> = 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<f64> = 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<i32> = 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<i32> = 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::<Vec<_>>(),
        &needle.iter().map(|&x| x as f64).collect::<Vec<_>>(),
        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");
}